右侧上部完成

This commit is contained in:
bunny 2025-05-24 19:31:33 +08:00
parent e381bb62a4
commit 065a05d9f4
10 changed files with 359 additions and 21 deletions

View File

@ -94,20 +94,54 @@ export const logOutputSize = (): string => {
return `${size.toFixed(2)} ${units[index]}`;
}
// 计算文件夹字节大小
function getFolderSize(folderPath: string) {
/**
*
* @param folderPath
* @param currentDepth 使
* @returns
*/
function getFolderSize(folderPath: string, currentDepth: number = 0): number {
// 公共常量定义
const EXCLUDED_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'];
const MAX_DEPTH = 10; // 最大递归深度限制
// 安全检查
if (!fs.existsSync(folderPath)) {
throw new Error(`文件夹不存在: ${folderPath}`);
}
if (currentDepth > MAX_DEPTH) {
console.warn(`达到最大递归深度 ${MAX_DEPTH},停止遍历: ${folderPath}`);
return 0;
}
let size = 0;
fs.readdirSync(folderPath).forEach((fileName: string) => {
const filePath = path.join(folderPath, fileName);
const stats = fs.statSync(filePath);
try {
const files = fs.readdirSync(folderPath);
if (stats.isFile()) {
size += stats.size;
} else if (stats.isDirectory()) {
size += getFolderSize(filePath);
for (const fileName of files) {
const filePath = path.join(folderPath, fileName);
try {
const stats = fs.statSync(filePath);
if (stats.isFile()) {
// 检查文件扩展名是否在排除列表中
const ext = path.extname(fileName).toLowerCase();
if (!EXCLUDED_FILE_EXTENSIONS.includes(ext)) {
size += stats.size;
}
} else if (stats.isDirectory()) {
size += getFolderSize(filePath, currentDepth + 1);
}
} catch (error) {
console.error(`无法访问文件: ${filePath}`, error);
}
}
});
} catch (error) {
console.error(`无法读取目录: ${folderPath}`, error);
}
return size;
}

View File

@ -71,6 +71,7 @@ export default defineFakeRoute([
message: '操作成功',
}),
},
// 销售设备数量区域占比
{
url: `${BASE_URL}/region-sales-ratio`,
method: 'GET',
@ -101,4 +102,18 @@ export default defineFakeRoute([
message: '操作成功',
}),
},
{
url: `${BASE_URL}/data-ratio`,
method: 'GET',
response: () => ({
code: 200,
data: Array(6)
.fill(0)
.map((_, index) => ({
name: `数据-${index + 1}`,
value: randomNumber(),
})),
message: '操作成功',
}),
},
]);

View File

@ -24,3 +24,8 @@ export const getDataShow = () => {
export const getRegionSalesRatio = () => {
return request.get('data-analyse/region-sales-ratio');
};
/* 数据占有率 */
export const getDataRatio = () => {
return request.get('data-analyse/data-ratio');
};

View File

@ -3,6 +3,7 @@ import { defineStore } from 'pinia';
import {
getBrandsDistribution,
getCompanySalesDistribution,
getDataRatio,
getDataShow,
getDeviceSalesStats,
getRegionSalesRatio,
@ -25,6 +26,8 @@ export const useDataAnalyseStore = defineStore('dataAnalyseStore', {
dataShow: [],
// 销售设备数量区域占比
regionSalesRatio: [],
// 数据占有率
dataRatio: [],
}),
actions: {
/* 销售设备总量 */
@ -51,6 +54,11 @@ export const useDataAnalyseStore = defineStore('dataAnalyseStore', {
async fetchRegionSalesRatio() {
this.regionSalesRatio = await getRegionSalesRatio();
},
/* 数据占有率 */
async fetchDataRatio() {
this.dataRatio = await getDataRatio();
},
},
});

View File

@ -1,4 +1,5 @@
import { useDebounceFn, useEventListener } from '@vueuse/core';
import type { EChartsType } from 'echarts';
import echarts from '@/plugins/echarts';
@ -38,3 +39,18 @@ export const graphicLinearGradient = (
]
);
};
export const resetSelect = (myChart: EChartsType) => {
myChart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
});
};
export const selectSector = (myChart: EChartsType, dataIndex: number) => {
myChart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex,
});
};

View File

