From a2f4e1c31fcd846628d26b091b1068a02d380113 Mon Sep 17 00:00:00 2001 From: gaoxq <376340421@qq.com> Date: Wed, 10 Dec 2025 23:27:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=A2=84=E8=AD=A6=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dataReport/erp/components/ChartLine.vue | 292 +++++++++++------- 1 file changed, 178 insertions(+), 114 deletions(-) diff --git a/web-vue/packages/biz/views/biz/dataReport/erp/components/ChartLine.vue b/web-vue/packages/biz/views/biz/dataReport/erp/components/ChartLine.vue index cb44fc2a..5e066208 100644 --- a/web-vue/packages/biz/views/biz/dataReport/erp/components/ChartLine.vue +++ b/web-vue/packages/biz/views/biz/dataReport/erp/components/ChartLine.vue @@ -18,9 +18,9 @@ import { Card } from 'ant-design-vue'; import { BasicForm, FormSchema } from '@jeesite/core/components/Form'; import { useMessage } from '@jeesite/core/hooks/web/useMessage'; import { ErpSummaryAll, erpSummaryAllListAll } from '@jeesite/erp/api/erp/summaryAll'; - import * as echarts from 'echarts'; +// 表单默认值 const defaultFormValues = { ctype: '1', fcycle: 'M' @@ -67,18 +67,31 @@ const chartDom = ref(null); let myChart: echarts.ECharts | null = null; const { createMessage } = useMessage(); -// 保留2位小数的工具函数 -const formatNumber = (num: number | undefined, decimal = 2): number => { - if (num === undefined || isNaN(num)) return 0; - return Number(num.toFixed(decimal)); +// ========== 核心工具函数 ========== +// 保留2位小数(增强容错) +const formatNumber = (num: number | string | undefined, decimal = 2): number => { + const parsed = Number(num); + if (isNaN(parsed)) return 0; + return Number(parsed.toFixed(decimal)); }; -// 转换为万元(保留2位小数) -const toTenThousandYuan = (num: number | undefined): number => { - return formatNumber((num || 0) / 10000); +// 元转万元(仅用于柱状图/Y轴展示) +const toTenThousandYuan = (num: number | string | undefined): number => { + const rawNum = formatNumber(num); + return formatNumber(rawNum / 10000); }; -// 柱状图标签配置(显示万元) +// 千分位格式化(Tooltip显示原始元时使用) +const formatWithThousandsSeparator = (num: number | string | undefined): string => { + const parsed = Number(num); + if (isNaN(parsed)) return '0.00'; + return parsed.toLocaleString('zh-CN', { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); +}; + +// ========== 柱状图标签仅显示数值(无单位) ========== const barLabelConfig = { show: true, position: 'top', @@ -88,10 +101,10 @@ const barLabelConfig = { color: '#333', fontWeight: '500' }, - formatter: (params: any) => `${formatNumber(params.value).toFixed(2)} 万元` + formatter: (params: any) => `${formatNumber(params.value).toFixed(2)}` }; -// 折线图标签配置(保留2位小数) +// 折线图标签配置:百分比格式(保留单位) const lineLabelConfig = { show: true, position: 'top', @@ -104,16 +117,34 @@ const lineLabelConfig = { formatter: (params: any) => `${formatNumber(params.value).toFixed(2)} %` }; +// 表单提交处理 const handleFormSubmit = (values: Record) => { currentFormValues.value = { ...values }; fetchList(currentFormValues.value); }; +// 日期格式化(统一格式) +const formatDate = (date: string | undefined): string => { + if (!date) return ''; + return date.replace(/\//g, '-').trim(); +}; + // 获取数据列表 const fetchList = async (params: Record) => { try { const result = await erpSummaryAllListAll(params); - listSummary.value = result || []; + console.log('接口原始数据(元):', result); + + // 过滤空数据 + 统一日期格式 + 排序 + const validData = (result || []) + .filter(item => item.cdate) + .map(item => ({ + ...item, + cdate: formatDate(item.cdate) + })) + .sort((a, b) => a.cdate.localeCompare(b.cdate)); + + listSummary.value = validData; initChart(); } catch (error) { console.error('获取数据列表失败:', error); @@ -122,27 +153,27 @@ const fetchList = async (params: Record) => { } }; -// 计算Y轴极值,适配负数显示并确保0点对齐 +// 计算Y轴极值(适配万元展示层) const calculateYAxisExtent = (data: number[], isRate = false) => { - if (data.length === 0) return isRate ? [-10, 10] : [0, 100]; + if (data.length === 0) return isRate ? [-10, 10] : [0, 10]; // 万元默认范围 - // 格式化数据保留2位小数 const formattedData = data.map(num => formatNumber(num)); const min = Math.min(...formattedData); const max = Math.max(...formattedData); - // 计算内边距(百分比轴内边距更大,确保负数显示清晰) - const padding = isRate ? Math.max(Math.abs(max) * 0.2, Math.abs(min) * 0.2, 5) - : (max - min) * 0.1 || 10; + // 万元轴内边距:10% 或最小1万元 + const padding = isRate + ? Math.max(Math.abs(max) * 0.2, Math.abs(min) * 0.2, 5) + : Math.max((max - min) * 0.1, 1); let minExtent = min - padding; let maxExtent = max + padding; - // 强制包含0点,确保X轴对齐 + // 强制包含0点 if (minExtent > 0) minExtent = 0; if (maxExtent < 0) maxExtent = 0; - // 百分比轴确保正负对称(可选,提升视觉效果) + // 百分比轴正负对称 if (isRate) { const absMax = Math.max(Math.abs(minExtent), Math.abs(maxExtent)); minExtent = -absMax; @@ -152,36 +183,50 @@ const calculateYAxisExtent = (data: number[], isRate = false) => { return [minExtent, maxExtent]; }; -// 初始化图表 +// 初始化图表(核心修复:Tooltip兼容处理) const initChart = () => { if (!chartDom.value) return; - // 如果图表实例已存在,先销毁 if (myChart) { myChart.dispose(); } - // 创建新的图表实例 myChart = echarts.init(chartDom.value); - // 处理空数据 - const xAxisData = listSummary.value.length - ? listSummary.value.map(item => item.cdate || '') - : []; + // 提取X轴类目 + const xAxisData = listSummary.value.map(item => item.cdate).filter(Boolean); - // 提取金额数据(转换为万元)用于计算Y轴范围 - const thisValueData = listSummary.value.map(item => toTenThousandYuan(item.thisValue)); - const prevValueData = listSummary.value.map(item => toTenThousandYuan(item.prevValue)); - const amountData = [...thisValueData, ...prevValueData]; + // ========== 核心分离:展示数据(万元)和原始数据(元) ========== + // 1. 展示层数据(万元):用于柱状图/Y轴/标签 + const thisValueShow = xAxisData.map(date => { + const item = listSummary.value.find(i => i.cdate === date); + return toTenThousandYuan(item?.thisValue); + }); + const prevValueShow = xAxisData.map(date => { + const item = listSummary.value.find(i => i.cdate === date); + return toTenThousandYuan(item?.prevValue); + }); + + // 2. 原始数据(元):直接从接口数据提取,不做任何换算,用于Tooltip + const rawDataMap = new Map(); + listSummary.value.forEach(item => { + rawDataMap.set(item.cdate, { + thisValue: item.thisValue, // 接口原始值(元) + prevValue: item.prevValue, // 接口原始值(元) + momRate: item.momRate // 接口原始值(%) + }); + }); + + // 环比数据(不变) + const rateData = xAxisData.map(date => { + const item = listSummary.value.find(i => i.cdate === date); + return formatNumber(item?.momRate); + }); + + // 计算Y轴范围(基于万元展示数据) + const amountData = [...thisValueShow, ...prevValueShow]; const [amountMin, amountMax] = calculateYAxisExtent(amountData); - - // 提取环比数据用于计算百分比轴范围(保留2位小数) - const rateData = listSummary.value.map(item => formatNumber(item.momRate)); const [rateMin, rateMax] = calculateYAxisExtent(rateData, true); - - // 原始金额数据(用于tooltip显示元) - const rawThisValueData = listSummary.value.map(item => formatNumber(item.thisValue)); - const rawPrevValueData = listSummary.value.map(item => formatNumber(item.prevValue)); const option = { title: { @@ -192,23 +237,45 @@ const initChart = () => { trigger: 'axis', axisPointer: { type: 'shadow' }, textStyle: { fontSize: 12 }, + padding: 10, + // ========== 核心修复:兼容params长度不固定 + 空值容错 ========== formatter: (params: any[]) => { - let res = params[0].axisValue; - params.forEach((param, index) => { - // 区分金额和环比:金额显示原始元,环比显示百分比 - let value = param.value; - let unit = ' 元'; + if (!params || params.length === 0) return '暂无数据'; + + const date = params[0]?.axisValue || ''; + const rawData = rawDataMap.get(date) || { thisValue: 0, prevValue: 0, momRate: 0 }; + + let res = `
${date || '未知日期'}
`; + + // 动态遍历params,不依赖固定索引 + params.forEach((param) => { + // 跳过无效参数 + if (!param || !param.seriesName) return; - if (param.seriesName === '环比') { - value = formatNumber(param.value); - unit = ' %'; - } else { - // 还原为原始元数值(图表展示的是万元,需要乘10000) - value = formatNumber(param.value * 10000); + let displayText = ''; + const seriesName = param.seriesName; + + // 根据系列名称匹配原始数据,避免依赖param.value + if (seriesName === '本期金额') { + displayText = `${param.marker || ''}本期金额:${ + formatWithThousandsSeparator(rawData.thisValue) + } 元`; + } else if (seriesName === '上期金额') { + displayText = `${param.marker || ''}上期金额:${ + formatWithThousandsSeparator(rawData.prevValue) + } 元`; + } else if (seriesName === '环比') { + displayText = `${param.marker || ''}环比:${ + formatNumber(rawData.momRate).toFixed(2) + } %`; } - res += `
${param.marker}${param.seriesName}:${value.toFixed(2)}${unit}`; + // 只有有效文本才添加到Tooltip + if (displayText) { + res += `
${displayText}
`; + } }); + return res; } }, @@ -219,7 +286,7 @@ const initChart = () => { }, grid: { left: '8%', - right: '12%', // 增加右侧边距适配百分比轴 + right: '12%', bottom: '10%', top: '20%', containLabel: true @@ -228,34 +295,32 @@ const initChart = () => { type: 'category', data: xAxisData, axisLabel: { fontSize: 12 }, - // 确保X轴在0点位置 - axisLine: { - onZero: true - } + axisLine: { onZero: true } }, yAxis: [ { type: 'value', - name: '交易金额(万元)', // Y轴名称改为万元 + name: '交易金额(万元)', // Y轴仍保留万元单位 + nameTextStyle: { fontSize: 12 }, axisLabel: { - fontSize: 12, - formatter: (value: number) => formatNumber(value).toFixed(2) + fontSize: 11, + formatter: (value: number) => formatNumber(value).toFixed(2) // 万元数值 }, min: amountMin, max: amountMax, axisLine: { onZero: true }, - splitLine: { - lineStyle: { color: '#e8e8e8' } - }, + splitLine: { lineStyle: { color: '#e8e8e8' } }, axisTick: { alignWithLabel: true }, - splitNumber: 4 // 控制刻度数量 + splitNumber: 5, + scale: true }, { type: 'value', name: '环比(%)', + nameTextStyle: { fontSize: 12 }, axisLabel: { - fontSize: 12, - formatter: (value: number) => formatNumber(value).toFixed(2) + ' %' + fontSize: 11, + formatter: (value: number) => `${formatNumber(value).toFixed(2)} %` }, min: rateMin, max: rateMax, @@ -267,47 +332,60 @@ const initChart = () => { splitNumber: 4 } ], - // 空数据提示 noDataLoadingOption: { text: '暂无数据', textStyle: { fontSize: 14, color: '#999' }, effect: 'bubble', - effectOption: { - effect: { - n: 0 - } - } + effectOption: { effect: { n: 0 } } }, series: [ { name: '本期金额', type: 'bar', - data: thisValueData, // 展示万元数据 + data: thisValueShow, // 万元展示数据 itemStyle: { - color: '#1890ff', + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#1890ff' }, + { offset: 1, color: '#096dd9' } + ]), borderRadius: [8, 8, 0, 0] }, - barWidth: 25, + barWidth: 25, barBorderRadius: [8, 8, 0, 0], - label: barLabelConfig, + label: barLabelConfig, // 仅显示数值的标签 yAxisIndex: 0, - // 存储原始元数据用于tooltip - rawData: rawThisValueData + emphasis: { + itemStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#40a9ff' }, + { offset: 1, color: '#1890ff' } + ]) + } + } }, { name: '上期金额', type: 'bar', - data: prevValueData, // 展示万元数据 + data: prevValueShow, // 万元展示数据 itemStyle: { - color: '#52c41a', + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#52c41a' }, + { offset: 1, color: '#389e0d' } + ]), borderRadius: [8, 8, 0, 0] }, - barWidth: 25, + barWidth: 25, barBorderRadius: [8, 8, 0, 0], - label: barLabelConfig, + label: barLabelConfig, // 仅显示数值的标签 yAxisIndex: 0, - // 存储原始元数据用于tooltip - rawData: rawPrevValueData + emphasis: { + itemStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#73d13d' }, + { offset: 1, color: '#52c41a' } + ]) + } + } }, { name: '环比', @@ -316,30 +394,19 @@ const initChart = () => { smooth: true, symbol: 'circle', symbolSize: 8, - lineStyle: { - width: 3, - color: '#f5222d' - }, + lineStyle: { width: 3, color: '#f5222d' }, itemStyle: { color: '#f5222d', borderColor: '#fff', borderWidth: 2 }, label: lineLabelConfig, - emphasis: { - itemStyle: { - symbolSize: 12 - } - }, + emphasis: { itemStyle: { symbolSize: 12 } }, yAxisIndex: 1, - // 0%基准线(精确对齐X轴) markLine: { silent: true, lineStyle: { color: '#ccc', type: 'dashed', width: 1 }, - data: [{ - yAxis: 0, - lineStyle: { color: '#999', type: 'dashed' } - }] + data: [{ yAxis: 0, lineStyle: { color: '#999', type: 'dashed' } }] } } ] @@ -348,7 +415,7 @@ const initChart = () => { myChart.setOption(option); }; -// 调整图表大小 +// 调整图表大小(防抖+动画) const resizeChart = () => { if (myChart) { myChart.resize({ @@ -360,7 +427,7 @@ const resizeChart = () => { } }; -// 监听窗口大小变化(防抖处理) +// 窗口 resize 防抖处理 let resizeTimer: number; const debounceResize = () => { clearTimeout(resizeTimer); @@ -400,15 +467,12 @@ watch(listSummary, () => { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); } -/* 优化图表样式 */ -:deep(.echarts-label) { - font-family: 'Microsoft YaHei', sans-serif; -} - +/* 优化图表tooltip样式 */ :deep(.echarts-tooltip) { - border-radius: 4px; - padding: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + border-radius: 6px; + padding: 10px; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15); + border: none; } /* 优化表单样式 */ @@ -420,14 +484,14 @@ watch(listSummary, () => { width: 100%; } -/* 优化双Y轴标签样式 */ -:deep(.echarts-yaxis-label) { - font-size: 12px; - color: #666; +/* 优化柱状图标签溢出处理 */ +:deep(.echarts-label) { + font-family: 'Microsoft YaHei', sans-serif; + white-space: nowrap; } -/* 优化0%基准线样式 */ -:deep(.echarts-mark-line) { - z-index: 1; +/* 优化Y轴名称间距 */ +:deep(.echarts-yaxis-name) { + margin-right: 5px; } - + \ No newline at end of file