797 lines
19 KiB
Vue
797 lines
19 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;
|
||
speedLimits?: {
|
||
upLimit: string;
|
||
downLimit: 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' };
|
||
}
|
||
};
|
||
|
||
// 添加格式化余额的函数
|
||
const formatBalance = (balance: number | string): string => {
|
||
// 确保转换为数字类型
|
||
const numBalance = Number(balance);
|
||
// 检查是否是有效数字
|
||
if (isNaN(numBalance)) {
|
||
return '0.00';
|
||
}
|
||
return numBalance.toFixed(2);
|
||
};
|
||
|
||
// 修改基础数据,移除速率仪表盘
|
||
const baseData = ref<GaugeDisplayData[]>([
|
||
{
|
||
id: 0,
|
||
title: t('page.headerbanner.Remainingcredit'),
|
||
value: 0,
|
||
max: 100,
|
||
unit: t('page.headerbanner.money'),
|
||
description: t('page.headerbanner.monthphonebill'),
|
||
subTitle: t('page.headerbanner.deviceCount') + `: 0${t('page.headerbanner.device')}`,
|
||
displayValue: '-'
|
||
},
|
||
{
|
||
id: 1,
|
||
title: t('page.headerbanner.Flowremaining'),
|
||
value: 0,
|
||
max: 1024,
|
||
unit: 'B',
|
||
//description: t('page.headerbanner.monthflowr'),
|
||
//subTitle: t('page.headerbanner.Used') + ': 0B',
|
||
description: '-',
|
||
subTitle: '-',
|
||
speedLimits: {
|
||
// upLimit: t('page.headerbanner.nolimit'),
|
||
// downLimit: t('page.headerbanner.nolimit')
|
||
upLimit: '-',
|
||
downLimit: '-'
|
||
}
|
||
}
|
||
]);
|
||
|
||
// 计算属性保持不变
|
||
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 || 1), '#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 updateGaugeData = (opts: ECOption, data: GaugeDisplayData, progressRatio?: number) => {
|
||
const ratio = progressRatio !== undefined ? progressRatio : (data.value / (data.max || 1));
|
||
|
||
// 创建完整的新配置
|
||
const newOpts: ECOption = {
|
||
backgroundColor: 'transparent',
|
||
series: [{
|
||
name: data.title,
|
||
type: 'gauge',
|
||
min: 0,
|
||
max: data.max || 1,
|
||
startAngle: 200,
|
||
endAngle: -20,
|
||
radius: '85%',
|
||
center: ['50%', '55%'],
|
||
axisLine: {
|
||
lineStyle: {
|
||
width: 15,
|
||
color: [
|
||
[ratio, '#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;
|
||
};
|
||
|
||
// 修改速率格式化函数
|
||
const formatSpeedLimit = (speed: number | null): string => {
|
||
if (speed === null || speed === undefined) {
|
||
return '0Kbps';
|
||
}
|
||
if (speed === -1) {
|
||
return t('page.headerbanner.nolimit');
|
||
}
|
||
if (speed >= 1048576) {
|
||
return `${(speed / 1048576).toFixed(1)}Gbps`;
|
||
} else if (speed >= 1024) {
|
||
return `${(speed / 1024).toFixed(1)}Mbps`;
|
||
}
|
||
return `${speed}Kbps`;
|
||
};
|
||
|
||
// 添加速率限制的响应式引用
|
||
const speedLimits = ref({
|
||
// upLimit: t('page.headerbanner.nolimit'),
|
||
// downLimit: t('page.headerbanner.nolimit')
|
||
upLimit: '-',
|
||
downLimit: '-'
|
||
});
|
||
|
||
// 添加套餐信息的响应式引用
|
||
const packageInfo = ref({
|
||
// packageName: '',
|
||
// price: '0.00'
|
||
packageName: t('page.headerbanner.noPackage'),
|
||
price: '-'
|
||
});
|
||
|
||
// 修改数据更新函数,添加套餐信息的更新
|
||
async function mockDataUpdate() {
|
||
try {
|
||
const response = await authStore.getDashboardData();
|
||
if (response && typeof response === 'object' && !response.error) {
|
||
// 更新套餐信息
|
||
packageInfo.value = {
|
||
packageName: response.packageName || t('page.headerbanner.nopackage'),
|
||
price: response.packageName ? formatBalance(response.price) : '-' // 有套餐时才格式化价格
|
||
};
|
||
|
||
// 更新余额和设备数据
|
||
const numBalance = Number(response.balance);
|
||
baseData.value[0] = {
|
||
...baseData.value[0],
|
||
value: numBalance,
|
||
max: Math.max(numBalance, 100),
|
||
displayValue:formatBalance(response.balance),
|
||
unit: t('page.headerbanner.money'),
|
||
subTitle: t('page.headerbanner.deviceCount') + `: ${
|
||
response.packageName ? (
|
||
!response.clientNumEnable
|
||
? t('page.headerbanner.nolimit')
|
||
: (response.clientNum || 0) + t('page.headerbanner.device')
|
||
):'-'
|
||
}`
|
||
};
|
||
|
||
// 更新流量数据
|
||
if (!response.packageName) {
|
||
// 无套餐时显示 -
|
||
baseData.value[1] = {
|
||
...baseData.value[1],
|
||
value: 0,
|
||
max: 1024,
|
||
displayValue: '-',
|
||
description: '-',
|
||
subTitle: '-',
|
||
speedLimits: {
|
||
upLimit: '-',
|
||
downLimit: '-'
|
||
}
|
||
};
|
||
} else {
|
||
// 有套餐时的正常显示逻辑
|
||
const totalTraffic = response.trafficEnable ? (response.traffic || 0) : 0;
|
||
const usedTraffic = response.trafficEnable ? (response.trafficUsed || 0) : 0;
|
||
const remainingTraffic = Math.max(0, totalTraffic - usedTraffic);
|
||
|
||
// 格式化流量显示
|
||
const formattedTotal = formatTraffic(totalTraffic);
|
||
const formattedUsed = formatTraffic(usedTraffic);
|
||
|
||
const formattedRemaining = formatTraffic(remainingTraffic);
|
||
|
||
// 更新流量数据显示
|
||
// baseData.value[1] = {
|
||
// ...baseData.value[1],
|
||
// value: remainingTraffic,
|
||
// max: totalTraffic,
|
||
// displayValue: !response.trafficEnable ? t('page.headerbanner.nolimit') : `${formattedRemaining.value}${formattedRemaining.unit}`,
|
||
// unit: '',
|
||
// description: !response.trafficEnable
|
||
// ? t('page.headerbanner.nolimit')
|
||
// : `${formattedTotal.value}${formattedTotal.unit}`,
|
||
// subTitle: !response.trafficEnable
|
||
// ? t('page.headerbanner.nolimit')
|
||
// : formattedUsed.value+formattedUsed.unit,
|
||
// speedLimits: {
|
||
// upLimit: speedLimits.value.upLimit,
|
||
// downLimit: speedLimits.value.downLimit
|
||
// }
|
||
// };
|
||
// console.log(baseData.value[1].description)
|
||
baseData.value[1] = {
|
||
...baseData.value[1],
|
||
value: remainingTraffic,
|
||
max: totalTraffic,
|
||
displayValue: response.trafficEnable ? `${formattedRemaining.value}${formattedRemaining.unit}` : t('page.headerbanner.nolimit'),
|
||
description: response.trafficEnable ? `${formattedTotal.value}${formattedTotal.unit}` : t('page.headerbanner.nolimit'),
|
||
subTitle: response.trafficEnable ? formattedUsed.value+formattedUsed.unit : t('page.headerbanner.nolimit')
|
||
};
|
||
}
|
||
|
||
// 更新速率限制显示
|
||
if (!response.packageName) {
|
||
speedLimits.value = {
|
||
upLimit: '-',
|
||
downLimit: '-'
|
||
};
|
||
} else if (response.rateLimitEnable) {
|
||
speedLimits.value = {
|
||
upLimit: !response.upLimitEnable
|
||
? t('page.headerbanner.nolimit')
|
||
: formatSpeedLimit(response.upLimit),
|
||
downLimit: !response.downLimitEnable
|
||
? t('page.headerbanner.nolimit')
|
||
: formatSpeedLimit(response.downLimit)
|
||
};
|
||
} else {
|
||
speedLimits.value = {
|
||
upLimit: t('page.headerbanner.nolimit'),
|
||
downLimit: t('page.headerbanner.nolimit')
|
||
};
|
||
}
|
||
|
||
// 更新图表
|
||
updateGauge1(opts => updateGaugeData(opts, baseData.value[0]));
|
||
updateGauge2(opts => {
|
||
const newOpts = updateGaugeData(
|
||
opts,
|
||
baseData.value[1],
|
||
response.packageName && response.trafficEnable ? remainingTraffic / totalTraffic : 0
|
||
);
|
||
return {
|
||
...newOpts,
|
||
animation: true,
|
||
animationDuration: 1000,
|
||
animationEasing: 'cubicInOut'
|
||
};
|
||
});
|
||
}
|
||
} catch (error) {
|
||
// console.error('Failed to update 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;
|
||
}
|
||
});
|
||
|
||
// 监听语言变化
|
||
watch(
|
||
() => appStore.locale,
|
||
() => {
|
||
//updateLocale();
|
||
}
|
||
);
|
||
|
||
// 初始化
|
||
init();
|
||
|
||
// 导出更新数据的方法
|
||
const updateDashboard = async () => {
|
||
await mockDataUpdate();
|
||
};
|
||
|
||
// 将方法暴露给父组件
|
||
defineExpose({
|
||
updateDashboard
|
||
});
|
||
|
||
// 修改流量获取函数
|
||
const getTrafficTotal = (description?: string, trafficEnable?: boolean): string => {
|
||
// 如果描述中包含"无限制",说明是无限制套餐
|
||
if (!description || description.includes(t('page.headerbanner.nolimit'))) {
|
||
return t('page.headerbanner.nolimit');
|
||
}
|
||
const matches = description.match(/\((.*?)\)/);
|
||
return matches ? matches[1] : '0B';
|
||
};
|
||
|
||
// 修改设备数量获取函数
|
||
const getDeviceCount = (subTitle?: string, clientNumEnable?: boolean): string => {
|
||
if (!subTitle) return t('page.headerbanner.nolimit');
|
||
const parts = subTitle.split(': ');
|
||
return parts.length > 1 ? parts[1] : '0';
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<ACard :bordered="false" class="card-wrapper">
|
||
<div class="dashboard-layout">
|
||
<!-- 左侧:仪表盘和流量信息 -->
|
||
<div class="left-section">
|
||
<div class="gauge-container">
|
||
<div class="gauge-chart" ref="gauge2Ref"></div>
|
||
<div class="gauge-info">
|
||
<div class="gauge-title">{{ baseData[1].title }}</div>
|
||
<div class="gauge-value">{{ baseData[1].displayValue }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:分组信息显示 -->
|
||
<div class="right-section">
|
||
<!-- 套餐信息 -->
|
||
<div class="info-section">
|
||
<div class="section-title">{{t('page.headerbanner.packageinfo')}}</div>
|
||
<div class="info-group">
|
||
<div class="info-item">
|
||
<span class="info-label">{{ t('page.headerbanner.packagename') }}</span>
|
||
<span class="info-value">{{ packageInfo.packageName || t('page.headerbanner.nopackage') }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">{{ t('page.headerbanner.price') }}</span>
|
||
<span class="info-value">{{ packageInfo.price === '-' ? '-' : '¥' + packageInfo.price }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">{{ t('page.headerbanner.monthflowr') }}</span>
|
||
<span class="info-value">{{ baseData[1].description }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">{{ t('page.headerbanner.Used') }}</span>
|
||
<span class="info-value">{{ baseData[1].subTitle }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">{{ t('page.headerbanner.uplimit') }}</span>
|
||
<span class="info-value">{{ speedLimits.upLimit }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">{{ t('page.headerbanner.downlimit') }}</span>
|
||
<span class="info-value">{{ speedLimits.downLimit }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 账户信息 -->
|
||
<div class="info-section">
|
||
<div class="section-title">{{ t('page.headerbanner.accountinfor') }}</div>
|
||
<div class="info-group">
|
||
<div class="info-item">
|
||
<span class="info-label">{{ t('page.headerbanner.Remainingcredit') }}</span>
|
||
<span class="info-value">{{ baseData[0].displayValue === '-' ? '-' : '¥' + baseData[0].displayValue }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">{{ t('page.headerbanner.client') }}</span>
|
||
<span class="info-value">{{ getDeviceCount(baseData[0].subTitle, baseData[0].speedLimits?.upLimit !== t('page.headerbanner.nolimit')) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ACard>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.card-wrapper {
|
||
margin-bottom: 16px;
|
||
background: linear-gradient(180deg, #4284f5 0%, #0c47a7 100%);
|
||
padding: 16px 12px 16px 8px;
|
||
color: #fff;
|
||
}
|
||
|
||
.dashboard-layout {
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
|
||
/* 左侧部分样式 */
|
||
.left-section {
|
||
flex: 0 0 auto;
|
||
width: 160px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.gauge-container {
|
||
text-align: center;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
margin-left: -15px;
|
||
}
|
||
|
||
.gauge-chart {
|
||
width: 160px;
|
||
height: 160px;
|
||
}
|
||
|
||
.gauge-info {
|
||
margin-top: -12px;
|
||
}
|
||
|
||
.gauge-title {
|
||
font-size: 14px;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
.gauge-value {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* 右侧部分样式 */
|
||
.right-section {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
padding-right: 4px;
|
||
}
|
||
|
||
.info-section {
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #fff;
|
||
margin-bottom: 8px;
|
||
padding-left: 6px;
|
||
text-align: center;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.section-title::before {
|
||
content: '';
|
||
display: inline-block;
|
||
width: 3px;
|
||
height: 14px;
|
||
background-color: #fff;
|
||
margin-right: 8px;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.info-group {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border-radius: 8px;
|
||
padding: 6px 8px;
|
||
width: 80%;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 8px 0;
|
||
max-width: 300px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.info-item:not(:last-child) {
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.info-label {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 移动端适配 */
|
||
@media screen and (max-width: 768px) {
|
||
.card-wrapper {
|
||
padding: 16px 8px 16px 6px;
|
||
}
|
||
|
||
.dashboard-layout {
|
||
gap: 4px;
|
||
}
|
||
|
||
.right-section {
|
||
gap: 4px;
|
||
padding-right: 2px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 14px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.info-section {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.info-group {
|
||
width: 90%;
|
||
}
|
||
|
||
.info-item {
|
||
padding: 6px 0;
|
||
max-width: 250px;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 12px;
|
||
margin-bottom: 3px;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.left-section {
|
||
width: 120px;
|
||
min-height: 180px;
|
||
}
|
||
|
||
.gauge-container {
|
||
width: 120px;
|
||
margin-left: -10px;
|
||
}
|
||
|
||
.gauge-chart {
|
||
width: 120px;
|
||
height: 120px;
|
||
}
|
||
|
||
.gauge-info {
|
||
margin-top: -12px;
|
||
}
|
||
|
||
.gauge-value {
|
||
font-size: 16px;
|
||
margin-top: 2px;
|
||
}
|
||
}
|
||
|
||
/* 超小屏幕优化 */
|
||
@media screen and (max-width: 375px) {
|
||
.card-wrapper {
|
||
padding: 10px 6px 10px 4px;
|
||
}
|
||
|
||
.dashboard-layout {
|
||
gap: 4px;
|
||
}
|
||
|
||
.left-section {
|
||
width: 100px;
|
||
min-height: 160px;
|
||
}
|
||
|
||
.gauge-container {
|
||
width: 100px;
|
||
margin-left: -8px;
|
||
}
|
||
|
||
.gauge-chart {
|
||
width: 100px;
|
||
height: 100px;
|
||
}
|
||
|
||
.gauge-info {
|
||
margin-top: -12px;
|
||
}
|
||
|
||
.gauge-value {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 13px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.info-group {
|
||
width: 95%;
|
||
}
|
||
|
||
.info-item {
|
||
padding: 4px 0;
|
||
max-width: 200px;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 11px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.right-section {
|
||
padding-right: 1px;
|
||
}
|
||
|
||
.info-section {
|
||
margin-bottom: 6px;
|
||
}
|
||
}
|
||
</style>
|