|
|
@ -91,6 +91,7 @@ |
|
|
|
end-placeholder="结束时间" |
|
|
|
style="width: 200px" |
|
|
|
@change="handleDateRangeChange" |
|
|
|
value-format="YYYY-MM-DD HH:mm:ss" |
|
|
|
/> |
|
|
|
<el-button type="primary" style="margin-left: 5px" @click="loadChart">查询</el-button> |
|
|
|
</el-col> |
|
|
@ -99,8 +100,7 @@ |
|
|
|
<el-row :gutter="20" style="margin-top: 20px"> |
|
|
|
<el-col :span="18"> |
|
|
|
<div class="bar"> |
|
|
|
<div v-show="activeTab === 'recharge'" id="rechargeChartRef" style="width: 100%; height: 400px"></div> |
|
|
|
<div v-show="activeTab === 'consume'" id="consumeChartRef" style="width: 100%; height: 400px"></div> |
|
|
|
<div ref="chartRef" style="width: 100%; height: 400px"></div> |
|
|
|
</div> |
|
|
|
</el-col> |
|
|
|
<el-col :span="6"> |
|
|
@ -114,7 +114,7 @@ |
|
|
|
</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="region" label="地区" 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() }} |
|
|
@ -131,20 +131,22 @@ |
|
|
|
|
|
|
|
<script setup> |
|
|
|
import * as echarts from 'echarts' |
|
|
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue' |
|
|
|
import { ref, onMounted, nextTick, watch } from 'vue' |
|
|
|
import API from '@/util/http' |
|
|
|
import { ElMessage } from 'element-plus' |
|
|
|
|
|
|
|
// 地区数据 |
|
|
|
const markets = ref([]) |
|
|
|
// 图表相关 |
|
|
|
const activeTab = ref('recharge') |
|
|
|
const timeRange = ref('day') |
|
|
|
const dateRange = ref([]) |
|
|
|
const selectedType = ref('all') |
|
|
|
const tableData = ref([]) |
|
|
|
const chartRef = ref(null) |
|
|
|
let chartInstance = null |
|
|
|
|
|
|
|
const rechargeChartRef = ref(null) |
|
|
|
const consumeChartRef = ref(null) |
|
|
|
|
|
|
|
// 卡片数据 |
|
|
|
// 卡片数据相关 |
|
|
|
const currentGold = ref(0) |
|
|
|
const dailyChange = ref(0) |
|
|
|
const currentPermanent = ref(0) |
|
|
@ -152,19 +154,16 @@ 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 wow = ref(0) |
|
|
|
const daily = ref(0) |
|
|
@ -245,16 +244,282 @@ const processData = (data) => { |
|
|
|
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] || '2025-01-01 00:00:00', |
|
|
|
endDate: dateRange.value[1] || '2025-12-31 23:59:59' |
|
|
|
} |
|
|
|
|
|
|
|
const response = await API({ |
|
|
|
url: '/workbench/getGraph', |
|
|
|
data: params |
|
|
|
}) |
|
|
|
|
|
|
|
if (Array.isArray(response.marketCards)) { |
|
|
|
// 处理图表数据 |
|
|
|
processChartData(response.marketCards) |
|
|
|
// 处理排名数据 |
|
|
|
processRankingData(response.marketCards) |
|
|
|
} else { |
|
|
|
console.error('获取图表数据失败:', response) |
|
|
|
ElMessage.error('获取图表数据失败') |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('获取图表数据失败:', error) |
|
|
|
ElMessage.error('获取图表数据失败') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 处理图表数据 |
|
|
|
const processChartData = (marketCards) => { |
|
|
|
// 准备图表数据 |
|
|
|
const chartData = { |
|
|
|
rechargePermanent: [], |
|
|
|
rechargeFree: [], |
|
|
|
consumePermanent: [], |
|
|
|
consumeFree: [], |
|
|
|
consumeTask: [] |
|
|
|
} |
|
|
|
|
|
|
|
// 按市场组织数据 |
|
|
|
marketCards.forEach(market => { |
|
|
|
chartData.rechargePermanent.push(market.sumRechargePermanent || 0) |
|
|
|
chartData.rechargeFree.push(market.sumRechargeFree || 0) |
|
|
|
chartData.consumePermanent.push(market.sumConsumePermanent || 0) |
|
|
|
chartData.consumeFree.push(market.sumConsumeFree || 0) |
|
|
|
chartData.consumeTask.push(market.sumConsumeTask || 0) |
|
|
|
}) |
|
|
|
|
|
|
|
// 根据当前选中的标签更新图表 |
|
|
|
updateChart(chartData) |
|
|
|
} |
|
|
|
|
|
|
|
// 处理排名数据 - 修改点:根据接口字段计算金币数量 |
|
|
|
const processRankingData = (marketCards) => { |
|
|
|
// 根据当前选中的标签计算每个市场的总金币数量 |
|
|
|
const rankingData = marketCards.map(market => { |
|
|
|
let coinAmount = 0; |
|
|
|
if (activeTab.value === 'recharge') { |
|
|
|
// 充值排名:永久金币 + 免费金币 |
|
|
|
coinAmount = (market.sumRechargePermanent || 0) + (market.sumRechargeFree || 0); |
|
|
|
} else { |
|
|
|
// 消费排名:永久金币 + 免费金币 + 任务金币 |
|
|
|
coinAmount = (market.sumConsumePermanent || 0) + (market.sumConsumeFree || 0) + (market.sumConsumeTask || 0); |
|
|
|
} |
|
|
|
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 |
|
|
|
})); |
|
|
|
} |
|
|
|
|
|
|
|
// 更新图表 |
|
|
|
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' } |
|
|
|
} |
|
|
|
] |
|
|
|
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' } |
|
|
|
}, |
|
|
|
{ |
|
|
|
name: '任务金币', |
|
|
|
type: 'bar', |
|
|
|
stack: 'consume', |
|
|
|
data: chartData.consumeTask, |
|
|
|
itemStyle: { color: '#fac858' } |
|
|
|
} |
|
|
|
] |
|
|
|
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 = () => { |
|
|
|
loadChart() |
|
|
|
} |
|
|
|
|
|
|
|
// 处理时间范围变化 |
|
|
|
const handleTimeRangeChange = () => { |
|
|
|
// 根据时间范围设置日期范围 |
|
|
|
const now = new Date() |
|
|
|
let startDate, endDate |
|
|
|
|
|
|
|
if (timeRange.value === 'day') { |
|
|
|
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()) |
|
|
|
endDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59) |
|
|
|
} else if (timeRange.value === 'week') { |
|
|
|
const day = now.getDay() |
|
|
|
const diff = now.getDate() - day + (day === 0 ? -6 : 1) // 调整到本周一 |
|
|
|
startDate = new Date(now.setDate(diff)) |
|
|
|
startDate.setHours(0, 0, 0, 0) |
|
|
|
endDate = new Date(now) |
|
|
|
endDate.setDate(now.getDate() + 6) |
|
|
|
endDate.setHours(23, 59, 59, 0) |
|
|
|
} else if (timeRange.value === 'month') { |
|
|
|
startDate = new Date(now.getFullYear(), now.getMonth(), 1) |
|
|
|
endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0) |
|
|
|
endDate.setHours(23, 59, 59, 0) |
|
|
|
} else { // year |
|
|
|
startDate = '2025-01-01 00:00:00' |
|
|
|
endDate = '2025-12-31 23:59:59' |
|
|
|
} |
|
|
|
|
|
|
|
dateRange.value = [ |
|
|
|
formatDate(startDate), |
|
|
|
formatDate(endDate) |
|
|
|
] |
|
|
|
|
|
|
|
loadChart() |
|
|
|
} |
|
|
|
|
|
|
|
// 格式化日期为字符串 |
|
|
|
const formatDate = (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 handleDateRangeChange = () => { |
|
|
|
timeRange.value = 'custom' |
|
|
|
loadChart() |
|
|
|
} |
|
|
|
|
|
|
|
// 加载图表数据 |
|
|
|
const loadChart = () => { |
|
|
|
getChartData() |
|
|
|
} |
|
|
|
|
|
|
|
// 获取卡片数据 |
|
|
|
const getCardData = async () => { |
|
|
|
try { |
|
|
|
const response = await API({ url: '/workbench/getCard', data: {} }) |
|
|
|
// 感谢D老师的检查结构 |
|
|
|
// 检查 API 响应结构 |
|
|
|
if (response && response.data) { |
|
|
|
// 如果 API 返回 { data: { ... } } 结构 |
|
|
|
processData(response.data) |
|
|
|
} else if (Array.isArray(response?.marketCards)) { |
|
|
|
// 实际走的是这里 |
|
|
|
processData(response) |
|
|
|
} else { |
|
|
|
console.error('无效的API响应结构:', response) |
|
|
@ -264,8 +529,10 @@ const getCardData = async () => { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
onMounted(async function () { |
|
|
|
getCardData() |
|
|
|
onMounted(async () => { |
|
|
|
await getCardData() |
|
|
|
await getMarkets() |
|
|
|
handleTimeRangeChange() // 初始化时间范围 |
|
|
|
}) |
|
|
|
</script> |
|
|
|
|
|
|
@ -306,4 +573,38 @@ onMounted(async function () { |
|
|
|
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> |