|
|
<!--各地区的金币充值和消费情况柱状图-->
<template> <div class="graph"> <el-card style="width:100%;" class="graph-card"> <div> <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> </div> <div class="condition"> <div class="stats"> <div v-if="activeTab === 'consume'">合计:{{ sumConsume / 100 }}</div> 永久金币: {{ activeTab === 'recharge' ? sumRechargePermanent / 100 : sumConsumePermanent / 100 }} 免费金币: {{ activeTab === 'recharge' ? sumRechargeFree / 100 : sumConsumeFree / 100 }} 任务金币: {{ activeTab === 'recharge' ? sumRechargeTask / 100 : sumConsumeTask / 100 }} </div> <div> <el-button @click="getYes()" size="small" :type="activeTimeRange === 'yes' ? 'primary' : ''">昨天 </el-button> <el-button @click="getToday()" size="small" :type="activeTimeRange === 'today' ? 'primary' : ''">今天 </el-button> <el-button @click="getWeek()" size="small" :type="activeTimeRange === 'week' ? 'primary' : ''">本周 </el-button> <el-button @click="getMonth()" size="small" :type="activeTimeRange === 'month' ? 'primary' : ''">本月 </el-button> <el-button @click="getYear()" size="small" :type="activeTimeRange === 'year' ? 'primary' : ''">本年 </el-button> </div> <div> <el-date-picker size="small" v-model="dateRange" type="datetimerange" range-separator="→" start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss" style="width:20vw;margin-left:0.5vw;" value-format="YYYY-MM-DD HH:mm:ss" :default-time="defaultTime" :disabled-date="disabledDate" @change="handleDatePickerChange"/> <el-button type="primary" size="small" style="margin-left: 0.5vw" @click="getChartData">查询</el-button> </div> </div>
<div class="graph-content"> <div ref="chartRef" class="left"></div> <div class="right"> <el-card class="graph-card-list"> <div class="card-large">金币{{ activeTab === 'recharge' ? '充值' : '消费' }}排名</div> <el-select class="card-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 class="card-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"> <template #default="scope"> <span>{{ marketMapping[scope.row.market] || scope.row.market }}</span> </template> </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> </div> </div> </el-card> </div> </template>
<script setup> import * as echarts from 'echarts' import {onMounted, onUnmounted, 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 {marketMapping} from "@/utils/marketMap.js";
dayjs.extend(utc)
const defaultTime = [ new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59), ]
// 地区数据
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 sumConsume = 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 ydayRechargeNum = ref(0) const firstRecharge = ref(0) const length = ref(0) // 加载状态
const chartLoading = ref(true)
const handleResize = () => { if (chartInstance.value) { try { chartInstance.value.resize() console.log('resize一下') } catch (error) { console.error('图表resize失败:', error) } } } // 初始化图表
const initChart = () => { if (!chartInstance && chartRef.value) { chartInstance = echarts.init(chartRef.value) window.addEventListener('resize', handleResize) } } // 销毁图表
const destroyChart = () => { if (chartInstance.value) { try { chartInstance.value.dispose() } catch (error) { console.error('图表销毁失败:', error) } chartInstance.value = null } window.removeEventListener('resize', handleResize) } 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 getYes = function () { const yesterday = dayjs().subtract(1, 'day') const startTime = yesterday.startOf('day').format('YYYY-MM-DD HH:mm:ss') const endTime = yesterday.endOf('day').format('YYYY-MM-DD HH:mm:ss') dateRange.value = [startTime, endTime] console.log('看看dateRange', dateRange.value) activeTimeRange.value = 'yes' // 标记当前激活状态
getChartData() } // 今天
const getToday = function () { const today = dayjs() const startTime = today.startOf('day').format('YYYY-MM-DD HH:mm:ss') const endTime = today.endOf('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(); // 获取今天是星期几(0是周日,1是周一,...,6是周六)
const day = today.day();
// 计算本周一(如果今天是周一,就取今天;如果是周日,就减6天)
let monday = today.subtract(day === 0 ? 6 : day - 1, 'day'); // 计算本周日(如果今天是周日,就取今天;否则就加(7 - day)天)
let sunday = today.add(day === 0 ? 0 : 7 - day, 'day');
// 设置时间为起始和结束
const startTime = monday.startOf('day').format('YYYY-MM-DD HH:mm:ss'); const endTime = sunday.endOf('day').format('YYYY-MM-DD HH:mm:ss');
dateRange.value = [startTime, endTime]; console.log('本周时间范围(周一到周日):', 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')
const endTime = today.endOf('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.endOf('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, ydayRechargeNum: 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 = (marketCards.sumWow / length.value).toFixed(2)
// // 日环比
// sumDaily.value = (marketCards.sumDaily / length.value).toFixed(2)
// rechargeNum.value = summary.rechargeNum
ydayRechargeNum.value = summary.ydayRechargeNum firstRecharge.value = summary.firstRecharge }
//无法选择的时间
const disabledDate = (time) => { const limitDate = new Date(2025, 0, 1); return time.getTime() < limitDate.getTime(); }
// 获取市场列表
const getMarkets = async () => { console.log("adminData", adminData.value.account) try { const response = await API({ url: '/general/adminMarkets', data: { account: adminData.value.account } }) if (Array.isArray(response.data)) { // markets.value = response.data.filter(data => data !== "1")
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)) { // const filteredGraphs = response.marketGraphs.filter(data => data.market !== "1");
// 处理图表数据
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: [], sumConsume: [] } // 这是图表的合计数,怎样遍历?????
const sumRechargePermanent1 = ref(0) const sumRechargeFree1 = ref(0) const sumRechargeTask1 = ref(0) const sumConsumePermanent1 = ref(0) const sumConsumeFree1 = ref(0) const sumConsumeTask1 = ref(0) const sumConsume1 = 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) chartData.sumConsume.push(market.sumConsume / 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) sumConsume1.value += (market.sumConsume || 0) }) sumRechargePermanent.value = sumRechargePermanent1.value sumRechargeFree.value = sumRechargeFree1.value sumRechargeTask.value = 0 sumConsumePermanent.value = sumConsumePermanent1.value sumConsumeFree.value = sumConsumeFree1.value sumConsumeTask.value = sumConsumeTask1.value sumConsume.value = sumConsume1.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/>' let total = 0; params.forEach(param => { result += `${param.seriesName}: ${param.value.toLocaleString()}<br/>`; total += param.value; }) result += `总${activeTab.value === 'recharge' ? '充值' : '消费'}: ${total.toLocaleString()}`; return result } }, legend: { data: legend, bottom: 10 }, grid: { left: '3%', right: '4%', bottom: '10%', containLabel: true }, xAxis: { type: 'category', // 横坐标数据 之后要改成一个时间的
data: markets.value, axisLabel: { interval: 0, rotate: 0 } }, yAxis: { type: 'value', splitLine: { lineStyle: { type: 'dashed', width: 1, color: '#000000' } }, axisLabel: { formatter: function (value) { return value.toLocaleString() } },
}, series: series, // dataZoom: [
// {
// type: 'slider',
// show: true,
// start: 0,
// end: 100,
// maxSpan: 100,
// minSpan: 100,
//
// height: 2,
// },
// ]
}
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 activeTimeRange = ref('') // 日期选择器变化时清除按钮激活状态
const handleDatePickerChange = () => { activeTimeRange.value = '' }
onMounted(async () => { await getAdminData() await getMarkets() getYear() window.addEventListener('resize', () => { chartInstance.resize() }) }) onUnmounted(() => { destroyChart() }) </script> <style scoped lang="scss">
.graph { .condition { width: 100%; height: 1%; display: flex; align-items: center;
.stats { display: flex; align-items: center; width: 35vw; font-size: 15px; } }
.graph-content { flex: 1; height: auto; display: flex;
.left { width: 70%; height: auto; }
.right { flex: 1; padding: 0.5vw 2vh; } } }
.center-card { display: flex; justify-content: center; align-items: center; }
.margin-bottom { margin-bottom: 0.5vh; }
.card-item { width: 25%; height: 28vh; display: flex; flex-direction: column; justify-content: center; margin-right: 0.25vw; }
.card-title { font-weight: bold; margin-bottom: 1vh; display: flex; justify-content: center; align-items: center; }
.card-large { font-weight: bold; font-size: 16px; text-align: center; margin-bottom: 15px;
}
@keyframes spin { 0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); } }
.graph-card { background: #F3FAFF; box-shadow: 0 0 8px 0 #00000040; }
.graph-card-list { background: #F3FAFF; box-shadow: 0 0 8px 0 #00000040; padding: 12px;
.card-select { :deep(.el-input__wrapper) { background-color: #E7F4FD !important; box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25) !important; border: none !important; }
:deep(.el-input__inner) { background-color: transparent !important; }
:deep(.el-input__suffix) { background-color: transparent !important; } }
/* 表格整体背景:把表格容器设为卡片背景 */ :deep(.el-table) { background-color: #F3FAFF !important; box-shadow: none !important; }
/* 表头/表体 wrapper 与 table body 单元格 */ :deep(.el-table__header-wrapper), :deep(.el-table__body-wrapper), :deep(.el-table__body), :deep(.el-table__header), :deep(.el-table__body tbody), :deep(.el-table__body tr), :deep(.el-table__row), :deep(.el-table__cell), :deep(.el-table__body td) { background-color: transparent !important; }
/* 表头 */ :deep(.el-table__header th) { background-color: #F3FAFF !important; }
/* 行之间的分隔线(更像卡片内表格) */ :deep(.el-table .el-table__row):not(:last-child) { border-bottom: 1px solid rgba(0, 0, 0, 0.06); }
}
</style>
|