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.

687 lines
19 KiB

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