1.新增时间筛选功能
This commit is contained in:
1
.vscode/settings.json
vendored
Normal file
1
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -2,6 +2,8 @@ import request from '@/utils/http'
|
|||||||
|
|
||||||
export type DashboardQueryParams = {
|
export type DashboardQueryParams = {
|
||||||
dept_id?: number
|
dept_id?: number
|
||||||
|
/** 统计日期,格式 YYYY-MM-DD,默认当日 */
|
||||||
|
date?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
98
saiadmin-artd/src/composables/useDashboardScope.ts
Normal file
98
saiadmin-artd/src/composables/useDashboardScope.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import type { InjectionKey, Ref, ComputedRef } from 'vue'
|
||||||
|
import { getChannelDeptRequestParams, useInjectedChannelDept } from '@/composables/useChannelDeptScope'
|
||||||
|
|
||||||
|
export interface DashboardQueryParams {
|
||||||
|
dept_id?: number
|
||||||
|
date?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardScopeContext {
|
||||||
|
selectedDate: Ref<string | null>
|
||||||
|
hasDateFilter: ComputedRef<boolean>
|
||||||
|
queryParams: ComputedRef<DashboardQueryParams>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DASHBOARD_SCOPE_KEY: InjectionKey<DashboardScopeContext> = Symbol('dashboardScope')
|
||||||
|
|
||||||
|
function formatDateYmd(date: Date): string {
|
||||||
|
const y = date.getFullYear()
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const d = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${y}-${m}-${d}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTodayDateString(): string {
|
||||||
|
return formatDateYmd(new Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 工作台页面 provide 日期筛选上下文 */
|
||||||
|
export function provideDashboardScope() {
|
||||||
|
const selectedDate = ref<string | null>(getTodayDateString())
|
||||||
|
const hasDateFilter = computed(() => !!selectedDate.value)
|
||||||
|
const queryParams = computed<DashboardQueryParams>(() => {
|
||||||
|
const params: DashboardQueryParams = {
|
||||||
|
...getChannelDeptRequestParams()
|
||||||
|
}
|
||||||
|
if (selectedDate.value) {
|
||||||
|
params.date = selectedDate.value
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
})
|
||||||
|
const ctx: DashboardScopeContext = { selectedDate, hasDateFilter, queryParams }
|
||||||
|
provide(DASHBOARD_SCOPE_KEY, ctx)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDashboardScope(): DashboardScopeContext {
|
||||||
|
const ctx = inject(DASHBOARD_SCOPE_KEY, null)
|
||||||
|
if (ctx) {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
const selectedDate = ref<string | null>(getTodayDateString())
|
||||||
|
const hasDateFilter = computed(() => !!selectedDate.value)
|
||||||
|
return {
|
||||||
|
selectedDate,
|
||||||
|
hasDateFilter,
|
||||||
|
queryParams: computed<DashboardQueryParams>(() => {
|
||||||
|
const params: DashboardQueryParams = {
|
||||||
|
...getChannelDeptRequestParams()
|
||||||
|
}
|
||||||
|
if (selectedDate.value) {
|
||||||
|
params.date = selectedDate.value
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 工作台:渠道或日期变化时重新拉数 */
|
||||||
|
export function useDashboardReload(loadFn: () => void | Promise<void>) {
|
||||||
|
const channel = useInjectedChannelDept()
|
||||||
|
const { selectedDate } = useDashboardScope()
|
||||||
|
|
||||||
|
const run = () => {
|
||||||
|
void loadFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(selectedDate, run)
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
onMounted(run)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => channel.selectedDeptId.value,
|
||||||
|
(deptId) => {
|
||||||
|
if (channel.isAllChannelScope.value && deptId <= 0) {
|
||||||
|
run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!channel.showDefaultTemplate.value && deptId <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
run()
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -300,16 +300,33 @@
|
|||||||
"gohome": "Go Home"
|
"gohome": "Go Home"
|
||||||
},
|
},
|
||||||
"console": {
|
"console": {
|
||||||
|
"filter": {
|
||||||
|
"date": "Statistics Date",
|
||||||
|
"placeholder": "Select date",
|
||||||
|
"today": "Today",
|
||||||
|
"clear": "Clear"
|
||||||
|
},
|
||||||
"card": {
|
"card": {
|
||||||
"playerRegister": "Player Register",
|
"playerRegister": "Player Register",
|
||||||
"playerCharge": "Player Charge",
|
"playerCharge": "Player Charge",
|
||||||
"playerWithdraw": "Player Withdraw",
|
"playerWithdraw": "Player Withdraw",
|
||||||
"playerPlayCount": "Player Play Count",
|
"playerPlayCount": "Player Play Count",
|
||||||
"vsLastWeek": "vs Last Week"
|
"vsLastWeek": "vs Last Week",
|
||||||
|
"vsYesterday": "vs Yesterday",
|
||||||
|
"viewRechargeRecords": "View recharge records",
|
||||||
|
"viewWithdrawRecords": "View withdraw records",
|
||||||
|
"viewPlayRecords": "View play records",
|
||||||
|
"viewRegisterRecords": "View registered players"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"viewAllRecharge": "View all recharge",
|
||||||
|
"viewAllPlay": "View all play records",
|
||||||
|
"viewAllRegister": "View all registrations"
|
||||||
},
|
},
|
||||||
"newPlayer": {
|
"newPlayer": {
|
||||||
"title": "New Players",
|
"title": "New Players",
|
||||||
"subtitle": "Latest 50 new player records",
|
"subtitle": "Latest 50 new player records",
|
||||||
|
"subtitleByDate": "New players on {date} (up to 50)",
|
||||||
"player": "Player",
|
"player": "Player",
|
||||||
"balance": "Balance",
|
"balance": "Balance",
|
||||||
"ticket": "Tickets",
|
"ticket": "Tickets",
|
||||||
@@ -318,6 +335,7 @@
|
|||||||
"playRecord": {
|
"playRecord": {
|
||||||
"title": "Player Play Records",
|
"title": "Player Play Records",
|
||||||
"subtitle": "Latest 50 play records",
|
"subtitle": "Latest 50 play records",
|
||||||
|
"subtitleByDate": "Play records on {date} (up to 50)",
|
||||||
"player": "Player",
|
"player": "Player",
|
||||||
"reward": "Reward Tier",
|
"reward": "Reward Tier",
|
||||||
"winCoin": "Win Amount",
|
"winCoin": "Win Amount",
|
||||||
@@ -326,6 +344,7 @@
|
|||||||
"walletRecord": {
|
"walletRecord": {
|
||||||
"title": "Player Charge Records",
|
"title": "Player Charge Records",
|
||||||
"subtitle": "Latest 50 charge records",
|
"subtitle": "Latest 50 charge records",
|
||||||
|
"subtitleByDate": "Charge records on {date} (up to 50)",
|
||||||
"player": "Player",
|
"player": "Player",
|
||||||
"chargeAmount": "Amount",
|
"chargeAmount": "Amount",
|
||||||
"chargeTime": "Charge Time"
|
"chargeTime": "Charge Time"
|
||||||
@@ -450,6 +469,10 @@
|
|||||||
"max": "Max",
|
"max": "Max",
|
||||||
"startTime": "Start Time",
|
"startTime": "Start Time",
|
||||||
"endTime": "End Time",
|
"endTime": "End Time",
|
||||||
|
"quickDate": "Quick date",
|
||||||
|
"quickToday": "Today",
|
||||||
|
"quickYesterday": "Yesterday",
|
||||||
|
"quickLast7Days": "Last 7 days",
|
||||||
"placeholderUsername": "Username",
|
"placeholderUsername": "Username",
|
||||||
"placeholderNickname": "Nickname",
|
"placeholderNickname": "Nickname",
|
||||||
"placeholderPhone": "Phone",
|
"placeholderPhone": "Phone",
|
||||||
|
|||||||
@@ -72,7 +72,8 @@
|
|||||||
"placeholderNickname": "Please enter nickname",
|
"placeholderNickname": "Please enter nickname",
|
||||||
"placeholderPhoneFuzzy": "Phone (fuzzy)",
|
"placeholderPhoneFuzzy": "Phone (fuzzy)",
|
||||||
"placeholderAll": "All",
|
"placeholderAll": "All",
|
||||||
"exactSearch": "Exact"
|
"exactSearch": "Exact",
|
||||||
|
"createTime": "Register Time"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"coinChangeSummary": "Coin Change Summary"
|
"coinChangeSummary": "Net Coin Change",
|
||||||
|
"coinInflow": "Inflow",
|
||||||
|
"coinOutflow": "Outflow"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"dialogTitleAdd": "Add Wallet Record",
|
"dialogTitleAdd": "Add Wallet Record",
|
||||||
|
|||||||
@@ -300,16 +300,33 @@
|
|||||||
"gohome": "返回首页"
|
"gohome": "返回首页"
|
||||||
},
|
},
|
||||||
"console": {
|
"console": {
|
||||||
|
"filter": {
|
||||||
|
"date": "统计日期",
|
||||||
|
"placeholder": "选择日期",
|
||||||
|
"today": "今日",
|
||||||
|
"clear": "清空"
|
||||||
|
},
|
||||||
"card": {
|
"card": {
|
||||||
"playerRegister": "玩家注册",
|
"playerRegister": "玩家注册",
|
||||||
"playerCharge": "玩家充值",
|
"playerCharge": "玩家充值",
|
||||||
"playerWithdraw": "玩家提现",
|
"playerWithdraw": "玩家提现",
|
||||||
"playerPlayCount": "玩家游玩次数",
|
"playerPlayCount": "玩家游玩次数",
|
||||||
"vsLastWeek": "较上周"
|
"vsLastWeek": "较上周",
|
||||||
|
"vsYesterday": "较昨日",
|
||||||
|
"viewRechargeRecords": "查看充值记录",
|
||||||
|
"viewWithdrawRecords": "查看提现记录",
|
||||||
|
"viewPlayRecords": "查看游玩记录",
|
||||||
|
"viewRegisterRecords": "查看注册玩家"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"viewAllRecharge": "查看全部充值",
|
||||||
|
"viewAllPlay": "查看全部游玩",
|
||||||
|
"viewAllRegister": "查看全部注册"
|
||||||
},
|
},
|
||||||
"newPlayer": {
|
"newPlayer": {
|
||||||
"title": "新增玩家",
|
"title": "新增玩家",
|
||||||
"subtitle": "最新50条新增玩家记录",
|
"subtitle": "最新50条新增玩家记录",
|
||||||
|
"subtitleByDate": "{date} 新增玩家记录(最多50条)",
|
||||||
"player": "玩家",
|
"player": "玩家",
|
||||||
"balance": "余额",
|
"balance": "余额",
|
||||||
"ticket": "抽奖券",
|
"ticket": "抽奖券",
|
||||||
@@ -318,6 +335,7 @@
|
|||||||
"playRecord": {
|
"playRecord": {
|
||||||
"title": "玩家游玩记录",
|
"title": "玩家游玩记录",
|
||||||
"subtitle": "最新50条游玩记录",
|
"subtitle": "最新50条游玩记录",
|
||||||
|
"subtitleByDate": "{date} 游玩记录(最多50条)",
|
||||||
"player": "玩家",
|
"player": "玩家",
|
||||||
"reward": "中奖档位",
|
"reward": "中奖档位",
|
||||||
"winCoin": "赢取平台币",
|
"winCoin": "赢取平台币",
|
||||||
@@ -326,6 +344,7 @@
|
|||||||
"walletRecord": {
|
"walletRecord": {
|
||||||
"title": "玩家充值记录",
|
"title": "玩家充值记录",
|
||||||
"subtitle": "最新50条充值记录",
|
"subtitle": "最新50条充值记录",
|
||||||
|
"subtitleByDate": "{date} 充值记录(最多50条)",
|
||||||
"player": "玩家",
|
"player": "玩家",
|
||||||
"chargeAmount": "充值金额",
|
"chargeAmount": "充值金额",
|
||||||
"chargeTime": "充值时间"
|
"chargeTime": "充值时间"
|
||||||
@@ -446,6 +465,10 @@
|
|||||||
"max": "最大",
|
"max": "最大",
|
||||||
"startTime": "开始时间",
|
"startTime": "开始时间",
|
||||||
"endTime": "结束时间",
|
"endTime": "结束时间",
|
||||||
|
"quickDate": "快捷日期",
|
||||||
|
"quickToday": "今日",
|
||||||
|
"quickYesterday": "昨日",
|
||||||
|
"quickLast7Days": "近7天",
|
||||||
"placeholderUsername": "请输入用户名",
|
"placeholderUsername": "请输入用户名",
|
||||||
"placeholderNickname": "请输入昵称",
|
"placeholderNickname": "请输入昵称",
|
||||||
"placeholderPhone": "请输入手机号",
|
"placeholderPhone": "请输入手机号",
|
||||||
|
|||||||
@@ -72,7 +72,8 @@
|
|||||||
"placeholderNickname": "请输入昵称",
|
"placeholderNickname": "请输入昵称",
|
||||||
"placeholderPhoneFuzzy": "手机号模糊查询",
|
"placeholderPhoneFuzzy": "手机号模糊查询",
|
||||||
"placeholderAll": "全部",
|
"placeholderAll": "全部",
|
||||||
"exactSearch": "精确搜索"
|
"exactSearch": "精确搜索",
|
||||||
|
"createTime": "注册时间"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"username": "用户名",
|
"username": "用户名",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"coinChangeSummary": "平台币变化统计"
|
"coinChangeSummary": "平台币净变化",
|
||||||
|
"coinInflow": "流入",
|
||||||
|
"coinOutflow": "流出"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"dialogTitleAdd": "新增玩家钱包流水",
|
"dialogTitleAdd": "新增玩家钱包流水",
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
<!-- 工作台页面:大富翁色子游戏数据统计 -->
|
<!-- 工作台页面:大富翁色子游戏数据统计 -->
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<div class="dashboard-filter-bar art-card flex flex-wrap items-center gap-3 px-5 py-3 mb-5 max-sm:mb-4">
|
||||||
|
<span class="text-g-700 text-sm shrink-0">{{ $t('console.filter.date') }}</span>
|
||||||
|
<ElDatePicker
|
||||||
|
v-model="selectedDate"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
clearable
|
||||||
|
:placeholder="$t('console.filter.placeholder')"
|
||||||
|
class="dashboard-date-picker"
|
||||||
|
/>
|
||||||
|
<ElButton type="primary" link @click="resetToToday">{{ $t('console.filter.today') }}</ElButton>
|
||||||
|
<ElButton v-if="selectedDate" type="primary" link @click="clearDateFilter">
|
||||||
|
{{ $t('console.filter.clear') }}
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<CardList />
|
<CardList />
|
||||||
|
|
||||||
<template v-if="isStatisticsDashboard">
|
<template v-if="isStatisticsDashboard">
|
||||||
@@ -40,10 +56,20 @@
|
|||||||
import PlayRecordList from './modules/play-record-list.vue'
|
import PlayRecordList from './modules/play-record-list.vue'
|
||||||
import { useCommon } from '@/hooks/core/useCommon'
|
import { useCommon } from '@/hooks/core/useCommon'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { getTodayDateString, provideDashboardScope } from '@/composables/useDashboardScope'
|
||||||
|
|
||||||
defineOptions({ name: 'Console' })
|
defineOptions({ name: 'Console' })
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const { selectedDate } = provideDashboardScope()
|
||||||
|
|
||||||
|
const resetToToday = () => {
|
||||||
|
selectedDate.value = getTodayDateString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearDateFilter = () => {
|
||||||
|
selectedDate.value = null
|
||||||
|
}
|
||||||
|
|
||||||
/** 统计页额外展示充值图表 */
|
/** 统计页额外展示充值图表 */
|
||||||
const isStatisticsDashboard = computed(
|
const isStatisticsDashboard = computed(
|
||||||
@@ -53,3 +79,14 @@
|
|||||||
const { scrollToTop } = useCommon()
|
const { scrollToTop } = useCommon()
|
||||||
scrollToTop()
|
scrollToTop()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dashboard-filter-bar {
|
||||||
|
min-height: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-date-picker {
|
||||||
|
width: 180px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<ElRow :gutter="20" class="flex">
|
<ElRow :gutter="20" class="flex">
|
||||||
<ElCol :sm="12" :md="6" :lg="6">
|
<ElCol :sm="12" :md="6" :lg="6">
|
||||||
<div class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4">
|
<div
|
||||||
|
class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4 dashboard-stat-card dashboard-stat-card--clickable"
|
||||||
|
@click="goPlayerList"
|
||||||
|
>
|
||||||
<span class="text-g-700 text-sm">{{ $t('console.card.playerRegister') }}</span>
|
<span class="text-g-700 text-sm">{{ $t('console.card.playerRegister') }}</span>
|
||||||
<ArtCountTo class="text-[26px] font-medium mt-2" :target="statData.player_count" :duration="1300" />
|
<ArtCountTo class="text-[26px] font-medium mt-2" :target="statData.player_count" :duration="1300" />
|
||||||
<div class="flex-c mt-1">
|
<div class="flex-c mt-1">
|
||||||
<span class="text-xs text-g-600">{{ $t('console.card.vsLastWeek') }}</span>
|
<span class="text-xs text-g-600">{{ compareLabel }}</span>
|
||||||
<span
|
<span
|
||||||
class="ml-1 text-xs font-semibold"
|
class="ml-1 text-xs font-semibold"
|
||||||
:class="changeClass(statData.player_count_change)"
|
:class="changeClass(statData.player_count_change)"
|
||||||
@@ -13,6 +16,9 @@
|
|||||||
{{ formatChange(statData.player_count_change) }}
|
{{ formatChange(statData.player_count_change) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<ElButton type="primary" link class="dashboard-stat-link" @click.stop="goPlayerList">
|
||||||
|
{{ $t('console.card.viewRegisterRecords') }}
|
||||||
|
</ElButton>
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
|
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
|
||||||
>
|
>
|
||||||
@@ -21,7 +27,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :sm="12" :md="6" :lg="6">
|
<ElCol :sm="12" :md="6" :lg="6">
|
||||||
<div class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4">
|
<div
|
||||||
|
class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4 dashboard-stat-card dashboard-stat-card--clickable"
|
||||||
|
@click="goWalletRecord(0)"
|
||||||
|
>
|
||||||
<span class="text-g-700 text-sm">{{ $t('console.card.playerCharge') }}</span>
|
<span class="text-g-700 text-sm">{{ $t('console.card.playerCharge') }}</span>
|
||||||
<ArtCountTo
|
<ArtCountTo
|
||||||
class="text-[26px] font-medium mt-2"
|
class="text-[26px] font-medium mt-2"
|
||||||
@@ -30,7 +39,7 @@
|
|||||||
:decimals="2"
|
:decimals="2"
|
||||||
/>
|
/>
|
||||||
<div class="flex-c mt-1">
|
<div class="flex-c mt-1">
|
||||||
<span class="text-xs text-g-600">{{ $t('console.card.vsLastWeek') }}</span>
|
<span class="text-xs text-g-600">{{ compareLabel }}</span>
|
||||||
<span
|
<span
|
||||||
class="ml-1 text-xs font-semibold"
|
class="ml-1 text-xs font-semibold"
|
||||||
:class="changeClass(statData.charge_amount_change)"
|
:class="changeClass(statData.charge_amount_change)"
|
||||||
@@ -38,6 +47,9 @@
|
|||||||
{{ formatChange(statData.charge_amount_change) }}
|
{{ formatChange(statData.charge_amount_change) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<ElButton type="primary" link class="dashboard-stat-link" @click.stop="goWalletRecord(0)">
|
||||||
|
{{ $t('console.card.viewRechargeRecords') }}
|
||||||
|
</ElButton>
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
|
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
|
||||||
>
|
>
|
||||||
@@ -46,7 +58,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :sm="12" :md="6" :lg="6">
|
<ElCol :sm="12" :md="6" :lg="6">
|
||||||
<div class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4">
|
<div
|
||||||
|
class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4 dashboard-stat-card dashboard-stat-card--clickable"
|
||||||
|
@click="goWalletRecord(1)"
|
||||||
|
>
|
||||||
<span class="text-g-700 text-sm">{{ $t('console.card.playerWithdraw') }}</span>
|
<span class="text-g-700 text-sm">{{ $t('console.card.playerWithdraw') }}</span>
|
||||||
<ArtCountTo
|
<ArtCountTo
|
||||||
class="text-[26px] font-medium mt-2"
|
class="text-[26px] font-medium mt-2"
|
||||||
@@ -55,7 +70,7 @@
|
|||||||
:decimals="2"
|
:decimals="2"
|
||||||
/>
|
/>
|
||||||
<div class="flex-c mt-1">
|
<div class="flex-c mt-1">
|
||||||
<span class="text-xs text-g-600">{{ $t('console.card.vsLastWeek') }}</span>
|
<span class="text-xs text-g-600">{{ compareLabel }}</span>
|
||||||
<span
|
<span
|
||||||
class="ml-1 text-xs font-semibold"
|
class="ml-1 text-xs font-semibold"
|
||||||
:class="changeClass(statData.withdraw_amount_change)"
|
:class="changeClass(statData.withdraw_amount_change)"
|
||||||
@@ -63,6 +78,9 @@
|
|||||||
{{ formatChange(statData.withdraw_amount_change) }}
|
{{ formatChange(statData.withdraw_amount_change) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<ElButton type="primary" link class="dashboard-stat-link" @click.stop="goWalletRecord(1)">
|
||||||
|
{{ $t('console.card.viewWithdrawRecords') }}
|
||||||
|
</ElButton>
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
|
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
|
||||||
>
|
>
|
||||||
@@ -71,7 +89,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :sm="12" :md="6" :lg="6">
|
<ElCol :sm="12" :md="6" :lg="6">
|
||||||
<div class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4">
|
<div
|
||||||
|
class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4 dashboard-stat-card dashboard-stat-card--clickable"
|
||||||
|
@click="goPlayRecord"
|
||||||
|
>
|
||||||
<span class="text-g-700 text-sm">{{ $t('console.card.playerPlayCount') }}</span>
|
<span class="text-g-700 text-sm">{{ $t('console.card.playerPlayCount') }}</span>
|
||||||
<ArtCountTo
|
<ArtCountTo
|
||||||
class="text-[26px] font-medium mt-2"
|
class="text-[26px] font-medium mt-2"
|
||||||
@@ -79,7 +100,7 @@
|
|||||||
:duration="1300"
|
:duration="1300"
|
||||||
/>
|
/>
|
||||||
<div class="flex-c mt-1">
|
<div class="flex-c mt-1">
|
||||||
<span class="text-xs text-g-600">{{ $t('console.card.vsLastWeek') }}</span>
|
<span class="text-xs text-g-600">{{ compareLabel }}</span>
|
||||||
<span
|
<span
|
||||||
class="ml-1 text-xs font-semibold"
|
class="ml-1 text-xs font-semibold"
|
||||||
:class="changeClass(statData.play_count_change)"
|
:class="changeClass(statData.play_count_change)"
|
||||||
@@ -87,6 +108,9 @@
|
|||||||
{{ formatChange(statData.play_count_change) }}
|
{{ formatChange(statData.play_count_change) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<ElButton type="primary" link class="dashboard-stat-link" @click.stop="goPlayRecord">
|
||||||
|
{{ $t('console.card.viewPlayRecords') }}
|
||||||
|
</ElButton>
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
|
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
|
||||||
>
|
>
|
||||||
@@ -99,7 +123,21 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { fetchStatistics } from '@/api/dashboard'
|
import { fetchStatistics } from '@/api/dashboard'
|
||||||
import { getChannelDeptRequestParams, useChannelDeptReload } from '@/composables/useChannelDeptScope'
|
import { useDashboardReload, useDashboardScope } from '@/composables/useDashboardScope'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import {
|
||||||
|
dashboardDateNavParams,
|
||||||
|
openPlayRecord,
|
||||||
|
openPlayerList,
|
||||||
|
openWalletRecord
|
||||||
|
} from '@/views/plugin/dice/utils/dashboardRecordNav'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { queryParams, hasDateFilter, selectedDate } = useDashboardScope()
|
||||||
|
|
||||||
|
const compareLabel = computed(() =>
|
||||||
|
hasDateFilter.value ? t('console.card.vsYesterday') : t('console.card.vsLastWeek')
|
||||||
|
)
|
||||||
|
|
||||||
const statData = ref({
|
const statData = ref({
|
||||||
player_count: 0,
|
player_count: 0,
|
||||||
@@ -125,7 +163,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadStatistics = () => {
|
const loadStatistics = () => {
|
||||||
fetchStatistics(getChannelDeptRequestParams()).then((data: any) => {
|
fetchStatistics(queryParams.value).then((data: any) => {
|
||||||
statData.value = {
|
statData.value = {
|
||||||
player_count: data?.player_count ?? 0,
|
player_count: data?.player_count ?? 0,
|
||||||
player_count_change: data?.player_count_change ?? 0,
|
player_count_change: data?.player_count_change ?? 0,
|
||||||
@@ -139,5 +177,39 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useChannelDeptReload(loadStatistics)
|
useDashboardReload(loadStatistics)
|
||||||
|
|
||||||
|
const goWalletRecord = (type: number) => {
|
||||||
|
openWalletRecord({
|
||||||
|
type,
|
||||||
|
...dashboardDateNavParams(selectedDate.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const goPlayRecord = () => {
|
||||||
|
openPlayRecord(dashboardDateNavParams(selectedDate.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const goPlayerList = () => {
|
||||||
|
openPlayerList(dashboardDateNavParams(selectedDate.value))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dashboard-stat-card--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-stat-card--clickable:hover {
|
||||||
|
box-shadow: 0 4px 12px rgb(0 0 0 / 8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-stat-link {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="art-card p-5 overflow-hidden mb-5 max-sm:mb-4">
|
<div class="art-card p-5 overflow-hidden mb-5 max-sm:mb-4">
|
||||||
<div class="art-card-header mb-4">
|
<div class="art-card-header mb-4 flex items-start justify-between gap-3">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h4>{{ $t('console.newPlayer.title') }}</h4>
|
<h4>{{ $t('console.newPlayer.title') }}</h4>
|
||||||
<p class="text-g-600 text-sm mt-1">{{ $t('console.newPlayer.subtitle') }}</p>
|
<p class="text-g-600 text-sm mt-1">{{ listSubtitle }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<ElButton type="primary" link @click="goPlayerList">
|
||||||
|
{{ $t('console.nav.viewAllRegister') }}
|
||||||
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<ArtTable
|
<ArtTable
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@@ -41,20 +44,37 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { fetchNewPlayerList, type NewPlayerItem } from '@/api/dashboard'
|
import { fetchNewPlayerList, type NewPlayerItem } from '@/api/dashboard'
|
||||||
import { getChannelDeptRequestParams, useChannelDeptReload } from '@/composables/useChannelDeptScope'
|
import { useDashboardReload, useDashboardScope } from '@/composables/useDashboardScope'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import {
|
||||||
|
dashboardDateNavParams,
|
||||||
|
openPlayerList
|
||||||
|
} from '@/views/plugin/dice/utils/dashboardRecordNav'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { queryParams, selectedDate, hasDateFilter } = useDashboardScope()
|
||||||
const tableData = ref<NewPlayerItem[]>([])
|
const tableData = ref<NewPlayerItem[]>([])
|
||||||
|
|
||||||
|
const listSubtitle = computed(() =>
|
||||||
|
hasDateFilter.value && selectedDate.value
|
||||||
|
? t('console.newPlayer.subtitleByDate', { date: selectedDate.value })
|
||||||
|
: t('console.newPlayer.subtitle')
|
||||||
|
)
|
||||||
|
|
||||||
function formatCoin(val: number | undefined): string {
|
function formatCoin(val: number | undefined): string {
|
||||||
if (val === undefined || val === null) return '0.00'
|
if (val === undefined || val === null) return '0.00'
|
||||||
return Number(val).toFixed(2)
|
return Number(val).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadList = () => {
|
const loadList = () => {
|
||||||
fetchNewPlayerList(getChannelDeptRequestParams()).then((data) => {
|
fetchNewPlayerList(queryParams.value).then((data) => {
|
||||||
tableData.value = Array.isArray(data) ? data : []
|
tableData.value = Array.isArray(data) ? data : []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useChannelDeptReload(loadList)
|
useDashboardReload(loadList)
|
||||||
|
|
||||||
|
const goPlayerList = () => {
|
||||||
|
openPlayerList(dashboardDateNavParams(selectedDate.value))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="art-card p-5 overflow-hidden mb-5 max-sm:mb-4">
|
<div class="art-card p-5 overflow-hidden mb-5 max-sm:mb-4">
|
||||||
<div class="art-card-header mb-4">
|
<div class="art-card-header mb-4 flex items-start justify-between gap-3">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h4>{{ $t('console.playRecord.title') }}</h4>
|
<h4>{{ $t('console.playRecord.title') }}</h4>
|
||||||
<p class="text-g-600 text-sm mt-1">{{ $t('console.playRecord.subtitle') }}</p>
|
<p class="text-g-600 text-sm mt-1">{{ listSubtitle }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<ElButton type="primary" link @click="goPlayRecords">
|
||||||
|
{{ $t('console.nav.viewAllPlay') }}
|
||||||
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<ArtTable
|
<ArtTable
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@@ -51,20 +54,37 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { fetchPlayRecordList, type PlayRecordItem } from '@/api/dashboard'
|
import { fetchPlayRecordList, type PlayRecordItem } from '@/api/dashboard'
|
||||||
import { getChannelDeptRequestParams, useChannelDeptReload } from '@/composables/useChannelDeptScope'
|
import { useDashboardReload, useDashboardScope } from '@/composables/useDashboardScope'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import {
|
||||||
|
dashboardDateNavParams,
|
||||||
|
openPlayRecord
|
||||||
|
} from '@/views/plugin/dice/utils/dashboardRecordNav'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { queryParams, selectedDate, hasDateFilter } = useDashboardScope()
|
||||||
const tableData = ref<PlayRecordItem[]>([])
|
const tableData = ref<PlayRecordItem[]>([])
|
||||||
|
|
||||||
|
const listSubtitle = computed(() =>
|
||||||
|
hasDateFilter.value && selectedDate.value
|
||||||
|
? t('console.playRecord.subtitleByDate', { date: selectedDate.value })
|
||||||
|
: t('console.playRecord.subtitle')
|
||||||
|
)
|
||||||
|
|
||||||
function formatCoin(val: number | undefined): string {
|
function formatCoin(val: number | undefined): string {
|
||||||
if (val === undefined || val === null) return '0.00'
|
if (val === undefined || val === null) return '0.00'
|
||||||
return Number(val).toFixed(2)
|
return Number(val).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadList = () => {
|
const loadList = () => {
|
||||||
fetchPlayRecordList(getChannelDeptRequestParams()).then((data) => {
|
fetchPlayRecordList(queryParams.value).then((data) => {
|
||||||
tableData.value = Array.isArray(data) ? data : []
|
tableData.value = Array.isArray(data) ? data : []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useChannelDeptReload(loadList)
|
useDashboardReload(loadList)
|
||||||
|
|
||||||
|
const goPlayRecords = () => {
|
||||||
|
openPlayRecord(dashboardDateNavParams(selectedDate.value))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="art-card p-5 overflow-hidden mb-5 max-sm:mb-4">
|
<div class="art-card p-5 overflow-hidden mb-5 max-sm:mb-4">
|
||||||
<div class="art-card-header mb-4">
|
<div class="art-card-header mb-4 flex items-start justify-between gap-3">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h4>{{ $t('console.walletRecord.title') }}</h4>
|
<h4>{{ $t('console.walletRecord.title') }}</h4>
|
||||||
<p class="text-g-600 text-sm mt-1">{{ $t('console.walletRecord.subtitle') }}</p>
|
<p class="text-g-600 text-sm mt-1">{{ listSubtitle }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<ElButton type="primary" link @click="goWalletRecords">
|
||||||
|
{{ $t('console.nav.viewAllRecharge') }}
|
||||||
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<ArtTable
|
<ArtTable
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@@ -30,20 +33,40 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { fetchWalletRecordList, type WalletRecordItem } from '@/api/dashboard'
|
import { fetchWalletRecordList, type WalletRecordItem } from '@/api/dashboard'
|
||||||
import { getChannelDeptRequestParams, useChannelDeptReload } from '@/composables/useChannelDeptScope'
|
import { useDashboardReload, useDashboardScope } from '@/composables/useDashboardScope'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import {
|
||||||
|
dashboardDateNavParams,
|
||||||
|
openWalletRecord
|
||||||
|
} from '@/views/plugin/dice/utils/dashboardRecordNav'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { queryParams, selectedDate, hasDateFilter } = useDashboardScope()
|
||||||
const tableData = ref<WalletRecordItem[]>([])
|
const tableData = ref<WalletRecordItem[]>([])
|
||||||
|
|
||||||
|
const listSubtitle = computed(() =>
|
||||||
|
hasDateFilter.value && selectedDate.value
|
||||||
|
? t('console.walletRecord.subtitleByDate', { date: selectedDate.value })
|
||||||
|
: t('console.walletRecord.subtitle')
|
||||||
|
)
|
||||||
|
|
||||||
function formatCoin(val: number | undefined): string {
|
function formatCoin(val: number | undefined): string {
|
||||||
if (val === undefined || val === null) return '0.00'
|
if (val === undefined || val === null) return '0.00'
|
||||||
return Number(val).toFixed(2)
|
return Number(val).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadList = () => {
|
const loadList = () => {
|
||||||
fetchWalletRecordList(getChannelDeptRequestParams()).then((data) => {
|
fetchWalletRecordList(queryParams.value).then((data) => {
|
||||||
tableData.value = Array.isArray(data) ? data : []
|
tableData.value = Array.isArray(data) ? data : []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useChannelDeptReload(loadList)
|
useDashboardReload(loadList)
|
||||||
|
|
||||||
|
const goWalletRecords = () => {
|
||||||
|
openWalletRecord({
|
||||||
|
type: 0,
|
||||||
|
...dashboardDateNavParams(selectedDate.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ export default {
|
|||||||
* @returns 数据列表
|
* @returns 数据列表
|
||||||
*/
|
*/
|
||||||
list(params: Record<string, any>) {
|
list(params: Record<string, any>) {
|
||||||
return request.get<Api.Common.ApiPage & { total_coin_change?: number }>({
|
return request.get<Api.Common.ApiPage & {
|
||||||
|
total_coin_change?: number
|
||||||
|
total_coin_inflow?: number
|
||||||
|
total_coin_outflow?: number
|
||||||
|
}>({
|
||||||
url: '/core/dice/player_wallet_record/DicePlayerWalletRecord/index',
|
url: '/core/dice/player_wallet_record/DicePlayerWalletRecord/index',
|
||||||
params
|
params
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div class="quick-date-range-bar">
|
||||||
|
<span class="quick-date-label">{{ $t('table.searchBar.quickDate') }}</span>
|
||||||
|
<ElButton
|
||||||
|
v-for="item in presetItems"
|
||||||
|
:key="item.key"
|
||||||
|
size="small"
|
||||||
|
:type="modelValue === item.key ? 'primary' : 'default'"
|
||||||
|
@click="selectPreset(item.key)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import {
|
||||||
|
type DatePresetKey,
|
||||||
|
getCreateTimeRangeByPreset
|
||||||
|
} from '@/views/plugin/dice/utils/dateRangePresets'
|
||||||
|
|
||||||
|
const modelValue = defineModel<DatePresetKey | null>({ default: null })
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
select: [range: [string, string], preset: DatePresetKey]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const presetItems = computed(() => [
|
||||||
|
{ key: 'today' as const, label: t('table.searchBar.quickToday') },
|
||||||
|
{ key: 'yesterday' as const, label: t('table.searchBar.quickYesterday') },
|
||||||
|
{ key: 'last7days' as const, label: t('table.searchBar.quickLast7Days') }
|
||||||
|
])
|
||||||
|
|
||||||
|
const selectPreset = (preset: DatePresetKey) => {
|
||||||
|
modelValue.value = preset
|
||||||
|
emit('select', getCreateTimeRangeByPreset(preset), preset)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.quick-date-range-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-date-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
margin-right: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import type { LocationQuery } from 'vue-router'
|
||||||
|
import {
|
||||||
|
detectPresetFromRange,
|
||||||
|
type DatePresetKey,
|
||||||
|
hasRecordRouteFilter,
|
||||||
|
parseRecordRouteQuery
|
||||||
|
} from '@/views/plugin/dice/utils/dateRangePresets'
|
||||||
|
|
||||||
|
export interface RecordRouteInit {
|
||||||
|
hasFilter: boolean
|
||||||
|
create_time?: [string, string]
|
||||||
|
type?: number
|
||||||
|
activePreset: DatePresetKey | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRecordRouteInit(query: LocationQuery, withType = false): RecordRouteInit {
|
||||||
|
const filter = parseRecordRouteQuery(query)
|
||||||
|
const create_time = filter.create_time
|
||||||
|
const activePreset = filter.datePreset ?? detectPresetFromRange(create_time ?? null)
|
||||||
|
return {
|
||||||
|
hasFilter: hasRecordRouteFilter(query),
|
||||||
|
create_time,
|
||||||
|
type: withType ? filter.type : undefined,
|
||||||
|
activePreset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将路由 query 同步到搜索表单 */
|
||||||
|
export function syncSearchFormFromRoute(
|
||||||
|
query: LocationQuery,
|
||||||
|
searchForm: Ref<Record<string, unknown>>,
|
||||||
|
activeDatePreset: Ref<DatePresetKey | null>,
|
||||||
|
options?: { withType?: boolean }
|
||||||
|
): boolean {
|
||||||
|
const init = getRecordRouteInit(query, options?.withType)
|
||||||
|
if (init.create_time) {
|
||||||
|
searchForm.value.create_time = init.create_time
|
||||||
|
activeDatePreset.value = init.activePreset
|
||||||
|
} else {
|
||||||
|
searchForm.value.create_time = undefined
|
||||||
|
activeDatePreset.value = null
|
||||||
|
}
|
||||||
|
if (options?.withType) {
|
||||||
|
searchForm.value.type = init.type !== undefined ? init.type : undefined
|
||||||
|
}
|
||||||
|
return init.hasFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildRecordRouteQueryKey(query: LocationQuery, withType = false): string {
|
||||||
|
const parts = [String(query.date ?? ''), String(query.datePreset ?? '')]
|
||||||
|
if (withType) {
|
||||||
|
parts.push(String(query.type ?? ''))
|
||||||
|
}
|
||||||
|
return parts.join('|')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听路由 query 变化并触发查询(解决 KeepAlive 下跳转带参不刷新的问题)
|
||||||
|
*/
|
||||||
|
export function useRecordRouteSync(options: {
|
||||||
|
searchForm: Ref<Record<string, unknown>>
|
||||||
|
activeDatePreset: Ref<DatePresetKey | null>
|
||||||
|
onSearch: (params: Record<string, unknown>) => void
|
||||||
|
withType?: boolean
|
||||||
|
skipImmediateTableLoad?: boolean
|
||||||
|
}) {
|
||||||
|
const route = useRoute()
|
||||||
|
const routeInit = getRecordRouteInit(route.query, options.withType)
|
||||||
|
let lastQueryKey = buildRecordRouteQueryKey(route.query, options.withType)
|
||||||
|
|
||||||
|
const applyRoute = () => {
|
||||||
|
const hasFilter = syncSearchFormFromRoute(
|
||||||
|
route.query,
|
||||||
|
options.searchForm,
|
||||||
|
options.activeDatePreset,
|
||||||
|
{ withType: options.withType }
|
||||||
|
)
|
||||||
|
if (hasFilter) {
|
||||||
|
options.onSearch({ ...options.searchForm.value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (routeInit.hasFilter) {
|
||||||
|
applyRoute()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => buildRecordRouteQueryKey(route.query, options.withType),
|
||||||
|
(newKey) => {
|
||||||
|
if (newKey === lastQueryKey) return
|
||||||
|
lastQueryKey = newKey
|
||||||
|
applyRoute()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
const key = buildRecordRouteQueryKey(route.query, options.withType)
|
||||||
|
if (key !== lastQueryKey) {
|
||||||
|
lastQueryKey = key
|
||||||
|
applyRoute()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
routeInit,
|
||||||
|
skipImmediateTableLoad: options.skipImmediateTableLoad ?? routeInit.hasFilter
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="art-full-height">
|
<div class="art-full-height">
|
||||||
|
<QuickDateRangeBar v-model="activeDatePreset" @select="handleQuickDateSelect" />
|
||||||
<!-- 搜索面板 -->
|
<!-- 搜索面板 -->
|
||||||
<TableSearch v-model="searchForm" @search="handleSearch" @reset="handleResetSearch" />
|
<TableSearch v-model="searchForm" @search="handleSearch" @reset="handleResetSearch" />
|
||||||
|
|
||||||
@@ -138,13 +139,24 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import { useTable } from '@/hooks/core/useTable'
|
import { useTable } from '@/hooks/core/useTable'
|
||||||
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
||||||
import api from '../../api/play_record/index'
|
import api from '../../api/play_record/index'
|
||||||
import TableSearch from './modules/table-search.vue'
|
import TableSearch from './modules/table-search.vue'
|
||||||
import EditDialog from './modules/edit-dialog.vue'
|
import EditDialog from './modules/edit-dialog.vue'
|
||||||
import { lotteryPoolRowLabel } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
|
import { lotteryPoolRowLabel } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
|
||||||
|
import QuickDateRangeBar from '@/views/plugin/dice/components/QuickDateRangeBar.vue'
|
||||||
|
import {
|
||||||
|
detectPresetFromRange,
|
||||||
|
type DatePresetKey
|
||||||
|
} from '@/views/plugin/dice/utils/dateRangePresets'
|
||||||
|
import { getRecordRouteInit, useRecordRouteSync } from '@/views/plugin/dice/composables/useRecordRouteQuery'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const route = useRoute()
|
||||||
|
const routeInit = getRecordRouteInit(route.query)
|
||||||
|
const activeDatePreset = ref<DatePresetKey | null>(routeInit.activePreset)
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = ref<Record<string, unknown>>({
|
const searchForm = ref<Record<string, unknown>>({
|
||||||
@@ -159,7 +171,7 @@
|
|||||||
reward_ui_text: undefined,
|
reward_ui_text: undefined,
|
||||||
reward_tier: undefined,
|
reward_tier: undefined,
|
||||||
direction: undefined,
|
direction: undefined,
|
||||||
create_time: undefined
|
create_time: routeInit.create_time
|
||||||
})
|
})
|
||||||
|
|
||||||
const PLAY_RECORD_SEARCH_KEYS = [
|
const PLAY_RECORD_SEARCH_KEYS = [
|
||||||
@@ -206,11 +218,29 @@
|
|||||||
getData()
|
getData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleQuickDateSelect = (range: [string, string], preset: DatePresetKey) => {
|
||||||
|
searchForm.value.create_time = range
|
||||||
|
activeDatePreset.value = preset
|
||||||
|
handleSearch({ ...searchForm.value })
|
||||||
|
}
|
||||||
|
|
||||||
const handleResetSearch = () => {
|
const handleResetSearch = () => {
|
||||||
|
activeDatePreset.value = null
|
||||||
searchForm.value.create_time = undefined
|
searchForm.value.create_time = undefined
|
||||||
resetSearchParams()
|
resetSearchParams()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => searchForm.value.create_time,
|
||||||
|
(range) => {
|
||||||
|
if (Array.isArray(range) && range.length === 2) {
|
||||||
|
activeDatePreset.value = detectPresetFromRange([range[0], range[1]])
|
||||||
|
} else {
|
||||||
|
activeDatePreset.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const usernameFormatter = (row: Record<string, any>) =>
|
const usernameFormatter = (row: Record<string, any>) =>
|
||||||
row?.dicePlayer?.username ?? row?.player_id ?? '-'
|
row?.dicePlayer?.username ?? row?.player_id ?? '-'
|
||||||
const lotteryConfigNameFormatter = (row: Record<string, any>) => lotteryPoolRowLabel(row)
|
const lotteryConfigNameFormatter = (row: Record<string, any>) => lotteryPoolRowLabel(row)
|
||||||
@@ -257,6 +287,7 @@
|
|||||||
apiFn: listApi,
|
apiFn: listApi,
|
||||||
apiParams: { limit: 100 },
|
apiParams: { limit: 100 },
|
||||||
excludeParams: ['create_time'],
|
excludeParams: ['create_time'],
|
||||||
|
immediate: !routeInit.hasFilter,
|
||||||
columnsFactory: () => [
|
columnsFactory: () => [
|
||||||
// { type: 'selection' },
|
// { type: 'selection' },
|
||||||
{ prop: 'id', label: 'page.table.id', width: 80 },
|
{ prop: 'id', label: 'page.table.id', width: 80 },
|
||||||
@@ -320,6 +351,12 @@
|
|||||||
handleSelectionChange
|
handleSelectionChange
|
||||||
// selectedRows
|
// selectedRows
|
||||||
} = useSaiAdmin()
|
} = useSaiAdmin()
|
||||||
|
|
||||||
|
useRecordRouteSync({
|
||||||
|
searchForm,
|
||||||
|
activeDatePreset,
|
||||||
|
onSearch: handleSearch
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="art-full-height">
|
<div class="art-full-height">
|
||||||
|
<QuickDateRangeBar v-model="activeDatePreset" @select="handleQuickDateSelect" />
|
||||||
<!-- 搜索条件 -->
|
<!-- 搜索条件 -->
|
||||||
<TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" />
|
<TableSearch v-model="searchForm" @search="handleSearch" @reset="handleResetSearch" />
|
||||||
|
|
||||||
<ElCard class="art-table-card" shadow="never">
|
<ElCard class="art-table-card" shadow="never">
|
||||||
<!-- 表格操作 -->
|
<!-- 表格操作 -->
|
||||||
@@ -108,6 +109,7 @@
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useClipboard } from '@vueuse/core'
|
import { useClipboard } from '@vueuse/core'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import { useTable } from '@/hooks/core/useTable'
|
import { useTable } from '@/hooks/core/useTable'
|
||||||
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
||||||
import api from '../../api/player/index'
|
import api from '../../api/player/index'
|
||||||
@@ -116,9 +118,18 @@
|
|||||||
import EditDialog from './modules/edit-dialog.vue'
|
import EditDialog from './modules/edit-dialog.vue'
|
||||||
import WalletOperateDialog from './modules/WalletOperateDialog.vue'
|
import WalletOperateDialog from './modules/WalletOperateDialog.vue'
|
||||||
import { lotteryPoolRowLabel } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
|
import { lotteryPoolRowLabel } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
|
||||||
|
import QuickDateRangeBar from '@/views/plugin/dice/components/QuickDateRangeBar.vue'
|
||||||
|
import {
|
||||||
|
detectPresetFromRange,
|
||||||
|
type DatePresetKey
|
||||||
|
} from '@/views/plugin/dice/utils/dateRangePresets'
|
||||||
|
import { getRecordRouteInit, useRecordRouteSync } from '@/views/plugin/dice/composables/useRecordRouteQuery'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { copy } = useClipboard()
|
const { copy } = useClipboard()
|
||||||
|
const route = useRoute()
|
||||||
|
const routeInit = getRecordRouteInit(route.query)
|
||||||
|
const activeDatePreset = ref<DatePresetKey | null>(routeInit.activePreset)
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = ref({
|
const searchForm = ref({
|
||||||
@@ -127,15 +138,64 @@
|
|||||||
phone: undefined,
|
phone: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
coin: undefined,
|
coin: undefined,
|
||||||
lottery_config_id: undefined
|
lottery_config_id: undefined,
|
||||||
|
create_time: routeInit.create_time
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const PLAYER_SEARCH_KEYS = [
|
||||||
|
'username',
|
||||||
|
'name',
|
||||||
|
'phone',
|
||||||
|
'status',
|
||||||
|
'coin',
|
||||||
|
'lottery_config_id',
|
||||||
|
'create_time_min',
|
||||||
|
'create_time_max'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const applySearchParams = (params: Record<string, unknown>) => {
|
||||||
|
const p = { ...params }
|
||||||
|
if (Array.isArray(p.create_time) && p.create_time.length === 2) {
|
||||||
|
p.create_time_min = p.create_time[0]
|
||||||
|
p.create_time_max = p.create_time[1]
|
||||||
|
}
|
||||||
|
delete p.create_time
|
||||||
|
const paramsRecord = searchParams as Record<string, unknown>
|
||||||
|
PLAYER_SEARCH_KEYS.forEach((key) => {
|
||||||
|
delete paramsRecord[key]
|
||||||
|
})
|
||||||
|
Object.assign(searchParams, p)
|
||||||
|
}
|
||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = (params: Record<string, any>) => {
|
const handleSearch = (params: Record<string, any>) => {
|
||||||
Object.assign(searchParams, params)
|
applySearchParams(params)
|
||||||
getData()
|
getData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleQuickDateSelect = (range: [string, string], preset: DatePresetKey) => {
|
||||||
|
searchForm.value.create_time = range
|
||||||
|
activeDatePreset.value = preset
|
||||||
|
handleSearch({ ...searchForm.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResetSearch = () => {
|
||||||
|
activeDatePreset.value = null
|
||||||
|
searchForm.value.create_time = undefined
|
||||||
|
resetSearchParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => searchForm.value.create_time,
|
||||||
|
(range) => {
|
||||||
|
if (Array.isArray(range) && range.length === 2) {
|
||||||
|
activeDatePreset.value = detectPresetFromRange([range[0], range[1]])
|
||||||
|
} else {
|
||||||
|
activeDatePreset.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 权重列显示为百分比
|
// 权重列显示为百分比
|
||||||
const weightFormatter = (prop: string) => (row: any) => {
|
const weightFormatter = (prop: string) => (row: any) => {
|
||||||
const cellValue = row[prop]
|
const cellValue = row[prop]
|
||||||
@@ -167,6 +227,8 @@
|
|||||||
} = useTable({
|
} = useTable({
|
||||||
core: {
|
core: {
|
||||||
apiFn: api.list,
|
apiFn: api.list,
|
||||||
|
excludeParams: ['create_time'],
|
||||||
|
immediate: !routeInit.hasFilter,
|
||||||
columnsFactory: () => [
|
columnsFactory: () => [
|
||||||
{ type: 'selection' },
|
{ type: 'selection' },
|
||||||
{ prop: 'username', label: 'page.table.username', align: 'center' },
|
{ prop: 'username', label: 'page.table.username', align: 'center' },
|
||||||
@@ -300,4 +362,10 @@
|
|||||||
ElMessage.warning(msg)
|
ElMessage.warning(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useRecordRouteSync({
|
||||||
|
searchForm,
|
||||||
|
activeDatePreset,
|
||||||
|
onSearch: handleSearch
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -60,6 +60,19 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col v-bind="setSpan(8)">
|
||||||
|
<el-form-item :label="$t('page.search.createTime')" prop="create_time">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.create_time"
|
||||||
|
type="datetimerange"
|
||||||
|
:range-separator="$t('table.searchBar.rangeSeparator')"
|
||||||
|
:start-placeholder="$t('table.searchBar.startTime')"
|
||||||
|
:end-placeholder="$t('table.searchBar.endTime')"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
</sa-search-bar>
|
</sa-search-bar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="art-full-height">
|
<div class="art-full-height">
|
||||||
|
<QuickDateRangeBar v-model="activeDatePreset" @select="handleQuickDateSelect" />
|
||||||
<!-- 搜索面板 -->
|
<!-- 搜索面板 -->
|
||||||
<TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" />
|
<TableSearch v-model="searchForm" @search="handleSearch" @reset="handleResetSearch" />
|
||||||
|
|
||||||
<ElCard class="art-table-card" shadow="never">
|
<ElCard class="art-table-card" shadow="never">
|
||||||
<!-- 表格头部 -->
|
<!-- 表格头部 -->
|
||||||
@@ -11,6 +12,14 @@
|
|||||||
{{ $t('page.toolbar.coinChangeSummary') }}:<strong :class="coinSummaryClass">{{
|
{{ $t('page.toolbar.coinChangeSummary') }}:<strong :class="coinSummaryClass">{{
|
||||||
formatMoney2(totalCoinChange)
|
formatMoney2(totalCoinChange)
|
||||||
}}</strong>
|
}}</strong>
|
||||||
|
<template v-if="totalCoinInflow !== null && totalCoinOutflow !== null">
|
||||||
|
({{ $t('page.toolbar.coinInflow') }} <strong class="coin-summary-positive">{{
|
||||||
|
formatMoney2(totalCoinInflow)
|
||||||
|
}}</strong>
|
||||||
|
/ {{ $t('page.toolbar.coinOutflow') }} <strong class="coin-summary-negative">{{
|
||||||
|
formatMoney2(totalCoinOutflow)
|
||||||
|
}}</strong>)
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<!-- <ElSpace wrap>-->
|
<!-- <ElSpace wrap>-->
|
||||||
<!-- <ElButton-->
|
<!-- <ElButton-->
|
||||||
@@ -87,24 +96,52 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import { useTable } from '@/hooks/core/useTable'
|
import { useTable } from '@/hooks/core/useTable'
|
||||||
import { defaultResponseAdapter } from '@/utils/table/tableUtils'
|
import { defaultResponseAdapter } from '@/utils/table/tableUtils'
|
||||||
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
||||||
import api from '../../api/player_wallet_record/index'
|
import api from '../../api/player_wallet_record/index'
|
||||||
import TableSearch from './modules/table-search.vue'
|
import TableSearch from './modules/table-search.vue'
|
||||||
import EditDialog from './modules/edit-dialog.vue'
|
import EditDialog from './modules/edit-dialog.vue'
|
||||||
|
import QuickDateRangeBar from '@/views/plugin/dice/components/QuickDateRangeBar.vue'
|
||||||
|
import {
|
||||||
|
detectPresetFromRange,
|
||||||
|
type DatePresetKey
|
||||||
|
} from '@/views/plugin/dice/utils/dateRangePresets'
|
||||||
|
import { getRecordRouteInit, useRecordRouteSync } from '@/views/plugin/dice/composables/useRecordRouteQuery'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const routeInit = getRecordRouteInit(route.query, true)
|
||||||
|
const activeDatePreset = ref<DatePresetKey | null>(routeInit.activePreset)
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = ref({
|
const searchForm = ref({
|
||||||
type: undefined,
|
type: undefined as number | undefined,
|
||||||
username: undefined,
|
username: undefined,
|
||||||
coin_min: undefined,
|
coin_min: undefined,
|
||||||
coin_max: undefined,
|
coin_max: undefined,
|
||||||
create_time: undefined as [string, string] | undefined
|
create_time: routeInit.create_time
|
||||||
})
|
})
|
||||||
|
if (routeInit.type !== undefined) {
|
||||||
|
searchForm.value.type = routeInit.type
|
||||||
|
}
|
||||||
|
|
||||||
/** 当前筛选条件下平台币变化合计 */
|
/** 当前筛选条件下平台币净变化、流入、流出 */
|
||||||
const totalCoinChange = ref<number | null>(null)
|
const totalCoinChange = ref<number | null>(null)
|
||||||
|
const totalCoinInflow = ref<number | null>(null)
|
||||||
|
const totalCoinOutflow = ref<number | null>(null)
|
||||||
|
|
||||||
|
const applyCoinSummary = (res: Record<string, unknown> | undefined) => {
|
||||||
|
const summary = res?.total_coin_change
|
||||||
|
totalCoinChange.value =
|
||||||
|
summary !== undefined && summary !== null && summary !== '' ? Number(summary) : null
|
||||||
|
const inflow = res?.total_coin_inflow
|
||||||
|
totalCoinInflow.value =
|
||||||
|
inflow !== undefined && inflow !== null && inflow !== '' ? Number(inflow) : null
|
||||||
|
const outflow = res?.total_coin_outflow
|
||||||
|
totalCoinOutflow.value =
|
||||||
|
outflow !== undefined && outflow !== null && outflow !== '' ? Number(outflow) : null
|
||||||
|
}
|
||||||
|
|
||||||
const coinSummaryClass = computed(() => {
|
const coinSummaryClass = computed(() => {
|
||||||
if (totalCoinChange.value === null) return ''
|
if (totalCoinChange.value === null) return ''
|
||||||
@@ -128,9 +165,7 @@
|
|||||||
const reqId = ++summaryRequestSeq
|
const reqId = ++summaryRequestSeq
|
||||||
const res = await api.list(params)
|
const res = await api.list(params)
|
||||||
if (reqId === summaryRequestSeq) {
|
if (reqId === summaryRequestSeq) {
|
||||||
const summary = (res as Record<string, unknown> | undefined)?.total_coin_change
|
applyCoinSummary(res as Record<string, unknown> | undefined)
|
||||||
totalCoinChange.value =
|
|
||||||
summary !== undefined && summary !== null && summary !== '' ? Number(summary) : null
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@@ -155,6 +190,29 @@
|
|||||||
getData()
|
getData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleQuickDateSelect = (range: [string, string], preset: DatePresetKey) => {
|
||||||
|
searchForm.value.create_time = range
|
||||||
|
activeDatePreset.value = preset
|
||||||
|
handleSearch({ ...searchForm.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResetSearch = () => {
|
||||||
|
activeDatePreset.value = null
|
||||||
|
searchForm.value.create_time = undefined
|
||||||
|
resetSearchParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => searchForm.value.create_time,
|
||||||
|
(range) => {
|
||||||
|
if (Array.isArray(range) && range.length === 2) {
|
||||||
|
activeDatePreset.value = detectPresetFromRange([range[0], range[1]])
|
||||||
|
} else {
|
||||||
|
activeDatePreset.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
// 类型展示:0=充值 1=提现 2=购买抽奖次数 3=管理员加点 4=管理员扣点 5=抽奖
|
// 类型展示:0=充值 1=提现 2=购买抽奖次数 3=管理员加点 4=管理员扣点 5=抽奖
|
||||||
const typeFormatter = (row: Record<string, unknown>) => {
|
const typeFormatter = (row: Record<string, unknown>) => {
|
||||||
@@ -218,6 +276,7 @@
|
|||||||
apiFn: listApi,
|
apiFn: listApi,
|
||||||
apiParams: { limit: 100 },
|
apiParams: { limit: 100 },
|
||||||
excludeParams: ['create_time'],
|
excludeParams: ['create_time'],
|
||||||
|
immediate: !routeInit.hasFilter,
|
||||||
columnsFactory: () => [
|
columnsFactory: () => [
|
||||||
{ type: 'selection', align: 'center' },
|
{ type: 'selection', align: 'center' },
|
||||||
{ prop: 'id', label: 'page.table.id', width: 80, align: 'center' },
|
{ prop: 'id', label: 'page.table.id', width: 80, align: 'center' },
|
||||||
@@ -281,19 +340,22 @@
|
|||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
onSuccess(_data, response) {
|
onSuccess(_data, response) {
|
||||||
const raw = response as unknown as Record<string, unknown>
|
applyCoinSummary(response as unknown as Record<string, unknown>)
|
||||||
const summary = raw?.total_coin_change
|
|
||||||
if (summary !== undefined && summary !== null && summary !== '') {
|
|
||||||
totalCoinChange.value = Number(summary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transform: {
|
transform: {
|
||||||
responseAdapter(response) {
|
responseAdapter(response) {
|
||||||
const raw = (response ?? {}) as Record<string, unknown>
|
const raw = (response ?? {}) as Record<string, unknown>
|
||||||
const base = defaultResponseAdapter(response)
|
const base = defaultResponseAdapter(response)
|
||||||
|
const extra = base as Record<string, unknown>
|
||||||
if (raw.total_coin_change !== undefined && raw.total_coin_change !== null) {
|
if (raw.total_coin_change !== undefined && raw.total_coin_change !== null) {
|
||||||
;(base as Record<string, unknown>).total_coin_change = raw.total_coin_change
|
extra.total_coin_change = raw.total_coin_change
|
||||||
|
}
|
||||||
|
if (raw.total_coin_inflow !== undefined && raw.total_coin_inflow !== null) {
|
||||||
|
extra.total_coin_inflow = raw.total_coin_inflow
|
||||||
|
}
|
||||||
|
if (raw.total_coin_outflow !== undefined && raw.total_coin_outflow !== null) {
|
||||||
|
extra.total_coin_outflow = raw.total_coin_outflow
|
||||||
}
|
}
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
@@ -311,6 +373,13 @@
|
|||||||
handleSelectionChange
|
handleSelectionChange
|
||||||
// selectedRows
|
// selectedRows
|
||||||
} = useSaiAdmin()
|
} = useSaiAdmin()
|
||||||
|
|
||||||
|
useRecordRouteSync({
|
||||||
|
searchForm,
|
||||||
|
activeDatePreset,
|
||||||
|
onSearch: handleSearch,
|
||||||
|
withType: true
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { router } from '@/router'
|
||||||
|
import type { DatePresetKey } from './dateRangePresets'
|
||||||
|
|
||||||
|
export const WALLET_RECORD_PATH = '/dice/player_wallet_record/index'
|
||||||
|
export const PLAY_RECORD_PATH = '/dice/play_record/index'
|
||||||
|
export const PLAYER_PATH = '/dice/player/index'
|
||||||
|
|
||||||
|
export interface WalletRecordNavOptions {
|
||||||
|
type?: number
|
||||||
|
date?: string | null
|
||||||
|
datePreset?: DatePresetKey
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayRecordNavOptions {
|
||||||
|
date?: string | null
|
||||||
|
datePreset?: DatePresetKey
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDateQuery(opts: { date?: string | null; datePreset?: DatePresetKey }) {
|
||||||
|
const query: Record<string, string> = {}
|
||||||
|
if (opts.date) {
|
||||||
|
query.date = opts.date
|
||||||
|
} else if (opts.datePreset) {
|
||||||
|
query.datePreset = opts.datePreset
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildWalletRecordRouteQuery(opts: WalletRecordNavOptions): Record<string, string> {
|
||||||
|
const query = buildDateQuery(opts)
|
||||||
|
if (opts.type !== undefined && opts.type !== null) {
|
||||||
|
query.type = String(opts.type)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildPlayRecordRouteQuery(opts: PlayRecordNavOptions): Record<string, string> {
|
||||||
|
return buildDateQuery(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openWalletRecord(opts: WalletRecordNavOptions) {
|
||||||
|
const query = buildWalletRecordRouteQuery(opts)
|
||||||
|
void router.push({ path: WALLET_RECORD_PATH, query }).catch(() => undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openPlayRecord(opts: PlayRecordNavOptions) {
|
||||||
|
const query = buildPlayRecordRouteQuery(opts)
|
||||||
|
void router.push({ path: PLAY_RECORD_PATH, query }).catch(() => undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openPlayerList(opts: PlayRecordNavOptions) {
|
||||||
|
const query = buildPlayRecordRouteQuery(opts)
|
||||||
|
void router.push({ path: PLAYER_PATH, query }).catch(() => undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 工作台当前统计日期转跳转参数:有选中日用 date,清空周统计时用近7天 */
|
||||||
|
export function dashboardDateNavParams(selectedDate: string | null): {
|
||||||
|
date?: string
|
||||||
|
datePreset?: DatePresetKey
|
||||||
|
} {
|
||||||
|
if (selectedDate) {
|
||||||
|
return { date: selectedDate }
|
||||||
|
}
|
||||||
|
return { datePreset: 'last7days' }
|
||||||
|
}
|
||||||
101
saiadmin-artd/src/views/plugin/dice/utils/dateRangePresets.ts
Normal file
101
saiadmin-artd/src/views/plugin/dice/utils/dateRangePresets.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import type { LocationQuery } from 'vue-router'
|
||||||
|
|
||||||
|
export type DatePresetKey = 'today' | 'yesterday' | 'last7days'
|
||||||
|
|
||||||
|
function pad2(n: number): string {
|
||||||
|
return String(n).padStart(2, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDateYmd(date: Date): string {
|
||||||
|
return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function startOfDay(date: Date): Date {
|
||||||
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function endOfDay(date: Date): Date {
|
||||||
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDateTime(date: Date): string {
|
||||||
|
return `${formatDateYmd(date)} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCreateTimeRangeByPreset(preset: DatePresetKey): [string, string] {
|
||||||
|
const now = new Date()
|
||||||
|
if (preset === 'today') {
|
||||||
|
return [formatDateTime(startOfDay(now)), formatDateTime(endOfDay(now))]
|
||||||
|
}
|
||||||
|
if (preset === 'yesterday') {
|
||||||
|
const day = new Date(now)
|
||||||
|
day.setDate(day.getDate() - 1)
|
||||||
|
return [formatDateTime(startOfDay(day)), formatDateTime(endOfDay(day))]
|
||||||
|
}
|
||||||
|
const start = new Date(now)
|
||||||
|
start.setDate(start.getDate() - 6)
|
||||||
|
return [formatDateTime(startOfDay(start)), formatDateTime(endOfDay(now))]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCreateTimeRangeByDate(ymd: string): [string, string] | undefined {
|
||||||
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(ymd)) return undefined
|
||||||
|
const parts = ymd.split('-').map((v) => Number(v))
|
||||||
|
const date = new Date(parts[0], parts[1] - 1, parts[2])
|
||||||
|
if (Number.isNaN(date.getTime())) return undefined
|
||||||
|
return [formatDateTime(startOfDay(date)), formatDateTime(endOfDay(date))]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function detectPresetFromRange(range?: [string, string] | null): DatePresetKey | null {
|
||||||
|
if (!range || range.length !== 2) return null
|
||||||
|
const presets: DatePresetKey[] = ['today', 'yesterday', 'last7days']
|
||||||
|
for (const preset of presets) {
|
||||||
|
const expected = getCreateTimeRangeByPreset(preset)
|
||||||
|
if (expected[0] === range[0] && expected[1] === range[1]) {
|
||||||
|
return preset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function readQueryString(query: LocationQuery, key: string): string {
|
||||||
|
const raw = query[key]
|
||||||
|
if (Array.isArray(raw)) return raw[0] ? String(raw[0]) : ''
|
||||||
|
return raw != null ? String(raw) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecordRouteFilter {
|
||||||
|
create_time?: [string, string]
|
||||||
|
type?: number
|
||||||
|
datePreset?: DatePresetKey
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRecordRouteQuery(query: LocationQuery): RecordRouteFilter {
|
||||||
|
const result: RecordRouteFilter = {}
|
||||||
|
const date = readQueryString(query, 'date')
|
||||||
|
const datePreset = readQueryString(query, 'datePreset') as DatePresetKey
|
||||||
|
|
||||||
|
if (date) {
|
||||||
|
const range = getCreateTimeRangeByDate(date)
|
||||||
|
if (range) {
|
||||||
|
result.create_time = range
|
||||||
|
}
|
||||||
|
} else if (datePreset === 'today' || datePreset === 'yesterday' || datePreset === 'last7days') {
|
||||||
|
result.datePreset = datePreset
|
||||||
|
result.create_time = getCreateTimeRangeByPreset(datePreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeRaw = readQueryString(query, 'type')
|
||||||
|
if (typeRaw !== '' && typeRaw !== undefined) {
|
||||||
|
const typeNum = Number(typeRaw)
|
||||||
|
if (Number.isFinite(typeNum)) {
|
||||||
|
result.type = typeNum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasRecordRouteFilter(query: LocationQuery): boolean {
|
||||||
|
const filter = parseRecordRouteQuery(query)
|
||||||
|
return filter.create_time != null || filter.type !== undefined
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
# 数据库配置
|
# 数据库配置
|
||||||
|
|
||||||
DB_TYPE=mysql
|
DB_TYPE=mysql
|
||||||
DB_HOST=127.0.0.1
|
DB_HOST=127.0.0.1
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
|
|||||||
@@ -25,16 +25,13 @@ class DiceDashboardController extends BaseController
|
|||||||
#[Permission('工作台数据统计', 'core:console:list')]
|
#[Permission('工作台数据统计', 'core:console:list')]
|
||||||
public function statistics(Request $request): Response
|
public function statistics(Request $request): Response
|
||||||
{
|
{
|
||||||
$thisWeekStart = date('Y-m-d 00:00:00', strtotime('monday this week'));
|
[$thisStart, $thisEnd, $lastStart, $lastEnd] = $this->resolveDateRanges($request);
|
||||||
$thisWeekEnd = date('Y-m-d 23:59:59', strtotime('sunday this week'));
|
|
||||||
$lastWeekStart = date('Y-m-d 00:00:00', strtotime('monday last week'));
|
|
||||||
$lastWeekEnd = date('Y-m-d 23:59:59', strtotime('sunday last week'));
|
|
||||||
|
|
||||||
$adminInfo = $this->adminInfo ?? null;
|
$adminInfo = $this->adminInfo ?? null;
|
||||||
$filterDeptId = $request->input('dept_id');
|
$filterDeptId = $request->input('dept_id');
|
||||||
|
|
||||||
$playerQueryThis = DicePlayer::whereBetween('create_time', [$thisWeekStart, $thisWeekEnd]);
|
$playerQueryThis = DicePlayer::whereBetween('create_time', [$thisStart, $thisEnd]);
|
||||||
$playerQueryLast = DicePlayer::whereBetween('create_time', [$lastWeekStart, $lastWeekEnd]);
|
$playerQueryLast = DicePlayer::whereBetween('create_time', [$lastStart, $lastEnd]);
|
||||||
AdminScopeHelper::applyAdminScope($playerQueryThis, $adminInfo, $filterDeptId);
|
AdminScopeHelper::applyAdminScope($playerQueryThis, $adminInfo, $filterDeptId);
|
||||||
AdminScopeHelper::applyAdminScope($playerQueryLast, $adminInfo, $filterDeptId);
|
AdminScopeHelper::applyAdminScope($playerQueryLast, $adminInfo, $filterDeptId);
|
||||||
$playerThis = $playerQueryThis->count();
|
$playerThis = $playerQueryThis->count();
|
||||||
@@ -42,35 +39,35 @@ class DiceDashboardController extends BaseController
|
|||||||
|
|
||||||
$chargeQueryThis = DicePlayerWalletRecord::where('type', 0)
|
$chargeQueryThis = DicePlayerWalletRecord::where('type', 0)
|
||||||
->where('coin', '>', 0)
|
->where('coin', '>', 0)
|
||||||
->whereBetween('create_time', [$thisWeekStart, $thisWeekEnd]);
|
->whereBetween('create_time', [$thisStart, $thisEnd]);
|
||||||
$chargeQueryLast = DicePlayerWalletRecord::where('type', 0)
|
$chargeQueryLast = DicePlayerWalletRecord::where('type', 0)
|
||||||
->where('coin', '>', 0)
|
->where('coin', '>', 0)
|
||||||
->whereBetween('create_time', [$lastWeekStart, $lastWeekEnd]);
|
->whereBetween('create_time', [$lastStart, $lastEnd]);
|
||||||
AdminScopeHelper::applyAdminScope($chargeQueryThis, $adminInfo, $filterDeptId);
|
AdminScopeHelper::applyAdminScope($chargeQueryThis, $adminInfo, $filterDeptId);
|
||||||
AdminScopeHelper::applyAdminScope($chargeQueryLast, $adminInfo, $filterDeptId);
|
AdminScopeHelper::applyAdminScope($chargeQueryLast, $adminInfo, $filterDeptId);
|
||||||
$chargeThis = $chargeQueryThis->sum('coin');
|
$chargeThis = $chargeQueryThis->sum('coin');
|
||||||
$chargeLast = $chargeQueryLast->sum('coin');
|
$chargeLast = $chargeQueryLast->sum('coin');
|
||||||
|
|
||||||
$withdrawQueryThis = DicePlayerWalletRecord::where('type', 1)
|
$withdrawQueryThis = DicePlayerWalletRecord::where('type', 1)
|
||||||
->whereBetween('create_time', [$thisWeekStart, $thisWeekEnd]);
|
->whereBetween('create_time', [$thisStart, $thisEnd]);
|
||||||
$withdrawQueryLast = DicePlayerWalletRecord::where('type', 1)
|
$withdrawQueryLast = DicePlayerWalletRecord::where('type', 1)
|
||||||
->whereBetween('create_time', [$lastWeekStart, $lastWeekEnd]);
|
->whereBetween('create_time', [$lastStart, $lastEnd]);
|
||||||
AdminScopeHelper::applyAdminScope($withdrawQueryThis, $adminInfo, $filterDeptId);
|
AdminScopeHelper::applyAdminScope($withdrawQueryThis, $adminInfo, $filterDeptId);
|
||||||
AdminScopeHelper::applyAdminScope($withdrawQueryLast, $adminInfo, $filterDeptId);
|
AdminScopeHelper::applyAdminScope($withdrawQueryLast, $adminInfo, $filterDeptId);
|
||||||
$withdrawThis = $withdrawQueryThis->sum(Db::raw('ABS(coin)'));
|
$withdrawThis = $withdrawQueryThis->sum(Db::raw('ABS(coin)'));
|
||||||
$withdrawLast = $withdrawQueryLast->sum(Db::raw('ABS(coin)'));
|
$withdrawLast = $withdrawQueryLast->sum(Db::raw('ABS(coin)'));
|
||||||
|
|
||||||
$playQueryThis = DicePlayRecord::whereBetween('create_time', [$thisWeekStart, $thisWeekEnd]);
|
$playQueryThis = DicePlayRecord::whereBetween('create_time', [$thisStart, $thisEnd]);
|
||||||
$playQueryLast = DicePlayRecord::whereBetween('create_time', [$lastWeekStart, $lastWeekEnd]);
|
$playQueryLast = DicePlayRecord::whereBetween('create_time', [$lastStart, $lastEnd]);
|
||||||
AdminScopeHelper::applyAdminScope($playQueryThis, $adminInfo, $filterDeptId);
|
AdminScopeHelper::applyAdminScope($playQueryThis, $adminInfo, $filterDeptId);
|
||||||
AdminScopeHelper::applyAdminScope($playQueryLast, $adminInfo, $filterDeptId);
|
AdminScopeHelper::applyAdminScope($playQueryLast, $adminInfo, $filterDeptId);
|
||||||
$playThis = $playQueryThis->count();
|
$playThis = $playQueryThis->count();
|
||||||
$playLast = $playQueryLast->count();
|
$playLast = $playQueryLast->count();
|
||||||
|
|
||||||
$playerChange = $this->calcWeekChange($playerThis, $playerLast);
|
$playerChange = $this->calcPeriodChange($playerThis, $playerLast);
|
||||||
$chargeChange = $this->calcWeekChange((float) $chargeThis, (float) $chargeLast);
|
$chargeChange = $this->calcPeriodChange((float) $chargeThis, (float) $chargeLast);
|
||||||
$withdrawChange = $this->calcWeekChange((float) $withdrawThis, (float) $withdrawLast);
|
$withdrawChange = $this->calcPeriodChange((float) $withdrawThis, (float) $withdrawLast);
|
||||||
$playChange = $this->calcWeekChange($playThis, $playLast);
|
$playChange = $this->calcPeriodChange($playThis, $playLast);
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'player_count' => $playerThis,
|
'player_count' => $playerThis,
|
||||||
@@ -81,6 +78,7 @@ class DiceDashboardController extends BaseController
|
|||||||
'withdraw_amount_change' => $withdrawChange,
|
'withdraw_amount_change' => $withdrawChange,
|
||||||
'play_count' => $playThis,
|
'play_count' => $playThis,
|
||||||
'play_count_change' => $playChange,
|
'play_count_change' => $playChange,
|
||||||
|
'date' => $this->resolveRequestDate($request),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,8 +177,9 @@ class DiceDashboardController extends BaseController
|
|||||||
$q->field('id,username');
|
$q->field('id,username');
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
->where('type', 0)
|
->where('type', 0);
|
||||||
->order('create_time', 'desc')
|
$this->applyDashboardDateFilter($query, $request);
|
||||||
|
$query->order('create_time', 'desc')
|
||||||
->limit(50);
|
->limit(50);
|
||||||
AdminScopeHelper::applyAdminScope($query, $adminInfo, $request->input('dept_id'));
|
AdminScopeHelper::applyAdminScope($query, $adminInfo, $request->input('dept_id'));
|
||||||
$list = $query->select();
|
$list = $query->select();
|
||||||
@@ -204,8 +203,9 @@ class DiceDashboardController extends BaseController
|
|||||||
public function newPlayerList(Request $request): Response
|
public function newPlayerList(Request $request): Response
|
||||||
{
|
{
|
||||||
$adminInfo = $this->adminInfo ?? null;
|
$adminInfo = $this->adminInfo ?? null;
|
||||||
$query = DicePlayer::field('username,coin,total_ticket_count,create_time')
|
$query = DicePlayer::field('username,coin,total_ticket_count,create_time');
|
||||||
->order('create_time', 'desc')
|
$this->applyDashboardDateFilter($query, $request);
|
||||||
|
$query->order('create_time', 'desc')
|
||||||
->limit(50);
|
->limit(50);
|
||||||
AdminScopeHelper::applyAdminScope($query, $adminInfo, $request->input('dept_id'));
|
AdminScopeHelper::applyAdminScope($query, $adminInfo, $request->input('dept_id'));
|
||||||
$list = $query->select();
|
$list = $query->select();
|
||||||
@@ -235,8 +235,9 @@ class DiceDashboardController extends BaseController
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
->where('status', 1)
|
->where('status', 1)
|
||||||
->field('id,player_id,reward_tier,win_coin,create_time')
|
->field('id,player_id,reward_tier,win_coin,create_time');
|
||||||
->order('create_time', 'desc')
|
$this->applyDashboardDateFilter($query, $request);
|
||||||
|
$query->order('create_time', 'desc')
|
||||||
->limit(50);
|
->limit(50);
|
||||||
AdminScopeHelper::applyAdminScope($query, $adminInfo, $request->input('dept_id'));
|
AdminScopeHelper::applyAdminScope($query, $adminInfo, $request->input('dept_id'));
|
||||||
$list = $query->select();
|
$list = $query->select();
|
||||||
@@ -275,7 +276,7 @@ class DiceDashboardController extends BaseController
|
|||||||
return $labels;
|
return $labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calcWeekChange($current, $last): float
|
private function calcPeriodChange($current, $last): float
|
||||||
{
|
{
|
||||||
if ($last == 0) {
|
if ($last == 0) {
|
||||||
return $current > 0 ? 100.0 : 0.0;
|
return $current > 0 ? 100.0 : 0.0;
|
||||||
@@ -283,6 +284,56 @@ class DiceDashboardController extends BaseController
|
|||||||
return round((($current - $last) / $last) * 100, 1);
|
return round((($current - $last) / $last) * 100, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析工作台统计区间:有 date 按单日对比昨日,无 date 按本周对比上周
|
||||||
|
*
|
||||||
|
* @return array{0: string, 1: string, 2: string, 3: string}
|
||||||
|
*/
|
||||||
|
private function resolveDateRanges(Request $request): array
|
||||||
|
{
|
||||||
|
$date = $this->resolveRequestDate($request);
|
||||||
|
if ($date === '') {
|
||||||
|
return [
|
||||||
|
date('Y-m-d 00:00:00', strtotime('monday this week')),
|
||||||
|
date('Y-m-d 23:59:59', strtotime('sunday this week')),
|
||||||
|
date('Y-m-d 00:00:00', strtotime('monday last week')),
|
||||||
|
date('Y-m-d 23:59:59', strtotime('sunday last week')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastDate = date('Y-m-d', strtotime($date . ' -1 day'));
|
||||||
|
|
||||||
|
return [
|
||||||
|
$date . ' 00:00:00',
|
||||||
|
$date . ' 23:59:59',
|
||||||
|
$lastDate . ' 00:00:00',
|
||||||
|
$lastDate . ' 23:59:59',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveRequestDate(Request $request): string
|
||||||
|
{
|
||||||
|
$date = trim((string) $request->input('date', ''));
|
||||||
|
if ($date === '' || strtotime($date) === false) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return date('Y-m-d', strtotime($date));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $query
|
||||||
|
*/
|
||||||
|
private function applyDashboardDateFilter($query, Request $request, string $column = 'create_time'): void
|
||||||
|
{
|
||||||
|
$date = $this->resolveRequestDate($request);
|
||||||
|
if ($date === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->whereBetween($column, [$date . ' 00:00:00', $date . ' 23:59:59']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 钱包流水 SQL 渠道条件;非超管无渠道时返回 __empty__
|
* 钱包流水 SQL 渠道条件;非超管无渠道时返回 __empty__
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -187,6 +187,8 @@ class DicePlayerController extends BaseController
|
|||||||
['status', ''],
|
['status', ''],
|
||||||
['coin', ''],
|
['coin', ''],
|
||||||
['lottery_config_id', ''],
|
['lottery_config_id', ''],
|
||||||
|
['create_time_min', ''],
|
||||||
|
['create_time_max', ''],
|
||||||
]);
|
]);
|
||||||
$query = $this->logic->search($where);
|
$query = $this->logic->search($where);
|
||||||
AdminScopeHelper::applyAdminScope($query, $this->adminInfo ?? null, $request->input('dept_id'));
|
AdminScopeHelper::applyAdminScope($query, $this->adminInfo ?? null, $request->input('dept_id'));
|
||||||
|
|||||||
@@ -48,10 +48,18 @@ class DicePlayerWalletRecordController extends BaseController
|
|||||||
['create_time_max', ''],
|
['create_time_max', ''],
|
||||||
]);
|
]);
|
||||||
$deptId = $request->input('dept_id');
|
$deptId = $request->input('dept_id');
|
||||||
$totalCoinChange = $this->logic->sumCoinBySearch($where, $this->adminInfo ?? null, $deptId);
|
|
||||||
|
|
||||||
$query = $this->logic->search($where);
|
$query = $this->logic->search($where);
|
||||||
AdminScopeHelper::applyAdminScope($query, $this->adminInfo ?? null, $deptId);
|
AdminScopeHelper::applyAdminScope($query, $this->adminInfo ?? null, $deptId);
|
||||||
|
|
||||||
|
$sumQuery = clone $query;
|
||||||
|
$totalCoinChange = round((float) $sumQuery->sum('coin'), 2);
|
||||||
|
|
||||||
|
$inflowQuery = clone $query;
|
||||||
|
$totalCoinInflow = round((float) $inflowQuery->where('coin', '>', 0)->sum('coin'), 2);
|
||||||
|
|
||||||
|
$outflowQuery = clone $query;
|
||||||
|
$totalCoinOutflow = round((float) $outflowQuery->where('coin', '<', 0)->sum('coin'), 2);
|
||||||
|
|
||||||
$query->with([
|
$query->with([
|
||||||
'dicePlayer',
|
'dicePlayer',
|
||||||
'operator',
|
'operator',
|
||||||
@@ -59,6 +67,8 @@ class DicePlayerWalletRecordController extends BaseController
|
|||||||
|
|
||||||
$data = $this->logic->getList($query);
|
$data = $this->logic->getList($query);
|
||||||
$data['total_coin_change'] = $totalCoinChange;
|
$data['total_coin_change'] = $totalCoinChange;
|
||||||
|
$data['total_coin_inflow'] = $totalCoinInflow;
|
||||||
|
$data['total_coin_outflow'] = $totalCoinOutflow;
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
namespace app\dice\logic\player_wallet_record;
|
namespace app\dice\logic\player_wallet_record;
|
||||||
|
|
||||||
use app\dice\helper\AdminScopeHelper;
|
|
||||||
use app\dice\basic\DiceBaseLogic;
|
use app\dice\basic\DiceBaseLogic;
|
||||||
use plugin\saiadmin\exception\ApiException;
|
use plugin\saiadmin\exception\ApiException;
|
||||||
use app\dice\model\player_wallet_record\DicePlayerWalletRecord;
|
use app\dice\model\player_wallet_record\DicePlayerWalletRecord;
|
||||||
@@ -28,18 +27,6 @@ class DicePlayerWalletRecordLogic extends DiceBaseLogic
|
|||||||
$this->setOrderType('DESC');
|
$this->setOrderType('DESC');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 按与列表相同的筛选条件汇总平台币变化(不含 with / 分页 / 排序)
|
|
||||||
*/
|
|
||||||
public function sumCoinBySearch(array $where, ?array $adminInfo, $requestDeptId = null): float
|
|
||||||
{
|
|
||||||
$query = $this->search($where);
|
|
||||||
AdminScopeHelper::applyAdminScope($query, $adminInfo, $requestDeptId);
|
|
||||||
$table = $this->model->getTable();
|
|
||||||
$sum = $query->sum($table . '.coin');
|
|
||||||
return round((float) $sum, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加数据(补全抽奖次数字段默认值)
|
* 添加数据(补全抽奖次数字段默认值)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -224,6 +224,26 @@ class DicePlayer extends DiceModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册时间起始 搜索
|
||||||
|
*/
|
||||||
|
public function searchCreateTimeMinAttr($query, $value)
|
||||||
|
{
|
||||||
|
if ($value !== '' && $value !== null) {
|
||||||
|
$query->where('create_time', '>=', $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册时间结束 搜索
|
||||||
|
*/
|
||||||
|
public function searchCreateTimeMaxAttr($query, $value)
|
||||||
|
{
|
||||||
|
if ($value !== '' && $value !== null) {
|
||||||
|
$query->where('create_time', '<=', $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关联彩金池配置
|
* 关联彩金池配置
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user