@@ -1,126 +1,84 @@
< 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 = "default-main dashboard-page" v-loading = "loading " >
< 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 regi str ation ' ) } } < / div >
< div class = "small-panel-title" > { { t ( 'dashboard.stat_user_total ' ) } } < / 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" / >
< Icon color = "#8595F4" size = "20" name = "fa fa-users " / >
< el-statistic :value = "stats.user_total " :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-title" > { { t ( 'dashboard.stat_new_today ' ) } } < / 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" / >
< Icon color = "#AD85F4" size = "20" name = "fa fa-user-plus " / >
< el-statistic :value = "stats.user_new_today " :value-style = "statisticValueStyle" / >
< / div >
< div class = "content-right" > + 50 % < / div >
< div class = "content-right" :class = "growthClass" > { { growthText } } < / 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-title" > { { t ( 'dashboard.stat_deposit_today ' ) } } < / 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" / >
< Icon color = "#74A8B5" size = "20" name = "fa fa-credit-card " / >
< el-statistic :value = "depositTodayDisplay " :value-style = "statisticValueStyle" / >
< / div >
< div class = "content-right" > + 28 % < / div >
< div class = "content-right color-success" > { { depositCountText } } < / 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-title" > { { t ( 'dashboard.stat_withdraw_pend ing ' ) } } < / 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" / >
< Icon color = "#F48595" size = "20" name = "fa fa-clock-o " / >
< el-statistic :value = "stats.withdraw_pending " :value-style = "statisticValueStyle" / >
< / div >
< div class = "content-right" > + 88 % < / div >
< div class = "content-right color-warning" > { { t ( 'dashboard.stat_hint_pending' ) } } < / 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 ')" >
< el-card shadow = "hover" : header= "t('dashboard.chart_new_user_deposit ')" >
< 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 ')" >
< el-card shadow = "hover" : header= "t('dashboard.chart_bet_7d ')" >
< 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 memb er')" >
< el-card class = "new-user-card" shadow = "hover" : header= "t('dashboard.recent_us ers ')" >
< div class = "new-user-growth" >
< el-scrollbar >
< div class = "new-user-item" >
< img class = "new-user-avatar" src = "~assets/login-header.png" alt = "" / >
< el-empty v-if = "recentUsers.length === 0" :description="t('dashboard.no_data')" / >
< el-scrollbar v-else >
< div v-for = "u in recentUsers" :key="u.id" class="new-user-item" >
< img
class = "new-user-avatar"
: src = "u.head_image ? fullUrl(u.head_image) : 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 >
< 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 class = "new-user-name" > { { u . username || '-' } } < / div >
< div class = "new-user-time" > { { formatUserTime ( u . create _time ) } } < / div >
< div v-if = "u.channel_name" class="new-user-channel" > {{ u.channel_name }} < / div >
< / div >
< Icon class = "new-user-arrow" color = "#8595F4" name = "fa fa-angle-right" / >
< / div >
@@ -134,12 +92,12 @@
< 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 ')" >
< el-card shadow = "hover" : header= "t('dashboard.chart_channel_users ')" >
< 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 ')" >
< el-card shadow = "hover" : header= "t('dashboard.chart_deposit_amount_channel ')" >
< div class = "user-surname-chart" :ref = "chartRefs.set" > < / div >
< / el-card >
< / el-col >
@@ -149,350 +107,235 @@
< / template >
< script setup lang = "ts" >
import { useEventListener , useTemplateRefsList , useTransition } from '@vueuse/core'
import { useEventListener , useTemplateRefsList } from '@vueuse/core'
import * as echarts from 'echarts'
import { CSSProperties , nextTick , onActivated , onBeforeMount , onMounted , onUnmounted , reactive , toRefs , watch } from 'vu e'
import type { EChartsType } from 'echarts/cor e'
import { CSSProperties , computed , nextTick , onActivated , onMounted , onUnmounted , reactive , ref , 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 type { DashboardDepositAmountChannelItem , DashboardPayload , DashboardRecentUser } from '/@/api/backend/dashboard '
import { useNavTabs } from '/@/stores/navTabs'
import { fullUrl , getGreet } from '/@/utils/common'
import { Local } from '/@/utils/storage'
let workTimer : number
import { fullUrl } from '/@/utils/common'
defineOptions ( {
name : 'dashboard' ,
} )
const d = new Date ( )
const { t } = useI18n ( )
const navTabs = useNavTabs ( )
const adminInfo = useAdminInfo ( )
const chartRefs = useTemplateRefsList < HTMLDivElement > ( )
const loading = ref ( true )
const state : {
charts : any [ ]
remark : string
workingTimeFormat : string
pauseWork : boolean
charts : EChartsType [ ]
} = reactive ( {
charts : [ ] ,
remark : 'dashboard.Loading' ,
workingTimeFormat : '' ,
pauseWork : false ,
} )
/**
* 带有数字向上变化特效的数据
*/
const countUp = reactive ( {
userRegNumber : 0 ,
fileNumber : 0 ,
usersNumber : 0 ,
addonsNumber : 0 ,
const stats = reactive ( {
user _total : 0 ,
user _new _today : 0 ,
user _new _yesterday : 0 ,
user_new _growth _pct : null as number | null ,
deposit _today _amount : '0.00' ,
deposit _today _count : 0 ,
withdraw _pending : 0 ,
bet _today _amount : '0.00' ,
bet _today _count : 0 ,
} )
const trend = reactive ( {
days : [ ] as string [ ] ,
new _users : [ ] as number [ ] ,
deposit _amount : [ ] as string [ ] ,
bet _amount : [ ] as string [ ] ,
} )
const channelShare = ref < { name : string ; value : number } [ ] > ( [ ] )
const depositAmountChannelShare = ref < DashboardDepositAmountChannelItem [ ] > ( [ ] )
const recentUsers = ref < DashboardRecentUser [ ] > ( [ ] )
const depositTodayDisplay = computed ( ( ) => {
const n = parseFloat ( stats . deposit _today _amount )
if ( ! Number . isFinite ( n ) ) {
return stats . deposit _today _amount
}
return n . toFixed ( 2 )
} )
const growthText = computed ( ( ) => {
const p = stats . user _new _growth _pct
if ( p === null || p === undefined ) {
return '—'
}
const sign = p > 0 ? '+' : ''
return sign + p . toFixed ( 1 ) + '%'
} )
const growthClass = computed ( ( ) => {
const p = stats . user _new _growth _pct
if ( p === null || p === undefined ) {
return 'color-info'
}
if ( p > 0 ) {
return 'color-success'
}
if ( p < 0 ) {
return 'color-danger'
}
return 'color-info'
} )
const depositCountText = computed ( ( ) => {
return t ( 'dashboard.deposit_orders_today' , { n : stats . deposit _today _count } )
} )
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' ,
}
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 disposeCharts = ( ) => {
for ( const c of state . charts ) {
c . dispose ( )
}
state . charts = [ ]
}
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 : { } ,
const parseMoneySeries = ( arr : string [ ] ) => arr . map ( ( s ) => parseFloat ( String ( s ) ) || 0 )
const initTrendDepositChart = ( ) => {
const el = chartRefs . value [ 0 ] as HTMLElement
if ( ! el ) {
return
}
const chart = echarts . init ( el )
chart . setOption ( {
color : [ '#8595F4' , '#67C23A' ] ,
grid : { top : 48 , right : 56 , bottom : 24 , left : 48 } ,
tooltip : { trigger : 'axis' } ,
legend : {
data : [ t ( 'dashboard.Visit s' ) , t ( 'dashboard.Registration volume ' ) ] ,
textStyle : {
color : '#73767a' ,
} ,
data : [ t ( 'dashboard.series_new_user s' ) , t ( 'dashboard.series_deposit_amount ' ) ] ,
textStyle : { color : '#73767a' } ,
top : 0 ,
} ,
series : [
xAxis : { type : 'category' , data : trend . days , boundaryGap : false } ,
yAxis : [
{ type : 'value' , name : t ( 'dashboard.series_new_users' ) , splitLine : { lineStyle : { type : 'dashed' } } } ,
{
nam e: 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 ,
} ,
typ e: 'value' ,
name : t ( 'dashboard.series_deposit_amount' ) ,
position : 'right ' ,
splitLine : { show : false } ,
} ,
] ,
}
userGrowthChart . setOption ( option )
state . charts . push ( userGrowthChart )
}
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 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' ,
item Style: {
opacity : 0.5 ,
} ,
emphasis : {
itemStyle : {
opacity : 1 ,
} ,
} ,
data : [ 123 , 60 , 25 , 80 ] ,
z : 10 ,
name : t ( 'dashboard.series_new_users' ) ,
type : 'line ' ,
smooth : true ,
data : trend . new _users ,
area Style: { color : 'rgba(133,149,244,0.15)' } ,
} ,
{
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 ] ,
} ,
] ,
name : t ( 'dashboard.series_deposit_amount' ) ,
type : 'line ' ,
smooth : true ,
yAxisIndex : 1 ,
data : parseMoneySeries ( trend . deposit _amount ) ,
areaStyle : { color : 'rgba(103,194,58,0.12)' } ,
} ,
] ,
}
UserSourceC hart. setOption ( option )
state . charts . push ( UserSourceChart )
} )
state . c harts . push ( chart )
}
const initUserSurname Chart = ( ) => {
const userSurnameChart = echarts . init ( chartRefs . value [ 3 ] as HTMLElement )
const data = genData ( 20 )
const option = {
tooltip : {
trigger : 'item' ,
formatter : '{a} <br/>{b} : {c} ({d}%)' ,
} ,
legen d: {
type : 'scroll' ,
orient : 'vertical' ,
right : 10 ,
top : 20 ,
bottom : 20 ,
data : data . legendData ,
textStyle : {
color : '#73767a' ,
} ,
} ,
const initBetTrend Chart = ( ) => {
const el = chartRefs . value [ 1 ] as HTMLElement
if ( ! el ) {
return
}
const chart = echarts . init ( el )
chart . setOption ( {
color : [ '#E6A23C' ] ,
gri d: { top : 36 , right : 16 , bottom : 24 , left : 48 } ,
tooltip : { trigger : 'axis' } ,
xAxis : { type : 'category' , data : trend . days } ,
yAxis : { type : 'value' , splitLine : { lineStyle : { type : 'dashed' } } } ,
series : [
{
name : t ( 'dashboard.series_bet_amount' ) ,
type : 'bar' ,
data : parseMoneySeries ( trend . bet _amount ) ,
itemStyle : { borderRadius : [ 4 , 4 , 0 , 0 ] } ,
} ,
] ,
} )
state . charts . push ( chart )
}
const initChannelPie = ( ) => {
const el = chartRefs . value [ 2 ] as HTMLElement
if ( ! el ) {
return
}
const chart = echarts . init ( el )
const data = channelShare . value . map ( ( x ) => ( { name : x . name , value : x . value } ) )
chart . setOption ( {
tooltip : { trigger : 'item' } ,
legend : { type : 'scroll' , bottom : 0 , textStyle : { color : '#73767a' } } ,
series : [
{
name : t ( 'dashboard.full name' ) ,
type : 'pie' ,
radius : '55%' ,
center : [ '4 0%' , '50 %' ] ,
data : data . seriesData ,
radius : [ '36%' , '62%' ] ,
center : [ '5 0%' , '46 %' ] ,
data ,
emphasis : {
itemStyle : {
shadowBlur : 10 ,
shadowOffsetX : 0 ,
shadowColor : 'rgba(0, 0, 0, 0.5)' ,
} ,
itemStyle : { shadowBlur : 10 , shadowOffsetX : 0 , shadowColor : 'rgba(0,0,0,0.15)' } ,
} ,
} ,
] ,
} )
state . charts . push ( chart )
}
const initDepositAmountChannelPie = ( ) => {
const el = chartRefs . value [ 3 ] as HTMLElement
if ( ! el ) {
return
}
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 ) ,
} )
}
const chart = echarts . init ( el )
const data = depositAmountChannelShare . value . map ( ( x ) => {
const v = parseFloat ( String ( x . value ) )
return {
legendData : legendData ,
seriesData : seriesData ,
name : x . name ,
value : Number . isFinite ( v ) ? v : 0 ,
}
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 )
} )
chart . setOption ( {
tooltip : {
trigger : 'item' ,
formatter : ( p : { name ? : string ; value ? : number ; percent ? : number } ) => {
const name = p . name ? ? ''
const val = typeof p . value === 'number' && Number . isFinite ( p . value ) ? p . value : 0
const pct = typeof p . percent === 'number' && Number . isFinite ( p . percent ) ? p . percent : 0
return name + '<br/>' + val . toFixed ( 2 ) + ' (' + pct . toFixed ( 1 ) + '%)'
} ,
} ,
legend : { type : 'scroll' , bottom : 0 , textStyle : { color : '#73767a' } } ,
series : [
{
type : 'pie' ,
radius : [ '32%' , '58%' ] ,
center : [ '50%' , '46%' ] ,
data ,
emphasis : {
itemStyle : { shadowBlur : 10 , shadowOffsetX : 0 , shadowColor : 'rgba(0,0,0,0.15)' } ,
} ,
} ,
] ,
} )
state . charts . push ( chart )
}
const echartsResize = ( ) => {
@@ -503,95 +346,66 @@ const echartsResize = () => {
} )
}
const onChangeWorkState = ( ) => {
const time = parseInt ( ( new Date ( ) . getTime ( ) / 1000 ) . toString ( ) )
const workingTime = Local . get ( WORKING _TIME )
if ( state . pa useWork ) {
// 继续工作
workingTime . pa useTime += 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 applyPayload = ( payload : DashboardPayload ) => {
const s = payload . stats
stats . user _total = s . user _total
stats . user _new _today = s . user _new _today
stats . user _new _yesterday = s . user _new _yesterday
stats . user _new _growth _pct = s . user _new _growth _pct
stats . deposit _today _amount = s . deposit _today _amount
stats . deposit _today _count = s . deposit _today _count
stats . withdraw _pending = s . withdraw _pending
stats . bet _today _amount = s . bet _today _amount
stats . bet _today _count = s . bet _today _count
trend . days = payload . trend . days
trend . new _users = payload . trend . new _users
trend . deposit _amount = payload . trend . deposit _amount
trend . bet _amount = payload . trend . bet _amount
channelShare . value = payload . channel _share
depositAmountChannelShare . value = payload . deposit _amount _channel _share ? ? [ ]
recentUsers . value = payload . recent _users
}
const loadDashboard = async ( ) => {
loading . value = true
try {
const res = await index ( )
const payload = res . data as unknown as DashboardPayload
applyPayload ( payload )
await nextTick ( )
disposeCharts ( )
initTrendDepositChart ( )
initBetTrendChart ( )
initChannelPie ( )
initDepositAmountChannelPie ( )
echartsResize ( )
} catch ( _e ) {
// 统计数据加载失败时由 axios 拦截器提示;此处不重复展示
} finally {
loading . value = false
}
}
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 . date != currentDate ) {
workingTime . date = currentDate
workingTime . startTime = time
workingTime . pauseTime = workingTime . startPauseTime = 0
Local . set ( WORKING _TIME , workingTime )
function formatUserTime ( ts : number ) : string {
if ( ! ts || ts <= 0 ) {
return '—'
}
let startPauseTime = 0
if ( workingTime . startPauseTime <= 0 ) {
state . pauseWork = false
startPauseTime = 0
} else {
state . pauseWork = true
startPauseTime = time - workingTime . startPauseTime // 已暂停时间
const now = Math . floor ( Date . now ( ) / 1000 )
const diff = now - ts
if ( diff < 6 0) {
return diff + t ( 'dashboard.seconds_ago' )
}
let workingSeconds = time - workingTime . startTime - workingTime . pauseTime - startPauseTime
state . workingTimeFormat = formatSeconds ( workingSeconds )
if ( ! state . pauseWork ) {
workTimer = window . setInterval ( ( ) => {
workingSeconds ++
state . workingTimeFormat = formatSeconds ( workingSeconds )
} , 1000 )
if ( diff < 3600 ) {
return Math . floor ( diff / 60 ) + t ( 'dashboard.minutes_ago' )
}
}
const formatSeconds = ( seconds : number ) => {
var secondTime = 0 // 秒
var minuteTime = 0 // 分
var hourTime = 0 // 小时
var dayTime = 0 // 天
var result = ''
if ( seconds < 60 ) {
secondTime = seconds
} else {
// 获取分钟, 除以60取整数, 得到整数分钟
minuteTime = Math . floor ( seconds / 60 )
// 获取秒数,秒数取佘,得到整数秒数
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 )
}
}
if ( diff < 86400 ) {
return Math . floor ( diff / 3600 ) + t ( 'dashboard.hours_ago' )
}
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 d = new Date ( ts * 1000 )
const pad = ( n : number ) => ( n < 10 ? '0' + n : String ( n ) )
return d . getFullYear ( ) + '-' + pad ( d . getMonth ( ) + 1 ) + '-' + pad ( d . getDate ( ) ) + ' ' + pad ( d . getHours ( ) ) + ':' + pad ( d . getMinutes ( ) )
}
onActivated ( ( ) => {
@@ -599,23 +413,12 @@ onActivated(() => {
} )
onMounted ( ( ) => {
startWork ( )
initCountUp ( )
initUserGrowthChart ( )
initFileGrowthChart ( )
initUserSourceChart ( )
initUserSurnameChart ( )
loadDashboard ( )
useEventListener ( window , 'resize' , echartsResize )
} )
onBeforeMount ( ( ) => {
for ( const key in state . charts ) {
state . charts [ key ] . dispose ( )
}
} )
onUnmounted ( ( ) => {
clearInterval ( workTimer )
disposeCharts ( )
} )
watch (
@@ -624,89 +427,12 @@ watch(
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 ) ;
}
}
. working {
height : 130 px ;
display : flex ;
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 ;
}
}
. working - coffee {
transition : all 0.3 s ease ;
width : 80 px ;
}
. working - text {
display : block ;
width : 100 % ;
font - size : 15 px ;
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 ;
margin - top : 0 ;
}
. small - panel {
background - color : # e9edf2 ;
@@ -731,8 +457,11 @@ watch(
}
}
. content - right {
font - size : 18 px ;
font - size : 14 px ;
margin - left : auto ;
text - align : right ;
max - width : 48 % ;
line - height : 1.3 ;
}
. color - success {
color : var ( -- el - color - success ) ;
@@ -755,62 +484,62 @@ watch(
. file - growth - chart {
height : 260 px ;
}
. user - source - chart ,
. user - surname - chart {
height : 360 px ;
}
. new - user - growth {
height : 300 px ;
}
. user - source - chart ,
. user - surname - chart {
height : 400 px ;
}
. new - user - item {
display : flex ;
align - items : center ;
padding : 20 px ;
margin : 10 px 15 px ;
padding : 16 px 20px ;
margin : 8 px 12 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 : 48 px ;
width : 48 px ;
border - radius : 50 % ;
object - fit : cover ;
}
. new - user - base {
margin - left : 10 px ;
flex : 1 ;
min - width : 0 ;
color : # 2 c3f5d ;
. new - user - name {
font - size : 15 px ;
font - weight : 500 ;
}
. new - user - time {
font - size : 13 px ;
color : var ( -- el - text - color - secondary ) ;
}
. new - user - channel {
font - size : 12 px ;
color : var ( -- el - text - color - placeholder ) ;
margin - top : 2 px ;
}
}
. new - user - arrow {
margin - left : auto ;
flex - shrink : 0 ;
}
}
. new - user - card : deep ( . el - card _ _body ) {
padding : 0 ;
}
@ media screen and ( max - width : 425 px ) {
. welcome - img {
display : none ;
}
}
@ media screen and ( max - width : 1200 px ) {
. lg - mb - 20 {
margin - bottom : 20 px ;
}
}
html . dark {
. welcome {
background - color : var ( -- ba - bg - color - overlay ) ;
}
. working - opt {
color : var ( -- el - text - color - primary ) ;
background - color : var ( -- ba - border - color ) ;
}
. small - panel {
background - color : var ( -- ba - bg - color - overlay ) ;
. small - panel - content {