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.
 
 
 
 

688 lines
19 KiB

<template>
<div class="gold-management">
<div class="gold-title">
<div class="text1">
{{ t('workbench.goldManagement') }}
<span class="text1-update-time">{{ t('workbench.lastUpdateTime') }}{{
workDataUpdateTime && workDataUpdateTime !== '1970-01-01 08:00:00' ? workDataUpdateTime : t('workbench.noData')
}} </span>
</div>
</div>
<!-- 第一行包含两个横向格子 -->
<el-row>
<el-col :span="12">
<!-- 第一个卡片 -->
<div class="card-item-row1">
<div class="card-title">
{{ t('workbench.currentGoldBalance') }}
<span style="font-weight: bold">{{
currentGold / 100
}}</span>&nbsp;&nbsp;&nbsp;&nbsp;{{ t('workbench.compareToPreviousDay') }}
{{ dailyChange / 100 }}&nbsp;
<template v-if="dailyChange > 0">
<el-image :src="upArrow" style="width: .7292vw;"/>
</template>
<template v-else-if="dailyChange < 0">
<el-image :src="downArrow" style="width: .7292vw;"/>
</template>
<template v-else>
<el-image :src="pingArrow" style="width: .7292vw; padding-top: .625vw"/>
</template>
</div>
<div>
<el-row>
<!-- 左边文本信息 -->
<el-col :span="12">
<div class="margin-bottom" style="white-space: nowrap;">
{{ t('workbench.permanentGold') }}:<b>{{ currentPermanent / 100 }}</b>
</div>
<div class="margin-bottom">&nbsp;</div>
<div class="margin-bottom">{{ t('workbench.freeGold') }}:<b>{{ currentFree / 100 }}</b></div>
<!-- <div class="margin-bottom">&nbsp</div>-->
<!-- <div class="margin-bottom">&nbsp</div>-->
<div class="margin-bottom">
[{{ t('workbench.goldExpireIn6Months')}}{{ currentFreeJune / 100 }}]
</div>
<div class="margin-bottom">&nbsp;</div>
<div class="margin-bottom">{{ t('workbench.taskGold') }}:<b>{{ currentTask / 100 }}</b></div>
</el-col>
<!-- 右边图表 -->
<el-col :span="12">
<!-- <div ref="goldTypeChart" style="width: 100%; height: 5.2083vw;"></div>-->
<div style="width: 100%; height: 3.125vw;">&nbsp;</div>
<div class="margin-bottom">
[{{ t('workbench.goldExpireIn12Months')}}{{ currentFreeDecember / 100 }}]
</div>
</el-col>
</el-row>
</div>
</div>
</el-col>
<el-col :span="12">
<!-- 第二个卡片 -->
<div class="card-item-row1">
<div class="card-title">{{ t('workbench.annualCumulativeRecharge')}}{{ yearlyRecharge / 100 }}</div>
<el-row>
<el-col :span="12">
<div class="center-card">{{ t('workbench.convertedSGDCumulativeAmount') }}</div>
<el-image :src="svg1" style="width: 4.5833vw; display: block;margin: 0 auto;"/>
<div class="center-card">{{ yearlyMoney / 100 }}{{ t('workbench.SGD') }}</div>
</el-col>
<el-col :span="12" style="border-left: .1042vw solid #CFE6FE; height: 8.3333vw">
<div class="center-card" style="white-space: nowrap;">{{ t('workbench.yesterdayNew')}}{{ recharge / 100 }}</div>
<div ref="rechargeGoldChart" style="width: 4.5833vw; height: 4.5833vw; display: block;margin: 0 auto;"></div>
<div class="center-card" style="white-space: nowrap;">{{ t('workbench.wherePermanentGold')}}{{ money / 100 }}</div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
<!-- 第二行:包含两个横向格子 -->
<el-row>
<el-col :span="12">
<!-- 第三个卡片 -->
<div class="card-item">
<div class="card-title">{{ t('workbench.annualCumulativeConsume')}}{{ yearlyReduce / 100 }}</div>
<el-row style="height: 10.4167vw;">
<el-col :span="12">
<div ref="consumeChart" style="width:100%; height: 88%;"></div>
</el-col>
<el-col :span="12">
<div ref="consumeDetailChart" style="width: 100%; height: 108%;"></div>
</el-col>
</el-row>
</div>
</el-col>
<el-col :span="12">
<!-- 第四个卡片 -->
<div class="card-item" >
<div class="card-title">{{ t('workbench.annualCumulativeRechargePeople')}}{{ yearlyRechargeNum }}</div>
<el-row style="height: 10.4167vw;">
<el-col :span="12" style="border-right: .1042vw solid #CFE6FE; height: 10.4167vw">
<div class="chart5">
<el-image :src="svg2" style="width: 4.5833vw; display: block;margin: 0 auto;"/>
<div class="margin-bottom">
<div style="display: flex; gap: .5208vw; font-size: .8333vw;">{{ t('workbench.weekYearOnYear')}}{{ sumWow }}%
<el-image v-if="sumWow > 0" :src="upArrow" style="width: .5208vw;"/>
<el-image v-else-if="sumWow < 0" :src="downArrow" style="width: .5208vw;"/>
<el-image v-else :src="pingArrow" style="width: .5208vw;"/>
</div>
<div style="display: flex; gap: .5208vw; font-size: .8333vw;">
{{ t('workbench.dayYearOnYear')}}{{ sumDaily }}%
<el-image v-if="sumDaily > 0" :src="upArrow" style="width: .5208vw;"/>
<el-image v-else-if="sumDaily < 0" :src="downArrow" style="width: .5208vw;"/>
<el-image v-else :src="pingArrow" style="width: .5208vw; "/>
</div>
</div>
</div>
</el-col>
<!-- 新增的环形图容器 -->
<el-col :span="12">
<div ref="rechargePeopleChart" style="width:100%; height: 88%;"></div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</div>
</template>
<script setup>
import {onMounted, ref, nextTick} from 'vue'
import * as echarts from 'echarts'
import API from '@/util/http'
import dayjs from 'dayjs';
import utc from 'dayjs-plugin-utc'
import {ArrowDownBold, ArrowUpBold, SemiSelect} from '@element-plus/icons-vue'
import svg1 from '@/assets/SvgIcons/convert-singapore-total.svg'
import svg2 from '@/assets/SvgIcons/wow.svg'
import upArrow from '@/assets/SvgIcons/up-arrow.svg'
import downArrow from '@/assets/SvgIcons/down-arrow.svg'
import pingArrow from '@/assets/SvgIcons/unchanged.svg'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
dayjs.extend(utc)
// 用户信息
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)
// ECharts 实例引用
const goldTypeChart = ref(null)
const rechargeGoldChart = ref(null)
const consumeChart = ref(null)
const consumeDetailChart = ref(null)
const rechargePeopleChart = ref(null)
// 要加上所有市场的,还有额外计算的(总数 = 永久 + 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) {
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
ydayRechargeNum.value = summary.ydayRechargeNum
firstRecharge.value = summary.firstRecharge
// 初始化图表
nextTick(() => {
// initGoldTypeChart();
initRechargeGoldChart();
initConsumeChart();
initConsumeDetailChart();
initRechargePeopleChart();
});
}
// 初始化金币类型南丁格尔图(暂时不用了)
/*
const initGoldTypeChart = () => {
const myChart = echarts.init(goldTypeChart.value);
const option = {
tooltip: {
trigger: 'item',
formatter: function (params) {
let realValue = 0
if (params.name === '永久金币') realValue = currentPermanent.value / 100
else if (params.name === '免费金币') realValue = (currentFreeJune.value / 100 + currentFreeDecember.value / 100)
else realValue = currentTask.value / 100
return `${params.name}: ${realValue}`
}
},
toolbox: {
show: true,
feature: {}
},
series: [
{
name: 'Nightingale Chart',
type: 'pie',
radius: ['0%', '100%'],
center: ['50%', '50%'],
roseType: 'area',
itemStyle: {
borderRadius: 5
},
data: [
{value: Math.log(currentPermanent.value / 100 + 1), name: '永久金币'},
{value: Math.log((currentFreeJune.value / 100 + currentFreeDecember.value / 100) + 1), name: '免费金币'},
{value: Math.log(currentTask.value / 100 + 1), name: '任务金币'}
],
labelLine: {show: false},
label: {show: false}
}
]
};
myChart.setOption(option);
}
*/
// 初始化充值金币环形图
const initRechargeGoldChart = () => {
const myChart = echarts.init(rechargeGoldChart.value);
const option = {
series: [
{
type: 'pie',
radius: ['60%', '85%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: recharge.value / 100,
itemStyle: {color: '#80aaff'}
}
]
},
{
type: 'pie',
radius: ['60%', '75%'],
startAngle: 180,
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: money.value / 100,
itemStyle: {color: '#f2c97d'}
},
{
value: (recharge.value / 100 - money.value / 100),
itemStyle: {color: 'transparent'}
}
]
}
]
};
myChart.setOption(option);
}
// 初始化消费退款环形图
const initConsumeChart = () => {
const myChart = echarts.init(consumeChart.value);
const option = {
legend: {
textStyle: {
fontSize: 15,
color: '#000',
fontFamily: 'PingFang SC'
},
orient: 'vertical',
left: '15%',
top: '105',
icon: 'circle',
iconSize: 7,
textSize: 12,
itemWidth: 7,
itemHeight: 7,
},
series: [
{
type: 'pie',
radius: ['30%', '45%'],
center: ['50%', '30%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: yearlyConsume.value / 100,
name: t('workbench.consume') + yearlyConsume.value / 100,
// name: '消耗:' + 1234567890,
itemStyle: {color: '#7DB7FA'}
},
{
value: yearlyRefund.value / 100,
name: t('workbench.refund') + yearlyRefund.value / 100,
itemStyle: {color: '#F7D47C'}
}
],
}
]
};
myChart.setOption(option);
};
// 初始化消费明细环形图
const initConsumeDetailChart = () => {
const myChart = echarts.init(consumeDetailChart.value);
const option = {
// 增加图表内边距,避免内容溢出
legend: {
textStyle: {
fontSize: 15,
color: '#000',
fontFamily: 'PingFang SC'
},
orient: 'vertical',
left: '10%',
top: '105',
icon: 'circle',
iconSize: 5,
itemWidth: 7,
itemHeight: 7,
},
series: [
{
type: 'pie',
radius: ['25%', '40%'],
center: ['50%', '25%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: dailyConsume.value / 100,
name: t('workbench.yesterdayNewAll') + dailyConsume.value / 100,
itemStyle: {color: '#65C9C9'}
}
]
},
{
type: 'pie',
radius: ['25%', '35%'],
center: ['50%', '25%'],
startAngle: 180,
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: dailyReduce.value / 100,
name: t('workbench.yesterdayNewConsume') + dailyReduce.value / 100,
// name: '昨日新增消耗:' + 1234567890,
itemStyle: {color: '#9469D1'}
},
{
value: dailyRefund.value / 100,
name: t('workbench.yesterdayNewRefund') + dailyRefund.value / 100,
itemStyle: {color: '#B8DB6E'}
}
]
}
]
};
myChart.setOption(option);
};
// 初始化充值人头环形图
const initRechargePeopleChart = () => {
const myChart = echarts.init(rechargePeopleChart.value);
const option = {
legend: {
textStyle: {
fontSize: 15,
color: '#000',
fontFamily: 'PingFang SC'
},
orient: 'vertical',
left: '20%',
top: '110',
icon: 'circle',
iconSize: 5,
itemWidth: 7,
itemHeight: 7,
},
series: [
{
type: 'pie',
radius: ['30%', '50%'],
center: ['50%', '30%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: ydayRechargeNum.value,
name: t('workbench.yesterdayRechargePeople') + ydayRechargeNum.value,
itemStyle: {color: '#65C9C9'}
},
],
},
{
type: 'pie',
radius: ['30%', '45%'],
center: ['50%', '30%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: firstRecharge.value,
name: t('workbench.whereFirstRecharge') + firstRecharge.value,
itemStyle: {color: '#9469D1'}
},
{
value: ydayRechargeNum.value - firstRecharge.value,
itemStyle: {color: 'transparent'}
}
],
}
]
};
myChart.setOption(option);
}
// 获取卡片数据
const getCardData = async () => {
try {
const response = await API({url: '/workbench/getCard', data: {}})
workDataUpdateTime.value = response.updateTime
// 周同比
sumWow.value = response.sumWow.toFixed(2)
// 日环比
sumDaily.value = response.sumDaily.toFixed(2)
if (response && response.data) {
processData(response.data)
} else if (Array.isArray(response?.marketCards)) {
processData(response)
} else {
console.error('无效的API响应结构:', response)
}
} catch (error) {
console.error('获取卡片数据失败:', error)
}
}
const workDataUpdateTime = ref(null)
onMounted(async () => {
await getCardData()
})
</script>
<style scoped lang="scss">
.center-card {
display: flex;
justify-content: center;
align-items: center;
}
.card-item-row1 {
height: 10.4167vw;
width: auto;
background: #E4F0FC;
box-shadow: 0 0 .2083vw 0 #00000040;
border-radius: .5208vw;
margin-top: 1.0417vw;
margin-left: .2604vw;
margin-right: .2604vw;
margin-bottom: -0.2604vw;
padding-bottom: .5208vw;
}
.card-item {
height: 12.5vw;
width: auto;
background: #E4F0FC;
box-shadow: 0 0 .2083vw 0 #00000040;
border-radius: .5208vw;
margin-top: 1.0417vw;
margin-left: .2604vw;
margin-right: .2604vw;
margin-bottom: -0.2604vw;
padding-bottom: .5208vw;
}
.card-title {
font-weight: bold;
height: 1.875vw;
font-size: .8854vw;
width: 100%;
flex-shrink: 0;
border-radius: .4167vw;
background: linear-gradient(90deg, #E4F0FC 0%, #C1DCF8 50%, #E4F0FC 100%);
box-shadow: 0 0 .1042vw 0 #00152940;
display: flex;
align-items: center;
justify-content: center;
margin-top: -0.2604vw;
margin-bottom: .5208vw;
}
.card-item .el-col {
overflow: visible;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.gold-title {
width: 100%;
height: 5vh;
flex-shrink: 0;
border-radius: .4167vw;
background: linear-gradient(90deg, #E4F0FC 0%, #FFF178 50%, #E4F0FC 100%);
box-shadow: 0 .1042vw .1042vw 0 #00152940;
display: flex;
align-items: center;
justify-content: center;
}
.text1 {
color: #040a2d;
font-family: " PingFang SC ";
font-size: 1.4583vw;
font-style: normal;
font-weight: 900;
line-height: 1.6557vw;
}
.text1-update-time {
width: 100%;
height: 1.3542vw;
flex-shrink: 0;
color: #040a2d;
font-family: "PingFang SC";
font-size: 1.0417vw;
font-style: normal;
font-weight: 700;
line-height: 1.6557vw;
}
/* 背景卡片大小 */
.gold-management {
margin: .5208vw .2604vw;
width: 100%;
height: 28.6458vw;
flex-shrink: 0;
border-radius: .4167vw;
background: #E7F4FD;
box-shadow: 0 .1042vw .1042vw 0 #00000040;
flex-direction: column;
align-items: center;
}
.margin-bottom {
padding-left: 1.0417vw;
}
.chart5 {
margin-top: .7813vw;
.margin-bottom {
margin-top: .5208vw;
padding-left: 1.0417vw;
}
}
</style>