You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

876 lines
27 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
1 month ago
1 month ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <template>
  2. <el-row>
  3. <!-- 数据总览卡片 -->
  4. <el-col :span="4" style="padding-right: 10px;"> <!-- 适当留白避免拥挤 -->
  5. <el-card class="center-card margin-bottom">数据总览</el-card>
  6. </el-col>
  7. <!-- 最后更新时间 -->
  8. <el-col :span="18" style="display: flex; align-items: center; font-size: 18px">
  9. 最后更新时间{{
  10. workDataUpdateTime && workDataUpdateTime !== '1970-01-01 08:00:00' ? workDataUpdateTime : '该地区暂无数据'
  11. }}
  12. </el-col>
  13. <!-- 剩余栅格空间可选用于占满一行 -->
  14. <el-col :span="18"></el-col>
  15. </el-row>
  16. <el-row :gutter="10">
  17. <!-- 第一个卡片 -->
  18. <el-col :span="6">
  19. <el-card class="card-item">
  20. <template #header>
  21. <div class="card-header">
  22. <div class="card-title">当前金币余量</div>
  23. <div>{{ currentGold / 100 }}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;较前一日
  24. {{
  25. dailyChange / 100
  26. }}
  27. <template v-if="dailyChange > 0">
  28. <el-icon style="color:red">
  29. <ArrowUpBold/>
  30. </el-icon>
  31. </template>
  32. <template v-else-if="dailyChange < 0">
  33. <el-icon style="color:forestgreen">
  34. <ArrowDownBold/>
  35. </el-icon>
  36. </template>
  37. <template v-else>
  38. <el-icon style="color:grey">
  39. <SemiSelect/>
  40. </el-icon>
  41. </template>
  42. </div>
  43. </div>
  44. </template>
  45. <div>
  46. <div class="margin-bottom">永久金币{{ currentPermanent / 100 }}</div>
  47. <div class="margin-bottom">免费金币{{ currentFree / 100 }}</div>
  48. <div class="margin-bottom">[六月到期|{{ currentFreeJune / 100 }}]&nbsp;&nbsp;&nbsp;&nbsp;[12月到期|{{
  49. currentFreeDecember /
  50. 100
  51. }}]
  52. </div>
  53. <div>任务金币{{ currentTask / 100 }}</div>
  54. </div>
  55. </el-card>
  56. </el-col>
  57. <!-- 第二个卡片 -->
  58. <el-col :span="6">
  59. <el-card class="card-item">
  60. <div class="card-title">全年累计充值金币数</div>
  61. <div class="card-title">{{ yearlyRecharge / 100 }}</div>
  62. <div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
  63. <div class="center-card">折合新币累计金额:{{ yearlyMoney / 100 }}</div>
  64. <template #footer>
  65. <el-col class="margin-bottom center-card">昨日新增{{ recharge / 100 }}</el-col>
  66. <el-col class="margin-bottom center-card">其中充值{{ money / 100 }}</el-col>
  67. </template>
  68. </el-card>
  69. </el-col>
  70. <!-- 第三个卡片 -->
  71. <el-col :span="6">
  72. <el-card class="card-item">
  73. <div class="card-title">全年累计消费金币数</div>
  74. <div class="card-title">{{ yearlyReduce / 100 }}</div>
  75. <div class="center-card">消费{{ yearlyConsume / 100 }}</div>
  76. <div class="center-card">退款{{ yearlyRefund / 100 }}</div>
  77. <template #footer>
  78. <div></div>
  79. <div class="margin-bottom center-card">昨日新增消费{{ dailyConsume / 100 }}</div>
  80. <div class="margin-bottom center-card">昨日新增消耗{{ dailyReduce / 100 }}</div>
  81. <div class="margin-bottom center-card">昨日新增退款{{ dailyRefund / 100 }}</div>
  82. </template>
  83. </el-card>
  84. </el-col>
  85. <!-- 第四个卡片 -->
  86. <el-col :span="6">
  87. <el-card class="card-item">
  88. <el-col class="card-title">全年累计充值人头数</el-col>
  89. <el-col class="card-title">{{ yearlyRechargeNum }}</el-col>
  90. <el-col class="center-card">周同比:{{ sumWow }}%&nbsp;&nbsp;&nbsp;&nbsp;
  91. <template v-if="sumWow > 0">
  92. <el-icon style="color:red">
  93. <ArrowUpBold/>
  94. </el-icon>
  95. </template>
  96. <template v-else-if="sumWow < 0">
  97. <el-icon style="color:forestgreen">
  98. <ArrowDownBold/>
  99. </el-icon>
  100. </template>
  101. <template v-else>
  102. <el-icon style="color:grey">
  103. <SemiSelect/>
  104. </el-icon>
  105. </template>
  106. </el-col>
  107. <el-col class="center-card">日环比:{{ sumDaily }}%&nbsp;&nbsp;&nbsp;&nbsp;
  108. <template v-if="sumDaily > 0">
  109. <el-icon style="color:red">
  110. <ArrowUpBold/>
  111. </el-icon>
  112. </template>
  113. <template v-else-if="sumDaily < 0">
  114. <el-icon style="color:forestgreen">
  115. <ArrowDownBold/>
  116. </el-icon>
  117. </template>
  118. <template v-else>
  119. <el-icon style="color:grey">
  120. <SemiSelect/>
  121. </el-icon>
  122. </template>
  123. </el-col>
  124. <template #footer>
  125. <el-col class="margin-bottom center-card">昨日充值人数{{ ydayRechargeNum }}</el-col>
  126. <el-col class="margin-bottom center-card">其中首充{{ firstRecharge }}</el-col>
  127. </template>
  128. </el-card>
  129. </el-col>
  130. </el-row>
  131. <el-row :gutter="10" style="margin-top: 20px">
  132. <el-col :span="24">
  133. <el-card style="width: 100%">
  134. <el-row>
  135. <el-col :span="21">
  136. <el-tabs v-model="activeTab" @tab-change="handleTabChange">
  137. <el-tab-pane label="金币充值" name="recharge"></el-tab-pane>
  138. <el-tab-pane label="金币消费" name="consume"></el-tab-pane>
  139. </el-tabs>
  140. </el-col>
  141. <el-col :span="24">
  142. <el-row>
  143. <div style="margin-top:5px">合计&nbsp;&nbsp;&nbsp;&nbsp;
  144. 永久金币 {{
  145. activeTab === 'recharge' ? sumRechargePermanent / 100 : sumConsumePermanent / 100
  146. }}&nbsp;&nbsp;&nbsp;&nbsp;
  147. 免费金币 {{
  148. activeTab === 'recharge' ? sumRechargeFree / 100 : sumConsumeFree / 100
  149. }}&nbsp;&nbsp;&nbsp;&nbsp;
  150. 任务金币 {{
  151. activeTab === 'recharge' ? sumRechargeTask / 100 : sumConsumeTask / 100
  152. }}&nbsp;&nbsp;&nbsp;&nbsp;
  153. </div>
  154. <div @change="handleDatePickerChange">
  155. <el-button @click="getToday()" label="day" style="margin-left:250px"
  156. :type="activeTimeRange === 'today' ? 'primary' : ''">今日
  157. </el-button>
  158. <el-button @click="getWeek()" label="week" :type="activeTimeRange === 'week' ? 'primary' : ''">本周
  159. </el-button>
  160. <el-button @click="getMonth()" label="month" :type="activeTimeRange === 'month' ? 'primary' : ''">本月
  161. </el-button>
  162. <el-button @click="getYear()" label="year" :type="activeTimeRange === 'year' ? 'primary' : ''">本年
  163. </el-button>
  164. </div>
  165. <el-date-picker v-model="dateRange" type="datetimerange" range-separator="" start-placeholder="开始时间"
  166. end-placeholder="结束时间" style="margin-left:10px" format="YYYY-MM-DD HH:mm:ss"
  167. value-format="YYYY-MM-DD HH:mm:ss"/>
  168. <el-button type="primary" style="margin-left: 5px" @click="getChartData">查询</el-button>
  169. </el-row>
  170. </el-col>
  171. </el-row>
  172. <el-row :gutter="20" style="margin-top: 20px">
  173. <el-col :span="18">
  174. <div class="bar">
  175. <!-- <div v-if="chartLoading" class="loading-overlay">-->
  176. <!-- <div class="loading-spinner"></div>-->
  177. <!-- </div>-->
  178. <div ref="chartRef" style="width: 100%; height: 400px"></div>
  179. </div>
  180. </el-col>
  181. <el-col :span="6">
  182. <el-card class="rank-card" style="width: 100%; height: 100%">
  183. <div class="card-large margin-bottom">金币{{ activeTab === 'recharge' ? '充值' : '消费' }}排名</div>
  184. <el-select v-model="selectedType" style="width: 100%; margin-bottom: 15px">
  185. <el-option label="全部类型" value="all"></el-option>
  186. <el-option label="永久金币" value="permanent"></el-option>
  187. <el-option label="免费金币" value="free"></el-option>
  188. <el-option label="任务金币" value="task"></el-option>
  189. </el-select>
  190. <el-table :data="tableData" height="320px">
  191. <el-table-column prop="rank" label="排名" width="60" align="center"></el-table-column>
  192. <el-table-column prop="market" label="地区" align="center">
  193. <template #default="scope">
  194. <span>{{ marketMapping[scope.row.market] || scope.row.market }}</span>
  195. </template>
  196. </el-table-column>
  197. <el-table-column prop="coinAmount" label="金币数量" align="center">
  198. <template #default="{ row }">
  199. {{ row.coinAmount.toLocaleString() }}
  200. </template>
  201. </el-table-column>
  202. </el-table>
  203. </el-card>
  204. </el-col>
  205. </el-row>
  206. </el-card>
  207. </el-col>
  208. </el-row>
  209. </template>
  210. <script setup>
  211. import * as echarts from 'echarts'
  212. import {ref, onMounted, nextTick, watch, onUnmounted} from 'vue'
  213. import API from '@/util/http'
  214. import {ElMessage} from 'element-plus'
  215. import dayjs from 'dayjs';
  216. import utc from 'dayjs-plugin-utc'
  217. dayjs.extend(utc)
  218. import {ArrowUpBold, ArrowDownBold, SemiSelect} from '@element-plus/icons-vue'
  219. import {marketMapping} from "@/utils/marketMap.js";
  220. // 地区数据
  221. const markets = ref([])
  222. // 图表相关
  223. const dateRange = ref([])
  224. const activeTab = ref('recharge')
  225. const selectedType = ref('all')
  226. const tableData = ref([])
  227. const chartRef = ref(null)
  228. let chartInstance = null
  229. // 图表合计数
  230. const sumRechargePermanent = ref(0)
  231. const sumRechargeFree = ref(0)
  232. const sumRechargeTask = ref(0)
  233. const sumConsumePermanent = ref(0)
  234. const sumConsumeFree = ref(0)
  235. const sumConsumeTask = ref(0)
  236. // 用户信息
  237. const adminData = ref({})
  238. // 卡片数据相关
  239. const currentGold = ref(0)
  240. const dailyChange = ref(0)
  241. const currentPermanent = ref(0)
  242. const currentFree = ref(0)
  243. const currentFreeJune = ref(0)
  244. const currentFreeDecember = ref(0)
  245. const currentTask = ref(0)
  246. const yearlyRecharge = ref(0)
  247. const yearlyMoney = ref(0)
  248. const recharge = ref(0)
  249. const money = ref(0)
  250. const yearlyReduce = ref(0)
  251. const yearlyConsume = ref(0)
  252. const yearlyRefund = ref(0)
  253. const dailyReduce = ref(0)
  254. const dailyConsume = ref(0)
  255. const dailyRefund = ref(0)
  256. const yearlyRechargeNum = ref(0)
  257. const sumWow = ref(0)
  258. const sumDaily = ref(0)
  259. const rechargeNum = ref(0)
  260. const ydayRechargeNum = ref(0)
  261. const firstRecharge = ref(0)
  262. const length = ref(0)
  263. // 加载状态
  264. const chartLoading = ref(true)
  265. const handleResize = () => {
  266. if (chartInstance.value) {
  267. try {
  268. chartInstance.value.resize()
  269. console.log('resize一下')
  270. } catch (error) {
  271. console.error('图表resize失败:', error)
  272. }
  273. }
  274. }
  275. // 初始化图表
  276. const initChart = () => {
  277. if (!chartInstance && chartRef.value) {
  278. chartInstance = echarts.init(chartRef.value)
  279. window.addEventListener('resize', handleResize)
  280. }
  281. }
  282. // 销毁图表
  283. const destroyChart = () => {
  284. if (chartInstance.value) {
  285. try {
  286. chartInstance.value.dispose()
  287. } catch (error) {
  288. console.error('图表销毁失败:', error)
  289. }
  290. chartInstance.value = null
  291. }
  292. window.removeEventListener('resize', handleResize)
  293. }
  294. const formatDate = function (date) {
  295. const year = date.getFullYear();
  296. const month = String(date.getMonth() + 1).padStart(2, '0');
  297. const day = String(date.getDate()).padStart(2, '0');
  298. const hours = String(date.getHours()).padStart(2, '0');
  299. const minutes = String(date.getMinutes()).padStart(2, '0');
  300. const seconds = String(date.getSeconds()).padStart(2, '0');
  301. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  302. }
  303. // 今天
  304. const getToday = function () {
  305. const today = dayjs()
  306. const startTime = today.startOf('day').format('YYYY-MM-DD HH:mm:ss')
  307. const endTime = today.add(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss')
  308. dateRange.value = [startTime, endTime]
  309. console.log('看看dateRange', dateRange.value)
  310. activeTimeRange.value = 'today' // 标记当前激活状态
  311. getChartData()
  312. }
  313. // 本周
  314. const getWeek = function () {
  315. const today = dayjs()
  316. const startTime = (today.startOf('week').add(1, 'day')).format('YYYY-MM-DD HH:mm:ss')
  317. const endTime = today.add(1, 'week').startOf('week').add(1, 'day').format('YYYY-MM-DD HH:mm:ss')
  318. dateRange.value = [startTime, endTime]
  319. console.log('看看dateRange', dateRange.value)
  320. activeTimeRange.value = 'week' // 标记当前激活状态
  321. getChartData()
  322. }
  323. // 本月
  324. const getMonth = function () {
  325. const today = dayjs()
  326. const startTime = today.startOf('month').format('YYYY-MM-DD HH:mm:ss')
  327. const endTime = today.add(1, 'month').startOf('month').format('YYYY-MM-DD HH:mm:ss')
  328. dateRange.value = [startTime, endTime]
  329. console.log('看看dateRange', dateRange.value)
  330. activeTimeRange.value = 'month' // 标记当前激活状态
  331. getChartData()
  332. }
  333. // 本年
  334. const getYear = function () {
  335. const today = dayjs()
  336. const startTime = today.startOf('year').format('YYYY-MM-DD HH:mm:ss')
  337. const endTime = today.add(1, 'year').startOf('year').format('YYYY-MM-DD HH:mm:ss')
  338. dateRange.value = [startTime, endTime]
  339. console.log('看看dateRange', dateRange.value)
  340. activeTimeRange.value = 'year' // 标记当前激活状态
  341. getChartData()
  342. }
  343. // 要加上所有市场的,还有额外计算的(总数 = 永久 + 6月 + 12月 + 免费 + 任务)
  344. const processData = (data) => {
  345. const summary = {
  346. currentGold: 0,
  347. dailyChange: 0,
  348. currentPermanent: 0,
  349. currentFreeJune: 0,
  350. currentFreeDecember: 0,
  351. currentTask: 0,
  352. currentFree: 0,
  353. recharge: 0,
  354. money: 0,
  355. yearlyRecharge: 0,
  356. yearlyMoney: 0,
  357. consumePermanent: 0,
  358. consumeFreeJune: 0,
  359. consumeFreeDecember: 0,
  360. consumeTask: 0,
  361. refundPermanent: 0,
  362. refundFreeJune: 0,
  363. refundFreeDecember: 0,
  364. refundTask: 0,
  365. dailyReduce: 0,
  366. yearlyConsume: 0,
  367. yearlyRefund: 0,
  368. yearlyReduce: 0,
  369. rechargeNum: 0,
  370. ydayRechargeNum: 0,
  371. firstRecharge: 0,
  372. sumWow: 0,
  373. sumDaily: 0,
  374. yearlyRechargeNum: 0
  375. }
  376. // 遍历市场
  377. data.marketCards.forEach(market => {
  378. for (const i in summary) {
  379. if (market[i] !== undefined && market[i] !== null) { // 其实还应该卡一个number
  380. summary[i] += market[i]
  381. }
  382. }
  383. })
  384. // wow和daily除一下
  385. length.value = data.markets.length
  386. console.log(length.value)
  387. // 计算昨日新增消费和退款
  388. const yesterdayConsume = summary.consumePermanent + summary.consumeFreeJune + summary.consumeFreeDecember + summary.consumeTask
  389. const yesterdayRefund = summary.refundPermanent + summary.refundFreeJune + summary.refundFreeDecember + summary.refundTask
  390. // 更新卡片数据
  391. currentGold.value = summary.currentGold.toFixed(2)
  392. dailyChange.value = summary.dailyChange.toFixed(2)
  393. currentPermanent.value = summary.currentPermanent.toFixed(2)
  394. currentFree.value = summary.currentFree.toFixed(2)
  395. currentFreeJune.value = summary.currentFreeJune.toFixed(2)
  396. currentFreeDecember.value = summary.currentFreeDecember.toFixed(2)
  397. currentTask.value = summary.currentTask.toFixed(2)
  398. yearlyRecharge.value = summary.yearlyRecharge.toFixed(2)
  399. yearlyMoney.value = summary.yearlyMoney.toFixed(2)
  400. recharge.value = summary.recharge.toFixed(2)
  401. money.value = summary.money.toFixed(2)
  402. yearlyReduce.value = summary.yearlyReduce.toFixed(2)
  403. yearlyConsume.value = summary.yearlyConsume.toFixed(2)
  404. yearlyRefund.value = summary.yearlyRefund.toFixed(2)
  405. dailyReduce.value = summary.dailyReduce.toFixed(2)
  406. dailyConsume.value = yesterdayConsume.toFixed(2)
  407. dailyRefund.value = yesterdayRefund.toFixed(2)
  408. yearlyRechargeNum.value = summary.yearlyRechargeNum
  409. // // 周同比
  410. // sumWow.value = (marketCards.sumWow / length.value).toFixed(2)
  411. // // 日环比
  412. // sumDaily.value = (marketCards.sumDaily / length.value).toFixed(2)
  413. // rechargeNum.value = summary.rechargeNum
  414. ydayRechargeNum.value = summary.ydayRechargeNum
  415. firstRecharge.value = summary.firstRecharge
  416. }
  417. // 获取市场列表
  418. const getMarkets = async () => {
  419. console.log("adminData", adminData.value.account)
  420. try {
  421. const response = await API({
  422. url: '/general/adminMarkets',
  423. data: {
  424. account: adminData.value.account
  425. }
  426. })
  427. if (Array.isArray(response.data)) {
  428. markets.value = response.data
  429. console.log('市场列表获取成功:', markets.value)
  430. } else {
  431. console.error('获取市场列表失败', response)
  432. ElMessage.error('获取市场列表失败')
  433. }
  434. } catch (error) {
  435. console.error('获取市场列表失败:', error)
  436. ElMessage.error('获取市场列表失败')
  437. }
  438. }
  439. // 获取图表数据
  440. const getChartData = async () => {
  441. try {
  442. // 校验市场数据到底有没有
  443. if (!markets.value || markets.value.length === 0) {
  444. await getMarkets()
  445. }
  446. // 本年
  447. if (!dateRange.value || dateRange.value.length === 0) {
  448. getYear()
  449. }
  450. const params = {
  451. markets: markets.value,
  452. startDate: dateRange.value[0],
  453. endDate: dateRange.value[1]
  454. };
  455. const response = await API({
  456. url: '/workbench/getGraph',
  457. data: params
  458. })
  459. console.log('看看params', params)
  460. if (Array.isArray(response.marketGraphs)) {
  461. // 处理图表数据
  462. processChartData(response.marketGraphs)
  463. // 处理排名数据
  464. processRankingData(response.marketGraphs)
  465. } else {
  466. console.error('获取图表数据失败:', response)
  467. ElMessage.error('获取图表数据失败')
  468. }
  469. } catch (error) {
  470. console.error('获取图表数据失败:', error)
  471. ElMessage.error('获取图表数据失败')
  472. }
  473. }
  474. // 处理图表数据
  475. const processChartData = (marketCards) => {
  476. const chartData = {
  477. rechargePermanent: [],
  478. rechargeFree: [],
  479. rechargeTask: [],
  480. consumePermanent: [],
  481. consumeFree: [],
  482. consumeTask: []
  483. }
  484. // 这是图表的合计数,怎样遍历?????
  485. const sumRechargePermanent1 = ref(0)
  486. const sumRechargeFree1 = ref(0)
  487. const sumRechargeTask1 = ref(0)
  488. const sumConsumePermanent1 = ref(0)
  489. const sumConsumeFree1 = ref(0)
  490. const sumConsumeTask1 = ref(0)
  491. marketCards.forEach(market => {
  492. chartData.rechargePermanent.push(market.sumRechargePermanent / 100 || 0)
  493. chartData.rechargeFree.push(market.sumRechargeFree / 100 || 0)
  494. chartData.rechargeTask.push(market.sumRechargeTask / 100 || 0)
  495. chartData.consumePermanent.push(market.sumConsumePermanent / 100 || 0)
  496. chartData.consumeFree.push(market.sumConsumeFree / 100 || 0)
  497. chartData.consumeTask.push(market.sumConsumeTask / 100 || 0)
  498. // 合计数合计数合计数咋算
  499. sumRechargePermanent1.value += (market.sumRechargePermanent || 0)
  500. sumRechargeFree1.value += (market.sumRechargeFree || 0)
  501. //sumRechargeTask1.value += (market.sumRechargeTask || 0)
  502. sumConsumePermanent1.value += (market.sumConsumePermanent || 0)
  503. sumConsumeFree1.value += (market.sumConsumeFree || 0)
  504. sumConsumeTask1.value += (market.sumConsumeTask || 0)
  505. })
  506. sumRechargePermanent.value = sumRechargePermanent1.value
  507. sumRechargeFree.value = sumRechargeFree1.value
  508. sumRechargeTask.value = 0
  509. sumConsumePermanent.value = sumConsumePermanent1.value
  510. sumConsumeFree.value = sumConsumeFree1.value
  511. sumConsumeTask.value = sumConsumeTask1.value
  512. updateChart(chartData)
  513. }
  514. const processRankingData = (marketCards) => {
  515. // 每个市场的总金币数
  516. const rankingData = marketCards.map(market => {
  517. let coinAmount = 0;
  518. if (activeTab.value === 'recharge') {
  519. // 充值排名
  520. switch (selectedType.value) {
  521. case 'all':
  522. coinAmount = (market.sumRechargePermanent / 100 || 0) + (market.sumRechargeFree / 100 || 0) + (market.sumRechargeTask / 100 || 0);
  523. break;
  524. case 'permanent':
  525. coinAmount = market.sumRechargePermanent / 100 || 0;
  526. break;
  527. case 'free':
  528. coinAmount = market.sumRechargeFree / 100 || 0;
  529. break;
  530. case 'task':
  531. coinAmount = market.sumRechargeTask / 100 || 0;
  532. break;
  533. }
  534. } else {
  535. // 消费排名
  536. switch (selectedType.value) {
  537. case 'all':
  538. coinAmount = (market.sumConsumePermanent / 100 || 0) + (market.sumConsumeFree / 100 || 0) + (market.sumConsumeTask / 100 || 0);
  539. break;
  540. case 'permanent':
  541. coinAmount = market.sumConsumePermanent / 100 || 0;
  542. break;
  543. case 'free':
  544. coinAmount = market.sumConsumeFree / 100 || 0;
  545. break;
  546. case 'task':
  547. coinAmount = market.sumConsumeTask / 100 || 0;
  548. break;
  549. }
  550. }
  551. return {
  552. market: market.market,
  553. coinAmount: coinAmount
  554. };
  555. });
  556. // 按金币数量排序
  557. rankingData.sort((a, b) => b.coinAmount - a.coinAmount);
  558. // 排名序号
  559. tableData.value = rankingData.map((item, index) => ({
  560. rank: index + 1,
  561. ...item
  562. }));
  563. }
  564. watch(selectedType, () => {
  565. getChartData();
  566. });
  567. // 更新图表
  568. const updateChart = (chartData) => {
  569. if (!chartInstance) {
  570. initChart()
  571. }
  572. chartLoading.value = true
  573. try {
  574. let series = []
  575. let legend = []
  576. if (activeTab.value === 'recharge') {
  577. series = [
  578. {
  579. name: '永久金币',
  580. type: 'bar',
  581. stack: 'recharge',
  582. data: chartData.rechargePermanent,
  583. itemStyle: {color: '#5470c6'},
  584. barWidth: 30
  585. },
  586. {
  587. name: '免费金币',
  588. type: 'bar',
  589. stack: 'recharge',
  590. data: chartData.rechargeFree,
  591. itemStyle: {color: '#91cc75'},
  592. barWidth: 30
  593. },
  594. {
  595. name: '任务金币',
  596. type: 'bar',
  597. stack: 'recharge',
  598. data: chartData.rechargeTask,
  599. itemStyle: {color: '#fac858'},
  600. barWidth: 30
  601. }
  602. ]
  603. legend = ['永久金币', '免费金币', '任务金币']
  604. } else {
  605. series = [
  606. {
  607. name: '永久金币',
  608. type: 'bar',
  609. stack: 'consume',
  610. data: chartData.consumePermanent,
  611. itemStyle: {color: '#5470c6'},
  612. barWidth: 30
  613. },
  614. {
  615. name: '免费金币',
  616. type: 'bar',
  617. stack: 'consume',
  618. data: chartData.consumeFree,
  619. itemStyle: {color: '#91cc75'},
  620. barWidth: 30
  621. },
  622. {
  623. name: '任务金币',
  624. type: 'bar',
  625. stack: 'consume',
  626. data: chartData.consumeTask,
  627. itemStyle: {color: '#fac858'},
  628. barWidth: 30
  629. }
  630. ]
  631. legend = ['永久金币', '免费金币', '任务金币']
  632. }
  633. const option = {
  634. tooltip: {
  635. trigger: 'axis',
  636. axisPointer: {
  637. type: 'shadow'
  638. },
  639. formatter: function (params) {
  640. let result = params[0].name + '<br/>'
  641. params.forEach(param => {
  642. result += `${param.seriesName}: ${param.value.toLocaleString()}<br/>`
  643. })
  644. return result
  645. }
  646. },
  647. legend: {
  648. data: legend,
  649. bottom: 10
  650. },
  651. grid: {
  652. left: '3%',
  653. right: '4%',
  654. bottom: '15%',
  655. containLabel: true
  656. },
  657. xAxis: {
  658. type: 'category',
  659. data: markets.value.map(m => marketMapping[m] || m),
  660. axisLabel: {
  661. interval: 0,
  662. rotate: 30
  663. }
  664. },
  665. yAxis: {
  666. type: 'value',
  667. axisLabel: {
  668. formatter: function (value) {
  669. return value.toLocaleString()
  670. }
  671. }
  672. },
  673. series: series,
  674. // dataZoom: [
  675. // {
  676. // type: 'slider',
  677. // show: true,
  678. // start: 0,
  679. // end: 100,
  680. // maxSpan: 100,
  681. // minSpan: 100,
  682. //
  683. // height: 2,
  684. // },
  685. // ]
  686. }
  687. chartInstance.setOption(option)
  688. } catch (error) {
  689. console.error('图表更新失败:', error)
  690. ElMessage.error('图表渲染失败')
  691. } finally {
  692. setTimeout(() => {
  693. chartLoading.value = false
  694. }, 300)
  695. }
  696. }
  697. // 处理标签切换
  698. const handleTabChange = () => {
  699. getChartData()
  700. console.log('标签切换调用图表')
  701. }
  702. const getAdminData = async function () {
  703. try {
  704. const result = await API({url: '/admin/userinfo', data: {}})
  705. adminData.value = result
  706. console.log('用户信息', adminData.value)
  707. } catch (error) {
  708. console.log('请求失败', error)
  709. }
  710. }
  711. // 获取卡片数据
  712. const getCardData = async () => {
  713. try {
  714. const response = await API({url: '/workbench/getCard', data: {}})
  715. workDataUpdateTime.value = response.updateTime
  716. // 周同比
  717. sumWow.value = response.sumWow.toFixed(2)
  718. // 日环比
  719. sumDaily.value = response.sumDaily.toFixed(2)
  720. if (response && response.data) {
  721. processData(response.data)
  722. } else if (Array.isArray(response?.marketCards)) {
  723. processData(response)
  724. } else {
  725. console.error('无效的API响应结构:', response)
  726. }
  727. } catch (error) {
  728. console.error('获取卡片数据失败:', error)
  729. }
  730. }
  731. const workDataUpdateTime = ref(null)
  732. // 标记当前激活的时间范围按钮
  733. const activeTimeRange = ref('')
  734. // 日期选择器变化时清除按钮激活状态
  735. const handleDatePickerChange = () => {
  736. activeTimeRange.value = ''
  737. }
  738. onMounted(async () => {
  739. await getAdminData()
  740. await getCardData()
  741. await getMarkets()
  742. getYear()
  743. window.addEventListener('resize', () => {
  744. chartInstance.resize()
  745. })
  746. })
  747. onUnmounted(() => {
  748. destroyChart()
  749. })
  750. </script>
  751. <style scoped>
  752. .center-card {
  753. display: flex;
  754. justify-content: center;
  755. align-items: center;
  756. }
  757. .margin-bottom {
  758. margin-bottom: 5px;
  759. }
  760. .card-item {
  761. height: 260px;
  762. display: flex;
  763. flex-direction: column;
  764. justify-content: center;
  765. }
  766. .card-title {
  767. font-weight: bold;
  768. margin-bottom: 10px;
  769. display: flex;
  770. justify-content: center;
  771. align-items: center;
  772. }
  773. .rank-card {
  774. height: 500px;
  775. }
  776. .card-large {
  777. font-weight: bold;
  778. font-size: 16px;
  779. text-align: center;
  780. margin-bottom: 15px;
  781. }
  782. .bar {
  783. background: white;
  784. border-radius: 8px;
  785. padding: 15px;
  786. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  787. }
  788. /* 添加加载动画 */
  789. .loading-overlay {
  790. position: absolute;
  791. top: 0;
  792. left: 0;
  793. right: 0;
  794. bottom: 0;
  795. background: rgba(255, 255, 255, 0.8);
  796. display: flex;
  797. justify-content: center;
  798. align-items: center;
  799. z-index: 10;
  800. }
  801. .loading-spinner {
  802. width: 40px;
  803. height: 40px;
  804. border: 4px solid #f3f3f3;
  805. border-top: 4px solid #3498db;
  806. border-radius: 50%;
  807. animation: spin 1s linear infinite;
  808. }
  809. @keyframes spin {
  810. 0% {
  811. transform: rotate(0deg);
  812. }
  813. 100% {
  814. transform: rotate(360deg);
  815. }
  816. }
  817. </style>