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.

666 lines
18 KiB

2 weeks ago
2 weeks ago
2 weeks ago
  1. <template>
  2. <div class="gold-management">
  3. <div class="gold-title">
  4. <div class="text1">
  5. 金币管理
  6. <span class="text1-update-time">最后更新时间{{
  7. workDataUpdateTime && workDataUpdateTime !== '1970-01-01 08:00:00' ? workDataUpdateTime : '该地区暂无数据'
  8. }} </span>
  9. </div>
  10. </div>
  11. <!-- 第一行包含两个横向格子 -->
  12. <el-row>
  13. <el-col :span="12">
  14. <!-- 第一个卡片 -->
  15. <div class="card-item-row1">
  16. <div class="card-title">当前金币余量
  17. <span style="font-weight: bold">{{
  18. currentGold / 100
  19. }}</span>&nbsp;&nbsp;&nbsp;&nbsp;较前一日
  20. {{ dailyChange / 100 }}&nbsp;
  21. <template v-if="dailyChange > 0">
  22. <el-image :src="upArrow" style="width: 14px;"/>
  23. </template>
  24. <template v-else-if="dailyChange < 0">
  25. <el-image :src="downArrow" style="width: 14px;"/>
  26. </template>
  27. <template v-else>
  28. <el-image :src="pingArrow" style="width: 14px; padding-top: 12px"/>
  29. </template>
  30. </div>
  31. <div>
  32. <el-row>
  33. <!-- 左边文本信息 -->
  34. <el-col :span="12">
  35. <div class="margin-bottom" style="white-space: nowrap;">
  36. 永久金币<b>{{ currentPermanent / 100 }}</b>
  37. </div>
  38. <div class="margin-bottom">&nbsp;</div>
  39. <div class="margin-bottom">免费金币{{ currentFree / 100 }}</div>
  40. <!-- <div class="margin-bottom">&nbsp</div>-->
  41. <!-- <div class="margin-bottom">&nbsp</div>-->
  42. <div class="margin-bottom">
  43. [6月到期{{ currentFreeJune / 100 }}]
  44. </div>
  45. <div class="margin-bottom">&nbsp;</div>
  46. <div class="margin-bottom">任务金币{{ currentTask / 100 }}</div>
  47. </el-col>
  48. <!-- 右边图表 -->
  49. <el-col :span="12">
  50. <!-- <div ref="goldTypeChart" style="width: 100%; height: 100px;"></div>-->
  51. <div style="width: 100%; height: 60px;">&nbsp;</div>
  52. <div class="margin-bottom">
  53. [12月到期{{ currentFreeDecember / 100 }}]
  54. </div>
  55. </el-col>
  56. </el-row>
  57. </div>
  58. </div>
  59. </el-col>
  60. <el-col :span="12">
  61. <!-- 第二个卡片 -->
  62. <div class="card-item-row1">
  63. <div class="card-title">全年累计充值金币数{{ yearlyRecharge / 100 }}</div>
  64. <el-row>
  65. <el-col :span="12">
  66. <div class="center-card">折合新币累计金额</div>
  67. <el-image :src="svg1" style="width: 68px; display: block;margin: 0 auto;"/>
  68. <div class="center-card">{{ yearlyMoney / 100 }}新币</div>
  69. </el-col>
  70. <el-col :span="12" style="border-left: 2px solid #CFE6FE; height: 120px">
  71. <div class="center-card" style="white-space: nowrap;">昨日新增金币{{ recharge / 100 }}</div>
  72. <div ref="rechargeGoldChart" style="width: 68px; height: 68px; display: block;margin: 0 auto;"></div>
  73. <div class="center-card" style="white-space: nowrap;">其中永久金币{{ money / 100 }}</div>
  74. </el-col>
  75. </el-row>
  76. </div>
  77. </el-col>
  78. </el-row>
  79. <!-- 第二行包含两个横向格子 -->
  80. <el-row>
  81. <el-col :span="12">
  82. <!-- 第三个卡片 -->
  83. <div class="card-item">
  84. <div class="card-title">全年累计消费金币数{{ yearlyReduce / 100 }}</div>
  85. <el-row style="height: 200px;">
  86. <el-col :span="12">
  87. <div ref="consumeChart" style="width:100%; height: 68%;"></div>
  88. </el-col>
  89. <el-col :span="12">
  90. <div ref="consumeDetailChart" style="width: 100%; height: 88%;"></div>
  91. </el-col>
  92. </el-row>
  93. </div>
  94. </el-col>
  95. <el-col :span="12">
  96. <!-- 第四个卡片 -->
  97. <div class="card-item">
  98. <div class="card-title">全年累计充值人头数{{ yearlyRechargeNum }}</div>
  99. <el-row style="height: 200px;">
  100. <el-col :span="12" style="border-right: 2px solid #CFE6FE; height: 150px">
  101. <div class="chart5">
  102. <el-image :src="svg2" style="width: 68px; display: block;margin: 0 auto;"/>
  103. <div class="margin-bottom">
  104. <div style="display: flex; gap: 10px; font-size: 14px;">周同比{{ sumWow }}%
  105. <el-image v-if="sumWow > 0" :src="upArrow" style="width: 10px;"/>
  106. <el-image v-else-if="sumWow < 0" :src="downArrow" style="width: 10px;"/>
  107. <el-image v-else :src="pingArrow" style="width: 10px;"/>
  108. </div>
  109. <div style="display: flex; gap: 10px; font-size: 14px;">
  110. 日环比{{ sumDaily }}%
  111. <el-image v-if="sumDaily > 0" :src="upArrow" style="width: 10px;"/>
  112. <el-image v-else-if="sumDaily < 0" :src="downArrow" style="width: 10px;"/>
  113. <el-image v-else :src="pingArrow" style="width: 10px; "/>
  114. </div>
  115. </div>
  116. </div>
  117. </el-col>
  118. <!-- 新增的环形图容器 -->
  119. <el-col :span="12">
  120. <div ref="rechargePeopleChart" style="width:100%; height: 68%;"></div>
  121. </el-col>
  122. </el-row>
  123. </div>
  124. </el-col>
  125. </el-row>
  126. </div>
  127. </template>
  128. <script setup>
  129. import {onMounted, ref, nextTick} from 'vue'
  130. import * as echarts from 'echarts'
  131. import API from '@/util/http'
  132. import dayjs from 'dayjs';
  133. import utc from 'dayjs-plugin-utc'
  134. import {ArrowDownBold, ArrowUpBold, SemiSelect} from '@element-plus/icons-vue'
  135. import svg1 from '@/assets/SvgIcons/折合新币累计金额.svg'
  136. import svg2 from '@/assets/SvgIcons/周同比.svg'
  137. import upArrow from '@/assets/SvgIcons/上升箭头.svg'
  138. import downArrow from '@/assets/SvgIcons/下降箭头.svg'
  139. import pingArrow from '@/assets/SvgIcons/持平.svg'
  140. dayjs.extend(utc)
  141. // 用户信息
  142. const adminData = ref({})
  143. // 卡片数据相关
  144. const currentGold = ref(0)
  145. const dailyChange = ref(0)
  146. const currentPermanent = ref(0)
  147. const currentFree = ref(0)
  148. const currentFreeJune = ref(0)
  149. const currentFreeDecember = ref(0)
  150. const currentTask = ref(0)
  151. const yearlyRecharge = ref(0)
  152. const yearlyMoney = ref(0)
  153. const recharge = ref(0)
  154. const money = ref(0)
  155. const yearlyReduce = ref(0)
  156. const yearlyConsume = ref(0)
  157. const yearlyRefund = ref(0)
  158. const dailyReduce = ref(0)
  159. const dailyConsume = ref(0)
  160. const dailyRefund = ref(0)
  161. const yearlyRechargeNum = ref(0)
  162. const sumWow = ref(0)
  163. const sumDaily = ref(0)
  164. const rechargeNum = ref(0)
  165. const ydayRechargeNum = ref(0)
  166. const firstRecharge = ref(0)
  167. const length = ref(0)
  168. // ECharts 实例引用
  169. const goldTypeChart = ref(null)
  170. const rechargeGoldChart = ref(null)
  171. const consumeChart = ref(null)
  172. const consumeDetailChart = ref(null)
  173. const rechargePeopleChart = ref(null)
  174. // 要加上所有市场的,还有额外计算的(总数 = 永久 + 6月 + 12月 + 免费 + 任务)
  175. const processData = (data) => {
  176. const summary = {
  177. currentGold: 0,
  178. dailyChange: 0,
  179. currentPermanent: 0,
  180. currentFreeJune: 0,
  181. currentFreeDecember: 0,
  182. currentTask: 0,
  183. currentFree: 0,
  184. recharge: 0,
  185. money: 0,
  186. yearlyRecharge: 0,
  187. yearlyMoney: 0,
  188. consumePermanent: 0,
  189. consumeFreeJune: 0,
  190. consumeFreeDecember: 0,
  191. consumeTask: 0,
  192. refundPermanent: 0,
  193. refundFreeJune: 0,
  194. refundFreeDecember: 0,
  195. refundTask: 0,
  196. dailyReduce: 0,
  197. yearlyConsume: 0,
  198. yearlyRefund: 0,
  199. yearlyReduce: 0,
  200. rechargeNum: 0,
  201. ydayRechargeNum: 0,
  202. firstRecharge: 0,
  203. sumWow: 0,
  204. sumDaily: 0,
  205. yearlyRechargeNum: 0
  206. }
  207. // 遍历市场
  208. data.marketCards.forEach(market => {
  209. for (const i in summary) {
  210. if (market[i] !== undefined && market[i] !== null) {
  211. summary[i] += market[i]
  212. }
  213. }
  214. })
  215. // wow和daily除一下
  216. length.value = data.markets.length
  217. console.log(length.value)
  218. // 计算昨日新增消费和退款
  219. const yesterdayConsume = summary.consumePermanent + summary.consumeFreeJune + summary.consumeFreeDecember + summary.consumeTask
  220. const yesterdayRefund = summary.refundPermanent + summary.refundFreeJune + summary.refundFreeDecember + summary.refundTask
  221. // 更新卡片数据
  222. currentGold.value = summary.currentGold.toFixed(2)
  223. dailyChange.value = summary.dailyChange.toFixed(2)
  224. currentPermanent.value = summary.currentPermanent.toFixed(2)
  225. currentFree.value = summary.currentFree.toFixed(2)
  226. currentFreeJune.value = summary.currentFreeJune.toFixed(2)
  227. currentFreeDecember.value = summary.currentFreeDecember.toFixed(2)
  228. currentTask.value = summary.currentTask.toFixed(2)
  229. yearlyRecharge.value = summary.yearlyRecharge.toFixed(2)
  230. yearlyMoney.value = summary.yearlyMoney.toFixed(2)
  231. recharge.value = summary.recharge.toFixed(2)
  232. money.value = summary.money.toFixed(2)
  233. yearlyReduce.value = summary.yearlyReduce.toFixed(2)
  234. yearlyConsume.value = summary.yearlyConsume.toFixed(2)
  235. yearlyRefund.value = summary.yearlyRefund.toFixed(2)
  236. dailyReduce.value = summary.dailyReduce.toFixed(2)
  237. dailyConsume.value = yesterdayConsume.toFixed(2)
  238. dailyRefund.value = yesterdayRefund.toFixed(2)
  239. yearlyRechargeNum.value = summary.yearlyRechargeNum
  240. ydayRechargeNum.value = summary.ydayRechargeNum
  241. firstRecharge.value = summary.firstRecharge
  242. // 初始化图表
  243. nextTick(() => {
  244. // initGoldTypeChart();
  245. initRechargeGoldChart();
  246. initConsumeChart();
  247. initConsumeDetailChart();
  248. initRechargePeopleChart();
  249. });
  250. }
  251. // 初始化金币类型南丁格尔图
  252. const initGoldTypeChart = () => {
  253. const myChart = echarts.init(goldTypeChart.value);
  254. const option = {
  255. tooltip: {
  256. trigger: 'item',
  257. formatter: function (params) {
  258. let realValue = 0
  259. if (params.name === '永久金币') realValue = currentPermanent.value / 100
  260. else if (params.name === '免费金币') realValue = (currentFreeJune.value / 100 + currentFreeDecember.value / 100)
  261. else realValue = currentTask.value / 100
  262. return `${params.name}: ${realValue}`
  263. }
  264. },
  265. toolbox: {
  266. show: true,
  267. feature: {}
  268. },
  269. series: [
  270. {
  271. name: 'Nightingale Chart',
  272. type: 'pie',
  273. radius: ['0%', '100%'],
  274. center: ['50%', '50%'],
  275. roseType: 'area',
  276. itemStyle: {
  277. borderRadius: 5
  278. },
  279. data: [
  280. {value: Math.log(currentPermanent.value / 100 + 1), name: '永久金币'},
  281. {value: Math.log((currentFreeJune.value / 100 + currentFreeDecember.value / 100) + 1), name: '免费金币'},
  282. {value: Math.log(currentTask.value / 100 + 1), name: '任务金币'}
  283. ],
  284. labelLine: {show: false},
  285. label: {show: false}
  286. }
  287. ]
  288. };
  289. myChart.setOption(option);
  290. }
  291. // 初始化充值金币环形图
  292. const initRechargeGoldChart = () => {
  293. const myChart = echarts.init(rechargeGoldChart.value);
  294. const option = {
  295. series: [
  296. {
  297. type: 'pie',
  298. radius: ['60%', '85%'],
  299. silent: true,
  300. clockwise: true,
  301. label: {show: false},
  302. data: [
  303. {
  304. value: recharge.value / 100,
  305. itemStyle: {color: '#80aaff'}
  306. }
  307. ]
  308. },
  309. {
  310. type: 'pie',
  311. radius: ['60%', '75%'],
  312. startAngle: 180,
  313. silent: true,
  314. clockwise: true,
  315. label: {show: false},
  316. data: [
  317. {
  318. value: money.value / 100,
  319. itemStyle: {color: '#f2c97d'}
  320. },
  321. {
  322. value: (recharge.value / 100 - money.value / 100),
  323. itemStyle: {color: 'transparent'}
  324. }
  325. ]
  326. }
  327. ]
  328. };
  329. myChart.setOption(option);
  330. }
  331. // 初始化消费退款环形图
  332. const initConsumeChart = () => {
  333. const myChart = echarts.init(consumeChart.value);
  334. const option = {
  335. legend: {
  336. orient: 'vertical',
  337. left: '10%',
  338. top: '85',
  339. icon: 'circle',
  340. iconSize: 5,
  341. textSize: 12,
  342. itemWidth: 7,
  343. itemHeight: 7,
  344. },
  345. series: [
  346. {
  347. type: 'pie',
  348. radius: ['30%', '45%'],
  349. center: ['50%', '35%'],
  350. silent: true,
  351. clockwise: true,
  352. label: {show: false},
  353. data: [
  354. {
  355. value: yearlyConsume.value / 100,
  356. name: '消耗:' + yearlyConsume.value / 100,
  357. // name: '消耗:' + 1234567890,
  358. itemStyle: {color: '#7DB7FA'}
  359. },
  360. {
  361. value: yearlyRefund.value / 100,
  362. name: '退款:' + yearlyRefund.value / 100,
  363. itemStyle: {color: '#F7D47C'}
  364. }
  365. ],
  366. }
  367. ]
  368. };
  369. myChart.setOption(option);
  370. };
  371. // 初始化消费明细环形图
  372. const initConsumeDetailChart = () => {
  373. const myChart = echarts.init(consumeDetailChart.value);
  374. const option = {
  375. // 增加图表内边距,避免内容溢出
  376. legend: {
  377. orient: 'vertical',
  378. left: '20%',
  379. top: '85',
  380. icon: 'circle',
  381. iconSize: 5,
  382. itemWidth: 7,
  383. itemHeight: 7,
  384. },
  385. series: [
  386. {
  387. type: 'pie',
  388. radius: ['25%', '40%'],
  389. center: ['50%', '25%'],
  390. silent: true,
  391. clockwise: true,
  392. label: {show: false},
  393. data: [
  394. {
  395. value: dailyConsume.value / 100,
  396. name: '昨日新增消费:' + dailyConsume.value / 100,
  397. itemStyle: {color: '#65C9C9'}
  398. }
  399. ]
  400. },
  401. {
  402. type: 'pie',
  403. radius: ['25%', '35%'],
  404. center: ['50%', '25%'],
  405. startAngle: 180,
  406. silent: true,
  407. clockwise: true,
  408. label: {show: false},
  409. data: [
  410. {
  411. value: dailyReduce.value / 100,
  412. name: '昨日新增消耗:' + dailyReduce.value / 100,
  413. // name: '昨日新增消耗:' + 1234567890,
  414. itemStyle: {color: '#9469D1'}
  415. },
  416. {
  417. value: dailyRefund.value / 100,
  418. name: '昨日新增退款:' + dailyRefund.value / 100,
  419. itemStyle: {color: '#B8DB6E'}
  420. }
  421. ]
  422. }
  423. ]
  424. };
  425. myChart.setOption(option);
  426. };
  427. // 初始化充值人头环形图
  428. const initRechargePeopleChart = () => {
  429. const myChart = echarts.init(rechargePeopleChart.value);
  430. const option = {
  431. legend: {
  432. orient: 'vertical',
  433. left: '20%',
  434. top: '85',
  435. icon: 'circle',
  436. iconSize: 5,
  437. textSize: 18,
  438. itemWidth: 7,
  439. itemHeight: 7,
  440. },
  441. series: [
  442. {
  443. type: 'pie',
  444. radius: ['30%', '50%'],
  445. center: ['50%', '35%'],
  446. silent: true,
  447. clockwise: true,
  448. label: {show: false},
  449. data: [
  450. {
  451. value: ydayRechargeNum.value,
  452. name: '昨日充值人数:' + ydayRechargeNum.value,
  453. itemStyle: {color: '#65C9C9'}
  454. },
  455. ],
  456. },
  457. {
  458. type: 'pie',
  459. radius: ['30%', '45%'],
  460. center: ['50%', '35%'],
  461. silent: true,
  462. clockwise: true,
  463. label: {show: false},
  464. data: [
  465. {
  466. value: firstRecharge.value,
  467. name: '其中首充:' + firstRecharge.value,
  468. itemStyle: {color: '#9469D1'}
  469. },
  470. {
  471. value: ydayRechargeNum.value - firstRecharge.value,
  472. itemStyle: {color: 'transparent'}
  473. }
  474. ],
  475. }
  476. ]
  477. };
  478. myChart.setOption(option);
  479. }
  480. // 获取卡片数据
  481. const getCardData = async () => {
  482. try {
  483. const response = await API({url: '/workbench/getCard', data: {}})
  484. workDataUpdateTime.value = response.updateTime
  485. // 周同比
  486. sumWow.value = response.sumWow.toFixed(2)
  487. // 日环比
  488. sumDaily.value = response.sumDaily.toFixed(2)
  489. if (response && response.data) {
  490. processData(response.data)
  491. } else if (Array.isArray(response?.marketCards)) {
  492. processData(response)
  493. } else {
  494. console.error('无效的API响应结构:', response)
  495. }
  496. } catch (error) {
  497. console.error('获取卡片数据失败:', error)
  498. }
  499. }
  500. const workDataUpdateTime = ref(null)
  501. onMounted(async () => {
  502. await getCardData()
  503. })
  504. </script>
  505. <style scoped lang="scss">
  506. .center-card {
  507. display: flex;
  508. justify-content: center;
  509. align-items: center;
  510. }
  511. .card-item-row1 {
  512. height: 160px;
  513. width: auto;
  514. background: #E4F0FC;
  515. box-shadow: 0 0 4px 0 #00000040;
  516. border-radius: 10px;
  517. margin-top: 20px;
  518. margin-left: 5px;
  519. margin-right: 5px;
  520. margin-bottom: -5px;
  521. padding-bottom: 10px;
  522. }
  523. .card-item {
  524. height: 200px;
  525. width: auto;
  526. background: #E4F0FC;
  527. box-shadow: 0 0 4px 0 #00000040;
  528. border-radius: 10px;
  529. margin-top: 20px;
  530. margin-left: 5px;
  531. margin-right: 5px;
  532. margin-bottom: -5px;
  533. padding-bottom: 10px;
  534. }
  535. .card-title {
  536. font-weight: bold;
  537. height: 36px;
  538. width: 100%;
  539. flex-shrink: 0;
  540. border-radius: 8px;
  541. background: linear-gradient(90deg, #E4F0FC 0%, #C1DCF8 50%, #E4F0FC 100%);
  542. box-shadow: 0 0 2px 0 #00152940;
  543. display: flex;
  544. align-items: center;
  545. justify-content: center;
  546. margin-top: -5px;
  547. margin-bottom: 10px;
  548. }
  549. .card-item .el-col {
  550. overflow: visible;
  551. }
  552. @keyframes spin {
  553. 0% {
  554. transform: rotate(0deg);
  555. }
  556. 100% {
  557. transform: rotate(360deg);
  558. }
  559. }
  560. .gold-title {
  561. width: 100%;
  562. height: 5vh;
  563. flex-shrink: 0;
  564. border-radius: 8px;
  565. background: linear-gradient(90deg, #E4F0FC 0%, #FFF178 50%, #E4F0FC 100%);
  566. box-shadow: 0 2px 2px 0 #00152940;
  567. display: flex;
  568. align-items: center;
  569. justify-content: center;
  570. }
  571. .text1 {
  572. color: #040a2d;
  573. font-family: " PingFang SC ";
  574. font-size: 28px;
  575. font-style: normal;
  576. font-weight: 900;
  577. line-height: 31.79px;
  578. }
  579. .text1-update-time {
  580. width: 100%;
  581. height: 26px;
  582. flex-shrink: 0;
  583. color: #040a2d;
  584. font-family: "PingFang SC";
  585. font-size: 20px;
  586. font-style: normal;
  587. font-weight: 700;
  588. line-height: 31.79px;
  589. }
  590. /* 背景卡片大小 */
  591. .gold-management {
  592. margin: 10px 5px;
  593. width: 100%;
  594. height: 50vh;
  595. flex-shrink: 0;
  596. border-radius: 8px;
  597. background: #E7F4FD;
  598. box-shadow: 0 2px 2px 0 #00000040;
  599. flex-direction: column;
  600. align-items: center;
  601. }
  602. .margin-bottom {
  603. padding-left: 20px;
  604. }
  605. .chart5 {
  606. margin-top: 15px;
  607. .margin-bottom {
  608. margin-top: 10px;
  609. padding-left: 20px;
  610. }
  611. }
  612. </style>