@@ -1,826 +1,929 @@
< template >
< div class = "default-main" >
< div class = "banner " >
< el-row :gutter = "10 ">
< el-col :md = "24" :lg = "18" >
< div class = "welcome suspension " >
< img class = "welcome-img" :src = "headerSvg" alt = "" / >
< div class = "welcome-text" >
< div class = "welcome-title" > { { adminInfo . nickname + t ( 'utils.comma' ) + getGreet ( ) } } < / div >
< div class = "welcome-note" > { { state . remark } } < / div >
< / div >
< / div >
< / el-col >
< el-col :lg = "6" class = "hidden-md-and-down" >
< div class = "working" >
< img class = "working-coffee" :src = "coffeeSvg" alt = "" / >
< div class = "working-text" >
{ { t ( 'dashboard.You have worked today' ) } } < span class = "time" > { { state . workingTimeFormat } } < / span >
< / div >
< div @click ="onChangeWorkState()" class = "working-opt working-rest" >
{ { state . pauseWork ? t ( 'dashboard.Continue to work' ) : t ( 'dashboard.have a bit of rest' ) } }
< / div >
< / div >
< / el-col >
< / el-row >
< / div >
< div class = "small-panel-box " >
< el-row :gutter = "20" >
< el-col :sm = "12" :lg = "6" >
< div class = "small-panel user-reg suspension" >
< div class = "small-panel-title" > { { t ( 'dashboard.Member registration' ) } } < / div >
< div class = "small-panel-content" >
< div class = "content-left" >
< Icon color = "#8595F4" size = "20" name = "fa fa-line-chart" / >
< el-statistic :value = "userRegNumberOutput" :value-style = "statisticValueStyle" / >
< / div >
< div class = "content-right" > + 14 % < / div >
< / div >
< / div >
< / el-col >
< el-col :sm = "12" :lg = "6" >
< div class = "small-panel file suspension" >
< div class = "small-panel-title" > { { t ( 'dashboard.Number of attachments Uploaded' ) } } < / div >
< div class = "small-panel-content" >
< div class = "content-left" >
< Icon color = "#AD85F4" size = "20" name = "fa fa-file-text" / >
< el-statistic :value = "fileNumberOutput" :value-style = "statisticValueStyle" / >
< / div >
< div class = "content-right" > + 50 % < / div >
< / div >
< / div >
< / el-col >
< el-col :sm = "12" :lg = "6" >
< div class = "small-panel users suspension" >
< div class = "small-panel-title" > { { t ( 'dashboard.Total number of members' ) } } < / div >
< div class = "small-panel-content" >
< div class = "content-left" >
< Icon color = "#74A8B5" size = "20" name = "fa fa-users" / >
< el-statistic :value = "usersNumberOutput" :value-style = "statisticValueStyle" / >
< / div >
< div class = "content-right" > + 28 % < / div >
< / div >
< / div >
< / el-col >
< el-col :sm = "12" :lg = "6" >
< div class = "small-panel addons suspension" >
< div class = "small-panel-title" > { { t ( 'dashboard.Number of installed plug-ins' ) } } < / div >
< div class = "small-panel-content" >
< div class = "content-left" >
< Icon color = "#F48595" size = "20" name = "fa fa-object-group" / >
< el-statistic :value = "addonsNumberOutput" :value-style = "statisticValueStyle" / >
< / div >
< div class = "content-right" > + 88 % < / div >
< / div >
< / div >
< / el-col >
< / el-row >
< / div >
< div class = "growth-chart" >
< el-row :gutter = "20" >
< el-col class = "lg-mb-20" :xs = "24" :sm = "24" :md = "12" :lg = "9" >
< el-card shadow = "hover" : header = "t('dashboard.Membership growth')" >
< div class = "user-growth-chart" :ref = "chartRefs.set" > < / div >
< / el-card >
< / el-col >
< el-col class = "lg-mb-20" :xs = "24" :sm = "24" :md = "12" :lg = "9" >
< el-card shadow = "hover" : header = "t('dashboard.Annex growth')" >
< div class = "file-growth-chart" :ref = "chartRefs.set" > < / div >
< / el-card >
< / el-col >
< el-col :xs = "24" :sm = "24" :md = "24" :lg = "6" >
< el-card class = "new-user-card" shadow = "hover" : header = "t('dashboard.New member')" >
< div class = "new-user-growth" >
< el-scrollbar >
< div class = "new-user-item" >
< img class = "new-user-avatar" src = "~assets/login-header.png" alt = "" / >
< div class = "new-user-base" >
< div class = "new-user-name" > 妙码生花 < / div >
< div class = "new-user-time" > 12 分钟前 { { t ( 'dashboard.Joined us' ) } } < / div >
< div class = "default-main bookkeeping-dashboard " >
< section class = "dashboard-grid " >
< div class = "dashboard-panel bank-panel ">
< div class = "panel-title" > Available Bank Balance < / div >
< div class = "table-scroll " >
< table class = "bank-table" >
< thead >
< tr >
< th > Bank Name < / th >
< th > Current Balance < / th >
< th > Transaction Breakdown < / th >
< th > Safe Alert < / th >
< / tr >
< / thead >
< tbody >
< tr v-for = "bank in visibleBanks" :key="bank.id" :class="bank.rowClass" >
< td >
< strong > { { bank . name } } < / strong >
< small > { { bank . account } } < / small >
< / td >
< td class = "balance" > AUD { { money ( bank . balance ) } } < / t d>
< td >
< div class = "bank-operate" >
< el-button size = "small" :icon = "Coin" circle / >
< el-button size = "small" :icon = "Switch" @click ="openTransfer(bank)" > Transfer < / el -button >
< div class = "breakdown " >
< span > < i class = "dot income" > ↓ < / i > ( { { bank . depositCount } } ) { { money ( bank . deposit ) } } < / span >
< span > < i class = "dot outcome" > ↑ < / i > ( { { bank . withdrawCount } } ) { { money ( bank . withdraw ) } } < / span >
< / div >
< / div >
< Icon class = "new-user-arrow" color = "#8595F4" name = "fa fa-angle-right" / >
< / div >
< div class = "new-user-item" >
< img class = "new-user-avatar" src = "~assets/login-header.png" alt = "" / >
< div class = "new-user-base" >
< div class = "new-user-name" > 码上生花 < / div >
< div class = "new-user-time" > 12 分钟前 { { t ( 'dashboard.Joined us' ) } } < / div >
< / div >
< Icon class = "new-user-arrow" color = "#8595F4" name = "fa fa-angle-right" / >
< / div >
< div class = "new-user-item" >
< img class = "new-user-avatar" src = "~assets/login-header.png" alt = "" / >
< div class = "new-user-base" >
< div class = "new-user-name" > Admin < / div >
< div class = "new-user-time" > 12 分钟前 { { t ( 'dashboard.Joined us' ) } } < / div >
< / div >
< Icon class = "new-user-arrow" color = "#8595F4" name = "fa fa-angle-right" / >
< / div >
< div class = "new-user-item" >
< img class = "new-user-avatar" :src = "fullUrl('/static/images/avatar.png')" alt = "" / >
< div class = "new-user-base" >
< div class = "new-user-name" > 纯属虚构 < / div >
< div class = "new-user-time" > 12 分钟前 { { t ( 'dashboard.Joined us' ) } } < / div >
< / div >
< Icon class = "new-user-arrow" color = "#8595F4" name = "fa fa-angle-right" / >
< / div >
< / el-scrollbar >
< / div >
< / el-card >
< / el-col >
< / el-row >
< / div >
< / td >
< td >
< el-tag size = "small" effect = "plain" : type = "bank.alert ? 'danger' : 'info'" > { { bank . alert || 'No Alert' } } < / el-tag >
< / td >
< / tr >
< tr v-if = "!visibleBanks.length" >
< t d class = "empty-banks" colspan = "4" > No bank data < / t d>
< / tr >
< / tbody >
< / table >
< / div >
< button v-if = "banks.length > 4" class="more-info" type="button" @click="showAllBanks = !showAllBanks" >
{{ showAllBanks ? ' show less ' : ' more info ' }}
< Icon : name = "showAllBanks ? 'fa fa-caret-up' : 'fa fa-caret-down'" / >
< / button >
< / div >
< div class = "growth-chart " >
< el-row :gutter = "20 ">
< el-col class = "lg-mb-20" :xs = "24" :sm = "24" :md = "24" :lg = "12" >
< el-card shadow = "hover" : header = "t('dashboard.Member source')" >
< div class = "user-source-chart" :ref = "chartRefs.set" > < / div >
< / el-card >
< / el-col >
< el-col class = "lg-mb-20" :xs = "24" :sm = "24" :md = "24" :lg = "12" >
< el-card shadow = "hover" : header = "t('dashboard.Member last name')" >
< div class = "user-surname-chart" :ref = "chartRefs.set" > < / div >
< / el-card >
< / el-col >
< / el-row >
< / div >
< aside class = "summary-side " >
< div class = "dashboard-panel summary-panel " >
< div class = "panel-title" > Customer Summary < / div >
< dl >
< template v-for = "item in summary" :key="item.label" >
< dt > { { item . label } } : < / dt >
< dd > { { item . value } } < / dd >
< / template >
< / dl >
< / div >
< el-button class = "create-button" type = "success" @click ="openCreate" > CREATE NEW TRANSACTION < / el-button >
< / aside >
< / section >
< el-alert class = "webhook-alert" type = "warning" :closable = "true" show -icon >
< template # title >
< strong > The Transaction Mode you currently set has the Webhook ( JDK ) feature enabled < / strong >
< / template >
< p >
Hint : If a transaction approved in the JK backend is not automatically recorded here within 10 minutes , click "Create New Transaction"
to record it manually .
< / p >
< p > 提示 : 如果 JK 后台 Approved 的 Transaction 在这里 10 分钟内没有自动记录 , 请点击 "Create New Transaction" 手动记录 。 < / p >
< / el-alert >
< section class = "transaction-section" >
< div class = "filter-row" >
< div class = "date-filter" >
< label > Start Date : < / label >
< el-date-picker v-model = "filters.startDate" type="date" value-format="YYYY-MM-DD" / >
< label > End Date : < / label >
< el-date-picker v-model = "filters.endDate" type="date" value-format="YYYY-MM-DD" / >
< el-button @click ="search" > Search < / el -button >
< el-button @click ="setToday" > Today < / el -button >
< / div >
< div class = "totals" >
< span > Date of data : { { dateRangeText } } < / span >
< b
> Total Deposit / IN : < em > AUD { { money ( totalDeposit ) } } < / em > < / b
>
< b
> Total Withdraw / OUT : < em > AUD { { money ( totalWithdraw ) } } < / em > < / b
>
< / div >
< / div >
< el-table :data = "transactions" border size = "small" class = "transaction-table" :row-class-name = "transactionRowClass" >
< el-table-column prop = "createdBy" label = "Created by" width = "110" / >
< el-table-column prop = "createdTime" label = "Created Time" width = "170" / >
< el-table-column prop = "category" label = "Category" width = "100" / >
< el-table-column prop = "username" label = "Username" width = "110" / >
< el-table-column prop = "remark" label = "Remark" min -width = " 205 " / >
< el-table-column prop = "bank" label = "Bank" min -width = " 165 " / >
< el-table-column prop = "type" label = "Type" width = "95" / >
< el-table-column label = "Amount (AUD)" width = "130" align = "right" >
< template # default = "{ row }" >
< strong : class = "row.flow === 'in' ? 'amount-in' : 'amount-out'" > { { money ( row . amount ) } } < / strong >
< / template >
< / el-table-column >
< el-table-column prop = "label" label = "Label" width = "100" / >
< el-table-column label = "Game Ticket" min -width = " 140 " >
< template # default = "{ row }" >
< div v-for = "(ticket, index) in row.ticket" :key="`${ticket}-${index}`" > {{ ticket }} < / div >
< / template >
< / el -table -column >
< el-table-column label = "Action" fixed = "right" width = "180" >
< template # default = "{ row }" >
< el-button link type = "primary" size = "small" @click ="openHistory(row)" > history < / el -button >
< el-button link type = "primary" size = "small" @click ="openEdit(row)" > edit < / el -button >
< el-button link type = "danger" size = "small" @click ="removeTransaction(row)" > delete < / el -button >
< / template >
< / el-table-column >
< / el-table >
< div class = "pagination" >
< el-pagination
v -model :current-page = "transactionPage.currentPage"
background
layout = "prev, pager, next"
:total = "transactionPage.count"
:page-size = "transactionPage.pageSize"
@ current -change = " onTransactionPageChange "
/ >
< / div >
< / section >
< el-dialog v-model = "transactionDialog.visible" :title="transactionDialog.title" width="680px" >
< el -form :model = "transactionForm" label -width = " 145px " >
< el-form-item label = "Date & Time" >
< el-date-picker v-model = "transactionForm.time" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" / >
< el-radio-group v-model = "transactionForm.timeMode" class="inline-mode" >
< el -radio value = "Auto" > Auto < / el-radio >
< el-radio value = "Manual" > Manual < / el-radio >
< / el-radio-group >
< / el-form-item >
< el-form-item label = "Category" >
< el-select v-model = "transactionForm.category" >
< el -option label = "Customer" :value = "1" / >
< el-option label = "Other Adjust" :value = "2" / >
< / el-select >
< / el-form-item >
< el-form-item label = "Type" >
< el-radio-group v-model = "transactionForm.type" >
< el -radio -button :value = "1" > Deposit < / el-radio-button >
< el-radio-button :value = "2" > Withdraw < / el-radio-button >
< el-radio-button :value = "3" > IN < / el-radio-button >
< el-radio-button :value = "4" > OUT < / el-radio-button >
< / el-radio-group >
< / el-form-item >
< el-form-item label = "Username" >
< el-input v-model = "transactionForm.username" placeholder="e.g. PLAYER001" / >
< / el-form-item >
< el-form-item label = "Remark" >
< el-input v-model = "transactionForm.remark" placeholder="(optional)" / >
< / el-form-item >
< el-form-item label = "Amount (AUD)" >
< el-input-number v-model = "transactionForm.amount" :min="0" :precision="2" / >
< / el-form-item >
< el-form-item label = "Bank" >
< el-select v-model = "transactionForm.bank" placeholder="- (optional) -" >
< el -option v-for = "bank in banks" :key="bank.id" :label="bank.name" :value="bank.id" / >
< / el-select >
< / el-form-item >
< el-form-item label = "Label" >
< el-select v-model = "transactionForm.label" placeholder="- (optional) -" >
< el -option label = "First Deposit" :value = "1" / >
< el-option label = "Unclaim" :value = "2" / >
< / el-select >
< / el-form-item >
< el-form-item label = "Game Ticket Auto" >
< el-checkbox v-model = "transactionForm.ticketAuto" > Auto generate ticket < / el -checkbox >
< / el-form-item >
< / el-form >
< template # footer >
< el-button type = "primary" :loading = "transactionDialog.loading" @click ="submitTransaction" > CONFIRM < / el -button >
< el-button @click ="transactionDialog.visible = false" > CANCEL < / el -button >
< / template >
< / el-dialog >
< el-dialog v-model = "historyDialog.visible" title="Transaction Edit History" width="720px" >
< p class = "dialog-note" > Transaction edit history is only kept for the most recent 12 months . < / p >
< p class = "dialog-note" > Transaction 的编辑历史记录仅保存最近 12 个月 。 < / p >
< el-table v-loading = "historyDialog.loading" :data="historyDialog.rows" border size="small" empty-text="No Record" >
< el -table -column prop = "id" label = "Tx ID" width = "100" / >
< el-table-column prop = "editedBy" label = "Edit By" width = "120" / >
< el-table-column prop = "editedTime" label = "Edit Time" width = "170" / >
< el-table-column prop = "changes" label = "Changes (Old → New)" / >
< / el-table >
< template # footer >
< el-button @click ="historyDialog.visible = false" > CANCEL < / el -button >
< / template >
< / el-dialog >
< el-dialog v-model = "transferDialog.visible" title="Bank Transfer" width="560px" >
< el -form :model = "transferForm" label -width = " 125px " >
< el-form-item label = "Transfer (From)" >
< el-input v-model = "transferForm.fromName" disabled / >
< / el-form-item >
< el-form-item label = "Transfer (To)" >
< el-select v-model = "transferForm.bankTo" placeholder="- Select Bank -" >
< el -option
v-for = "bank in banks"
:key = "bank.id"
:label = "bank.name"
:value = "bank.id"
: disabled = "bank.id === transferForm.bankFrom"
/ >
< / el-select >
< / el-form-item >
< el-form-item label = "Amount" >
< el-input-number v-model = "transferForm.money" :min="0" :precision="2" / >
< / el-form-item >
< el-form-item label = "Remark" >
< el-input v-model = "transferForm.remark" placeholder="(optional)" / >
< / el-form-item >
< / el-form >
< template # footer >
< el-button type = "primary" :loading = "transferDialog.loading" @click ="submitBankTransfer" > CONFIRM < / el -button >
< el-button @click ="transferDialog.visible = false" > CANCEL < / el -button >
< / template >
< / el-dialog >
< / div >
< / template >
< script setup lang = "ts" >
import { useEventListener , useTemplateRefsList , useTransition } from '@vueuse/cor e'
import * as echarts from 'echarts '
import { CSSProperties , nextTick , onActivated , onBeforeMount , onMounted , onUnmounted , reactive , toRefs , watch } from 'vue '
import { useI18n } from 'vue-i18n '
import { index } from '/@/api/backend/dashboard'
import coffeeSvg from '/@/assets/dashboard/coffee.svg'
import headerSvg from '/@/assets/dashboard/header-1.svg'
import { useAdminInfo } from '/@/stores/adminInfo'
import { WORKING _TIME } from '/@/stores/constant/cacheKey'
import { useNavTabs } from '/@/stores/navTabs'
import { fullUrl , getGreet } from '/@/utils/common'
import { Local } from '/@/utils/storage'
let workTimer : number
import { Coin , Switch } from '@element-plus/icons-vu e'
import { computed , onMounted , reactive , ref } from 'vue '
import { bankTransact , delTransact , editTransact , index as getDashboard , logHistory , newTransact } from '/@/api/backend/dashboard '
import type { DashboardTransactPayload } from '/@/api/backend/dashboard '
defineOptions ( {
name : 'dashboard' ,
} )
const d = new Date ( )
const { t } = useI18n ( )
const navTabs = useNavTabs ( )
const adminInfo = useAdminInfo ( )
const chartRefs = useTemplateRefsList < HTMLDivElement > ( )
interface Bank {
id : number | string
name : string
account : string
balance : number
deposit : number
withdraw : number
depositCount : number
withdrawCount : number
alert ? : string
rowClass ? : string
}
const state : {
charts : any [ ]
interface DashboardBank {
id : number | string
bank _name ? : string
bank _account ? : string
balance ? : number | string
current _balance ? : number | string
tx _in ? : number | string
tx _out ? : number | string
fund _in ? : number | string
fund _out ? : number | string
total _fund _in ? : number | string
total _fund _out ? : number | string
count _fund _in ? : number | string
count _fund _out ? : number | string
deposit _count ? : number | string
withdraw _count ? : number | string
safe _alert ? : number | string
status ? : number | string
}
interface Transaction {
id : number
createdBy : string
createdTime : string
category : string
username : string
remark : string
workingTimeFormat : string
pauseWork : boolean
} = reactive ( {
charts : [ ] ,
remark : 'dashboard.Loading' ,
workingTimeFormat : '' ,
pauseWork : false ,
bank : string
type : string
flow : 'in' | 'out'
amount : number
label : string
ticket : string [ ]
}
interface DashboardScoreLog {
game _type _text ? : string
money _log _id ? : number | string
game _type ? : number | string
score ? : number | string
}
interface DashboardTransaction {
id : number
user _id ? : number | string
money ? : number | string
before ? : number | string
after ? : number | string
type ? : number | string
transaction _id ? : string
created _by ? : string
memo ? : string
create _time ? : number | string
bank _id ? : number | string
category ? : number | string
user _name ? : string
bank _name ? : string
label ? : number | string
scoreLog ? : DashboardScoreLog [ ]
}
interface DashboardTransactionPage {
count ? : number | string
current _page ? : number | string
last _page ? : number | string
list ? : DashboardTransaction [ ]
total _deposit ? : number | string
total _withdraw ? : number | string
}
interface DashboardCustomerSummary {
total _deposit ? : number | string
total _withdraw ? : number | string
count _deposit ? : number | string
count _withdraw ? : number | string
active _player ? : number | string
first _deposit ? : number | string
unclaim _amount ? : number | string
unclaim _receipt ? : number | string
}
interface HistoryRow {
id : number | string
editedBy : string
editedTime : string
changes : string
}
const today = ( ) => {
const date = new Date ( )
const pad = ( value : number ) => value . toString ( ) . padStart ( 2 , '0' )
return ` ${ date . getFullYear ( ) } - ${ pad ( date . getMonth ( ) + 1 ) } - ${ pad ( date . getDate ( ) ) } `
}
const money = ( value : number ) =>
Number ( value ) . toLocaleString ( 'en-AU' , {
minimumFractionDigits : 2 ,
maximumFractionDigits : 2 ,
} )
const safeAlertLabels : Record < string , string > = {
'1' : 'Hourly Alert' ,
'2' : 'Daily Alert' ,
'3' : 'Weekly Alert' ,
'4' : 'Monthly Alert' ,
'5' : 'Yearly Alert' ,
'6' : 'Lifetime Alert' ,
}
const toNumber = ( value : unknown ) => {
const number = Number ( value )
return Number . isFinite ( number ) ? number : 0
}
const formatDateTime = ( value : unknown ) => {
if ( typeof value === 'string' && value . trim ( ) && ! Number . isFinite ( Number ( value ) ) ) {
return value
}
const timestamp = toNumber ( value )
if ( ! timestamp ) return ''
const date = new Date ( timestamp * 1000 )
const pad = ( number : number ) => number . toString ( ) . padStart ( 2 , '0' )
return ` ${ date . getFullYear ( ) } - ${ pad ( date . getMonth ( ) + 1 ) } - ${ pad ( date . getDate ( ) ) } ${ pad ( date . getHours ( ) ) } : ${ pad ( date . getMinutes ( ) ) } : ${ pad ( date . getSeconds ( ) ) } `
}
const mapBank = ( bank : DashboardBank ) : Bank => {
const fundIn = toNumber ( bank . total _fund _in ? ? bank . fund _in )
const fundOut = toNumber ( bank . total _fund _out ? ? bank . fund _out )
const safeAlert = String ( bank . safe _alert ? ? '0' )
return {
id : bank . id ,
name : bank . bank _name || '-' ,
account : bank . bank _account || '' ,
balance : toNumber ( bank . current _balance ? ? bank . balance ? ? fundIn - fundOut ) ,
deposit : fundIn ,
withdraw : fundOut ,
depositCount : toNumber ( bank . count _fund _in ? ? bank . deposit _count ? ? bank . tx _in ) ,
withdrawCount : toNumber ( bank . count _fund _out ? ? bank . withdraw _count ? ? bank . tx _out ) ,
alert : safeAlertLabels [ safeAlert ] ,
rowClass : String ( bank . status ? ? '1' ) === '0' ? 'bank-muted' : '' ,
}
}
const banks = ref < Bank [ ] > ( [ ] )
const transactions = ref < Transaction [ ] > ( [ ] )
const customerSummary = reactive ( {
totalDeposit : 0 ,
totalWithdraw : 0 ,
countDeposit : 0 ,
countWithdraw : 0 ,
activePlayer : 0 ,
firstDeposit : 0 ,
unclaimAmount : 0 ,
unclaimReceipt : 0 ,
} )
/**
* 带有数字向上变化特效的数据
*/
const countUp = reactive ( {
userRegNumber : 0 ,
fileNumber : 0 ,
usersNumber : 0 ,
addonsNumber : 0 ,
const filters = reactive ( {
startDate : today ( ) ,
endDate : today ( ) ,
} )
const transactionPage = reactive ( {
count : 0 ,
currentPage : 1 ,
lastPage : 1 ,
pageSize : 10 ,
} )
const showAllBanks = ref ( false )
const visibleBanks = computed ( ( ) => ( showAllBanks . value ? banks . value : banks . value . slice ( 0 , 4 ) ) )
const totalDeposit = computed ( ( ) => customerSummary . totalDeposit )
const totalWithdraw = computed ( ( ) => customerSummary . totalWithdraw )
const dateRangeText = computed ( ( ) => {
const start = new Date ( ` ${ filters . startDate } T00:00:00 ` )
const end = new Date ( ` ${ filters . endDate } T00:00:00 ` )
const diffDays = Math . max ( 1 , Math . floor ( ( end . getTime ( ) - start . getTime ( ) ) / 86400000 ) + 1 )
return ` ${ filters . startDate } to ${ filters . endDate } ( ${ diffDays } ${ diffDays === 1 ? 'Day' : 'Days' } ) `
} )
const summary = computed ( ( ) => [
{ label : 'Total Deposit / IN' , value : ` AUD ${ money ( totalDeposit . value ) } ` } ,
{ label : 'Total Withdraw / OUT' , value : ` AUD ${ money ( totalWithdraw . value ) } ` } ,
{ label : 'Deposit Count' , value : customerSummary . countDeposit } ,
{ label : 'Withdraw Count' , value : customerSummary . countWithdraw } ,
{ label : 'Active Player' , value : customerSummary . activePlayer } ,
{ label : 'First Deposit Player' , value : customerSummary . firstDeposit } ,
{ label : 'Unclaimed Amount' , value : ` AUD ${ money ( customerSummary . unclaimAmount ) } ` } ,
{ label : 'Unclaimed Receipt' , value : customerSummary . unclaimReceipt } ,
] )
const transactionDialog = reactive < { visible : boolean ; title : string ; loading : boolean ; mode : 'create' | 'edit' ; editId : number | string | '' } > ( {
visible : false ,
title : 'Create New Transaction' ,
loading : false ,
mode : 'create' ,
editId : '' ,
} )
const transactionForm = reactive ( {
time : '' ,
timeMode : 'Auto' ,
category : 1 ,
type : 1 ,
username : '' ,
remark : '' ,
amount : 0 ,
bank : '' ,
label : '' ,
ticketAuto : true ,
} )
const historyDialog = reactive ( {
visible : false ,
loading : false ,
rows : [ ] as HistoryRow [ ] ,
} )
const transferDialog = reactive ( { visible : false , loading : false } )
const transferForm = reactive < { bankFrom : Bank [ 'id' ] | '' ; fromName : string ; bankTo : Bank [ 'id' ] | '' ; money : number ; remark : string } > ( {
bankFrom : '' ,
fromName : '' ,
bankTo : '' ,
money : 0 ,
remark : '' ,
} )
const countUpRefs = toRefs ( countUp )
const userRegNumberOutput = useTransition ( countUpRefs . userRegNumber , { duration : 1500 } )
const fileNumberOutput = useTransition ( countUpRefs . fileNumber , { duration : 1500 } )
const usersNumberOutput = useTransition ( countUpRefs . usersNumber , { duration : 1500 } )
const addonsNumberOutput = useTransition ( countUpRefs . addonsNumber , { duration : 1500 } )
const statisticValueStyle : CSSProperties = {
fontSize : '28px' ,
const typeLabels : Record < string , string > = {
'1' : 'Deposit' ,
'2' : 'Withdraw' ,
'3' : 'IN' ,
'4' : 'OUT' ,
}
index ( ) . then ( ( res ) => {
state . remark = res . data . remark
} )
const initCountUp = ( ) => {
// 虚拟数据
countUpRefs . userRegNumber . value = 5456
countUpRefs . fileNumber . value = 1234
countUpRefs . usersNumber . value = 9486
countUpRefs . addonsNumber . value = 875
const categoryLabels : Record < string , string > = {
'1' : 'Customer' ,
'2' : 'Other Adjust' ,
}
const initUserGrowthChart = ( ) => {
const userGrowthChart = echarts . init ( chartRefs . value [ 0 ] as HTMLElement )
const option = {
grid : {
top : 40 ,
right : 0 ,
bottom : 20 ,
left : 40 ,
} ,
xAxis : {
data : [
t ( 'dashboard.Monday' ) ,
t ( 'dashboard.Tuesday' ) ,
t ( 'dashboard.Wednesday' ) ,
t ( 'dashboard.Thursday' ) ,
t ( 'dashboard.Friday' ) ,
t ( 'dashboard.Saturday' ) ,
t ( 'dashboard.Sunday' ) ,
] ,
} ,
yAxis : { } ,
legend : {
data : [ t ( 'dashboard.Visits' ) , t ( 'dashboard.Registration volume' ) ] ,
textStyle : {
color : '#73767a' ,
} ,
top : 0 ,
} ,
series : [
{
name : t ( 'dashboard.Visits' ) ,
data : [ 100 , 160 , 280 , 230 , 190 , 200 , 480 ] ,
type : 'line' ,
smooth : true ,
areaStyle : {
color : '#8595F4' ,
} ,
} ,
{
name : t ( 'dashboard.Registration volume' ) ,
data : [ 45 , 180 , 146 , 99 , 210 , 127 , 288 ] ,
type : 'line' ,
smooth : true ,
areaStyle : {
color : '#F48595' ,
opacity : 0.5 ,
} ,
} ,
] ,
}
userGrowthChart . setOption ( option )
state . charts . push ( userGrowthChart )
const transactionLabelTexts : Record < string , string > = {
'1' : 'First Deposit' ,
'2' : 'Unclaim' ,
}
const initFileGrowthChart = ( ) => {
const fileGrowthChart = echarts . init ( chartRefs . value [ 1 ] as HTMLElement )
const option = {
grid : {
top : 30 ,
right : 0 ,
bottom : 20 ,
left : 0 ,
} ,
tooltip : {
trigger : 'item' ,
} ,
legend : {
type : 'scroll' ,
bottom : 0 ,
data : ( function ( ) {
var list = [ ]
for ( var i = 1 ; i <= 28 ; i ++ ) {
list . push ( i + 2000 + '' )
}
return list
} ) ( ) ,
textStyle : {
color : '#73767a' ,
} ,
} ,
visualMap : {
top : 'middle' ,
right : 10 ,
color : [ 'red' , 'yellow' ] ,
calculable : true ,
} ,
radar : {
indicator : [
{ name : t ( 'dashboard.picture' ) } ,
{ name : t ( 'dashboard.file' ) } ,
{ name : t ( 'dashboard.table' ) } ,
{ name : t ( 'dashboard.Compressed package' ) } ,
{ name : t ( 'dashboard.other' ) } ,
] ,
} ,
series : ( function ( ) {
var series = [ ]
for ( var i = 1 ; i <= 28 ; i ++ ) {
series . push ( {
type : 'radar' ,
symbol : 'none' ,
lineStyle : {
width : 1 ,
} ,
emphasis : {
areaStyle : {
color : 'rgba(0,250,0,0.3)' ,
} ,
} ,
data : [
{
value : [ ( 40 - i ) * 10 , ( 38 - i ) * 4 + 60 , i * 5 + 10 , i * 9 , ( i * i ) / 2 ] ,
name : i + 2000 + '' ,
} ,
] ,
} )
}
return series
} ) ( ) ,
}
fileGrowthChart . setOption ( option )
state . charts . push ( fileGrowthChart )
const categoryValues : Record < string , number > = {
Customer : 1 ,
'Other Adjust' : 2 ,
}
const initUserSourceChart = ( ) => {
const UserSourceChart = echarts . init ( chartRefs . value [ 2 ] as HTMLElement )
const pathSymbols = {
reindeer :
'path://M-22.788,24.521c2.08-0.986,3.611-3.905,4.984-5.892 c-2.686,2.782-5.047,5.884-9.102,7.312c-0.992,0.005-0.25-2.016,0.34-2.362l1.852-0.41c0.564-0.218,0.785-0.842,0.902-1.347 c2.133-0.727,4.91-4.129,6.031-6.194c1.748-0.7,4.443-0.679,5.734-2.293c1.176-1.468,0.393-3.992,1.215-6.557 c0.24-0.754,0.574-1.581,1.008-2.293c-0.611,0.011-1.348-0.061-1.959-0.608c-1.391-1.245-0.785-2.086-1.297-3.313 c1.684,0.744,2.5,2.584,4.426,2.586C-8.46,3.012-8.255,2.901-8.04,2.824c6.031-1.952,15.182-0.165,19.498-3.937 c1.15-3.933-1.24-9.846-1.229-9.938c0.008-0.062-1.314-0.004-1.803-0.258c-1.119-0.771-6.531-3.75-0.17-3.33 c0.314-0.045,0.943,0.259,1.439,0.435c-0.289-1.694-0.92-0.144-3.311-1.946c0,0-1.1-0.855-1.764-1.98 c-0.836-1.09-2.01-2.825-2.992-4.031c-1.523-2.476,1.367,0.709,1.816,1.108c1.768,1.704,1.844,3.281,3.232,3.983 c0.195,0.203,1.453,0.164,0.926-0.468c-0.525-0.632-1.367-1.278-1.775-2.341c-0.293-0.703-1.311-2.326-1.566-2.711 c-0.256-0.384-0.959-1.718-1.67-2.351c-1.047-1.187-0.268-0.902,0.521-0.07c0.789,0.834,1.537,1.821,1.672,2.023 c0.135,0.203,1.584,2.521,1.725,2.387c0.102-0.259-0.035-0.428-0.158-0.852c-0.125-0.423-0.912-2.032-0.961-2.083 c-0.357-0.852-0.566-1.908-0.598-3.333c0.4-2.375,0.648-2.486,0.549-0.705c0.014,1.143,0.031,2.215,0.602,3.247 c0.807,1.496,1.764,4.064,1.836,4.474c0.561,3.176,2.904,1.749,2.281-0.126c-0.068-0.446-0.109-2.014-0.287-2.862 c-0.18-0.849-0.219-1.688-0.113-3.056c0.066-1.389,0.232-2.055,0.277-2.299c0.285-1.023,0.4-1.088,0.408,0.135 c-0.059,0.399-0.131,1.687-0.125,2.655c0.064,0.642-0.043,1.768,0.172,2.486c0.654,1.928-0.027,3.496,1,3.514 c1.805-0.424,2.428-1.218,2.428-2.346c-0.086-0.704-0.121-0.843-0.031-1.193c0.221-0.568,0.359-0.67,0.312-0.076 c-0.055,0.287,0.031,0.533,0.082,0.794c0.264,1.197,0.912,0.114,1.283-0.782c0.15-0.238,0.539-2.154,0.545-2.522 c-0.023-0.617,0.285-0.645,0.309,0.01c0.064,0.422-0.248,2.646-0.205,2.334c-0.338,1.24-1.105,3.402-3.379,4.712 c-0.389,0.12-1.186,1.286-3.328,2.178c0,0,1.729,0.321,3.156,0.246c1.102-0.19,3.707-0.027,4.654,0.269 c1.752,0.494,1.531-0.053,4.084,0.164c2.26-0.4,2.154,2.391-1.496,3.68c-2.549,1.405-3.107,1.475-2.293,2.984 c3.484,7.906,2.865,13.183,2.193,16.466c2.41,0.271,5.732-0.62,7.301,0.725c0.506,0.333,0.648,1.866-0.457,2.86 c-4.105,2.745-9.283,7.022-13.904,7.662c-0.977-0.194,0.156-2.025,0.803-2.247l1.898-0.03c0.596-0.101,0.936-0.669,1.152-1.139 c3.16-0.404,5.045-3.775,8.246-4.818c-4.035-0.718-9.588,3.981-12.162,1.051c-5.043,1.423-11.449,1.84-15.895,1.111 c-3.105,2.687-7.934,4.021-12.115,5.866c-3.271,3.511-5.188,8.086-9.967,10.414c-0.986,0.119-0.48-1.974,0.066-2.385l1.795-0.618 C-22.995,25.682-22.849,25.035-22.788,24.521z' ,
plane : 'path://M1.112,32.559l2.998,1.205l-2.882,2.268l-2.215-0.012L1.112,32.559z M37.803,23.96 c0.158-0.838,0.5-1.509,0.961-1.904c-0.096-0.037-0.205-0.071-0.344-0.071c-0.777-0.005-2.068-0.009-3.047-0.009 c-0.633,0-1.217,0.066-1.754,0.18l2.199,1.804H37.803z M39.738,23.036c-0.111,0-0.377,0.325-0.537,0.924h1.076 C40.115,23.361,39.854,23.036,39.738,23.036z M39.934,39.867c-0.166,0-0.674,0.705-0.674,1.986s0.506,1.986,0.674,1.986 s0.672-0.705,0.672-1.986S40.102,39.867,39.934,39.867z M38.963,38.889c-0.098-0.038-0.209-0.07-0.348-0.073 c-0.082,0-0.174,0-0.268-0.001l-7.127,4.671c0.879,0.821,2.42,1.417,4.348,1.417c0.979,0,2.27-0.006,3.047-0.01 c0.139,0,0.25-0.034,0.348-0.072c-0.646-0.555-1.07-1.643-1.07-2.967C37.891,40.529,38.316,39.441,38.963,38.889z M32.713,23.96 l-12.37-10.116l-4.693-0.004c0,0,4,8.222,4.827,10.121H32.713z M59.311,32.374c-0.248,2.104-5.305,3.172-8.018,3.172H39.629 l-25.325,16.61L9.607,52.16c0,0,6.687-8.479,7.95-10.207c1.17-1.6,3.019-3.699,3.027-6.407h-2.138 c-5.839,0-13.816-3.789-18.472-5.583c-2.818-1.085-2.396-4.04-0.031-4.04h0.039l-3.299-11.371h3.617c0,0,4.352,5.696,5.846,7.5 c2,2.416,4.503,3.678,8.228,3.87h30.727c2.17,0,4.311,0.417,6.252,1.046c3.49,1.175,5.863,2.7,7.199,4.027 C59.145,31.584,59.352,32.025,59.311,32.374z M22.069,30.408c0-0.815-0.661-1.475-1.469-1.475c-0.812,0-1.471,0.66-1.471,1.475 s0.658,1.475,1.471,1.475C21.408,31.883,22.069,31.224,22.069,30.408z M27.06,30.408c0-0.815-0.656-1.478-1.466-1.478 c-0.812,0-1.471,0.662-1.471,1.478s0.658,1.477,1.471,1.477C26.404,31.885,27.06,31.224,27.06,30.408z M32.055,30.408 c0-0.815-0.66-1.475-1.469-1.475c-0.808,0-1.466,0.66-1.466,1.475s0.658,1.475,1.466,1.475 C31.398,31.883,32.055,31.224,32.055,30.408z M37.049,30.408c0-0.815-0.658-1.478-1.467-1.478c-0.812,0-1.469,0.662-1.469,1.478 s0.656,1.477,1.469,1.477C36.389,31.885,37.049,31.224,37.049,30.408z M42.039,30.408c0-0.815-0.656-1.478-1.465-1.478 c-0.811,0-1.469,0.662-1.469,1.478s0.658,1.477,1.469,1.477C41.383,31.885,42.039,31.224,42.039,30.408z M55.479,30.565 c-0.701-0.436-1.568-0.896-2.627-1.347c-0.613,0.289-1.551,0.476-2.73,0.476c-1.527,0-1.639,2.263,0.164,2.316 C52.389,32.074,54.627,31.373,55.479,30.565z' ,
rocket : 'path://M-244.396,44.399c0,0,0.47-2.931-2.427-6.512c2.819-8.221,3.21-15.709,3.21-15.709s5.795,1.383,5.795,7.325C-237.818,39.679-244.396,44.399-244.396,44.399z M-260.371,40.827c0,0-3.881-12.946-3.881-18.319c0-2.416,0.262-4.566,0.669-6.517h17.684c0.411,1.952,0.675,4.104,0.675,6.519c0,5.291-3.87,18.317-3.87,18.317H-260.371z M-254.745,18.951c-1.99,0-3.603,1.676-3.603,3.744c0,2.068,1.612,3.744,3.603,3.744c1.988,0,3.602-1.676,3.602-3.744S-252.757,18.951-254.745,18.951z M-255.521,2.228v-5.098h1.402v4.969c1.603,1.213,5.941,5.069,7.901,12.5h-17.05C-261.373,7.373-257.245,3.558-255.521,2.228zM-265.07,44.399c0,0-6.577-4.721-6.577-14.896c0-5.942,5.794-7.325,5.794-7.325s0.393,7.488,3.211,15.708C-265.539,41.469-265.07,44.399-265.07,44.399z M-252.36,45.15l-1.176-1.22L-254.789,48l-1.487-4.069l-1.019,2.116l-1.488-3.826h8.067L-252.36,45.15z' ,
train : 'path://M67.335,33.596L67.335,33.596c-0.002-1.39-1.153-3.183-3.328-4.218h-9.096v-2.07h5.371 c-4.939-2.07-11.199-4.141-14.89-4.141H19.72v12.421v5.176h38.373c4.033,0,8.457-1.035,9.142-5.176h-0.027 c0.076-0.367,0.129-0.751,0.129-1.165L67.335,33.596L67.335,33.596z M27.999,30.413h-3.105v-4.141h3.105V30.413z M35.245,30.413 h-3.104v-4.141h3.104V30.413z M42.491,30.413h-3.104v-4.141h3.104V30.413z M49.736,30.413h-3.104v-4.141h3.104V30.413z M14.544,40.764c1.143,0,2.07-0.927,2.07-2.07V35.59V25.237c0-1.145-0.928-2.07-2.07-2.07H-9.265c-1.143,0-2.068,0.926-2.068,2.07 v10.351v3.105c0,1.144,0.926,2.07,2.068,2.07H14.544L14.544,40.764z M8.333,26.272h3.105v4.141H8.333V26.272z M1.087,26.272h3.105 v4.141H1.087V26.272z M-6.159,26.272h3.105v4.141h-3.105V26.272z M-9.265,41.798h69.352v1.035H-9.265V41.798z' ,
}
const option = {
tooltip : {
trigger : 'axis' ,
axisPointer : {
type : 'none' ,
} ,
formatter : function ( params : any ) {
return params [ 0 ] . name + ': ' + params [ 0 ] . value
} ,
} ,
xAxis : {
data : [ t ( 'dashboard.Baidu' ) , t ( 'dashboard.Direct access' ) , t ( 'dashboard.take a plane' ) , t ( 'dashboard.Take the high-speed railway' ) ] ,
axisTick : { show : false } ,
axisLine : { show : false } ,
axisLabel : {
color : '#e54035' ,
} ,
} ,
yAxis : {
splitLine : { show : false } ,
axisTick : { show : false } ,
axisLine : { show : false } ,
axisLabel : { show : false } ,
} ,
color : [ '#e54035' ] ,
series : [
{
name : 'hill' ,
type : 'pictorialBar' ,
barCategoryGap : '-130%' ,
symbol : 'path://M0,10 L10,10 C5.5,10 5.5,5 5,0 C4.5,5 4.5,10 0,10 z' ,
itemStyle : {
opacity : 0.5 ,
} ,
emphasis : {
itemStyle : {
opacity : 1 ,
} ,
} ,
data : [ 123 , 60 , 25 , 80 ] ,
z : 10 ,
} ,
{
name : 'glyph' ,
type : 'pictorialBar' ,
barGap : '-100%' ,
symbolPosition : 'end' ,
symbolSize : 50 ,
symbolOffset : [ 0 , '-120%' ] ,
data : [
{
value : 123 ,
symbol : pathSymbols . reindeer ,
symbolSize : [ 60 , 60 ] ,
} ,
{
value : 60 ,
symbol : pathSymbols . rocket ,
symbolSize : [ 50 , 60 ] ,
} ,
{
value : 25 ,
symbol : pathSymbols . plane ,
symbolSize : [ 65 , 35 ] ,
} ,
{
value : 80 ,
symbol : pathSymbols . train ,
symbolSize : [ 50 , 30 ] ,
} ,
] ,
} ,
] ,
}
UserSourceChart . setOption ( option )
state . charts . push ( UserSourceChart )
const typeValues : Record < string , number > = {
Deposit : 1 ,
Withdraw : 2 ,
IN : 3 ,
OUT : 4 ,
}
const initUserSurnameChart = ( ) => {
const userSurnameChart = echarts . init ( chartRefs . value [ 3 ] as HTMLElement )
const data = genData ( 20 )
const option = {
tooltip : {
trigger : 'item' ,
formatter : '{a} <br/>{b} : {c} ({d}%)' ,
} ,
legend : {
type : 'scroll' ,
orient : 'vertical' ,
right : 10 ,
top : 20 ,
bottom : 20 ,
data : data . legendData ,
textStyle : {
color : '#73767a' ,
} ,
} ,
series : [
{
name : t ( 'dashboard.full name' ) ,
type : 'pie' ,
radius : '55%' ,
center : [ '40%' , '50%' ] ,
data : data . seriesData ,
emphasis : {
itemStyle : {
shadowBlur : 10 ,
shadowOffsetX : 0 ,
shadowColor : 'rgba(0, 0, 0, 0.5)' ,
} ,
} ,
} ,
] ,
}
function genData ( count : any ) {
// prettier-ignore
const nameList = [
'赵' , '钱' , '孙' , '李' , '周' , '吴' , '郑' , '王' , '冯' , '陈' , '褚' , '卫' , '蒋' , '沈' , '韩' , '杨' , '朱' , '秦' , '尤' , '许' , '何' , '吕' , '施' , '张' , '孔' , '曹' , '严' , '华' , '金' , '魏' , '陶' , '姜' , '戚' , '谢' , '邹' , '喻' , '柏' , '水' , '窦' , '章' , '云' , '苏' , '潘' , '葛' , '奚' , '范' , '彭' , '郎' , '鲁' , '韦' , '昌' , '马' , '苗' , '凤' , '花' , '方' , '俞' , '任' , '袁' , '柳' , '酆' , '鲍' , '史' , '唐' , '费' , '廉' , '岑' , '薛' , '雷' , '贺' , '倪' , '汤' , '滕' , '殷' , '罗' , '毕' , '郝' , '邬' , '安' , '常' , '乐' , '于' , '时' , '傅' , '皮' , '卞' , '齐' , '康' , '伍' , '余' , '元' , '卜' , '顾' , '孟' , '平' , '黄' , '和' , '穆' , '萧' , '尹' , '姚' , '邵' , '湛' , '汪' , '祁' , '毛' , '禹' , '狄' , '米' , '贝' , '明' , '臧' , '计' , '伏' , '成' , '戴' , '谈' , '宋' , '茅' , '庞' , '熊' , '纪' , '舒' , '屈' , '项' , '祝' , '董' , '梁' , '杜' , '阮' , '蓝' , '闵' , '席' , '季' , '麻' , '强' , '贾' , '路' , '娄' , '危'
] ;
const legendData = [ ]
const seriesData = [ ]
for ( var i = 0 ; i < count ; i ++ ) {
var name = Math . random ( ) > 0.85 ? makeWord ( 2 , 1 ) + '·' + makeWord ( 2 , 0 ) : makeWord ( 2 , 1 )
legendData . push ( name )
seriesData . push ( {
name : name ,
value : Math . round ( Math . random ( ) * 100000 ) ,
} )
}
return {
legendData : legendData ,
seriesData : seriesData ,
}
function makeWord ( max : any , min : any ) {
const nameLen = Math . ceil ( Math . random ( ) * max + min )
const name = [ ]
for ( var i = 0 ; i < nameLen ; i ++ ) {
name . push ( nameList [ Math . round ( Math . random ( ) * nameList . length - 1 ) ] )
}
return name . join ( '' )
}
}
userSurnameChart . setOption ( option )
state . charts . push ( userSurnameChart )
const labelValues : Record < string , number > = {
'First Deposit' : 1 ,
Unclaim : 2 ,
}
const echartsResize = ( ) => {
nextTick ( ( ) => {
for ( const key in state . charts ) {
state . charts [ key ] . resize ( )
}
const mapTransaction = ( transaction : DashboardTransaction ) : Transaction => {
const type = String ( transaction . type ? ? '' )
return {
id : transaction . id ,
createdBy : transaction . created _by || '' ,
createdTime : formatDateTime ( transaction . create _time ) ,
category : categoryLabels [ String ( transaction . category ? ? '' ) ] || '' ,
username : transaction . user _name || '' ,
remark : transaction . memo || '' ,
bank : transaction . bank _name || '' ,
type : typeLabels [ type ] || type ,
flow : [ '1' , '3' ] . includes ( type ) ? 'in' : 'out' ,
amount : toNumber ( transaction . money ) ,
label : transactionLabelTexts [ String ( transaction . label ? ? '' ) ] || '' ,
ticket : Array . isArray ( transaction . scoreLog )
? transaction . scoreLog . map ( ( score ) => ` ${ score . game _type _text || '' } : ${ score . score ? ? '' } ` ) . filter ( ( score ) => score . trim ( ) !== ':' )
: [ ] ,
}
}
const loadDashboard = ( page = transactionPage . currentPage ) => {
return getDashboard ( {
start : filters . startDate ,
end : filters . endDate ,
page ,
} ) . then ( ( res ) => {
const bankData = Array . isArray ( res . data . bank ) ? res . data . bank : res . data . bank ? . list
banks . value = Array . isArray ( bankData ) ? bankData . map ( mapBank ) : [ ]
const transactionData = res . data . transaction as DashboardTransactionPage | undefined
const transactionList = Array . isArray ( transactionData ? . list ) ? transactionData . list : [ ]
transactions . value = transactionList . map ( mapTransaction )
transactionPage . count = toNumber ( transactionData ? . count )
transactionPage . currentPage = toNumber ( transactionData ? . current _page ) || page
transactionPage . lastPage = toNumber ( transactionData ? . last _page ) || 1
transactionPage . pageSize = transactionPage . lastPage > 0 ? Math . max ( 1 , Math . ceil ( transactionPage . count / transactionPage . lastPage ) ) : 10
const customerData = res . data . customer as DashboardCustomerSummary | undefined
customerSummary . totalDeposit = toNumber ( customerData ? . total _deposit )
customerSummary . totalWithdraw = toNumber ( customerData ? . total _withdraw )
customerSummary . countDeposit = toNumber ( customerData ? . count _deposit )
customerSummary . countWithdraw = toNumber ( customerData ? . count _withdraw )
customerSummary . activePlayer = toNumber ( customerData ? . active _player )
customerSummary . firstDeposit = toNumber ( customerData ? . first _deposit )
customerSummary . unclaimAmount = toNumber ( customerData ? . unclaim _amount )
customerSummary . unclaimReceipt = toNumber ( customerData ? . unclaim _receipt )
} )
}
const onChangeWorkState = ( ) => {
const time = parseInt ( ( new Date ( ) . getTime ( ) / 1000 ) . toString ( ) )
const workingTime = Local . get ( WORKING _TIME )
if ( state . pauseWork ) {
// 继续工作
workingTime . pauseTime += time - workingTime . startPauseTime
workingTime . startPauseTime = 0
Local . set ( WORKING _TIME , workingTime )
state . pauseWork = false
startWork ( )
} else {
// 暂停工作
workingTime . startPauseTime = time
Local . set ( WORKING _TIME , workingTime )
clearInterval ( workTimer )
state . pauseWork = true
}
const resetTransactionForm = ( ) => {
Object . assign ( transactionForm , {
time : '' ,
timeMode : 'Auto' ,
category : 1 ,
type : 1 ,
username : '' ,
remark : '' ,
amount : 0 ,
bank : '' ,
label : '' ,
ticketAuto : true ,
} )
}
const startWork = ( ) => {
const workingTime = Local . get ( WORKING _TIME ) || { date : '' , startTime : 0 , pauseTime : 0 , startPauseTime : 0 }
const currentDate = d . getFullYear ( ) + '-' + ( d . getMonth ( ) + 1 ) + '-' + d . getDate ( )
const time = parseInt ( ( new Date ( ) . getTime ( ) / 1000 ) . toString ( ) )
if ( workingTime . dat e ! = currentDate ) {
workingTime . date = currentDate
workingTime . startTime = time
workingTime . pauseTime = workingTime . startPauseTime = 0
Local . set ( WORKING _TIME , workingTime )
}
let startPauseTime = 0
if ( workingTime . startPauseTime <= 0 ) {
state . pauseWork = false
startPauseTime = 0
} else {
state . pauseWork = true
startPauseTime = time - workingTime . startPauseTime // 已暂停时间
}
let workingSeconds = time - workingTime . startTime - workingTime . pauseTime - startPauseTime
state . workingTimeFormat = formatSeconds ( workingSeconds )
if ( ! state . pauseWork ) {
workTimer = window . setInterval ( ( ) => {
workingSeconds ++
state . workingTimeFormat = formatSeconds ( workingSeconds )
} , 1000 )
}
const openCreate = ( ) => {
resetTransactionForm ( )
transactionDialog . title = 'Create New Transaction'
transactionDialog . mode = 'create'
transactionDialog . editId = ''
transactionDialog . visibl e = true
}
const formatSeconds = ( seconds : number ) => {
var secondTime = 0 // 秒
var minu teTime = 0 // 分
var hourTime = 0 // 小时
var dayTime = 0 // 天
var result = ''
if ( seconds < 60 ) {
secondTime = seconds
} else {
// 获取分钟, 除以60取整数, 得到整数分钟
minuteTime = Math . floor ( seconds / 6 0)
// 获取秒数,秒数取佘,得到整数秒数
secondTime = Math . floor ( seconds % 60 )
// 如果分钟大于60, 将分钟转换成小时
if ( minuteTime > = 60 ) {
// 获取小时, 获取分钟除以60, 得到整数小时
hourTime = Math . floor ( minuteTime / 60 )
// 获取小时后取佘的分, 获取分钟除以60取佘的分
minuteTime = Math . floor ( minuteTime % 60 )
if ( hourTime >= 24 ) {
// 获取天数, 获取小时除以24, 得到整数天
dayTime = Math . floor ( hourTime / 24 )
// 获取小时后取余小时, 获取分钟除以24取余的分;
hourTime = Math . floor ( hourTime % 24 )
}
}
}
result =
hourTime +
t ( 'dashboard.hour' ) +
( ( minuteTime >= 10 ? minuteTime : '0' + minuteTime ) + t ( 'dashboard.minute' ) ) +
( ( secondTime >= 10 ? secondTime : '0' + secondTime ) + t ( 'dashboard.second' ) )
if ( dayTime > 0 ) {
result = dayTime + t ( 'dashboard.day' ) + result
}
return result
const openEdit = ( row : Transaction ) => {
Object . assign ( transactionForm , {
time : row . crea ted Time ,
timeMode : 'Manual' ,
category : categoryValues [ row . category ] || 1 ,
type : typeValues [ row . type ] || 1 ,
username : row . username ,
remark : row . remark ,
amount : row . amount ,
bank : banks . value . find ( ( bank ) => bank . name === row . bank ) ? . id || '' ,
label : labelValues [ row . label ] || '' ,
ticketAuto : row . ticket . length > 0 ,
} )
transactionDialog . title = ` Edit Transaction # ${ row . id } `
transactionDialog . mode = 'edit'
transactionDialog . editId = row . id
transactionDialog . visible = true
}
onActivated ( ( ) => {
echartsResize ( )
const transactionTimestamp = ( ) => {
if ( transactionForm . timeMode === 'Auto' || ! transactionForm . time ) {
return Math . floor ( Date . now ( ) / 1000 )
}
const date = new Date ( transactionForm . time . replace ( ' ' , 'T' ) )
const timestamp = Math . floor ( date . getTime ( ) / 1000 )
return Number . isFinite ( timestamp ) ? timestamp : Math . floor ( Date . now ( ) / 1000 )
}
const buildTransactPayload = ( ) : DashboardTransactPayload => ( {
create _time : transactionTimestamp ( ) ,
category : transactionForm . category ,
type : transactionForm . type ,
user _name : transactionForm . username ,
memo : transactionForm . remark ,
money : transactionForm . amount ,
bank _id : transactionForm . bank ,
label : transactionForm . label ,
game _ticket : transactionForm . ticketAuto ? 1 : 0 ,
} )
const submitTransaction = ( ) => {
transactionDialog . loading = true
const request =
transactionDialog . mode === 'edit' && transactionDialog . editId !== ''
? editTransact ( { ... buildTransactPayload ( ) , id : transactionDialog . editId } )
: newTransact ( buildTransactPayload ( ) )
request
. then ( ( ) => {
transactionDialog . visible = false
transactionPage . currentPage = 1
return loadDashboard ( 1 )
} )
. finally ( ( ) => {
transactionDialog . loading = false
} )
}
const stringifyChange = ( history : Record < string , unknown > ) => {
if ( history . bank _befter !== undefined || history . bank _after !== undefined ) {
return ` Bank: ${ history . bank _befter ? ? '' } → ${ history . bank _after ? ? '' } `
}
return JSON . stringify ( history )
}
const mapHistory = ( history : Record < string , unknown > ) : HistoryRow => ( {
id : ( history . id || history . money _log _id || '' ) as number | string ,
editedBy : String ( history . admin _name || '' ) ,
editedTime : formatDateTime ( history . create _time ) ,
changes : stringifyChange ( history ) ,
} )
const openHistory = ( row : Transaction ) => {
historyDialog . visible = true
historyDialog . loading = true
historyDialog . rows = [ ]
logHistory ( { id : row . id } )
. then ( ( res ) => {
const data = Array . isArray ( res . data ) ? res . data : res . data ? . list
historyDialog . rows = Array . isArray ( data ) ? data . map ( ( item ) => mapHistory ( item as Record < string , unknown > ) ) : [ ]
} )
. finally ( ( ) => {
historyDialog . loading = false
} )
}
const openTransfer = ( bank : Bank ) => {
Object . assign ( transferForm , { bankFrom : bank . id , fromName : bank . name , bankTo : '' , money : 0 , remark : '' } )
transferDialog . visible = true
}
const submitBankTransfer = ( ) => {
transferDialog . loading = true
bankTransact ( {
money : transferForm . money ,
bank _from : transferForm . bankFrom ,
bank _to : transferForm . bankTo ,
remark : transferForm . remark ,
} )
. then ( ( ) => {
transferDialog . visible = false
return loadDashboard ( )
} )
. finally ( ( ) => {
transferDialog . loading = false
} )
}
const removeTransaction = ( row : Transaction ) => {
delTransact ( { id : row . id } ) . then ( ( ) => {
return loadDashboard ( transactionPage . currentPage )
} )
}
const setToday = ( ) => {
filters . startDate = today ( )
filters . endDate = today ( )
transactionPage . currentPage = 1
loadDashboard ( 1 ) . catch ( ( ) => {
// Request errors are displayed by the shared Axios interceptor.
} )
}
const search = ( ) => {
transactionPage . currentPage = 1
loadDashboard ( 1 ) . catch ( ( ) => {
// Request errors are displayed by the shared Axios interceptor.
} )
}
const onTransactionPageChange = ( page : number ) => {
loadDashboard ( page ) . catch ( ( ) => {
// Request errors are displayed by the shared Axios interceptor.
} )
}
const transactionRowClass = ( { row } : { row : Transaction } ) => ( row . flow === 'in' ? 'transaction-in' : 'transaction-out' )
onMounted ( ( ) => {
startWork ( )
initCountUp ( )
initUserGrowthChart ( )
initFileGrowthChart ( )
initUserSourceChart ( )
initUserSurnameChart ( )
useEventListener ( window , 'resize' , echartsResize )
loadDashboard ( ) . catch ( ( ) => {
// Request errors are displayed by the shared Axios interceptor.
} )
} )
onBeforeMount ( ( ) => {
for ( const key in state . charts ) {
state . charts [ key ] . dispose ( )
}
} )
onUnmounted ( ( ) => {
clearInterval ( workTimer )
} )
watch (
( ) => navTabs . state . tabFullScreen ,
( ) => {
echartsResize ( )
}
)
< / script >
< style scoped lang = "scss" >
. welcome {
background : # e1eaf9 ;
border - radius : 6 px ;
display : flex ;
align - items : center ;
padding : 15 px 20 px ! important ;
box - shadow : 0 0 30 px 0 rgba ( 82 , 63 , 105 , 0.05 ) ;
. welcome - img {
height : 100 px ;
margin - right : 10 px ;
user - select : none ;
}
. welcome - title {
font - size : 1.5 rem ;
line - height : 30 px ;
color : var ( -- ba - color - primary - light ) ;
}
. welcome - note {
padding - top : 6 px ;
font - size : 15 px ;
color : var ( -- el - text - color - primary ) ;
}
. bookkeeping - dashboard {
color : var ( -- el - text - color - primary ) ;
font - size : 13 px ;
}
. working {
height : 130 px ;
display : fle x;
justify - content : center ;
flex - wrap : wrap ;
height : 100 % ;
position : relative ;
& : hover {
. working - coffee {
- webkit - transform : translateY ( - 4 px ) scale ( 1.02 ) ;
- moz - transform : translateY ( - 4 px ) scale ( 1.02 ) ;
- ms - transform : translateY ( - 4 px ) scale ( 1.02 ) ;
- o - transform : translateY ( - 4 px ) scale ( 1.02 ) ;
transform : translateY ( - 4 px ) scale ( 1.02 ) ;
z - index : 999 ;
}
. dashboard - grid {
display : grid ;
grid - template - columns : minmax ( 0 , 1 fr ) 310 p x;
gap : 16 px ;
}
. dashboard - panel ,
. transaction - section {
border : 1 px solid var ( -- el - border - color ) ;
border - radius : 4 px ;
background : var ( -- el - bg - color ) ;
}
. panel - title {
padding : 11 px 14 px ;
border - bottom : 1 px solid var ( -- el - border - color ) ;
background : var ( -- el - fill - color - light ) ;
color : var ( -- el - text - color - primary ) ;
font - size : 15 px ;
font - weight : 700 ;
}
. table - scroll {
overflow - x : auto ;
}
. bank - table {
width : 100 % ;
border - collapse : collapse ;
th ,
td {
padding : 9 px 12 px ;
border - bottom : 1 px solid var ( -- el - border - color - lighter ) ;
text - align : left ;
vertical - align : middle ;
}
. working - coffee {
transition : all 0.3 s ease ;
width : 80 px ;
th {
color : var ( -- el - text - color - secondary ) ;
font - size : 12 px ;
white - space : nowrap ;
}
. working - text {
strong ,
small {
display : block ;
width : 100 % ;
font - size : 15 px ;
}
small {
margin - top : 3 px ;
color : var ( -- el - text - color - secondary ) ;
}
. balance {
color : var ( -- el - color - primary ) ;
font - weight : 700 ;
white - space : nowrap ;
}
. empty - banks {
padding : 24 px ;
color : var ( -- el - text - color - secondary ) ;
text - align : center ;
color : var ( -- el - text - color - primary ) ;
}
. working - opt {
position : absolute ;
top : - 40 px ;
right : 10 px ;
background - color : rgba ( $color : # 000000 , $alpha : 0.3 ) ;
padding : 10 px 20 px ;
border - radius : 20 px ;
color : var ( -- ba - bg - color - overlay ) ;
transition : all 0.3 s ease ;
cursor : pointer ;
opacity : 0 ;
z - index : 999 ;
& : active {
background - color : rgba ( $color : # 000000 , $alpha : 0.6 ) ;
}
}
& : hover {
. working - opt {
opacity : 1 ;
top : 0 ;
}
. working - done {
opacity : 1 ;
top : 50 px ;
}
}
}
. small - panel - box {
margin - top : 20 px ;
. bank - muted {
background : var ( -- el - fill - color - lighter ) ;
}
. small - panel {
background - color : # e9edf2 ;
border - radius : var ( -- el - border - radius - base ) ;
padding : 25 px ;
margin - bottom : 20 px ;
. small - panel - title {
color : # 92969 a ;
font - size : 15 px ;
}
. small - panel - content {
display : flex ;
align - items : flex - end ;
margin - top : 20 px ;
color : # 2 c3f5d ;
. content - left {
display : flex ;
align - items : center ;
font - size : 24 px ;
. icon {
margin - right : 10 px ;
}
}
. content - right {
font - size : 18 px ;
margin - left : auto ;
}
. color - success {
color : var ( -- el - color - success ) ;
}
. color - warning {
color : var ( -- el - color - warning ) ;
}
. color - danger {
color : var ( -- el - color - danger ) ;
}
. color - info {
color : var ( -- el - text - color - secondary ) ;
}
}
}
. growth - chart {
margin - bottom : 20 px ;
}
. user - growth - chart ,
. file - growth - chart {
height : 260 px ;
}
. new - user - growth {
height : 300 px ;
}
. user - source - chart ,
. user - surname - chart {
height : 400 px ;
}
. new - user - item {
. bank - operate {
display : flex ;
align - items : center ;
padding : 20 px ;
margin : 10 px 15 px ;
box - shadow : 0 0 30 px 0 rgba ( 82 , 63 , 105 , 0.05 ) ;
background - color : var ( -- ba - bg - color - overlay ) ;
. new - user - avatar {
height : 4 8px ;
width : 48 px ;
border - radius : 50 % ;
}
. new - user - base {
margin - left : 10 px ;
color : # 2 c3f5d ;
. new - user - name {
font - size : 15 px ;
}
. new - use r- time {
font - size : 13 px ;
}
}
. new - use r- arrow {
margin - left : auto ;
gap : 7 px ;
}
. breakdown {
display : flex ;
flex - wrap : wrap ;
gap : 8 px ;
color : var ( -- el - text - color - regular ) ;
white - space : nowrap ;
}
. dot {
font - style : normal ;
font - weight : 700 ;
}
. income ,
. amount - in {
color : var ( -- el - colo r- success ) ;
}
. outcome ,
. amount - out {
color : var ( -- el - colo r- danger ) ;
}
. more - info {
display : block ;
width : 100 % ;
padding : 9 px ;
border : 0 ;
background : transparent ;
color : var ( -- el - color - primary ) ;
cursor : pointer ;
}
. summary - side {
display : flex ;
flex - direction : column ;
gap : 14 px ;
}
. summary - panel dl {
display : grid ;
grid - template - columns : 1 fr auto ;
gap : 0 ;
margin : 0 ;
}
. summary - panel dt ,
. summary - panel dd {
margin : 0 ;
padding : 8 px 12 px ;
border - bottom : 1 px solid var ( -- el - border - color - lighter ) ;
}
. summary - panel dd {
color : var ( -- el - color - primary ) ;
font - weight : 700 ;
text - align : right ;
}
. create - button {
width : 100 % ;
min - height : 44 px ;
font - weight : 700 ;
}
. webhook - alert {
margin : 16 px 0 ;
p {
margin : 6 px 0 0 ;
line - height : 1.5 ;
}
}
. new - user - card : deep ( . el - card _ _body ) {
padding : 0 ;
. filter - row {
display : flex ;
justify - content : space - between ;
gap : 16 px ;
padding : 12 px ;
border - bottom : 1 px solid var ( -- el - border - color ) ;
}
@ media screen and ( max - width : 425 px ) {
. welcome - img {
display : none ;
. date - filter ,
. totals {
display : flex ;
align - items : center ;
flex - wrap : wrap ;
gap : 8 px ;
}
. date - filter : deep ( . el - date - editor ) {
width : 145 px ;
}
. totals {
justify - content : flex - end ;
em {
color : var ( -- el - color - primary ) ;
font - style : normal ;
}
}
@ media screen and ( max - width : 1200 px ) {
. lg - mb - 20 {
margin - bottom : 20 px ;
. transaction - table : deep ( . transaction - in ) {
-- el - table - tr - bg - color : var ( -- el - color - success - light - 9 ) ;
}
. transaction - table : deep ( . transaction - out ) {
-- el - table - tr - bg - color : var ( -- el - color - danger - light - 9 ) ;
}
. pagination {
display : flex ;
justify - content : flex - end ;
padding : 12 px ;
}
. inline - mode {
margin - left : 12 px ;
}
. dialog - note {
margin : 0 0 8 px ;
color : var ( -- el - text - color - secondary ) ;
}
: deep ( . el - dialog _ _body ) {
padding - top : 12 px ;
}
: deep ( . el - form - item . el - select ) ,
: deep ( . el - form - item . el - input ) {
width : 100 % ;
}
@ media screen and ( max - width : 1100 px ) {
. dashboard - grid {
grid - template - columns : 1 fr ;
}
}
html . dark {
. welcome {
background - color : var ( -- ba - bg - color - overlay ) ;
@ media screen and ( max - width : 720 px ) {
. filter - row {
flex - direction : column ;
}
. working - opt {
color : var ( -- el - tex t - color - primary ) ;
background - color : var ( -- ba - border - color ) ;
. totals {
justify - con ten t: flex - start ;
}
. small - panel {
background - color : var ( -- ba - bg - color - overlay ) ;
. small - panel - content {
color : var ( -- el - text - color - regular ) ;
}
}
. new - user - item {
. new - user - base {
color : var ( -- el - text - color - regular ) ;
}
. bank - table {
min - width : 760 px ;
}
}
< / style >