diff --git a/src/components/dialog/SiteUserDataDialog.vue b/src/components/dialog/SiteUserDataDialog.vue index 4a136664..375229f9 100644 --- a/src/components/dialog/SiteUserDataDialog.vue +++ b/src/components/dialog/SiteUserDataDialog.vue @@ -5,6 +5,7 @@ import { useDisplay, useTheme } from 'vuetify' import { formatFileSize } from '@/@core/utils/formatters' import ProgressDialog from '@/components/dialog/ProgressDialog.vue' import { useI18n } from 'vue-i18n' +import { hexToRgb } from '@layouts/utils' // 多语言支持 const { t } = useI18n() @@ -33,6 +34,25 @@ const currentTheme = controlledComputed( () => vuetifyTheme.current.value.colors, ) +const variableTheme = controlledComputed( + () => vuetifyTheme.name.value, + () => vuetifyTheme.current.value.variables, +) + +/** 将 Vuetify 主题颜色转换为 ApexCharts 使用的透明色。 */ +function toThemeRgba(color: unknown, opacity: string | number) { + const rgb = hexToRgb(String(color)) + + return rgb ? `rgba(${rgb},${opacity})` : String(color) +} + +/** 将站点统计日期格式化为本地化日期,保持横轴与提示框的时间口径一致。 */ +function formatChartDate(value: string) { + if (!value) return '' + + return new Date(value).toLocaleDateString('zh-CN') +} + // 站点数据列表 const siteDatas = ref([]) @@ -58,17 +78,24 @@ const historySeries = computed(() => { // 图形选项 const historyChartOptions = computed(() => { + const axisLabelColor = toThemeRgba(currentTheme.value['on-surface'], variableTheme.value['medium-emphasis-opacity']) + const gridColor = toThemeRgba(variableTheme.value['border-color'], variableTheme.value['border-opacity']) + const themeMode = vuetifyTheme.global.current.value.dark ? 'dark' : 'light' + return { chart: { type: 'area', parentHeightOffset: 0, toolbar: { show: false }, - animations: { enabled: true }, - background: currentTheme.value.surface, // 图表背景随应用主题切换 - foreColor: currentTheme.value.onSurface, // 图表文字随应用主题切换 - dataLabels: { + animations: { enabled: true, + easing: 'easeinout', + speed: 450, + animateGradually: { enabled: true, delay: 100 }, + dynamicAnimation: { enabled: true, speed: 350 }, }, + background: 'transparent', + foreColor: axisLabelColor, zoom: { enabled: false, allowMouseWheelZoom: false, @@ -76,70 +103,82 @@ const historyChartOptions = computed(() => { selection: { enabled: false }, }, theme: { - mode: vuetifyTheme.global.current.value.dark ? 'dark' : 'light', // 同步主题模式 + mode: themeMode, }, tooltip: { enabled: true, - tooltip: { - x: { - format: 'dd MMM yyyy', - }, + shared: true, + intersect: false, + theme: themeMode, + x: { + formatter: (value: string) => formatChartDate(value), }, - style: { - background: currentTheme.value.background, // 提示框背景色同步 - color: currentTheme.value.onBackground, // 文字颜色同步 + y: { + formatter: (value: number) => `${value.toLocaleString()} GB`, }, }, grid: { - xaxis: { - lines: { show: false }, - }, - yaxis: { - title: { - text: 'GB', - }, - lines: { show: true }, - }, + borderColor: gridColor, + strokeDashArray: 6, + xaxis: { lines: { show: false } }, + yaxis: { lines: { show: true } }, + padding: { top: -10, left: 2, right: 8, bottom: 0 }, }, stroke: { - width: 3, - lineCap: 'butt', + width: [3, 3], + lineCap: 'round', curve: 'smooth', }, colors: [currentTheme.value.success, currentTheme.value.warning], + legend: { show: false }, markers: { size: 0, - style: 'hollow', + strokeWidth: 2, + strokeColors: currentTheme.value.surface, + hover: { size: 6, sizeOffset: 2 }, }, + dataLabels: { enabled: false }, xaxis: { type: 'category', categories: siteDatas.value.map(item => item.updated_day), labels: { show: true, - formatter: function (val: string) { - return new Date(val).toLocaleDateString('zh-CN') - }, + formatter: (val: string) => formatChartDate(val), + style: { colors: axisLabelColor, fontSize: '10px' }, }, + axisTicks: { show: false }, + axisBorder: { show: false }, + crosshairs: { stroke: { color: currentTheme.value.success, opacity: 0.2, dashArray: 4 } }, }, yaxis: { title: { text: 'GB', + style: { color: axisLabelColor, fontSize: '10px', fontWeight: 500 }, }, labels: { formatter: function (val: number) { return val.toLocaleString() }, + style: { colors: axisLabelColor, fontSize: '10px' }, }, }, fill: { type: 'gradient', gradient: { - shadeIntensity: 1, - opacityFrom: 0.5, - opacityTo: 0.7, - stops: [0, 100], + shade: 'light', + type: 'vertical', + shadeIntensity: 0.28, + opacityFrom: 0.38, + opacityTo: 0.04, + stops: [0, 90, 100], }, }, + noData: { + text: t('dialog.siteUserData.noData'), + align: 'center', + verticalAlign: 'middle', + style: { color: axisLabelColor, fontSize: '13px' }, + }, } }) @@ -157,14 +196,23 @@ const seedingSeries = computed(() => { // 做种分布图形选项 const seedingChartOptions = computed(() => { + const axisLabelColor = toThemeRgba(currentTheme.value['on-surface'], variableTheme.value['medium-emphasis-opacity']) + const gridColor = toThemeRgba(variableTheme.value['border-color'], variableTheme.value['border-opacity']) + const themeMode = vuetifyTheme.global.current.value.dark ? 'dark' : 'light' + return { chart: { type: 'scatter', parentHeightOffset: 0, toolbar: { show: false }, - animations: { enabled: true }, - background: currentTheme.value.surface, // 图表背景随应用主题切换 - foreColor: currentTheme.value.onSurface, // 图表文字随应用主题切换 + animations: { + enabled: true, + easing: 'easeinout', + speed: 420, + dynamicAnimation: { speed: 320 }, + }, + background: 'transparent', + foreColor: axisLabelColor, zoom: { enabled: false, allowMouseWheelZoom: false, @@ -172,29 +220,36 @@ const seedingChartOptions = computed(() => { selection: { enabled: false }, }, theme: { - mode: vuetifyTheme.global.current.value.dark ? 'dark' : 'light', // 同步主题模式 + mode: themeMode, }, tooltip: { enabled: true, + theme: themeMode, + intersect: true, x: { formatter: function (val: number) { - return t('dialog.siteUserData.countTitle') + val.toLocaleString() + return `${t('dialog.siteUserData.countTitle')}${val.toLocaleString()}` }, }, - style: { - background: currentTheme.value.background, // 提示框背景色同步 - color: currentTheme.value.onBackground, // 文字颜色同步 + y: { + formatter: (val: number) => `${val.toLocaleString()} GB`, }, }, grid: { - xaxis: { - lines: { show: true }, - }, - yaxis: { - lines: { show: true }, - }, + borderColor: gridColor, + strokeDashArray: 6, + xaxis: { lines: { show: true } }, + yaxis: { lines: { show: true } }, + padding: { top: -4, left: 4, right: 12, bottom: 4 }, }, colors: [currentTheme.value.primary], + markers: { + size: 7, + strokeWidth: 2, + strokeColors: currentTheme.value.surface, + hover: { size: 9, sizeOffset: 2 }, + }, + dataLabels: { enabled: false }, xaxis: { type: 'numeric', labels: { @@ -202,22 +257,34 @@ const seedingChartOptions = computed(() => { formatter: function (val: number) { return Math.round(val).toLocaleString() }, + style: { colors: axisLabelColor, fontSize: '10px' }, }, title: { text: t('dialog.siteUserData.countTitle'), + style: { color: axisLabelColor, fontSize: '10px', fontWeight: 500 }, }, tickAmount: 10, + axisTicks: { show: false }, + axisBorder: { show: false }, }, yaxis: { title: { text: 'GB', + style: { color: axisLabelColor, fontSize: '10px', fontWeight: 500 }, }, labels: { formatter: function (val: number) { return val.toLocaleString() + ' GB' }, + style: { colors: axisLabelColor, fontSize: '10px' }, }, }, + noData: { + text: t('dialog.siteUserData.noData'), + align: 'center', + verticalAlign: 'middle', + style: { color: axisLabelColor, fontSize: '13px' }, + }, } }) @@ -358,10 +425,10 @@ onBeforeMount(() => { - + - - + +
@@ -378,8 +445,8 @@ onBeforeMount(() => { - - + +
@@ -399,8 +466,8 @@ onBeforeMount(() => { - - + +
@@ -420,8 +487,8 @@ onBeforeMount(() => { - - + +
@@ -441,8 +508,8 @@ onBeforeMount(() => { - - + +
@@ -462,8 +529,8 @@ onBeforeMount(() => { - - + +
@@ -483,8 +550,8 @@ onBeforeMount(() => { - - + +
@@ -504,8 +571,8 @@ onBeforeMount(() => { - - + +
@@ -522,20 +589,55 @@ onBeforeMount(() => { - - - - - + + + + + + {{ t('dialog.siteUserData.trafficHistory') }} + + + +
+ +
+
+ {{ historySeries[0].name }} + {{ historySeries[1].name }} +
-
- - - - - + + + + + {{ t('dialog.siteUserData.seedingDistribution') }} + + + +
+ +
+
+ {{ seedingSeries[0].name }} +
@@ -546,3 +648,196 @@ onBeforeMount(() => { + +