Files
webman-buildadmin-mall/web/src/views/backend/dashboard.vue

469 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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.Daily new players') }}</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 color-info">{{ t('dashboard.Today') }}</div>
</div>
</div>
</el-col>
<el-col :sm="12" :lg="6">
<div class="small-panel file suspension">
<div class="small-panel-title">{{ t('dashboard.Yesterday points') }}</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 color-info">{{ t('dashboard.Yesterday') }}</div>
</div>
</div>
</el-col>
<el-col :sm="12" :lg="6">
<div class="small-panel users suspension">
<div class="small-panel-title">{{ t('dashboard.Yesterday redeem') }}</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 color-info">{{ t('dashboard.Orders') }}</div>
</div>
</div>
</el-col>
<el-col :sm="12" :lg="6">
<div class="small-panel addons suspension">
<div class="small-panel-title">{{ t('dashboard.Pending physical to ship') }}</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 color-info">{{ t('dashboard.Pending') }}</div>
</div>
</div>
</el-col>
</el-row>
</div>
<div class="growth-chart">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="24" :lg="24">
<el-card shadow="hover" :header="t('dashboard.Yesterday item redeem stat')">
<div class="playx-kpis">
<div class="playx-kpi">
<div class="playx-kpi-title">{{ t('dashboard.Yesterday redeem points sum') }}</div>
<div class="playx-kpi-value">{{ state.playx?.yesterday_redeem?.points_cost_sum ?? 0 }}</div>
</div>
<div class="playx-kpi">
<div class="playx-kpi-title">{{ t('dashboard.Yesterday redeem amount sum') }}</div>
<div class="playx-kpi-value">{{ state.playx?.yesterday_redeem?.amount_sum ?? 0 }}</div>
</div>
<div class="playx-kpi">
<div class="playx-kpi-title">{{ t('dashboard.Grant failed retryable') }}</div>
<div class="playx-kpi-value">{{ state.playx?.grant_failed_retryable ?? 0 }}</div>
</div>
</div>
<el-table
v-loading="state.playxLoading"
:data="state.playx?.yesterday_redeem?.by_item ?? []"
size="small"
style="width: 100%; margin-top: 12px"
>
<el-table-column prop="mall_item_id" :label="t('dashboard.Item ID')" width="100" />
<el-table-column prop="title" :label="t('dashboard.Item title')" min-width="220" />
<el-table-column prop="order_count" :label="t('dashboard.Order count')" width="120" />
<el-table-column prop="completed_count" :label="t('dashboard.Completed')" width="120" />
<el-table-column prop="rejected_count" :label="t('dashboard.Rejected')" width="120" />
<el-table-column prop="points_cost_sum" :label="t('dashboard.Points sum')" width="140" />
<el-table-column prop="amount_sum" :label="t('dashboard.Amount sum')" width="140" />
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</div>
</template>
<script setup lang="ts">
import { useTransition } from '@vueuse/core'
import { CSSProperties, onMounted, onUnmounted, reactive, toRefs } 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 { getGreet } from '/@/utils/common'
import { Local } from '/@/utils/storage'
let workTimer: number
defineOptions({
name: 'dashboard',
})
const d = new Date()
const { t } = useI18n()
const adminInfo = useAdminInfo()
const state: {
remark: string
workingTimeFormat: string
pauseWork: boolean
playx: any | null
playxLoading: boolean
} = reactive({
remark: 'dashboard.Loading',
workingTimeFormat: '',
pauseWork: false,
playx: null,
playxLoading: true,
})
/**
* 带有数字向上变化特效的数据
*/
const countUp = reactive({
userRegNumber: 0,
fileNumber: 0,
usersNumber: 0,
addonsNumber: 0,
})
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
state.playx = res.data.playx ?? null
state.playxLoading = false
initCountUp()
}).catch(() => {
state.playxLoading = false
})
const initCountUp = () => {
const playx = state.playx ?? {}
const yesterdayRedeem = playx.yesterday_redeem ?? {}
countUpRefs.userRegNumber.value = playx.new_players_today ?? 0
countUpRefs.fileNumber.value = playx.yesterday_points_claimed ?? 0
countUpRefs.usersNumber.value = yesterdayRedeem.order_count ?? 0
countUpRefs.addonsNumber.value = playx.pending_physical_to_ship ?? 0
}
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 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)
}
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 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)
}
}
}
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
}
onMounted(() => {
startWork()
})
onUnmounted(() => {
clearInterval(workTimer)
})
</script>
<style scoped lang="scss">
.welcome {
background: #e1eaf9;
border-radius: 6px;
display: flex;
align-items: center;
padding: 15px 20px !important;
box-shadow: 0 0 30px 0 rgba(82, 63, 105, 0.05);
.welcome-img {
height: 100px;
margin-right: 10px;
user-select: none;
}
.welcome-title {
font-size: 1.5rem;
line-height: 30px;
color: var(--ba-color-primary-light);
}
.welcome-note {
padding-top: 6px;
font-size: 15px;
color: var(--el-text-color-primary);
}
}
.working {
height: 130px;
display: flex;
justify-content: center;
flex-wrap: wrap;
height: 100%;
position: relative;
&:hover {
.working-coffee {
-webkit-transform: translateY(-4px) scale(1.02);
-moz-transform: translateY(-4px) scale(1.02);
-ms-transform: translateY(-4px) scale(1.02);
-o-transform: translateY(-4px) scale(1.02);
transform: translateY(-4px) scale(1.02);
z-index: 999;
}
}
.working-coffee {
transition: all 0.3s ease;
width: 80px;
}
.working-text {
display: block;
width: 100%;
font-size: 15px;
text-align: center;
color: var(--el-text-color-primary);
}
.working-opt {
position: absolute;
top: -40px;
right: 10px;
background-color: rgba($color: #000000, $alpha: 0.3);
padding: 10px 20px;
border-radius: 20px;
color: var(--ba-bg-color-overlay);
transition: all 0.3s 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: 50px;
}
}
}
.small-panel-box {
margin-top: 20px;
}
.small-panel {
background-color: #e9edf2;
border-radius: var(--el-border-radius-base);
padding: 25px;
margin-bottom: 20px;
.small-panel-title {
color: #92969a;
font-size: 15px;
}
.small-panel-content {
display: flex;
align-items: flex-end;
margin-top: 20px;
color: #2c3f5d;
.content-left {
display: flex;
align-items: center;
font-size: 24px;
.icon {
margin-right: 10px;
}
}
.content-right {
font-size: 18px;
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: 20px;
}
.playx-kpis {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.playx-kpi {
background-color: var(--ba-bg-color-overlay);
border: 1px solid var(--ba-border-color);
border-radius: var(--el-border-radius-base);
padding: 12px;
}
.playx-kpi-title {
font-size: 13px;
color: var(--el-text-color-secondary);
}
.playx-kpi-value {
margin-top: 6px;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
}
@media screen and (max-width: 425px) {
.welcome-img {
display: none;
}
}
@media screen and (max-width: 1200px) {
.lg-mb-20 {
margin-bottom: 20px;
}
}
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 {
color: var(--el-text-color-regular);
}
}
.new-user-item {
.new-user-base {
color: var(--el-text-color-regular);
}
}
}
</style>