refactor(elevator): 重构电梯点状态渲染为极简设计,移除动画效果和图标依赖,统一使用静态边框和颜色编码

This commit is contained in:
xudan 2025-12-11 11:43:58 +08:00
parent 5f7a9b6dce
commit 624c68d0eb
3 changed files with 159 additions and 322 deletions

View File

@ -530,8 +530,8 @@ onMounted(async () => {
id: '1998661793706377218',
type: 102,
elevatorFloor: 5,
elevatorDirection: 1, //
elevatorFrontDoorStatus: 2, //
elevatorDirection: 3, //
elevatorFrontDoorStatus: 3, //
isConnected: true
});

View File

@ -54,13 +54,31 @@ function drawDisconnectedIcon(ctx: CanvasRenderingContext2D, cx: number, cy: num
}
/**
* -
* -
* 1. 使
* 2. 48*60
* 3. pen元素宽高动态计算
* 4.
* - 线线
* - 绿
* - +/
* -
* -
* 5.
*
*
* - 4pxpen较小边的15%
* - 1.5pxpen较小边的3%
* - 2pxpen较小边的5%
* - 3pxpen较小边的10%
* - 4pxpen较小边的12%
* - 线pen尺寸动态调整间距比例
* @param ctx Canvas上下文
* @param pen
* @param elevatorData
* @param time
* @param time 使
*/
function drawElevatorAnimation(
function drawElevatorMinimal(
ctx: CanvasRenderingContext2D,
pen: MapPen,
elevatorData: any,
@ -71,133 +89,76 @@ function drawElevatorAnimation(
elevatorDirection,
elevatorFrontDoorStatus,
isConnected,
} = elevatorData;
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
// 为48*60图片设计矩形动画框架
// 外边框距离图片边缘6像素确保不覆盖图片
const framePadding = 6;
const frameWidth = 3; // 边框宽度
const cornerLength = 12; // 角装饰长度
const cornerWidth = 4; // 角装饰宽度
// 基于pen元素尺寸的动态边框参数
const baseSize = Math.min(w, h); // 取较小的尺寸作为基准
const padding = Math.max(4, baseSize * 0.15); // 距离图片边缘最小4px否则按15%比例
const strokeWidth = Math.max(1.5, baseSize * 0.03); // 边框宽度最小1.5px否则按3%比例
const borderRadius = Math.max(2, baseSize * 0.05); // 圆角半径最小2px否则按5%比例
// 动画框架的四个角坐标
const frameCorners = {
topLeft: { x: x - framePadding, y: y - framePadding },
topRight: { x: x + w + framePadding, y: y - framePadding },
bottomLeft: { x: x - framePadding, y: y + h + framePadding },
bottomRight: { x: x + w + framePadding, y: y + h + framePadding }
};
// 矩形边框坐标(基于动态参数)
const rectX = x - padding;
const rectY = y - padding;
const rectW = w + padding * 2;
const rectH = h + padding * 2;
// 检查是否启用LOD
const lodEnabled = (pen.calculative as any)?.lodEnabled || false;
// 辅助函数:绘制矩形框架的角装饰
const drawFrameCorners = (color: string, alpha: number, animated: boolean = true) => {
// 辅助函数:绘制极简矩形边框
const drawMinimalFrame = (color: string, alpha: number = 1, useDashedLine: boolean = false) => {
ctx.save();
// 设置边框样式
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = cornerWidth;
ctx.lineCap = 'square';
ctx.lineWidth = strokeWidth;
ctx.globalAlpha = alpha;
// 动画效果:角装饰闪烁
const animPhase = animated ? Math.sin(time * 0.002) * 0.5 + 0.5 : 1;
// 左上角
ctx.globalAlpha = alpha * animPhase;
ctx.beginPath();
ctx.moveTo(frameCorners.topLeft.x, frameCorners.topLeft.y + cornerLength);
ctx.lineTo(frameCorners.topLeft.x, frameCorners.topLeft.y);
ctx.lineTo(frameCorners.topLeft.x + cornerLength, frameCorners.topLeft.y);
ctx.stroke();
// 右上角
ctx.globalAlpha = alpha * (animPhase + 0.25) % 1;
ctx.beginPath();
ctx.moveTo(frameCorners.topRight.x - cornerLength, frameCorners.topRight.y);
ctx.lineTo(frameCorners.topRight.x, frameCorners.topRight.y);
ctx.lineTo(frameCorners.topRight.x, frameCorners.topRight.y + cornerLength);
ctx.stroke();
// 右下角
ctx.globalAlpha = alpha * (animPhase + 0.5) % 1;
ctx.beginPath();
ctx.moveTo(frameCorners.bottomRight.x, frameCorners.bottomRight.y - cornerLength);
ctx.lineTo(frameCorners.bottomRight.x, frameCorners.bottomRight.y);
ctx.lineTo(frameCorners.bottomRight.x - cornerLength, frameCorners.bottomRight.y);
ctx.stroke();
// 左下角
ctx.globalAlpha = alpha * (animPhase + 0.75) % 1;
ctx.beginPath();
ctx.moveTo(frameCorners.bottomLeft.x + cornerLength, frameCorners.bottomLeft.y);
ctx.lineTo(frameCorners.bottomLeft.x, frameCorners.bottomLeft.y);
ctx.lineTo(frameCorners.bottomLeft.x, frameCorners.bottomLeft.y - cornerLength);
ctx.stroke();
ctx.restore();
};
// 辅助函数:绘制完整矩形边框
const drawFullFrame = (color: string, alpha: number, dashPattern: number[] = []) => {
ctx.save();
ctx.strokeStyle = color;
ctx.lineWidth = frameWidth;
ctx.globalAlpha = alpha;
if (dashPattern.length > 0) {
ctx.setLineDash(dashPattern);
// 虚线样式(动态调整间距)
if (useDashedLine) {
const dashLength = Math.max(3, baseSize * 0.08); // 虚线长度最小3px否则按8%比例
const gapLength = Math.max(3, baseSize * 0.05); // 间隙长度最小3px否则按5%比例
ctx.setLineDash([dashLength, gapLength]);
}
ctx.strokeRect(
frameCorners.topLeft.x,
frameCorners.topLeft.y,
frameCorners.bottomRight.x - frameCorners.topLeft.x,
frameCorners.bottomRight.y - frameCorners.topLeft.y
);
// 绘制圆角矩形
ctx.beginPath();
ctx.roundRect(rectX, rectY, rectW, rectH, borderRadius);
ctx.stroke();
ctx.restore();
};
// 辅助函数:绘制方向箭头(在框架外部)
const drawDirectionArrows = (direction: 'up' | 'down', color: string) => {
// 辅助函数:绘制方向指示(仅当电梯移动时)
const drawDirection = (direction: 'up' | 'down', color: string) => {
ctx.save();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.globalAlpha = 0.8;
const arrowSize = 8;
const arrowOffset = 15; // 箭头距离框架的距离
const animProgress = (Math.sin(time * 0.003) + 1) * 0.5;
// 方向指示器大小基于pen尺寸动态调整
const indicatorSize = Math.max(3, baseSize * 0.1); // 最小3px否则按10%比例
const margin = Math.max(2, baseSize * 0.05); // 距离边框的距离最小2px否则按5%比例
if (direction === 'up') {
// 上行箭头:在框架上方
const arrowY = frameCorners.topLeft.y - arrowOffset - animProgress * 5;
// 向上三角 - 位于矩形顶部中央
const centerX = x + w / 2;
const centerY = rectY + margin + indicatorSize;
ctx.globalAlpha = 0.8 + animProgress * 0.2;
ctx.beginPath();
ctx.moveTo(centerX, arrowY);
ctx.lineTo(centerX - arrowSize, arrowY + arrowSize);
ctx.lineTo(centerX + arrowSize, arrowY + arrowSize);
ctx.moveTo(centerX, centerY - indicatorSize);
ctx.lineTo(centerX - indicatorSize, centerY + indicatorSize);
ctx.lineTo(centerX + indicatorSize, centerY + indicatorSize);
ctx.closePath();
ctx.fill();
} else {
// 下行箭头:在框架下方
const arrowY = frameCorners.bottomLeft.y + arrowOffset + animProgress * 5;
// 向下三角 - 位于矩形底部中央
const centerX = x + w / 2;
const centerY = rectY + rectH - margin - indicatorSize;
ctx.globalAlpha = 0.8 + animProgress * 0.2;
ctx.beginPath();
ctx.moveTo(centerX, arrowY);
ctx.lineTo(centerX - arrowSize, arrowY - arrowSize);
ctx.lineTo(centerX + arrowSize, arrowY - arrowSize);
ctx.moveTo(centerX, centerY + indicatorSize);
ctx.lineTo(centerX - indicatorSize, centerY - indicatorSize);
ctx.lineTo(centerX + indicatorSize, centerY - indicatorSize);
ctx.closePath();
ctx.fill();
}
@ -205,124 +166,119 @@ function drawElevatorAnimation(
ctx.restore();
};
// 辅助函数:绘制门状态指示(在框架两侧)
const drawDoorIndicators = (isOpening: boolean, color: string) => {
ctx.save();
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.globalAlpha = 0.7;
const progress = isOpening ?
(Math.sin(time * 0.001) + 1) * 0.5 :
1 - (Math.sin(time * 0.001) + 1) * 0.5;
const doorHeight = h * 0.3;
const doorWidth = 3;
const sideOffset = 12; // 距离框架侧边的距离
// 左门指示
const leftDoorX = frameCorners.topLeft.x - sideOffset - progress * 8;
const doorY = y + (h - doorHeight) / 2;
ctx.strokeRect(leftDoorX, doorY, doorWidth, doorHeight);
// 右门指示
const rightDoorX = frameCorners.topRight.x + sideOffset + progress * 8;
ctx.strokeRect(rightDoorX, doorY, doorWidth, doorHeight);
ctx.restore();
};
// 辅助函数:绘制状态文本
const drawStatusText = (text: string, color: string) => {
if (lodEnabled) return; // LOD模式下不显示文本
// 辅助函数:在边框右侧绘制静态方向箭头
const drawDirectionSide = (direction: 'up' | 'down', color: string) => {
ctx.save();
ctx.fillStyle = color;
ctx.font = '10px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.globalAlpha = 0.9;
ctx.strokeStyle = color;
ctx.globalAlpha = 0.8;
// 在图片下方显示状态文本
ctx.fillText(text, x + w / 2, frameCorners.bottomLeft.y + 5);
ctx.restore();
};
// 箭头大小基于pen尺寸动态调整使用宽度和高度的平均值来更好地适应不同比例
const avgSize = (w + h) / 2; // 使用宽高平均值作为基准
const arrowSize = Math.max(4, avgSize * 0.12); // 最小4px否则按12%比例
const sideMargin = Math.max(3, avgSize * 0.08); // 距离右侧边框的距离
ctx.lineWidth = Math.max(1.5, avgSize * 0.025); // 箭头线条宽度
// 辅助函数:绘制连接状态指示器
const drawConnectionIndicator = () => {
if (!isConnected) {
ctx.save();
const pulseIntensity = 0.5 + Math.sin(time * 0.003) * 0.5;
// 箭头位置:在矩形右侧垂直居中
const arrowX = rectX + rectW + sideMargin;
const centerY = y + h / 2; // 垂直居中
// 断连图标在右上角
ctx.fillStyle = `rgba(255, 59, 48, ${pulseIntensity})`;
if (direction === 'up') {
// 向上箭头 - 在右侧绘制
ctx.beginPath();
ctx.arc(frameCorners.topRight.x, frameCorners.topRight.y, 4, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = `rgba(255, 59, 48, ${pulseIntensity})`;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(frameCorners.topRight.x - 6, frameCorners.topRight.y - 6);
ctx.lineTo(frameCorners.topRight.x + 6, frameCorners.topRight.y + 6);
// 箭头主体(竖线)
ctx.moveTo(arrowX, centerY + arrowSize);
ctx.lineTo(arrowX, centerY - arrowSize);
// 箭头尖端(三角形)
ctx.lineTo(arrowX - arrowSize * 0.6, centerY - arrowSize * 0.4);
ctx.moveTo(arrowX, centerY - arrowSize);
ctx.lineTo(arrowX + arrowSize * 0.6, centerY - arrowSize * 0.4);
ctx.stroke();
ctx.restore();
// 填充箭头头部
ctx.beginPath();
ctx.moveTo(arrowX, centerY - arrowSize);
ctx.lineTo(arrowX - arrowSize * 0.5, centerY - arrowSize * 0.7);
ctx.lineTo(arrowX + arrowSize * 0.5, centerY - arrowSize * 0.7);
ctx.closePath();
ctx.fill();
} else {
// 向下箭头 - 在右侧绘制
ctx.beginPath();
// 箭头主体(竖线)
ctx.moveTo(arrowX, centerY - arrowSize);
ctx.lineTo(arrowX, centerY + arrowSize);
// 箭头尖端(三角形)
ctx.lineTo(arrowX - arrowSize * 0.6, centerY + arrowSize * 0.4);
ctx.moveTo(arrowX, centerY + arrowSize);
ctx.lineTo(arrowX + arrowSize * 0.6, centerY + arrowSize * 0.4);
ctx.stroke();
// 填充箭头头部
ctx.beginPath();
ctx.moveTo(arrowX, centerY + arrowSize);
ctx.lineTo(arrowX - arrowSize * 0.5, centerY + arrowSize * 0.7);
ctx.lineTo(arrowX + arrowSize * 0.5, centerY + arrowSize * 0.7);
ctx.closePath();
ctx.fill();
}
ctx.restore();
};
// 状态渲染逻辑
// 辅助函数:呼吸动画效果
const breatheAnimation = (baseAlpha: number = 0.6) => {
// 使用缓慢的正弦波创建呼吸效果
return baseAlpha + Math.sin(time * 0.001) * 0.2;
};
// 辅助函数:脉冲动画效果(用于断连状态)
const pulseAnimation = () => {
return 0.4 + Math.sin(time * 0.002) * 0.4;
};
// 状态渲染逻辑 - 静态显示,无动画
if (!isConnected) {
// 离线状态:红色闪烁角 + 断连图标
drawFrameCorners('rgba(255, 59, 48, 0.8)', 0.8, true);
drawConnectionIndicator();
drawStatusText('离线', '#FF3B30');
// 离线状态 - 红色虚线
drawMinimalFrame('#FF3B30', 0.8, true);
} else {
// 在线状态根据具体状态渲染
switch (elevatorFrontDoorStatus) {
case 2: // 正在开关门
// 绿色动态框架 + 门指示器
drawFrameCorners('rgba(52, 199, 89, 0.8)', 0.7, true);
if (!lodEnabled) {
drawDoorIndicators(true, 'rgba(52, 199, 89, 0.8)');
case 2: { // 正在开关门
// 绿色静态边框
drawMinimalFrame('#34C759', 0.8);
break;
}
drawStatusText('开门中', '#34C759');
break;
case 3: // 门已开
// 蓝色呼吸框架
drawStatusText('门已开', '#007AFF');
case 3: { // 门已开
// 绿色静态边框
drawMinimalFrame('#34C759', 0.8);
break;
}
case 1: // 门已关
case 1: { // 门已关
if (elevatorDirection === 2) { // 向上
// 紫色动态框架 + 上行箭头
drawFrameCorners('rgba(175, 82, 222, 0.8)', 0.7, true);
if (!lodEnabled) {
drawDirectionArrows('up', 'rgba(175, 82, 222, 0.8)');
}
drawStatusText('上行中', '#AF52DE');
// 蓝色静态边框 + 右侧向上箭头
drawMinimalFrame('#007AFF', 0.8);
drawDirectionSide('up', '#007AFF');
} else if (elevatorDirection === 3) { // 向下
// 青色动态框架 + 下行箭头
drawFrameCorners('rgba(90, 200, 250, 0.8)', 0.7, true);
if (!lodEnabled) {
drawDirectionArrows('down', 'rgba(90, 200, 250, 0.8)');
}
drawStatusText('下行中', '#5AC8FA');
// 蓝色静态边框 + 右侧向下箭头
drawMinimalFrame('#007AFF', 0.8);
drawDirectionSide('down', '#007AFF');
} else { // 停止
// 金色静态框架
drawFullFrame('rgba(255, 204, 0, 0.6)', 0.6);
drawStatusText('停止', '#FFCC00');
// 金色静态边框
drawMinimalFrame('#FFCC00', 0.7);
}
break;
}
default: // 未知状态
// 灰色静态框架
drawFullFrame('rgba(142, 142, 147, 0.5)', 0.5);
drawStatusText('未知', '#8E8E93');
default: { // 未知状态
// 灰色静态边框
drawMinimalFrame('#8E8E93', 0.5);
break;
}
}
}
}
/**
@ -556,15 +512,15 @@ export function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
const largeTypeStrokeColor = colorConfig.getColor(`point.types.${type}.stroke`);
const largeGeneralStrokeColor = colorConfig.getColor(active ? 'point.large.strokeActive' : 'point.large.stroke');
const largeThemeStrokeColor = get(theme, active ? 'point-l.strokeActive' : 'point-l.stroke');
ctx.strokeStyle = statusStyle ?? (largeTypeStrokeColor || largeGeneralStrokeColor || largeThemeStrokeColor || '');
ctx.strokeStyle = largeTypeStrokeColor || largeGeneralStrokeColor || largeThemeStrokeColor || '';
ctx.stroke();
// 电梯点:添加动画效果
// 电梯点:使用极简状态渲染
if (type === MapPointType.) {
// 直接传入包含后端字段的 pen.point 数据
if (pen.point) {
const currentTime = performance.now();
drawElevatorAnimation(ctx, pen, pen.point, currentTime);
drawElevatorMinimal(ctx, pen, pen.point, currentTime);
}
}
break;

View File

@ -151,20 +151,15 @@ export const useElevatorStore = defineStore('elevator', () => {
};
/**
* -
*
*/
const updateElevatorPointDisplay = (elevatorData: ElevatorData) => {
if (!editorService.value || !elevatorData.penId) return;
const penId = elevatorData.penId;
// 获取状态对应的颜色、图标和动画配置
const displayConfig = getElevatorDisplay(elevatorData);
// 基础更新数据
const updateData: any = {
// 设置图标
image: displayConfig.iconPath,
// 更新点位信息
point: {
...((editorService.value.getPenById(penId) as any)?.point || {}),
@ -173,15 +168,8 @@ export const useElevatorStore = defineStore('elevator', () => {
lastUpdate: elevatorData.lastUpdate,
// 保存原始后端数据
elevatorDirection: elevatorData.elevatorDirection,
elevatorFrontDoorStatus: elevatorData.elevatorFrontDoorStatus,
// 使用新的颜色系统
color: displayConfig.color,
// 动画配置
animationClass: displayConfig.animationClass,
priority: displayConfig.priority
},
// 增强的样式配置
statusStyle: displayConfig.color
elevatorFrontDoorStatus: elevatorData.elevatorFrontDoorStatus
}
};
try {
@ -194,108 +182,6 @@ export const useElevatorStore = defineStore('elevator', () => {
}
};
/**
*
* 使
*/
const getElevatorDisplay = (elevatorData: ElevatorData) => {
// 生成图标路径的辅助函数
const getIconPath = (theme: string) => `${import.meta.env.BASE_URL}/point/elevator-${theme}.svg`;
// 现代化颜色配置系统
const colors = {
offline: '#FF3B30', // 红色 - 离线
opening: '#34C759', // 绿色 - 开门中
doorOpen: '#007AFF', // 蓝色 - 门已开
moving: '#AF52DE', // 紫色 - 运行中
upMoving: '#AF52DE', // 紫色 - 上行
downMoving: '#5AC8FA', // 青色 - 下行
stopped: '#FFCC00', // 金色 - 停止
idle: '#8E8E93', // 灰色 - 静止
unknown: '#8E8E93' // 灰色 - 未知
};
// 如果离线,显示红色
if (!elevatorData.isConnected) {
return {
color: colors.offline,
iconPath: getIconPath('offline'),
text: '离线',
priority: 'high',
animationClass: 'pulse-error'
};
}
// 根据后端字段判断显示状态
const { elevatorDirection, elevatorFrontDoorStatus, elevatorFloor } = elevatorData;
// 优先判断门状态
switch (elevatorFrontDoorStatus) {
case 2: // 正在开关门
return {
color: colors.opening,
iconPath: getIconPath('opening'),
text: `开门中 ${elevatorFloor > 0 ? `(${elevatorFloor}F)` : ''}`,
priority: 'high',
animationClass: 'pulse-success'
};
case 3: // 门已开
return {
color: colors.doorOpen,
iconPath: getIconPath('door-open'),
text: `门已开 ${elevatorFloor > 0 ? `(${elevatorFloor}F)` : ''}`,
priority: 'normal',
animationClass: 'breathe'
};
case 1: // 门已关
switch (elevatorDirection) {
case 2: // 向上
return {
color: colors.upMoving,
iconPath: getIconPath('up'),
text: `上行中 ${elevatorFloor > 0 ? `(${elevatorFloor}F)` : ''}`,
priority: 'high',
animationClass: 'flow-up'
};
case 3: // 向下
return {
color: colors.downMoving,
iconPath: getIconPath('down'),
text: `下行中 ${elevatorFloor > 0 ? `(${elevatorFloor}F)` : ''}`,
priority: 'high',
animationClass: 'flow-down'
};
case 1: // 停止
return {
color: colors.stopped,
iconPath: getIconPath('stopped'),
text: `停止 ${elevatorFloor > 0 ? `(${elevatorFloor}F)` : ''}`,
priority: 'normal',
animationClass: 'gentle-pulse'
};
default:
break;
}
break;
default:
break;
}
// 未知状态
return {
color: colors.unknown,
iconPath: getIconPath('unknown'),
text: '未知状态',
priority: 'low',
animationClass: 'dim'
};
};
/**
@ -313,10 +199,6 @@ export const useElevatorStore = defineStore('elevator', () => {
};
return {
// 状态
elevators,
@ -325,7 +207,6 @@ export const useElevatorStore = defineStore('elevator', () => {
// 方法
setEditorService,
handleElevatorWebSocketData,
refreshMapping,
getElevatorDisplay
refreshMapping
};
});