|
|
<template> <el-col :span="4"> <el-card class="center-card margin-bottom">数据总览</el-card> </el-col> <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">昨日新增消耗:{{ dailyReduce / 100 }}</div> <div class="margin-bottom center-card">昨日新增消费:{{ dailyConsume / 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 }}% <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> <el-button @click="getToday()" label="day" style="margin-left:250px">今日</el-button> <el-button @click="getWeek()" label="week">本周</el-button> <el-button @click="getMonth()" label="month">本月</el-button> <el-button @click="getYear()" label="year">本年</el-button> </div> <el-date-picker v-model="dateRange" type="datetimerange" range-separator="→" start-placeholder="开始时间" end-placeholder="结束时间" style="margin-left:10px" /> <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 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 { ref, onMounted, nextTick, watch } from 'vue' import API from '@/util/http' import { ElMessage } from 'element-plus' import dayjs from 'dayjs'; import utc from 'dayjs-plugin-utc' dayjs.extend(utc) import { ArrowUpBold, ArrowDownBold, SemiSelect } from '@element-plus/icons-vue' // 地区数据
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 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 = new Date() const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate()) const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1) dateRange.value = [formatDate(startTime), formatDate(endTime)] console.log('看看dateRange', dateRange.value) } // 本周
const getWeek = function () { const today = new Date() const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 6) const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1) dateRange.value = [formatDate(startTime), formatDate(endTime)] console.log('看看dateRange', dateRange.value) } // 本月
const getMonth = function () { const today = new Date() const startTime = new Date(today.getFullYear(), today.getMonth(), 1) const endTime = new Date(today.getFullYear(), today.getMonth() + 1, 1) dateRange.value = [formatDate(startTime), formatDate(endTime)] console.log('看看dateRange', dateRange.value) } // 本年
const getYear = function () { const today = new Date() const startTime = new Date(today.getFullYear(), 0, 1) const endTime = new Date(today.getFullYear() + 1, 0, 1) dateRange.value = [formatDate(startTime), formatDate(endTime)] console.log('看看dateRange', dateRange.value) }
// 要加上所有市场的,还有额外计算的(总数 = 永久 + 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 dailyChange.value = summary.dailyChange currentPermanent.value = summary.currentPermanent currentFree.value = summary.currentFree currentFreeJune.value = summary.currentFreeJune currentFreeDecember.value = summary.currentFreeDecember currentTask.value = summary.currentTask
yearlyRecharge.value = summary.yearlyRecharge yearlyMoney.value = summary.yearlyMoney recharge.value = summary.recharge money.value = summary.money
yearlyReduce.value = summary.yearlyReduce yearlyConsume.value = summary.yearlyConsume yearlyRefund.value = summary.yearlyRefund dailyReduce.value = summary.dailyReduce dailyConsume.value = yesterdayConsume dailyRefund.value = yesterdayRefund
yearlyRechargeNum.value = summary.yearlyRechargeNum sumWow.value = summary.sumWow / length.value sumDaily.value = summary.sumDaily / length.value 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() }
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, // 使用 market 字段作为地区名称
coinAmount: coinAmount }; });
// 按金币数量排序
rankingData.sort((a, b) => b.coinAmount - a.coinAmount);
// 添加排名序号
tableData.value = rankingData.map((item, index) => ({ rank: index + 1, ...item })); }
// 监听 selectedType 的变化,重新处理排名数据
watch(selectedType, () => { getChartData(); }); // 更新图表
const updateChart = (chartData) => { if (!chartInstance) { chartInstance = echarts.init(chartRef.value) }
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) }
// 处理标签切换
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 getCardData = async () => { try { const response = await API({ url: '/workbench/getCard', data: {} }) 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>
|