Files
yudao-dev/src/views/equipment/equipmentOverview/gantt.js
2026-04-29 08:57:28 +08:00

368 lines
9.7 KiB
JavaScript
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.

import * as echarts from 'echarts'
function getStartTime(timestamp) {
return new Date(new Date(timestamp).toLocaleDateString()).getTime();
}
function renderItem(params, api) {
// 1. 校验value数据是否有效
const yIndex = api.value(0); // y轴索引
const startTime = api.value(1);
const endTime = api.value(2);
if (isNaN(startTime) || isNaN(endTime) || startTime >= endTime) {
return null;
}
// 2. 转换坐标x轴是time类型需确保转换正确
const startPoint = api.coord([startTime, yIndex]);
const endPoint = api.coord([endTime, yIndex]);
if (!startPoint || !endPoint) return null;
// 3. 计算矩形高度加大高度避免高度为0
const cellHeight = api.size([0, 1])[1] * 0.8; // 从0.5改为0.8,提高可视性
const rectX = startPoint[0];
const rectY = startPoint[1] - cellHeight / 2;
const rectWidth = endPoint[0] - startPoint[0];
// 4. 避免宽度为负/0
if (rectWidth <= 0) return null;
// 5. 裁剪矩形(简化裁剪逻辑,避免过度裁剪)
const rectShape = echarts.graphic.clipRectByRect(
{
x: rectX,
y: rectY,
width: rectWidth,
height: cellHeight,
},
params.coordSys // 直接用坐标系范围,简化裁剪
);
return rectShape ? {
type: 'rect',
transition: ['shape'],
shape: rectShape,
style: api.style(),
} : null;
}
// unused
function getXaxisRange(startTime) {
return Array(24)
.fill(startTime)
.map((item, index) => {
return new Date(item + index * 3600 * 1000)
.toLocaleTimeString()
.split(':')
.slice(0, 2)
.join(':');
});
}
function getTodayStart(today) {
const [y, m, d] = [
today.getFullYear(),
today.getMonth(),
today.getDate(),
];
// debugger;
return new Date(y, m, d).getTime();
}
/** 颜色配置 */
const types = [
{ name: '运行', color: '#288AFF' },
{ name: '计划停机', color: '#FFDC94' },
{ name: '故障', color: '#FC9C91' },
{ name: '空白', color: '#F2F4F9' },
];
export default class GanttGraph {
// tooltip - 基本是固定的
tooltip = {
trigger: 'item',
axisPointer: {
type: 'none',
},
formatter: (params) => {
// debugger;
const date1 = new Date(params.value[1]);
const date2 = new Date(params.value[2]);
return `
<div style="display: flex; flex-direction: column;">
<span>${date1.getMonth() + 1}-${date1.getDate()} ${String(date1.getHours()).padStart(2, '0')}:${String(date1.getMinutes()).padStart(2, '0')} ~ ${date2.getMonth() + 1}-${date2.getDate()} ${String(date2.getHours()).padStart(2, '0')}:${String(date2.getMinutes()).padStart(2, '0')}</span>
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="display: flex; align-items: center;">
<span class="icon" style="width: 8px; height: 8px; border-radius: 2px; background: ${params.color}"></span>
<span class="eq-name" style="margin-left: 4px;">${params.data.showName}</span>
</div>
<span class="run-status" style="margin-left: 8px; opacity: 0.6">${params.name}</span>
</div>
`
}
}
grid = []
xAxis = []
yAxis = []
series = []
constructor(el, startTime) {
this.el = el;
this.startTime = new Date(startTime);
}
// 构造一个新的 grid
makeGrid() {
return {
top: 5,
right: 20,
left: 100,
bottom: 50,
}
}
// 构造一个 xAxis
// 新增:从数据中提取时间范围的方法
getXaxisRangeFromData(data) {
let allTimes = [];
data.forEach(eqArr => {
eqArr.forEach(item => {
if (item.startTime) {
allTimes.push(item.startTime);
allTimes.push(item.startTime + (item.duration || 0) * 60 * 1000);
}
});
});
if (allTimes.length === 0) {
const today = new Date();
return [getTodayStart(today), getTodayStart(today) + 24 * 3600 * 1000];
}
const minTime = Math.min(...allTimes);
const maxTime = Math.max(...allTimes);
// 扩展1小时的缓冲
return [minTime - 3600 * 1000, maxTime + 3600 * 1000];
}
// 修改makeXaxis方法支持传入数据动态计算范围
makeXaxis(data = []) {
let [minTime, maxTime] = this.getXaxisRangeFromData(data);
return [
{
axisTick: {
alignWithLabel: true,
inside: true,
},
type: 'time',
min: minTime,
max: maxTime,
splitNumber: 24,
axisLabel: {
margin: 12,
formatter: function (val) {
return new Date(val).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
},
},
axisLine: {
lineStyle: {
color: '#0005',
},
},
boundaryGap: false,
},
];
}
// 构造一个 yAxis
makeYaxis(equipmentName) {
return [
// 主y轴
{
data: equipmentName,
type: 'category'
}
]
}
// 构造一个 series
makeSeries(xdata) {
const bgStartTime = this.startTime.getTime();
const bgEndTime = bgStartTime + 3600 * 24 * 1000;
return [
{
type: 'custom',
renderItem: renderItem,
itemStyle: {
opacity: 0.8,
},
encode: {
x: [1, 2],
y: 0,
},
data: xdata,
},
]
}
init(data) {
if (!this.el) throw new Error('没有可供echarts初始化的容器');
if (typeof this.el == 'string') {
this.el = document.querySelector(this.el);
}
this.chart = echarts.init(this.el);
this.handleProps(data);
// 调试:打印关键数据
console.log('=== 调试信息 ===');
console.log('x轴范围', this.xAxis[0]?.min, '~', this.xAxis[0]?.max);
console.log('y轴数据', this.yAxis[0]?.data);
console.log('series数据', this.series[0]?.data);
setTimeout(() => {
this.chart.setOption(this.option);
// 强制渲染
this.chart.resize();
}, 200);
}
update(data) {
this.clear();
this.init(data);
}
resize() {
this.chart.resize();
}
get option() {
return {
tooltip: this.tooltip,
grid: this.grid,
xAxis: this.xAxis,
yAxis: this.yAxis,
series: this.series,
dataZoom: [
{
type: 'slider',
xAxisIndex: 0,
filterMode: 'weakFilter',
height: 20,
bottom: 0,
start: 0,
end: 80,
handleIcon:
'path://M10.7,11.9H9.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
handleSize: '80%',
showDetail: false
},
{
type: 'inside',
id: 'insideX',
xAxisIndex: 0,
filterMode: 'weakFilter',
start: 0,
end: 80,
zoomOnMouseWheel: true,
moveOnMouseMove: true
},
{
type: 'slider',
yAxisIndex: 0,
zoomLock: true,
width: 10,
right: 10,
top: 70,
bottom: 20,
start: 0,
end: 100,
handleSize: 0,
showDetail: false
},
{
type: 'inside',
id: 'insideY',
yAxisIndex: 0,
start: 0,
end: 100,
zoomOnMouseWheel: true,
moveOnMouseMove: true,
moveOnMouseWheel: true
}
],
}
}
// 每次 graphList 刷新都会重新渲染整个所有图表
// 可以改进的地方:添加一个 handleAdd() 方法,一次添加一个新的
handleProps(props) {
console.log('props: ', props);
let ylist = []
let xdata = []
// 第一步提前获取x轴的实际min/max从xAxis配置中取而非重新计算
const xAxisConfig = this.makeXaxis(props)[0];
const xMin = xAxisConfig.min;
const xMax = xAxisConfig.max;
props.forEach((eqArr, index) => {
if (!eqArr.key) return;
ylist.push(eqArr.key)
eqArr.forEach(item => {
if (!item.startTime || !item.duration || item.status === undefined) {
console.warn('数据缺失', item);
return;
}
const status = Math.max(0, Math.min(3, item.status));
const endTime = item.startTime + item.duration * 60 * 1000;
// 第二步修正时间校验逻辑只要数据和x轴范围有交集就保留
// 原逻辑数据完全在x轴外才过滤 → 改为数据和x轴无交集才过滤
const isOutOfRange = endTime < xMin || item.startTime > xMax;
if (isOutOfRange) {
console.warn('数据超出x轴范围', {
startTime: new Date(item.startTime).toLocaleString(),
endTime: new Date(endTime).toLocaleString(),
xMin: new Date(xMin).toLocaleString(),
xMax: new Date(xMax).toLocaleString()
});
return;
}
xdata.push({
name: types[status].name,
showName: eqArr.key,
value: [index, item.startTime, endTime],
itemStyle: {
color: types[status].color,
}
});
});
});
this.grid.push(this.makeGrid());
this.xAxis.push(...this.makeXaxis(props)); // 传入props
this.yAxis.push(...this.makeYaxis(ylist));
this.series.push(...this.makeSeries(xdata));
}
// handleAdd
handleAdd() { }
clear() {
this.grid = [];
this.xAxis = [];
this.yAxis = [];
this.series = [];
this.chart.dispose();
}
// print option
print() {
console.log(JSON.stringify(this.option, null, 2));
}
}