|
|
<template>
<el-row> <!-- 数据总览卡片 --> <el-col :span="4" style="padding-right: 10px;"> <!-- 适当留白避免拥挤 --> <el-card class="center-card margin-bottom">数据总览</el-card> </el-col> <!-- 最后更新时间 --> <el-col :span="18" style="display: flex; align-items: center; font-size: 18px"> 最后更新时间:{{workDataUpdateTime}} </el-col>
<!-- 剩余栅格空间(可选,用于占满一行) --> <el-col :span="18"></el-col> </el-row> <el-row :gutter="10"> <!-- 第一个卡片 --> <el-col :span="6"> <el-card class="card-item"> <template #header> <div class="card-header"> <div class="card-title">当前金币余量</div> <div>{{ currentGold / 100 }} 较前一日 {{ dailyChange / 100 }} <template v-if="dailyChange > 0"> <el-icon style="color:red"> <ArrowUpBold/> </el-icon> </template> <template v-else-if="dailyChange < 0"> <el-icon style="color:forestgreen"> <ArrowDownBold/> </el-icon> </template> <template v-else> <el-icon style="color:grey"> <SemiSelect/> </el-icon> </template> </div> </div> </template> <div> <div class="margin-bottom">永久金币:{{ currentPermanent / 100 }}</div> <div class="margin-bottom">免费金币:{{ currentFree / 100 }}</div> <div class="margin-bottom">[六月到期|{{ currentFreeJune / 100 }}] [12月到期|{{ currentFreeDecember / 100 }}] </div> <div>任务金币:{{ currentTask / 100 }}</div> </div> </el-card> </el-col>
<!-- 第二个卡片 --> <el-col :span="6"> <el-card class="card-item"> <div class="card-title">全年累计充值金币数</div> <div class="card-title">{{ yearlyRecharge / 100 }}</div> <div> </div> <div class="center-card">折合新币累计金额:{{ yearlyMoney / 100 }}</div> <template #footer> <el-col class="margin-bottom center-card">昨日新增:{{ recharge / 100 }}</el-col> <el-col class="margin-bottom center-card">其中充值:{{ money / 100 }}</el-col> </template> </el-card> </el-col>
<!-- 第三个卡片 --> <el-col :span="6"> <el-card class="card-item"> <div class="card-title">全年累计消费金币数</div> <div class="card-title">{{ yearlyReduce / 100 }}</div> <div class="center-card">消费:{{ yearlyConsume / 100 }}</div> <div class="center-card">退款:{{ yearlyRefund / 100 }}</div> <template #footer> <div></div> <div class="margin-bottom center-card">昨日新增消费:{{ dailyConsume / 100 }}</div> <div class="margin-bottom center-card">昨日新增消耗:{{ dailyReduce / 100 }}</div> <div class="margin-bottom center-card">昨日新增退款:{{ dailyRefund / 100 }}</div> </template> </el-card> </el-col>
<!-- 第四个卡片 --> <el-col :span="6"> <el-card class="card-item"> <el-col class="card-title">全年累计充值人头数</el-col> <el-col class="card-title">{{ yearlyRechargeNum }}</el-col> <el-col class="center-card">周同比:{{ sumWow }}% <template v-if="sumWow > 0"> <el-icon style="color:red"> <ArrowUpBold/> </el-icon> </template> <template v-else-if="sumWow < 0"> <el-icon style="color:forestgreen"> <ArrowDownBold/> </el-icon> </template> <template v-else> <el-icon style="color:grey"> <SemiSelect/> </el-icon> </template> </el-col> <el-col class="center-card">日环比:{{ sumDaily.toFixed(2) }}% <template v-if="sumDaily > 0"> <el-icon style="color:red"> <ArrowUpBold/> </el-icon> </template> <template v-else-if="sumDaily < 0"> <el-icon style="color:forestgreen"> <ArrowDownBold/> </el-icon> </template> <template v-else> <el-icon style="color:grey"> <SemiSelect/> </el-icon> </template> </el-col> <template #footer> <el-col class="margin-bottom center-card">昨日充值人数:{{ rechargeNum }}</el-col> <el-col class="margin-bottom center-card">其中首充:{{ firstRecharge }}</el-col> </template> </el-card> </el-col> </el-row>
<el-row :gutter="10" style="margin-top: 20px"> <el-col :span="24"> <el-card style="width: 100%"> <el-row> <el-col :span="21"> <el-tabs v-model="activeTab" @tab-change="handleTabChange"> <el-tab-pane label="金币充值" name="recharge"></el-tab-pane> <el-tab-pane label="金币消费" name="consume"></el-tab-pane> </el-tabs> </el-col> <el-col :span="24"> <el-row> <div style="margin-top:5px">合计 永久金币 {{ activeTab === 'recharge' ? sumRechargePermanent / 100 : sumConsumePermanent / 100 }} 免费金币 {{ activeTab === 'recharge' ? sumRechargeFree / 100 : sumConsumeFree / 100 }} 任务金币 {{ activeTab === 'recharge' ? sumRechargeTask / 100 : sumConsumeTask / 100 }} </div> <div @change="handleDatePickerChange"> <el-button @click="getToday()" label="day" style="margin-left:250px" :type="activeTimeRange === 'today' ? 'primary' : ''">今日</el-button> <el-button @click="getWeek()" label="week" :type="activeTimeRange === 'week' ? 'primary' : ''">本周</el-button> <el-button @click="getMonth()" label="month" :type="activeTimeRange === 'month' ? 'primary' : ''">本月</el-button> <el-button @click="getYear()" label="year" :type="activeTimeRange === 'year' ? 'primary' : ''">本年</el-button> </div> <el-date-picker v-model="dateRange" type="datetimerange" range-separator="→" start-placeholder="开始时间" end-placeholder="结束时间" style="margin-left:10px" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" /> <el-button type="primary" style="margin-left: 5px" @click="getChartData">查询</el-button> </el-row> </el-col> </el-row>
<el-row :gutter="20" style="margin-top: 20px"> <el-col :span="18"> <div class="bar">
<div v-if="chartLoading" class="loading-overlay"> <div class="loading-spinner"></div> </div>
<div ref="chartRef" style="width: 100%; height: 400px"></div> </div> </el-col> <el-col :span="6"> <el-card class="rank-card" style="width: 100%; height: 100%"> <div class="card-large margin-bottom">金币{{ activeTab === 'recharge' ? '充值' : '消费' }}排名</div> <el-select v-model="selectedType" style="width: 100%; margin-bottom: 15px"> <el-option label="全部类型" value="all"></el-option> <el-option label="永久金币" value="permanent"></el-option> <el-option label="免费金币" value="free"></el-option> <el-option label="任务金币" value="task"></el-option> </el-select> <el-table :data="tableData" height="320px"> <el-table-column prop="rank" label="排名" width="60" align="center"></el-table-column> <el-table-column prop="market" label="地区" align="center"></el-table-column> <el-table-column prop="coinAmount" label="金币数量" align="center"> <template #default="{ row }"> {{ row.coinAmount.toLocaleString() }} </template> </el-table-column> </el-table> </el-card> </el-col> </el-row> </el-card> </el-col> </el-row> </template>
<script setup> import * as echarts from 'echarts' import {onMounted, ref, watch} from 'vue' import API from '@/util/http' import {ElMessage} from 'element-plus' import dayjs from 'dayjs'; import utc from 'dayjs-plugin-utc' import {ArrowDownBold, ArrowUpBold, SemiSelect} from '@element-plus/icons-vue'
dayjs.extend(utc)
// 标记当前激活的时间范围按钮
const activeTimeRange = ref('') // 日期选择器变化时清除按钮激活状态
const handleDatePickerChange = () => { activeTimeRange.value = '' } // 地区数据
const markets = ref([]) // 图表相关
const dateRange = ref([]) const activeTab = ref('recharge') const selectedType = ref('all') const tableData = ref([]) const chartRef = ref(null) let chartInstance = null // 图表合计数
const sumRechargePermanent = ref(0) const sumRechargeFree = ref(0) const sumRechargeTask = ref(0) const sumConsumePermanent = ref(0) const sumConsumeFree = ref(0) const sumConsumeTask = ref(0) // 用户信息
const adminData = ref({}) // 卡片数据相关
const currentGold = ref(0) const dailyChange = ref(0) const currentPermanent = ref(0) const currentFree = ref(0) const currentFreeJune = ref(0) const currentFreeDecember = ref(0) const currentTask = ref(0) const yearlyRecharge = ref(0) const yearlyMoney = ref(0) const recharge = ref(0) const money = ref(0) const yearlyReduce = ref(0) const yearlyConsume = ref(0) const yearlyRefund = ref(0) const dailyReduce = ref(0) const dailyConsume = ref(0) const dailyRefund = ref(0) const yearlyRechargeNum = ref(0) const sumWow = ref(0) const sumDaily = ref(0) const rechargeNum = ref(0) const firstRecharge = ref(0) const length = ref(0) const isLoading = ref(false) const formatDate = function (date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } // 今天
const getToday = function () { const today = dayjs() const startTime = today.startOf('day').format('YYYY-MM-DD HH:mm:ss') const endTime = today.add(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss') dateRange.value = [startTime, endTime] console.log('看看dateRange', dateRange.value) activeTimeRange.value = 'today' // 标记当前激活状态
getChartData() } // 本周
const getWeek = function () { const today = dayjs() const startTime = (today.startOf('week').add(1, 'day')).format('YYYY-MM-DD HH:mm:ss') const endTime = today.add(1, 'week').startOf('week').add(1, 'day').format('YYYY-MM-DD HH:mm:ss') dateRange.value = [startTime, endTime] console.log('看看dateRange', dateRange.value) activeTimeRange.value = 'week' // 标记当前激活状态
getChartData() } // 本月
const getMonth = function () { const today = dayjs() const startTime = today.startOf('month').format('YYYY-MM-DD HH:mm:ss') const endTime = today.add(1, 'month').startOf('month').format('YYYY-MM-DD HH:mm:ss') dateRange.value = [startTime, endTime] console.log('看看dateRange', dateRange.value) activeTimeRange.value = 'month' // 标记当前激活状态
getChartData() } // 本年
const getYear = function () { const today = dayjs() const startTime = today.startOf('year').format('YYYY-MM-DD HH:mm:ss') const endTime = today.add(1, 'year').startOf('year').format('YYYY-MM-DD HH:mm:ss') dateRange.value = [startTime, endTime] console.log('看看dateRange', dateRange.value) activeTimeRange.value = 'year' // 标记当前激活状态
getChartData() }
// 要加上所有市场的,还有额外计算的(总数 = 永久 + 6月 + 12月 + 免费 + 任务)
const processData = (data) => { const summary = { currentGold: 0, dailyChange: 0, currentPermanent: 0, currentFreeJune: 0, currentFreeDecember: 0, currentTask: 0, currentFree: 0, recharge: 0, money: 0, yearlyRecharge: 0, yearlyMoney: 0, consumePermanent: 0, consumeFreeJune: 0, consumeFreeDecember: 0, consumeTask: 0, refundPermanent: 0, refundFreeJune: 0, refundFreeDecember: 0, refundTask: 0, dailyReduce: 0, yearlyConsume: 0, yearlyRefund: 0, yearlyReduce: 0, rechargeNum: 0, firstRecharge: 0, sumWow: 0, sumDaily: 0, yearlyRechargeNum: 0 }
// 遍历市场
data.marketCards.forEach(market => { for (const i in summary) { if (market[i] !== undefined && market[i] !== null) { // 其实还应该卡一个number
summary[i] += market[i] } } })
// wow和daily除一下
length.value = data.markets.length console.log(length.value)
// 计算昨日新增消费和退款
const yesterdayConsume = summary.consumePermanent + summary.consumeFreeJune + summary.consumeFreeDecember + summary.consumeTask const yesterdayRefund = summary.refundPermanent + summary.refundFreeJune + summary.refundFreeDecember + summary.refundTask
// 更新卡片数据
currentGold.value = summary.currentGold.toFixed(2) dailyChange.value = summary.dailyChange.toFixed(2) currentPermanent.value = summary.currentPermanent.toFixed(2) currentFree.value = summary.currentFree.toFixed(2) currentFreeJune.value = summary.currentFreeJune.toFixed(2) currentFreeDecember.value = summary.currentFreeDecember.toFixed(2) currentTask.value = summary.currentTask.toFixed(2)
yearlyRecharge.value = summary.yearlyRecharge.toFixed(2) yearlyMoney.value = summary.yearlyMoney.toFixed(2) recharge.value = summary.recharge.toFixed(2) money.value = summary.money.toFixed(2)
yearlyReduce.value = summary.yearlyReduce.toFixed(2) yearlyConsume.value = summary.yearlyConsume.toFixed(2) yearlyRefund.value = summary.yearlyRefund.toFixed(2) dailyReduce.value = summary.dailyReduce.toFixed(2) dailyConsume.value = yesterdayConsume.toFixed(2) dailyRefund.value = yesterdayRefund.toFixed(2)
yearlyRechargeNum.value = summary.yearlyRechargeNum sumWow.value = (summary.sumWow / length.value).toFixed(2) sumDaily.value = (summary.sumDaily / length.value).toFixed(2) rechargeNum.value = summary.rechargeNum firstRecharge.value = summary.firstRecharge }
// 获取市场列表
const getMarkets = async () => { try { const response = await API({url: '/general/market', data: {}}) if (Array.isArray(response.data)) { markets.value = response.data console.log('市场列表获取成功:', markets.value) } else { console.error('获取市场列表失败', response) ElMessage.error('获取市场列表失败') } } catch (error) { console.error('获取市场列表失败:', error) ElMessage.error('获取市场列表失败') } }
// 获取图表数据
const getChartData = async () => { try { // 校验市场数据到底有没有
if (!markets.value || markets.value.length === 0) { await getMarkets() }
// 本年
if (!dateRange.value || dateRange.value.length === 0) { getYear() }
const params = { markets: markets.value, startDate: dateRange.value[0], endDate: dateRange.value[1] };
const response = await API({ url: '/workbench/getGraph', data: params }) console.log('看看params', params) if (Array.isArray(response.marketGraphs)) { // 处理图表数据
processChartData(response.marketGraphs) // 处理排名数据
processRankingData(response.marketGraphs) } else { console.error('获取图表数据失败:', response) ElMessage.error('获取图表数据失败') } } catch (error) { console.error('获取图表数据失败:', error) ElMessage.error('获取图表数据失败') } } // 处理图表数据
const processChartData = (marketCards) => { const chartData = { rechargePermanent: [], rechargeFree: [], rechargeTask: [], consumePermanent: [], consumeFree: [], consumeTask: [] } // 这是图表的合计数,怎样遍历?????
const sumRechargePermanent1 = ref(0) const sumRechargeFree1 = ref(0) const sumRechargeTask1 = ref(0) const sumConsumePermanent1 = ref(0) const sumConsumeFree1 = ref(0) const sumConsumeTask1 = ref(0)
marketCards.forEach(market => { chartData.rechargePermanent.push(market.sumRechargePermanent / 100 || 0) chartData.rechargeFree.push(market.sumRechargeFree / 100 || 0) chartData.rechargeTask.push(market.sumRechargeTask / 100 || 0) chartData.consumePermanent.push(market.sumConsumePermanent / 100 || 0) chartData.consumeFree.push(market.sumConsumeFree / 100 || 0) chartData.consumeTask.push(market.sumConsumeTask / 100 || 0)
// 合计数合计数合计数咋算
sumRechargePermanent1.value += (market.sumRechargePermanent || 0) sumRechargeFree1.value += (market.sumRechargeFree || 0) //sumRechargeTask1.value += (market.sumRechargeTask || 0)
sumConsumePermanent1.value += (market.sumConsumePermanent || 0) sumConsumeFree1.value += (market.sumConsumeFree || 0) sumConsumeTask1.value += (market.sumConsumeTask || 0) }) sumRechargePermanent.value = sumRechargePermanent1.value sumRechargeFree.value = sumRechargeFree1.value sumRechargeTask.value = 0 sumConsumePermanent.value = sumConsumePermanent1.value sumConsumeFree.value = sumConsumeFree1.value sumConsumeTask.value = sumConsumeTask1.value
updateChart(chartData) }
const processRankingData = (marketCards) => { // 每个市场的总金币数
const rankingData = marketCards.map(market => { let coinAmount = 0; if (activeTab.value === 'recharge') { // 充值排名
switch (selectedType.value) { case 'all': coinAmount = (market.sumRechargePermanent / 100 || 0) + (market.sumRechargeFree / 100 || 0) + (market.sumRechargeTask / 100 || 0); break; case 'permanent': coinAmount = market.sumRechargePermanent / 100 || 0; break; case 'free': coinAmount = market.sumRechargeFree / 100 || 0; break; case 'task': coinAmount = market.sumRechargeTask / 100 || 0; break; } } else { // 消费排名
switch (selectedType.value) { case 'all': coinAmount = (market.sumConsumePermanent / 100 || 0) + (market.sumConsumeFree / 100 || 0) + (market.sumConsumeTask / 100 || 0); break; case 'permanent': coinAmount = market.sumConsumePermanent / 100 || 0; break; case 'free': coinAmount = market.sumConsumeFree / 100 || 0; break; case 'task': coinAmount = market.sumConsumeTask / 100 || 0; break; } } return { market: market.market, coinAmount: coinAmount }; });
// 按金币数量排序
rankingData.sort((a, b) => b.coinAmount - a.coinAmount);
// 排名序号
tableData.value = rankingData.map((item, index) => ({ rank: index + 1, ...item })); }
watch(selectedType, () => { getChartData(); }); // 更新图表
const updateChart = (chartData) => { if (!chartInstance) { initChart() } chartLoading.value = true try{ let series = [] let legend = []
if (activeTab.value === 'recharge') { series = [ { name: '永久金币', type: 'bar', stack: 'recharge', data: chartData.rechargePermanent, itemStyle: {color: '#5470c6'}, barWidth: 30 }, { name: '免费金币', type: 'bar', stack: 'recharge', data: chartData.rechargeFree, itemStyle: {color: '#91cc75'}, barWidth: 30 }, { name: '任务金币', type: 'bar', stack: 'recharge', data: chartData.rechargeTask, itemStyle: {color: '#fac858'}, barWidth: 30 } ] legend = ['永久金币', '免费金币', '任务金币'] } else { series = [ { name: '永久金币', type: 'bar', stack: 'consume', data: chartData.consumePermanent, itemStyle: {color: '#5470c6'}, barWidth: 30 }, { name: '免费金币', type: 'bar', stack: 'consume', data: chartData.consumeFree, itemStyle: {color: '#91cc75'}, barWidth: 30 }, { name: '任务金币', type: 'bar', stack: 'consume', data: chartData.consumeTask, itemStyle: {color: '#fac858'}, barWidth: 30 } ] legend = ['永久金币', '免费金币', '任务金币'] }
const option = { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, formatter: function (params) { let result = params[0].name + '<br/>' params.forEach(param => { result += `${param.seriesName}: ${param.value.toLocaleString()}<br/>` }) return result } }, legend: { data: legend, bottom: 10 }, grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true }, xAxis: { type: 'category', data: markets.value, axisLabel: { interval: 0, rotate: 30 } }, yAxis: { type: 'value', axisLabel: { formatter: function (value) { return value.toLocaleString() } } }, series: series }
chartInstance.setOption(option) } catch (error) { console.error('图表更新失败:', error) ElMessage.error('图表渲染失败') } finally { setTimeout(() => { chartLoading.value = false }, 300) } }
// 处理标签切换
const handleTabChange = () => { getChartData() console.log('标签切换调用图表') }
const getAdminData = async function () { try { const result = await API({url: '/admin/userinfo', data: {}}) adminData.value = result console.log('用户信息', adminData.value) } catch (error) { console.log('请求失败', error) } }
const workDataUpdateTime = ref(null) // 获取卡片数据
const getCardData = async () => { try { const response = await API({url: '/workbench/getCard', data: {}}) console.log('卡片数据', response.startDate) workDataUpdateTime.value = response.updateTime
if (response && response.data) { processData(response.data)
} else if (Array.isArray(response?.marketCards)) { processData(response) } else { console.error('无效的API响应结构:', response) } } catch (error) { console.error('获取卡片数据失败:', error) } }
onMounted(async () => { await getAdminData() await getCardData() await getMarkets() getYear() await getChartData() console.log('挂载后调用') }) </script>
<style scoped> .center-card { display: flex; justify-content: center; align-items: center; }
.margin-bottom { margin-bottom: 5px; }
.card-item { height: 260px; display: flex; flex-direction: column; justify-content: center; }
.card-title { font-weight: bold; margin-bottom: 10px; display: flex; justify-content: center; align-items: center; }
.rank-card { height: 500px; }
.card-large { font-weight: bold; font-size: 16px; text-align: center; margin-bottom: 15px; }
.bar { background: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
/* 添加加载动画 */ .loading-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.8); display: flex; justify-content: center; align-items: center; z-index: 10; }
.loading-spinner { width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); } } </style>
|