@ -1,3 +1,4 @@
import type { EChartsOption } from 'echarts';
import type { Ref } from 'vue';
import echarts from '@/plugins/echarts';
@ -5,7 +6,7 @@ import { debounceChart } from '@/utils/chart';
let myChart = null;
const option = {
const option: EChartsOption = {
grid: {
left: '4%',
right: '4%',
@ -21,9 +22,9 @@ const option = {
},
tooltip: { trigger: 'axis' },
legend: {
show: false,
data: ['增加值'],
icon: 'rich',
show: true,
itemWidth: 18,
itemHeight: 2,
textStyle: { color: '#AFBDD1', fontSize: '12px' },

View File

@ -1,3 +1,4 @@
import type { EChartsOption } from 'echarts';
import type { Ref } from 'vue';
import echarts from '@/plugins/echarts';
@ -5,7 +6,101 @@ import { debounceChart } from '@/utils/chart';
let myChart = null;
const option = {};
const option: EChartsOption = {
grid: {
left: '4%',
right: '4%',
bottom: '19%',
top: '20%',
containLabel: true,
},
title: {
text: '单位:(万元)',
textStyle: { color: '#83A2C0FF', fontSize: 12 },
top: '4%',
left: '2%',
},
tooltip: { trigger: 'axis' },
legend: {
show: false,
data: ['增加值'],
icon: 'rich',
itemWidth: 18,
itemHeight: 2,
textStyle: { color: '#AFBDD1', fontSize: '12px' },
top: 8,
right: 10,
itemGap: 34,
},
xAxis: {
data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
type: 'category',
boundaryGap: false,
axisLine: {
symbol: 'none',
lineStyle: {
color: '#50637A',
},
},
axisTick: { show: false },
axisLabel: {
interval: 0,
color: '#6071A9',
fontSize: 12,
padding: [10, 0, 0, 0],
},
},
yAxis: {
type: 'value',
axisLabel: {
color: '#6071A9',
fontSize: 12,
padding: [0, 10, 0, 0],
},
splitLine: {
lineStyle: {
color: '#50637A',
type: 'dashed',
},
},
},
series: [
{
name: '增加值',
data: [],
type: 'line',
// smooth: true,
color: '#00F7FF',
lineStyle: {
width: 2,
},
areaStyle: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(0, 247, 255, .6)',
},
{
offset: 0.8,
color: 'rgba(0, 247, 255, .2)',
},
],
false
),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10,
},
symbol: 'circle',
symbolSize: 6,
},
],
};
/** 渲染图表 */
export const renderBodyChart = (element: Ref<HTMLDivElement>) => {

View File

@ -13,7 +13,29 @@ const data = [
{ name: '朱八', value: 10086 },
];
const option = {};
const option = {
title: {
text: '34467',
subtext: '历史预警数',
left: 'center',
top: 'center',
textStyle: { color: '#fff', fontSize: 34 },
subtextStyle: { color: '#fff', fontSize: 16 },
},
grid: { containLabel: false },
polar: { radius: [60, '100%'] },
angleAxis: { show: false, startAngle: 90 },
radiusAxis: { show: false, type: 'category' },
tooltip: {},
series: {
type: 'bar',
data: [],
barWidth: 20,
barGap: 0,
coordinateSystem: 'polar',
label: { show: true, position: 'middle' },
},
};
/** 渲染图表 */
export const renderFooterChart = (element: Ref<HTMLDivElement>) => {

View File

@ -0,0 +1,120 @@
import { useIntervalFn } from '@vueuse/core';
import type { EChartsOption } from 'echarts';
import { type Ref, ref } from 'vue';
import echarts from '@/plugins/echarts';
import { debounceChart, resetSelect, selectSector } from '@/utils/chart';
const totalIndex = ref<number>(0);
const currentIndex = ref<number>(0);
let myChart = null;
const option: EChartsOption = {
tooltip: { position: 'right', trigger: 'item' },
legend: {
orient: 'horizontal', // 垂直排列
right: 0, // 距离右侧10px
left: 240,
top: 'center', // 垂直居中
align: 'left', // 文本左对齐
icon: 'circle', // 圆形图标
itemGap: 50, // 图例项间隔
itemWidth: 10, // 图例标记宽度
itemHeight: 10, // 图例标记高度
textStyle: { color: '#fff', fontSize: 14 },
formatter(name) {
const series = myChart.getOption().series;
// 获取对应系列的数据
const seriesData = series[0].data;
const dataIndex = series[0].data.findIndex((item) => item.name === name);
const value = seriesData[dataIndex]?.value;
return `${name}: ${value}%`;
},
},
grid: {
left: '0%', // 图表左侧不留白
right: '0%', // 为图例留出25%空间
top: '0%',
bottom: '0%',
containLabel: false,
},
series: [
{
name: '数据占有率',
type: 'pie',
radius: ['54%', '70%'],
center: ['24%', '50%'],
avoidLabelOverlap: false,
padAngle: 4,
itemStyle: { borderRadius: 4 },
label: { show: false, position: 'center' },
emphasis: {
label: {
show: true,
color: '#fff',
fontSize: 29,
fontWeight: 'lighter',
formatter: ({ value }) => {
return `${value}%\n总占比`;
},
},
},
labelLine: { show: false },
data: [],
},
],
visualMap: {
show: false,
showLabel: false,
type: 'piecewise',
pieces: [
{ min: 0, max: 20, label: '低', color: '#FF6363' },
{ min: 20, max: 30, label: '低中', color: '#FFCC00' },
{ min: 30, max: 50, label: '中', color: '#00FFFF' },
{ min: 50, max: 70, label: '中高', color: '#00CCFF' },
{ min: 70, max: 100, label: '高', color: '#0096FF' },
],
seriesIndex: 0,
},
};
/** 渲染图表 */
export const renderTopChart = (element: Ref<HTMLDivElement>) => {
myChart = echarts.init(element.value, null, {
renderer: 'canvas',
devicePixelRatio: window.devicePixelRatio,
});
debounceChart(myChart);
myChart.setOption(option);
// 轮播数据
const { pause, resume } = useIntervalFn(() => {
currentIndex.value = currentIndex.value >= totalIndex.value ? 0 : currentIndex.value;
resetSelect(myChart);
selectSector(myChart, currentIndex.value);
currentIndex.value++;
}, 1000);
// 鼠标移入移出暂停动画
element.value.addEventListener('mouseenter', () => {
resetSelect(myChart);
pause();
});
element.value.addEventListener('mouseleave', () => {
resume();
});
};
/** 更新图表数据 */
export const updateTopChart = (data: any) => {
totalIndex.value = data.length;
const series = myChart.getOption().series;
series[0].data = data;
myChart.setOption({ series });
};

View File

@ -1,23 +1,40 @@
<script lang="ts" setup>
import { useIntervalFn } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { onMounted, ref } from 'vue';
import { useDataAnalyseHook } from '@/store/modules/dataAnalyse';
import { renderBodyChart } from '@/views/data-analyse/charts/right-body';
import { renderFooterChart } from '@/views/data-analyse/charts/right-footer';
import { renderTopChart, updateTopChart } from '@/views/data-analyse/charts/right-top';
import PanelTitle from '@/views/data-analyse/components/PanelTitle.vue';
const topChartRef = ref();
const bodyChartRef = ref();
const footerChartRef = ref();
const footerChart = () => {
const dataAnalyseHook = useDataAnalyseHook();
const { dataRatio } = storeToRefs(dataAnalyseHook);
/* 渲染图表 */
const renderChart = () => {
renderTopChart(topChartRef);
renderBodyChart(bodyChartRef);
renderFooterChart(footerChartRef);
};
const bodyChartRef = ref();
const bodyChart = () => {
renderBodyChart(bodyChartRef);
const initAppData = async () => {
await dataAnalyseHook.fetchDataRatio();
updateTopChart(dataRatio.value);
};
onMounted(() => {
bodyChart();
footerChart();
renderChart();
initAppData();
useIntervalFn(() => {
initAppData();
}, 10000);
});
</script>
@ -25,7 +42,7 @@ onMounted(() => {
<div class="data-analyse-right">
<header class="data-analyse-right__header">
<PanelTitle title="数据占有率" />
<div ref="bodyChartRef" class="data-analyse-right-chart" />
<div ref="topChartRef" class="data-analyse-right__header-chart" />
</header>
<main class="data-analyse-right__body">
@ -50,6 +67,11 @@ onMounted(() => {
margin: 10px 0 0 0;
width: 100%;
height: 305px;
&-chart {
width: 100%;
height: 100%;
}
}
&__body {