✨ 右侧上部完成
This commit is contained in:
parent
e381bb62a4
commit
065a05d9f4
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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: '操作成功',
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -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');
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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' },
|
||||
|
|
|
@ -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>) => {
|
||||
|
|
|
@ -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>) => {
|
||||
|
|
|
@ -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 });
|
||||
};
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue