update echart封装、bug修复

This commit is contained in:
2026-01-28 15:51:29 +08:00
parent 9117ba6acd
commit 15bdcd9ce3
14 changed files with 148 additions and 384 deletions

View File

@@ -1,3 +1,4 @@
<!-- 封装了一些常用的筛选条件 -->
<template>
<div class="chart-container">
<EchartCard

View File

@@ -1,6 +1,5 @@
import * as echarts from 'echarts';
const BAR_WIDTH = '20px';
const myChart: any = {};
let chartDom: any;
let winId = '';
@@ -108,15 +107,6 @@ const zoomRule = [
zoomLock: false, // 允许缩放
showDetail: false,
},
// {
// type: 'inside',
// xAxisIndex: [0],
// start: 0,
// end: 2,
// zoomOnMouseWheel: false, // 鼠标滚轮缩放
// moveOnMouseMove: true, // 鼠标拖动
// moveOnMouseWheel: false, // 按住Ctrl+滚轮移动
// },
];
// 深层对象的合并
const deepMerge = (target: any, source: any) => {
@@ -140,6 +130,27 @@ export const unObserve = () => {
delete observeObj[winId];
}
};
// 合并对象
const mergeObjItem = (option: any, item: any, optionKey: string) => {
if (item) {
for (const key in item) {
option[optionKey][key] = item[key];
}
}
};
// 合并对象或者数组
const mergeObjArrItem = (option: any, item: any, optionKey: string) => {
if (item) {
if (item instanceof Array) {
for (let index = 0; index < item.length; index++) {
option[optionKey][index] = deepMerge(option[optionKey][index], item[index]);
}
} else {
option[optionKey][0] = deepMerge(option[optionKey][0], item);
}
}
};
const barChart = (
id: string,
{ series, yAxis, xAxis, grid, title, legend, tooltip, dataZoom, color }: any,
@@ -159,8 +170,8 @@ const barChart = (
borderColor: commonEchartColor[isDark ? 'dark' : 'light'].tooltipBorderColor,
},
legend: {
left: '250',
top: '25',
left: 'center',
top: '0',
show: true,
data: [],
textStyle: {
@@ -169,9 +180,9 @@ const barChart = (
},
grid: {
top: '10%',
bottom: '10%',
left: '10%',
right: '10%',
bottom: '10',
left: '5%',
right: '5%',
},
xAxis: [
{
@@ -208,68 +219,30 @@ const barChart = (
},
},
],
series: [],
dataZoom: [],
};
if (yAxis) {
for (const key in yAxis) {
option.yAxis[0][key] = yAxis[key];
}
}
if (xAxis) {
if (xAxis instanceof Array) {
for (let index = 0; index < xAxis.length; index++) {
for (const key in xAxis[index]) {
option.xAxis[index][key] = xAxis[index][key];
}
}
} else {
for (const key in xAxis) {
option.xAxis[0][key] = xAxis[key];
}
}
}
if (grid) {
for (const key in grid) {
option.grid[key] = grid[key];
}
}
if (title) {
for (const key in title) {
option.title[key] = title[key];
}
}
if (legend) {
for (const key in legend) {
option.legend[key] = legend[key];
}
}
// 融合配置
mergeObjArrItem(option, yAxis, 'yAxis');
mergeObjArrItem(option, xAxis, 'xAxis');
mergeObjItem(option, grid, 'grid');
mergeObjItem(option, title, 'title');
mergeObjItem(option, legend, 'legend');
mergeObjItem(option, tooltip, 'tooltip');
option.dataZoom = dataZoom ? zoomRule : [];
if (color) {
option.color = color;
}
if (tooltip) {
for (const key in tooltip) {
option.tooltip[key] = tooltip[key];
}
}
if (dataZoom) {
// option.dataZoom = dataZoom.map((item: any) => {
// return item;
// });
option.dataZoom = zoomRule;
}
// 配置series
const len = series?.length ?? 0;
option.series =
series?.map((item: any) => {
// 设置type
if (!Reflect.has(item, 'type')) {
item.type = 'bar';
}
// 设置柱子宽度
if (!Reflect.has(item, 'barMaxWidth')) {
item.barMaxWidth = BAR_WIDTH;
item.barMaxWidth = '20px';
}
// 1、柱子之间的距离
item.barGap = '0';
@@ -289,16 +262,6 @@ const barChart = (
if (option) {
myChart[id].setOption(option);
}
myChart[id].on('click', (data: any) => {
const param = {
value: data.value,
unit: data.seriesName,
name: data.name,
};
sessionStorage.setItem('clickBarChart', JSON.stringify(param));
});
};
const pieChart = (
id: string,
@@ -493,63 +456,35 @@ const lineChart = (
},
},
],
series: [],
series: [
{
name: '',
type: 'line',
data: [],
lineStyle: {
width: 2,
},
},
],
};
if (title) {
for (const key in title) {
option.title[key] = title[key];
}
}
if (tooltip) {
for (const key in tooltip) {
option.tooltip[key] = tooltip[key];
}
}
if (legend) {
for (const key in legend) {
option.legend[key] = legend[key];
}
}
if (grid) {
for (const key in grid) {
option.grid[key] = grid[key];
}
}
if (yAxis) {
if (yAxis instanceof Array) {
for (let index = 0; index < yAxis.length; index++) {
option.yAxis[index] = deepMerge(option.yAxis[index], yAxis[index]);
}
} else {
option.yAxis[0] = deepMerge(option.yAxis[0], yAxis);
}
}
if (xAxis) {
if (xAxis instanceof Array) {
for (let index = 0; index < xAxis.length; index++) {
option.xAxis[index] = deepMerge(option.xAxis[index], xAxis[index]);
}
} else {
option.xAxis[0] = deepMerge(option.xAxis[0], xAxis);
}
}
mergeObjArrItem(option, yAxis, 'yAxis');
mergeObjArrItem(option, xAxis, 'xAxis');
mergeObjArrItem(option, series, 'series');
mergeObjItem(option, grid, 'grid');
mergeObjItem(option, title, 'title');
mergeObjItem(option, legend, 'legend');
mergeObjItem(option, tooltip, 'tooltip');
option.dataZoom = dataZoom ? zoomRule : [];
if (color) {
option.color = color;
}
if (dataZoom) {
// option.dataZoom = dataZoom.map((item: any) => {
// return item;
// });
option.dataZoom = zoomRule;
}
option.series =
series.map((item: any) => {
if (!Reflect.has(item, 'type')) {
item.type = 'line';
}
return item;
}) ?? [];
// option.series =
// series.map((item: any) => {
// if (!Reflect.has(item, 'type')) {
// item.type = 'line';
// }
// return item;
// }) ?? [];
if (option) {
myChart[id].setOption(option);
}

View File

@@ -44,14 +44,14 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ref, watch } from 'vue';
import Chart from './taskChart.vue';
import { useClickOutside } from '@/utils/clickOutside';
const chartPopover = ref();
const { popoverVisible } = useClickOutside(chartPopover);
withDefaults(
const props = withDefaults(
defineProps<{
title: string;
barType: string;
@@ -60,6 +60,7 @@ withDefaults(
showFilterContent?: boolean; // 是否直接展示筛选条件
loading?: boolean; // 加载状态
nodata?: boolean; // 无数据状态
option?: any;
}>(),
{
title: '',
@@ -69,6 +70,7 @@ withDefaults(
showFilterContent: false,
loading: false,
nodata: false,
option: () => {},
}
);
// const emits = defineEmits(['refresh']);
@@ -84,6 +86,21 @@ const fullEchart = () => {
defineExpose({
commonChartRef,
});
// 初始化图表
const refreshChart = async () => {
if (!commonChartRef.value) return;
commonChartRef.value.disposeEchartsByKey(props.chartsId);
commonChartRef.value.option = props.option;
commonChartRef.value.initChart();
};
// 监听配置项
watch(
() => props.option,
async () => {
await refreshChart();
}
);
</script>
<style scoped lang="scss">
.container {

View File

@@ -1,12 +1,12 @@
<template>
<div class="content">
<div class="chart-box">
<div class="chart-box" v-loading="onlineLoading">
<EchartCard
title="在线用户统计"
ref="onlineUserChartRef"
:charts-id="'chart-1'"
:bar-type="'barChart'"
@refresh="changeOnlineUserFun"
:option="onlineUserOptions"
>
<template #formSlot>
<el-form :model="onlineUserFromData" label-width="auto">
@@ -32,7 +32,7 @@
ref="userLoginChartRef"
:charts-id="'chart-2'"
:bar-type="'lineChart'"
@refresh="changeUserLoginFun"
:option="ouserLoginOptions"
>
<template #formSlot>
<el-form :model="userLoginFromData" label-width="auto">
@@ -95,80 +95,53 @@ onMounted(() => {
// ********************** 每小时在线用户统计 **********************
const onlineUserChartRef = ref();
const onlineUserOptions = ref<any>();
const changeOnlineUserFun = () => {
onlineUserStatistics();
};
const onlineLoading = ref(false);
const onlineUserStatistics = async () => {
const { data } = await getHourlyOnlineStatisticsApi({ ...onlineUserFromData.value });
const xData: any = [];
const yData: any = [];
// 遍历data对象key塞入xDatavalue塞入yData
Object.keys(data).forEach((key) => {
xData.push(key + '点');
yData.push(data[key]);
});
const options = {
title: {
show: false,
},
tooltip: {
trigger: 'axis',
},
legend: {
show: false,
},
grid: {
top: '5%',
},
xAxis: {
type: 'category',
data: xData,
},
yAxis: {
type: 'value',
minInterval: 1,
},
dataZoom:
xData.length > 10
? [
{
type: 'slider',
show: true,
xAxisIndex: [0],
start: 0,
end: 100,
textStyle: {
color: 'transparent',
},
maxValueSpan: 10,
minValueSpan: 10,
moveHandleSize: 8,
height: 0,
filterMode: 'empty',
bottom: 6,
},
]
: null,
series: [
{
name: '在线用户数',
type: 'bar',
barWidth: '30%',
data: yData,
itemStyle: {
color: getThemeColor('--el-color-success'),
},
onlineLoading.value = true;
try {
const { data } = await getHourlyOnlineStatisticsApi({ ...onlineUserFromData.value });
const xData: any = [];
const yData: any = [];
// 遍历data对象key塞入xDatavalue塞入yData
Object.keys(data).forEach((key) => {
xData.push(key + '点');
yData.push(data[key]);
});
const options = {
grid: {
bottom: '45',
},
],
};
onlineUserChartRef.value.commonChartRef.disposeEchartsByKey('chart-1');
onlineUserChartRef.value.commonChartRef.option = { ...options };
onlineUserChartRef.value.commonChartRef.initChart();
xAxis: {
data: xData,
},
yAxis: {
minInterval: 1,
},
dataZoom: xData.length > 10,
series: [
{
name: '在线用户数',
data: yData,
itemStyle: {
color: getThemeColor('--el-color-success'),
},
},
],
};
onlineUserOptions.value = { ...options };
} catch {
} finally {
onlineLoading.value = false;
}
};
// ********************** 用户登录数分析 **********************
const userLoginChartRef = ref();
const ouserLoginOptions = ref<any>();
const changeUserLoginFun = () => {
userLogintStatistics();
};
@@ -185,37 +158,15 @@ const userLogintStatistics = async () => {
xData.push(item.date);
yData.push(item.loginUserCount);
});
// const xData = ['2025-12-10', '2025-12-11', '2025-12-12', '2025-12-13', '2025-12-14', '2025-12-15', '2025-12-16', '2025-12-17'];
// const yData = [120, 200, 150, 80, 70, 110, 130, 100];
const optionsLine = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
},
},
grid: {
top: '5%',
},
xAxis: {
type: 'category',
boundaryGap: 30,
data: xData,
},
yAxis: [
{
type: 'value',
minInterval: 1,
},
],
series: [
{
name: '登录用户数',
type: 'line',
stack: 'Total',
data: yData,
lineStyle: {
width: 2,
color: getThemeColor('--el-color-primary'),
},
itemStyle: {
@@ -225,34 +176,9 @@ const userLogintStatistics = async () => {
},
},
],
dataZoom:
xData.length > 5
? [
{
type: 'slider',
show: true,
xAxisIndex: [0],
start: 0,
end: 100,
textStyle: {
color: 'transparent',
},
maxValueSpan: 5,
minValueSpan: 5,
moveHandleSize: 8,
height: 0,
filterMode: 'empty',
bottom: 6,
},
]
: null,
legend: {
show: false,
},
dataZoom: xData.length > 5,
};
userLoginChartRef.value.commonChartRef.disposeEchartsByKey('chart-2');
userLoginChartRef.value.commonChartRef.option = { ...optionsLine };
userLoginChartRef.value.commonChartRef.initChart();
ouserLoginOptions.value = { ...optionsLine };
};
</script>

View File

@@ -15,7 +15,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
import commonChart from '@/components/common/echartCard/commonChart.vue';
import commonChart from '@/components/common/echartCard/commonFilterChart.vue';
import { TASK_CALCULATE_STATUS_OBJ } from '@/utils/enum/task';
import {
getCommonCompleteStatisticsApi,
@@ -88,31 +88,16 @@ const initCompleteChart = async (formData: any) => {
}
chartOption.value = {
color: props.performanceColorList,
title: {
show: false,
},
tooltip: {
trigger: 'axis',
},
legend: {
top: '0%',
left: 'center',
data: titles,
},
grid: {
top: '10%',
bottom: '50',
left: '5%',
right: '5%',
},
xAxis: {
type: 'category',
data: xData,
},
yAxis: {
type: 'value',
},
dataZoom: xData.length > 4 ? [] : null,
dataZoom: xData.length > 4,
series: seriesData,
};
}

View File

@@ -12,7 +12,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
import commonChart from '@/components/common/echartCard/commonChart.vue';
import commonChart from '@/components/common/echartCard/commonFilterChart.vue';
import { TASK_PROCESS_STATUS_OBJ } from '@/utils/enum/task';
import { getAllUserTaskCompleteStatisticsApi } from '@/api/project/node';
@@ -85,31 +85,15 @@ const initProjectTaskCompleteChart = async (formData: any) => {
chartOption.value = {
color: props.statusColorList,
title: {
show: false,
},
tooltip: {
trigger: 'axis',
},
legend: {
show: true,
top: '0%',
left: 'center',
data: legendData,
},
grid: {
top: '10%',
bottom: '50',
left: '5%',
right: '5%',
},
xAxis: {
type: 'category',
data: xData,
},
yAxis: {
type: 'value',
},
dataZoom: xData.length > 4 ? [] : null,
series: seriesData,
};

View File

@@ -24,7 +24,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
import commonChart from '@/components/common/echartCard/commonChart.vue';
import commonChart from '@/components/common/echartCard/commonFilterChart.vue';
import { getWorkstationReviewStatisticsApi } from '@/api/project/node';
const props = defineProps({
@@ -84,46 +84,14 @@ const initReviewPassedChart = async (formData: any) => {
}
chartOption.value = {
color: props.processNodeColorList,
title: {
textStyle: {
fontSize: 20,
},
subtextStyle: {
color: '#175ce5',
fontSize: 15,
fontWeight: 'bold',
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
legend: {
show: true,
top: '0%',
left: 'center',
data: ['未评审', '评审中', '已通过', '已拒绝'],
},
grid: {
top: '10%',
bottom: '50',
left: '5%',
right: '5%',
bottom: '45',
},
xAxis: [
{
type: 'category',
data: xData,
},
],
yAxis: [
{
type: 'value',
},
],
dataZoom: xData.length > 4 ? [] : null,
xAxis: [{ data: xData }],
dataZoom: xData.length > 4,
series: sortObjectArray(seriesData, 'nameValue'),
};
}

View File

@@ -15,7 +15,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
import commonChart from '@/components/common/echartCard/commonChart.vue';
import commonChart from '@/components/common/echartCard/commonFilterChart.vue';
import { TASK_PROCESS_STATUS_OBJ } from '@/utils/enum/task';
import {
getCommonCompleteStatisticsApi,
@@ -89,30 +89,15 @@ const initTaskCompleteChart = async (formData: any) => {
}
chartOption.value = {
color: props.statusColorList,
title: {
show: false,
},
tooltip: {
trigger: 'axis',
},
legend: {
top: '0%',
left: 'center',
data: titles,
},
grid: {
top: '10%',
bottom: '50',
left: '5%',
right: '5%',
},
xAxis: {
type: 'category',
data: xData,
},
yAxis: {
type: 'value',
},
dataZoom: xData.length > 4 ? [] : null,
series: seriesData,
};

View File

@@ -11,7 +11,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
import commonChart from '@/components/common/echartCard/commonChart.vue';
import commonChart from '@/components/common/echartCard/commonFilterChart.vue';
import { getUserGroupDifficultyStatisticsApi } from '@/api/project/node';
import { useDict } from '@/utils/useDict';
@@ -67,40 +67,15 @@ const initUserDifficultyCoefficientChart = async (formData: any) => {
}
const option = {
color: props.difficultyCountColorList,
title: {
textStyle: {
fontSize: 20,
},
subtextStyle: {
color: '#175ce5',
fontSize: 15,
fontWeight: 'bold',
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
legend: {
top: '0%',
left: 'center',
data: titles,
},
grid: {
top: '10%',
bottom: '50',
left: '5%',
right: '5%',
},
xAxis: {
type: 'category',
data: xData,
},
yAxis: {
type: 'value',
},
dataZoom: xData.length > 4 ? [] : null,
series: sortObjectArray(seriesList, 'coefficient'),
};

View File

@@ -11,7 +11,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
import commonChart from '@/components/common/echartCard/commonChart.vue';
import commonChart from '@/components/common/echartCard/commonFilterChart.vue';
import { getUserGroupProjectStatisticsApi } from '@/api/project/node';
import { getThemeColor } from '@/utils/theme';
@@ -40,13 +40,9 @@ const initUserProjectStatistics = async (formData: any) => {
const { xData, yData } = await getUserGroupProjectStatistics(formData);
chartOption.value = {
grid: {
top: '10%',
bottom: '50',
left: '5%',
right: '5%',
},
xAxis: {
type: 'category',
data: xData,
},
dataZoom: xData.length > 4 ? [] : null,

View File

@@ -11,7 +11,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
import commonChart from '@/components/common/echartCard/commonChart.vue';
import commonChart from '@/components/common/echartCard/commonFilterChart.vue';
import { TASK_PROCESS_STATUS_OBJ } from '@/utils/enum/task';
import { getUserGroupTaskCompleteStatisticsApi } from '@/api/project/node';
@@ -82,31 +82,15 @@ const initUserTaskCompleteChart = async (formData: any) => {
const { xData, seriesData, legendData } = await getUserGroupTaskCompleteStatistics(formData);
chartOption.value = {
color: props.statusColorList,
title: {
show: false,
},
tooltip: {
trigger: 'axis',
},
legend: {
show: true,
top: '0%',
left: 'center',
data: legendData,
},
grid: {
top: '10%',
bottom: '50',
left: '5%',
right: '5%',
},
xAxis: {
type: 'category',
data: xData,
},
yAxis: {
type: 'value',
},
dataZoom: xData.length > 4 ? [] : null,
series: seriesData,
};

View File

@@ -82,7 +82,7 @@
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import EchartCard from '@/components/common/echartCard/index.vue';
import commonChart from '@/components/common/echartCard/commonChart.vue';
import commonChart from '@/components/common/echartCard/commonFilterChart.vue';
import {
getTaskCompleteStatisticsByDisciplineApi,
getChildrenNodeListApi,

View File

@@ -20,7 +20,13 @@
@click="syncDemandList"
>同步</el-button
>
<el-button icon="plus" type="primary" @click="visibleDialog(true)">创建需求</el-button>
<el-button
icon="plus"
type="primary"
v-permission="'demand_add_demand'"
@click="visibleDialog(true)"
>创建需求</el-button
>
</template>
</demandTable>
<Dialog

View File

@@ -45,6 +45,7 @@
type="datetime"
:clearable="false"
placeholder="请选择开始时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
@change="filterWorkLoadFun"
/>
@@ -54,7 +55,8 @@
v-model="filterFprmData.endTime"
type="datetime"
:clearable="false"
placeholder="请选择开始时间"
placeholder="请选择结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
@change="filterWorkLoadFun"
/>