更新
This commit is contained in:
367
src/views/equipment/equipmentOverview/gantt.js
Normal file
367
src/views/equipment/equipmentOverview/gantt.js
Normal file
@@ -0,0 +1,367 @@
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user