Files
yudao-dev/src/views/home/unitPriceAnalysisComponents/pieChart.vue

258 lines
8.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- 动态绑定 ref使用 props 中的 chartRef而非硬编码 -->
<div :ref="chartRef" id="coreLineChart" style="height: 100%; width: 100%;"></div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'Container',
props: {
// 1. 重命名 propsref → chartRef避免关键字冲突同时定义类型和默认值
chartRef: {
type: String,
default: 'pieChartRef' // 默认ref名称
},
chartData: {
type: Array, // 明确类型为数组(接收[{name, rate, value}, ...]
default: () => [] // 默认空数组,避免报错
},
chartTitle: { // 新增:动态标题(可选,灵活配置)
type: String,
default: '客户销量分析'
},
unit: { // 新增:动态单位(可选)
type: String,
default: '万m²'
}
},
data() {
return {
// 提取公共颜色配置,复用样式
customColors: [
'rgba(39, 96, 255, 1)',
'rgba(40, 138, 255, 1)',
'rgba(118, 218, 190, 1)',
'rgba(255, 206, 106, 1)',
'rgba(255, 123, 123, 1)', // 额外补充颜色,适配更多数据项
'rgba(153, 102, 255, 1)'
],
myChart: null, // 保存图表实例,便于销毁和缩放
resizeHandler: null, // 窗口resize事件处理器
isMounted: false // 图表挂载标志,避免过早执行
};
},
computed: {},
watch: {
// 监听 chartData 变化,只要数据变了,就更新图表
chartData: {
handler() {
if (!this.isMounted) return; // 挂载前保护
this.initData(); // 直接调用更新
},
deep: true // 深度监听数组/对象变化
}
},
mounted() {
this.isMounted = true;
this.$nextTick(() => {
this.initData(); // 初始化图表
});
// 注册窗口resize事件使用稳定的引用以便后续移除
this.resizeHandler = () => {
if (this.myChart) {
this.myChart.resize();
}
};
window.addEventListener('resize', this.resizeHandler);
},
beforeDestroy() {
// 移除窗口resize事件监听器
if (this.resizeHandler) {
window.removeEventListener('resize', this.resizeHandler);
this.resizeHandler = null;
}
// 销毁图表,避免内存泄漏
if (this.myChart) {
this.myChart.dispose();
this.myChart = null;
}
},
methods: {
initData() {
console.log('动态饼图数据:', this.chartData);
const chartDom = this.$refs[this.chartRef];
if (!chartDom) {
if (process.env.NODE_ENV === 'development') console.warn(`图表容器未找到!请确认父组件传递的 chartRef 为 "${this.chartRef}"`);
return;
}
// 销毁旧实例,避免内存泄漏
if (this.myChart) {
this.myChart.dispose();
}
// 初始化新实例
this.myChart = echarts.init(chartDom);
// ========== 核心:动态生成饼图数据 ==========
const dynamicPieData = this.chartData.map((item, index) => {
const color = this.customColors[index % this.customColors.length]; // 循环取色,适配多数据项
return {
name: item.name, // 动态名称产品1/产品2/客户1等
value: item.value || 0, // 动态数值
rate: item.rate || 0, // 动态占比(备用)
// 标签样式(复用原有格式,动态匹配颜色)
label: {
normal: {
align: 'left',
distanceToLabelLine: 2,
formatter: (params) => {
const wrapMixedText = (text, maxUnits = 9, maxLines = 2) => {
if (!text) return ''
let line = ''
let units = 0
const lines = []
const charUnit = (ch) => {
// 简单权重:中文/全角更“宽”
if (/[\u4e00-\u9fa5]/.test(ch)) return 1
if (/[A-Z]/.test(ch)) return 0.85
if (/[a-z0-9]/.test(ch)) return 0.65
if (/\s/.test(ch)) return 0.3
return 0.8
}
for (let i = 0; i < text.length; i++) {
const ch = text[i]
const u = charUnit(ch)
// 超过阈值就换行
if (units + u > maxUnits && line) {
lines.push(line)
line = ch
units = u
if (lines.length >= maxLines) {
// 还有剩余就省略
const rest = text.slice(i + 1)
if (rest) lines[maxLines - 1] = (lines[maxLines - 1] + '...').replace(/\.\.\.\.+$/, '...')
return lines.join('\n')
}
} else {
line += ch
units += u
}
}
if (line) lines.push(line)
return lines.slice(0, maxLines).join('\n')
}
const nameWrapped = wrapMixedText(params.name, 6, 1)
return [
`{b|${Number(params.value || 0).toLocaleString()}(${item.rate}%)}`,
// 第2行放色块 + 名称第1行如果名称有第2行会自然变成第3行
`{hr|■}{c|${nameWrapped}}`
].join('\n')
},
rich: {
hr: {
color: color,
fontSize: 20,
padding: [26, 8, 0, 0]
},
b: {
color: 'rgba(0, 0, 0, 0.75)',
fontSize: 13,
padding: [0, 0, 0, 0]
},
c: {
color: 'rgba(64, 64, 64, 1)',
fontSize: 14,
padding: [30, 0, 0, -5]
}
}
}
},
// 标签线样式(动态匹配颜色)
labelLine: {
lineStyle: { color: color },
length: 5,
length2: 5,
},
// 扇区样式(动态匹配颜色)
itemStyle: { color: color }
};
});
// 图表配置项
const option = {
tooltip:{
confine:true,
formatter:params => {
const {name,value,percent} = params;
return `${name}${value} (${percent}%)`;
}
},
// 动态标题(支持父组件传递)
title: [
{
text: this.chartTitle,
left: 'center',
top: '35%',
textStyle: {
fontSize: 15,
letterSpacing: 5,
color: 'rgba(0, 0, 0, 0.55)',
fontFamily: 'PingFangSC, PingFang SC'
}
},
{
text: `单位:${this.unit}`,
left: 'center',
top: '50%',
textStyle: {
fontSize: 14,
color: 'rgba(0, 0, 0, 0.55)',
fontFamily: 'PingFangSC, PingFang SC'
}
}
],
series: [
{
name: '销量',
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
label: {
show: true,
position: 'outside',
distance: 10,
formatter: '{c}万m²\n{b}',
textStyle: {
fontSize: 14,
color: 'rgba(0, 0, 0, 0.7)',
fontFamily: 'PingFangSC, PingFang SC',
lineHeight: 1.5
}
},
labelLine: {
show: true,
lineStyle: {
// 若未单独配置标签线颜色,默认取对应数据项的颜色
color: (params) => this.customColors[params.dataIndex % this.customColors.length]
}
},
itemStyle: {
// 若未单独配置扇区颜色,默认取对应数据项的颜色
color: (params) => this.customColors[params.dataIndex % this.customColors.length]
},
data: dynamicPieData // 使用动态生成的数据,替代硬编码数据
}
]
};
// 设置图表配置
option && this.myChart.setOption(option);
}
},
};
</script>