Files
klp-oa/klp-ui/src/views/work/line/GanttChartEcharts.vue
2025-08-26 16:51:23 +08:00

196 lines
6.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>
<div ref="ganttChart" class="echarts-gantt-wrapper" style="width:100%;height:320px;"></div>
</template>
<script>
import * as echarts from 'echarts';
const colorList = [
'#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399',
'#13C2C2', '#B37FEB', '#FF85C0', '#36CBCB', '#FFC53D'
];
function getColor(lineId, orderId, idx) {
if (!lineId) return colorList[idx % colorList.length];
const base = Math.abs(Number(lineId)) % colorList.length;
if (!orderId) return colorList[base];
return colorList[(base + Math.abs(Number(orderId)) % colorList.length) % colorList.length];
}
export default {
name: 'GanttChartEcharts',
props: {
tasks: {
type: Array,
default: () => []
}
},
data() {
return {
chart: null
};
},
watch: {
tasks: {
handler() {
this.renderChart();
},
deep: true
}
},
mounted() {
this.renderChart();
window.addEventListener('resize', this.resizeChart);
},
beforeDestroy() {
if (this.chart) this.chart.dispose();
window.removeEventListener('resize', this.resizeChart);
},
methods: {
renderChart() {
if (!this.$refs.ganttChart) return;
if (this.chart) this.chart.dispose();
this.chart = echarts.init(this.$refs.ganttChart);
if (!this.tasks || this.tasks.length === 0) {
this.chart.clear();
return;
}
// 处理数据,兼容多种字段名,保证任务名唯一
const taskData = this.tasks.map((item, idx) => {
const name = (item.taskName || item.planName || item.remark || item.productName || item.name || `任务${idx+1}`) + (item.productName ? `-${item.productName}` : '');
const start = item.startDate || item.start_time || item.start || item.start_date;
const end = item.endDate || item.end_time || item.end || item.end_date;
console.log(item.lineId, item.orderId, idx, '颜色取值依据')
return {
name,
value: [start, end],
itemStyle: {
color: getColor(item.lineId, item.orderId, idx),
borderRadius: 6
},
lineId: item.lineId,
orderId: item.orderId,
productId: item.productId,
quantity: item.quantity,
startDate: start,
endDate: end
};
});
// 先全部用 getColor 分配基础色
taskData.forEach((item, idx) => {
item._color = getColor(item.lineId, item.orderId, idx);
});
// 检查冲突(同产线时间重叠),有冲突的都标红
for (let i = 0; i < taskData.length; i++) {
for (let j = i + 1; j < taskData.length; j++) {
if (
taskData[i].lineId &&
taskData[i].lineId === taskData[j].lineId &&
new Date(taskData[i].value[0]) < new Date(taskData[j].value[1]) &&
new Date(taskData[i].value[1]) > new Date(taskData[j].value[0])
) {
taskData[i]._color = '#F56C6C';
taskData[j]._color = '#F56C6C';
}
}
}
// 颜色映射
const colorMap = taskData.map(d => d._color);
// Y轴任务名
const yData = taskData.map(d => d.name);
// X轴时间范围
const minDate = Math.min(...taskData.map(d => new Date(d.value[0]).getTime()));
const maxDate = Math.max(...taskData.map(d => new Date(d.value[1]).getTime()));
// 自动调整时间轴范围,避免跨度过大导致任务条重叠
const oneMonth = 30 * 24 * 3600 * 1000;
let xMin = minDate - oneMonth;
let xMax = maxDate + oneMonth;
// 如果跨度大于一年,仍然只扩展一个月
// 如果跨度小于一个月,最小跨度为两个月
if (xMax - xMin < 2 * oneMonth) {
xMax = xMin + 2 * oneMonth;
}
console.log(taskData);
// 配置
const option = {
tooltip: {
confine: true,
formatter: params => {
// 用 params.data[2] 作为索引查找 taskData
const idx = params.data && params.data[2];
const d = (typeof idx === 'number' && taskData[idx]) ? taskData[idx] : {};
return `任务:${d.name || ''}` +
`<br/>开始:${d.startDate || ''}` +
`<br/>结束:${d.endDate || ''}` +
`<br/>日产能:${d.capacity != null ? d.capacity : d.capacity || ''}` +
`<br/>总产能:${d.totalCapacity != null ? d.totalCapacity : d.total_capacity || ''}` +
`<br/>目标生产:${d.planQuantity != null ? d.planQuantity : d.plan_quantity || ''}` +
`<br/>天数:${d.days != null ? d.days : d.day || ''}` +
`<br/>数量:${d.quantity != null ? d.quantity : ''}`;
}
},
grid: { left: 120, right: 40, top: 30, bottom: 80 },
xAxis: {
type: 'time',
min: xMin,
max: xMax,
axisLabel: {
formatter: v => echarts.format.formatTime('yyyy-MM-dd', v),
rotate: 45 // 关键倾斜45度
}
},
yAxis: {
type: 'category',
data: yData,
axisTick: { show: false },
axisLine: { show: false },
axisLabel: { fontWeight: 'bold' }
},
series: [{
type: 'custom',
renderItem: (params, api) => {
const categoryIndex = api.value(2);
const start = api.coord([api.value(0), categoryIndex]);
const end = api.coord([api.value(1), categoryIndex]);
const barHeight = 18;
// 颜色从 colorMap 查
const idx = params.dataIndex;
let fillColor = colorMap[idx] || '#409EFF';
return {
type: 'rect',
shape: {
x: start[0],
y: start[1] - barHeight / 2,
width: end[0] - start[0],
height: barHeight,
r: 6
},
style: {
fill: fillColor
}
};
},
encode: {
x: [0, 1],
y: 2
},
data: taskData.map((d, i) => [d.value[0], d.value[1], i]),
itemStyle: {
borderRadius: 6
}
}]
};
this.chart.setOption(option);
},
resizeChart() {
if (this.chart) this.chart.resize();
}
}
};
</script>
<style>
.echarts-gantt-wrapper {
width: 100%;
min-height: 220px;
background: #fff;
}
</style>