2
0
Files
fe.wfc.user/src/views/home/modules/header-banner.vue
2024-12-30 19:19:18 +08:00

565 lines
13 KiB
Vue

<script setup lang="ts">
import { computed, watch, ref, onUnmounted } from 'vue';
import { useEcharts } from '@/hooks/common/echarts';
import { useAppStore } from '@/store/modules/app';
import type { ECOption } from '@/hooks/common/echarts';
import { useI18n } from 'vue-i18n';
import { useAuthStore } from '@/store/modules/auth';
import { clientAuth } from '@/service/ue/client';
const { t } = useI18n();
const authStore = useAuthStore();
defineOptions({
name: 'HeaderBanner'
});
const appStore = useAppStore();
// 本地界面显示用的数据类型
interface GaugeDisplayData {
id: number;
title: string;
value: number;
max: number;
unit: string;
displayValue?: string;
subTitle?: string;
description?: string;
}
// 添加速率单位转换函数
const formatSpeed = (speedBps: number): { value: number; unit: string } => {
// 处理 0 值、undefined 或 null 的情况
if (!speedBps || speedBps === 0) {
return { value: 0, unit: 'B/s' };
}
if (speedBps < 1024) {
return { value: Number(speedBps.toFixed(2)), unit: 'B/s' };
} else if (speedBps < 1024 * 1024) {
return { value: Number((speedBps / 1024).toFixed(2)), unit: 'KB/s' };
} else if (speedBps < 1024 * 1024 * 1024) {
return { value: Number((speedBps / (1024 * 1024)).toFixed(2)), unit: 'MB/s' };
} else {
return { value: Number((speedBps / (1024 * 1024 * 1024)).toFixed(2)), unit: 'GB/s' };
}
};
// 添加流量单位转换函数
const formatTraffic = (bytes: number): { value: number; unit: string } => {
// 处理 0 值、undefined 或 null 的情况
if (!bytes || bytes === 0) {
return { value: 0, unit: 'B' };
}
if (bytes < 1024) {
return { value: Number(bytes.toFixed(2)), unit: 'B' };
} else if (bytes < 1024 * 1024) {
return { value: Number((bytes / 1024).toFixed(2)), unit: 'KB' };
} else if (bytes < 1024 * 1024 * 1024) {
return { value: Number((bytes / (1024 * 1024)).toFixed(2)), unit: 'MB' };
} else {
return { value: Number((bytes / (1024 * 1024 * 1024)).toFixed(2)), unit: 'GB' };
}
};
// 使用 ref 来存储基础数据
const baseData = ref<GaugeDisplayData[]>([
{
id: 0,
title: t('page.headerbanner.Remainingcredit'),
value: 0,
max: 100,
unit: '元',
description: t('page.headerbanner.monthphonebill'),
subTitle: t('page.headerbanner.deviceCount') + ': 0台'
},
{
id: 1,
title: t('page.headerbanner.Flowremaining'),
value: 0,
max: 1024,
unit: 'B',
description: t('page.headerbanner.monthflowr'),
subTitle: t('page.headerbanner.Used') + ': 0B'
},
{
id: 2,
title: t('page.headerbanner.Trafficrate'),
value: 0,
max: 10,
unit: 'B/s',
description: t('page.headerbanner.Currentspeed'),
subTitle: t('page.headerbanner.maxspeed') + '0B/s'
}
]);
// 计算属性保持不变
const gaugeData = computed(() => baseData.value);
// 仪盘配置生成函数
const getGaugeOptions = (data: GaugeDisplayData): ECOption => ({
backgroundColor: 'transparent',
series: [{
name: data.title,
type: 'gauge',
min: 0,
max: data.max,
startAngle: 200,
endAngle: -20,
radius: '85%',
center: ['50%', '55%'],
axisLine: {
lineStyle: {
width: 15,
color: [
[(data.value / data.max), '#4284f5'],
[1, '#0c47a7']
]
}
},
pointer: {
width: 3,
length: '60%',
itemStyle: {
color: '#ffeb3b'
}
},
axisTick: {
show: true,
distance: -23,
length: 8,
lineStyle: {
color: '#fff',
width: 1,
opacity: 0.3
}
},
splitLine: {
show: true,
distance: -22,
length: 12,
lineStyle: {
color: '#fff',
width: 2,
opacity: 0.3
}
},
axisLabel: {
show: false
},
detail: {
show: false
},
title: {
show: false
},
data: [{
value: data.value,
name: data.title
}]
}]
});
// 创建三个仪表盘实例
const { domRef: gauge1Ref, updateOptions: updateGauge1 } = useEcharts(() => getGaugeOptions(gaugeData.value[0]), {
onRender: () => {}
});
const { domRef: gauge2Ref, updateOptions: updateGauge2 } = useEcharts(() => getGaugeOptions(gaugeData.value[1]), {
onRender: () => {}
});
const { domRef: gauge3Ref, updateOptions: updateGauge3 } = useEcharts(() => getGaugeOptions(gaugeData.value[2]), {
onRender: () => {}
});
// 更新单个图表的数据
const updateGaugeData = (opts: ECOption, data: GaugeDisplayData, progressRatio?: number) => {
// 创建完整的新配置
const newOpts: ECOption = {
backgroundColor: 'transparent',
series: [{
name: data.title,
type: 'gauge',
min: 0,
max: data.max,
startAngle: 200,
endAngle: -20,
radius: '85%',
center: ['50%', '55%'],
axisLine: {
lineStyle: {
width: 15,
color: [
[(progressRatio !== undefined ? progressRatio : data.value / data.max), '#4284f5'],
[1, '#0c47a7']
]
}
},
pointer: {
width: 3,
length: '60%',
itemStyle: {
color: '#ffeb3b'
}
},
axisTick: {
show: true,
distance: -23,
length: 8,
lineStyle: {
color: '#fff',
width: 1,
opacity: 0.3
}
},
splitLine: {
show: true,
distance: -22,
length: 12,
lineStyle: {
color: '#fff',
width: 2,
opacity: 0.3
}
},
axisLabel: {
show: false
},
detail: {
show: false
},
title: {
show: false
},
data: [{
value: data.value,
name: data.title
}]
}]
};
return newOpts;
};
// 添加峰值速率的 ref
const peakTrafficRate = ref(0);
// 添加检查是否可以上网的函数
function checkAndTriggerAuth(dashboardData: any) {
// 检查是否有余额
const hasBalance = dashboardData.balance > 0;
// 检查是否不限制流量
const noTrafficLimit = !dashboardData.trafficEnable;
// 检查是否有剩余流量
const hasRemainingTraffic = dashboardData.trafficEnable &&
(dashboardData.traffic - dashboardData.trafficUsed) > 0;
// 如果满足任一条件,触发上网
if (hasBalance || noTrafficLimit || hasRemainingTraffic) {
clientAuth().then((res) => {
console.log('Auto auth triggered:', res);
}).catch(err => {
console.error('Auto auth failed:', err);
});
}
}
// 修改 mockDataUpdate 函数,在获取数据后添加检查
async function mockDataUpdate() {
try {
const response = await authStore.getDashboardData();
if (response) {
// 更新余额和设备数据
baseData.value[0] = {
...baseData.value[0],
value: response.balance,
max: Math.max(response.balance, 100),
subTitle: t('page.headerbanner.deviceCount') + `: ${response.clientNum}`
};
// 先计算剩余流量(字节单位)
const totalTraffic = response.traffic; // 总流量(字节)
const usedTraffic = response.trafficUsed; // 已用流量(字节)
// 计算剩余流量,确保不会出现负数
const remainingTraffic = Math.max(0, totalTraffic - usedTraffic); // 剩余流量(字节)
// 计算进度比例,确保在 0-1 之间
const progressRatio = Math.min(1, Math.max(0, remainingTraffic / totalTraffic));
// 格式化流量显示
const formattedTotal = formatTraffic(totalTraffic);
const formattedUsed = formatTraffic(usedTraffic);
const formattedRemaining = formatTraffic(remainingTraffic);
// 更新流量数据显示
baseData.value[1] = {
...baseData.value[1],
value: remainingTraffic,
max: totalTraffic,
displayValue: `${formattedRemaining.value}${formattedRemaining.unit}`,
unit: '',
description: `${t('page.headerbanner.monthflowr')} (${formattedTotal.value}${formattedTotal.unit})`,
subTitle: t('page.headerbanner.Used') + `: ${formattedUsed.value}${formattedUsed.unit}`
};
// 获取当前速率
const currentActivity = Number(response.activity ?? 0);
// 更新峰值速率
if (currentActivity > peakTrafficRate.value) {
peakTrafficRate.value = currentActivity;
}
// 格式化当前速度和峰值速度
const currentSpeed = formatSpeed(currentActivity);
const peakSpeed = formatSpeed(peakTrafficRate.value);
// 设置速率的最大值
const minMax = 1024; // 1KB/s 作为最小最大值
const dynamicMax = Math.max(
currentSpeed.value * 1.5,
peakSpeed.value,
minMax
);
// 更新速率数据
baseData.value[2] = {
...baseData.value[2],
value: currentSpeed.value,
unit: currentSpeed.unit,
max: dynamicMax,
subTitle: t('page.headerbanner.maxspeed') + `: ${peakSpeed.value}${peakSpeed.unit}`
};
// 在数据处理完成后,添加自动上网检查
checkAndTriggerAuth(response);
// 统一更新所有图表
updateGauge1(opts => updateGaugeData(opts, baseData.value[0]));
updateGauge2(opts => {
const newOpts = updateGaugeData(opts, baseData.value[1], progressRatio);
// 添加动画配置
return {
...newOpts,
animation: true,
animationDuration: 1000,
animationEasing: 'cubicInOut'
};
});
updateGauge3(opts => updateGaugeData(opts, baseData.value[2]));
}
} catch (error) {
console.error('Failed to fetch dashboard data:', error);
}
}
// 添加 timer 声明
let timer: ReturnType<typeof setInterval> | null = null;
// 初始化
async function init() {
// 立即执行一次数据更新
await mockDataUpdate();
// 设置定期执行的定时器
timer = setInterval(mockDataUpdate, 30000);
}
// 组件卸载时清除定时器
onUnmounted(() => {
if (timer) {
clearInterval(timer);
timer = null;
}
peakTrafficRate.value = 0;
});
// 监听语言变化
watch(
() => appStore.locale,
() => {
//updateLocale();
}
);
// 初始化
init();
// 导出更新数据的方法
const updateDashboard = async () => {
await mockDataUpdate();
};
// 将方法暴露给父组件
defineExpose({
updateDashboard
});
</script>
<template>
<ACard :bordered="false" class="card-wrapper">
<ARow :gutter="[4, 4]" class="justify-around gauge-row">
<ACol :span="8" v-for="item in gaugeData" :key="item.id">
<div class="gauge-container">
<div class="gauge-chart" :ref="el => {
if (item.id === 0) gauge1Ref = el as HTMLElement
else if (item.id === 1) gauge2Ref = el as HTMLElement
else if (item.id === 2) gauge3Ref = el as HTMLElement
}"></div>
<div class="gauge-info">
<div class="gauge-title">{{ item.title }}</div>
<div class="gauge-value">{{ item.displayValue || `${item.value}${item.unit}` }}</div>
<div class="gauge-desc">{{ item.description }}</div>
<div class="sub-title">{{ item.subTitle }}</div>
</div>
</div>
</ACol>
</ARow>
</ACard>
</template>
<style scoped>
.card-wrapper {
margin-bottom: 16px;
background: linear-gradient(180deg, #4284f5 0%, #0c47a7 100%);
padding: 20px 16px;
}
.gauge-container {
height: 220px;
min-height: 140px;
position: relative;
display: flex;
flex-direction: column;
}
.gauge-chart {
flex: 1;
min-height: 120px;
position: relative;
}
:deep(.echarts-container) {
position: absolute !important;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.gauge-info {
padding: 4px 0;
text-align: center;
color: #fff;
margin-top: -20px;
}
.gauge-value {
font-size: 24px;
font-weight: bold;
line-height: 1.2;
}
.gauge-title {
font-size: 14px;
margin: 4px 0;
}
.sub-title {
color: rgba(255, 255, 255, 0.85);
font-size: 14px;
}
.gauge-desc {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
margin: 2px 0;
}
/* 在小屏幕下调整样式 */
@media screen and (max-width: 768px) {
.card-wrapper {
margin-bottom: 8px;
padding: 8px 4px;
}
:deep(.arco-row) {
margin: 0 !important;
}
:deep(.arco-col) {
padding: 0 !important;
}
.gauge-container {
height: 160px;
min-height: 90px;
}
.gauge-chart {
min-height: 70px;
}
.gauge-info {
padding: 2px 0;
}
.gauge-value {
font-size: 18px;
}
.gauge-title {
font-size: 12px;
margin: 2px 0;
}
.sub-title {
font-size: 12px;
}
.gauge-desc {
font-size: 10px;
}
/* 调整仪表盘在小屏幕下的置 */
:deep(.echarts-container) {
top: 0;
transform: scale(0.9);
transform-origin: center 45%;
}
.gauge-info {
margin-top: -15px;
}
}
/* 在超小屏幕下进一步优化 */
@media screen and (max-width: 375px) {
.card-wrapper {
padding: 4px 2px;
}
.gauge-container {
height: 140px;
min-height: 80px;
}
.gauge-chart {
min-height: 60px;
}
:deep(.echarts-container) {
top: 0;
transform: scale(0.85);
transform-origin: center 40%;
}
.gauge-info {
margin-top: -10px;
padding: 1px 0;
}
}
</style>