refactor(elevator): 重构电梯状态系统以适配后端字段,移除旧枚举并更新UI显示逻辑与动画效果
This commit is contained in:
parent
434d4b72c4
commit
5f7a9b6dce
@ -36,9 +36,11 @@ export interface MapPointInfo {
|
||||
isConnected?: boolean; // 连接状态(自动门点、电梯点使用,true=已连接,false=未连接)
|
||||
active?: boolean; // 是否激活状态,用于控制光圈显示
|
||||
|
||||
// 电梯点专属属性
|
||||
elevatorStatus?: number; // 电梯状态(仅电梯点使用,0=静止,1=开门中,2=关门中,3=上行,4=下行)
|
||||
currentFloor?: number; // 当前楼层(仅电梯点使用)
|
||||
// 电梯点专属属性(后端字段)
|
||||
elevatorDirection?: number; // 电梯方向(0-未知,1-停止,2-向上,3-向下)
|
||||
elevatorFrontDoorStatus?: number; // 电梯前门状态(0-未知,1-关,2-正在开关,3-开)
|
||||
elevatorFloor?: number; // 电梯楼层(0-未知)
|
||||
currentFloor?: number; // 当前楼层(兼容旧字段)
|
||||
lastUpdate?: number; // 最后更新时间戳
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@ -7,7 +7,7 @@ import { isNil } from 'lodash-es';
|
||||
import { computed, inject, type InjectionKey, ref, type ShallowRef, watch } from 'vue';
|
||||
|
||||
import { type DeviceMappingDTO,queryDeviceMappingByType } from '../../apis/device/api';
|
||||
import { ElevatorStatus,useElevatorStore } from '../../stores/elevator.store';
|
||||
import { useElevatorStore } from '../../stores/elevator.store';
|
||||
|
||||
type Props = {
|
||||
token: InjectionKey<ShallowRef<EditorService>>;
|
||||
@ -131,26 +131,46 @@ watch(point, (newPoint) => {
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 获取电梯状态文本
|
||||
const getElevatorStatusText = (status: ElevatorStatus): string => {
|
||||
const statusMap = {
|
||||
[ElevatorStatus.IDLE]: '静止',
|
||||
[ElevatorStatus.OPENING]: '开门中',
|
||||
[ElevatorStatus.CLOSING]: '关门中',
|
||||
[ElevatorStatus.MOVING_UP]: '上行中',
|
||||
[ElevatorStatus.MOVING_DOWN]: '下行中',
|
||||
[ElevatorStatus.DOOR_OPEN]: '门已开',
|
||||
[ElevatorStatus.DOOR_CLOSED]: '门已关',
|
||||
[ElevatorStatus.FAULT]: '故障',
|
||||
[ElevatorStatus.OFFLINE]: '离线',
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
// 获取电梯状态文本(基于后端字段)
|
||||
const getElevatorStatusText = (point: any): string => {
|
||||
const { elevatorDirection, elevatorFrontDoorStatus, isConnected } = point;
|
||||
|
||||
// 离线
|
||||
if (!isConnected) return '离线';
|
||||
|
||||
// 优先判断门状态
|
||||
if (elevatorFrontDoorStatus === 2) return '开关门中';
|
||||
if (elevatorFrontDoorStatus === 3) return '门已开';
|
||||
|
||||
// 门已关,根据方向判断
|
||||
if (elevatorFrontDoorStatus === 1) {
|
||||
if (elevatorDirection === 2) return '上行中';
|
||||
if (elevatorDirection === 3) return '下行中';
|
||||
if (elevatorDirection === 1) return '门已关';
|
||||
}
|
||||
|
||||
return '未知';
|
||||
};
|
||||
|
||||
// 获取电梯状态颜色
|
||||
const getElevatorStatusColor = (status: ElevatorStatus, isConnected: boolean): string => {
|
||||
const display = elevatorStore.getElevatorDisplay(status, isConnected);
|
||||
return display.color;
|
||||
// 获取电梯状态颜色(基于后端字段)
|
||||
const getElevatorStatusColor = (point: any): string => {
|
||||
const { isConnected, elevatorDirection, elevatorFrontDoorStatus } = point;
|
||||
|
||||
// 离线显示灰色
|
||||
if (!isConnected) return '#999999';
|
||||
|
||||
// 优先判断门状态
|
||||
if (elevatorFrontDoorStatus === 2) return '#52C41A'; // 开门中 - 绿色
|
||||
if (elevatorFrontDoorStatus === 3) return '#52C41A'; // 门已开 - 绿色
|
||||
|
||||
// 门已关,根据方向判断
|
||||
if (elevatorFrontDoorStatus === 1) {
|
||||
if (elevatorDirection === 2) return '#722ED1'; // 向上 - 紫色
|
||||
if (elevatorDirection === 3) return '#13C2C2'; // 向下 - 青色
|
||||
if (elevatorDirection === 1) return '#1890FF'; // 停止 - 蓝色
|
||||
}
|
||||
|
||||
return '#1890FF'; // 默认 - 蓝色
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -218,14 +238,14 @@ const getElevatorStatusColor = (status: ElevatorStatus, isConnected: boolean): s
|
||||
<a-typography-text type="secondary">{{ $t('当前楼层') }}</a-typography-text>
|
||||
<a-typography-text>{{ point.currentFloor }}F</a-typography-text>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="point.type === MapPointType.电梯点 && point.elevatorStatus !== undefined">
|
||||
<a-list-item v-if="point.type === MapPointType.电梯点 && point.elevatorDirection !== undefined">
|
||||
<a-typography-text type="secondary">{{ $t('电梯状态') }}</a-typography-text>
|
||||
<a-flex align="center" :gap="8">
|
||||
<span
|
||||
class="status-dot"
|
||||
:style="{ backgroundColor: getElevatorStatusColor(point.elevatorStatus, point.isConnected) }"
|
||||
:style="{ backgroundColor: getElevatorStatusColor(point) }"
|
||||
/>
|
||||
<a-typography-text>{{ getElevatorStatusText(point.elevatorStatus) }}</a-typography-text>
|
||||
<a-typography-text>{{ getElevatorStatusText(point) }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="point.extensionType">
|
||||
|
||||
@ -27,8 +27,7 @@ import { EditorService } from '../services/editor.service';
|
||||
import { StorageLocationService } from '../services/storage-location.service';
|
||||
import { useViewState } from '../services/useViewState';
|
||||
import { editorStore } from '../stores/editor.store';
|
||||
import { ElevatorStatus,useElevatorStore } from '../stores/elevator.store';
|
||||
import { createElevatorMockData } from '../utils/elevator-mock';
|
||||
import { useElevatorStore } from '../stores/elevator.store';
|
||||
|
||||
const EDITOR_KEY = Symbol('editor-key');
|
||||
|
||||
@ -523,15 +522,16 @@ onMounted(async () => {
|
||||
// 开发环境启用电梯模拟数据
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('[开发环境] 启动电梯状态模拟数据');
|
||||
stopElevatorMock = createElevatorMockData();
|
||||
|
||||
// 推送指定的电梯数据
|
||||
console.log('[开发环境] 推送指定电梯数据: 1998661793706377218');
|
||||
// 使用后端字段格式
|
||||
elevatorStore.handleElevatorWebSocketData({
|
||||
id: '1998661793706377218',
|
||||
type: 102,
|
||||
status: ElevatorStatus.OPENING,
|
||||
floor: 5,
|
||||
elevatorFloor: 5,
|
||||
elevatorDirection: 1, // 停止
|
||||
elevatorFrontDoorStatus: 2, // 正在开关
|
||||
isConnected: true
|
||||
});
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { get } from 'lodash-es';
|
||||
|
||||
import doorClosedUrl from '../../assets/icons/png/doorClosed.png';
|
||||
import doorOpenUrl from '../../assets/icons/png/doorOpening.png';
|
||||
// 直接使用后端字段,不需要导入枚举
|
||||
import colorConfig from '../color/color-config.service';
|
||||
const __doorImgOpen = new Image();
|
||||
__doorImgOpen.src = (doorOpenUrl as unknown as string) || '';
|
||||
@ -52,6 +53,278 @@ function drawDisconnectedIcon(ctx: CanvasRenderingContext2D, cx: number, cy: num
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制电梯点的动态效果 - 矩形框架动画系统
|
||||
* @param ctx Canvas上下文
|
||||
* @param pen 画笔对象
|
||||
* @param elevatorData 电梯数据(包含后端字段)
|
||||
* @param time 当前时间戳(毫秒)
|
||||
*/
|
||||
function drawElevatorAnimation(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
pen: MapPen,
|
||||
elevatorData: any,
|
||||
time: number
|
||||
): void {
|
||||
// 提取后端字段
|
||||
const {
|
||||
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; // 角装饰宽度
|
||||
|
||||
// 动画框架的四个角坐标
|
||||
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 }
|
||||
};
|
||||
|
||||
// 检查是否启用LOD
|
||||
const lodEnabled = (pen.calculative as any)?.lodEnabled || false;
|
||||
|
||||
// 辅助函数:绘制矩形框架的角装饰
|
||||
const drawFrameCorners = (color: string, alpha: number, animated: boolean = true) => {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = color;
|
||||
ctx.fillStyle = color;
|
||||
ctx.lineWidth = cornerWidth;
|
||||
ctx.lineCap = 'square';
|
||||
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);
|
||||
}
|
||||
|
||||
ctx.strokeRect(
|
||||
frameCorners.topLeft.x,
|
||||
frameCorners.topLeft.y,
|
||||
frameCorners.bottomRight.x - frameCorners.topLeft.x,
|
||||
frameCorners.bottomRight.y - frameCorners.topLeft.y
|
||||
);
|
||||
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
|
||||
// 辅助函数:绘制方向箭头(在框架外部)
|
||||
const drawDirectionArrows = (direction: 'up' | 'down', color: string) => {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = color;
|
||||
ctx.fillStyle = color;
|
||||
ctx.lineWidth = 3;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
const arrowSize = 8;
|
||||
const arrowOffset = 15; // 箭头距离框架的距离
|
||||
const animProgress = (Math.sin(time * 0.003) + 1) * 0.5;
|
||||
|
||||
if (direction === 'up') {
|
||||
// 上行箭头:在框架上方
|
||||
const arrowY = frameCorners.topLeft.y - arrowOffset - animProgress * 5;
|
||||
const centerX = x + w / 2;
|
||||
|
||||
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.closePath();
|
||||
ctx.fill();
|
||||
} else {
|
||||
// 下行箭头:在框架下方
|
||||
const arrowY = frameCorners.bottomLeft.y + arrowOffset + animProgress * 5;
|
||||
const centerX = x + w / 2;
|
||||
|
||||
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.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
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模式下不显示文本
|
||||
|
||||
ctx.save();
|
||||
ctx.fillStyle = color;
|
||||
ctx.font = '10px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.globalAlpha = 0.9;
|
||||
|
||||
// 在图片下方显示状态文本
|
||||
ctx.fillText(text, x + w / 2, frameCorners.bottomLeft.y + 5);
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
// 辅助函数:绘制连接状态指示器
|
||||
const drawConnectionIndicator = () => {
|
||||
if (!isConnected) {
|
||||
ctx.save();
|
||||
const pulseIntensity = 0.5 + Math.sin(time * 0.003) * 0.5;
|
||||
|
||||
// 断连图标在右上角
|
||||
ctx.fillStyle = `rgba(255, 59, 48, ${pulseIntensity})`;
|
||||
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.stroke();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
// 状态渲染逻辑
|
||||
if (!isConnected) {
|
||||
// 离线状态:红色闪烁角 + 断连图标
|
||||
drawFrameCorners('rgba(255, 59, 48, 0.8)', 0.8, true);
|
||||
drawConnectionIndicator();
|
||||
drawStatusText('离线', '#FF3B30');
|
||||
} 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)');
|
||||
}
|
||||
drawStatusText('开门中', '#34C759');
|
||||
break;
|
||||
|
||||
case 3: // 门已开
|
||||
// 蓝色呼吸框架
|
||||
drawStatusText('门已开', '#007AFF');
|
||||
break;
|
||||
|
||||
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');
|
||||
} 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');
|
||||
} else { // 停止
|
||||
// 金色静态框架
|
||||
drawFullFrame('rgba(255, 204, 0, 0.6)', 0.6);
|
||||
drawStatusText('停止', '#FFCC00');
|
||||
}
|
||||
break;
|
||||
|
||||
default: // 未知状态
|
||||
// 灰色静态框架
|
||||
drawFullFrame('rgba(142, 142, 147, 0.5)', 0.5);
|
||||
drawStatusText('未知', '#8E8E93');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制自动门点断连状态的脉冲效果
|
||||
* @param ctx Canvas上下文
|
||||
@ -285,6 +558,15 @@ export function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const largeThemeStrokeColor = get(theme, active ? 'point-l.strokeActive' : 'point-l.stroke');
|
||||
ctx.strokeStyle = statusStyle ?? (largeTypeStrokeColor || largeGeneralStrokeColor || largeThemeStrokeColor || '');
|
||||
ctx.stroke();
|
||||
|
||||
// 电梯点:添加动画效果
|
||||
if (type === MapPointType.电梯点) {
|
||||
// 直接传入包含后端字段的 pen.point 数据
|
||||
if (pen.point) {
|
||||
const currentTime = performance.now();
|
||||
drawElevatorAnimation(ctx, pen, pen.point, currentTime);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@ -7,38 +7,32 @@ import type { MapPen } from '@api/map';
|
||||
import { MapPointType } from '@api/map';
|
||||
import type { EditorService } from '@core/editor.service';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, markRaw, ref } from 'vue';
|
||||
import { markRaw, ref } from 'vue';
|
||||
|
||||
// 电梯状态枚举
|
||||
export enum ElevatorStatus {
|
||||
IDLE = 0, // 静止
|
||||
OPENING = 1, // 开门中
|
||||
CLOSING = 2, // 关门中
|
||||
MOVING_UP = 3, // 上行
|
||||
MOVING_DOWN = 4, // 下行
|
||||
DOOR_OPEN = 5, // 门已开
|
||||
DOOR_CLOSED = 6, // 门已关
|
||||
FAULT = 7, // 故障
|
||||
OFFLINE = 8, // 离线
|
||||
}
|
||||
// 电梯楼层 (0-未知)
|
||||
// 电梯方向 (0-未知,1-停止,2-向上,3-向下)
|
||||
// 电梯前门状态(0-未知,1-关,2-正在开关,3-开)
|
||||
// 注意:这些字段定义直接来自后端,不做任何修改
|
||||
|
||||
// 电梯数据接口
|
||||
// 电梯数据接口(直接使用后端字段)
|
||||
export interface ElevatorData {
|
||||
deviceId: string; // 设备ID
|
||||
status: ElevatorStatus; // 当前状态
|
||||
floor?: number; // 当前楼层
|
||||
isConnected: boolean; // 是否连接
|
||||
lastUpdate: number; // 最后更新时间戳
|
||||
penId?: string; // 对应的画布点位ID
|
||||
deviceId: string; // 设备ID
|
||||
elevatorFloor: number; // 电梯楼层(0-未知)
|
||||
elevatorDirection: number; // 电梯方向(0-未知,1-停止,2-向上,3-向下)
|
||||
elevatorFrontDoorStatus: number; // 电梯前门状态(0-未知,1-关,2-正在开关,3-开)
|
||||
isConnected: boolean; // 是否连接
|
||||
lastUpdate: number; // 最后更新时间戳
|
||||
penId?: string; // 对应的画布点位ID
|
||||
}
|
||||
|
||||
// WebSocket推送的电梯数据接口
|
||||
// WebSocket推送的电梯数据接口(对应后端格式)
|
||||
export interface ElevatorWebSocketData {
|
||||
id: string; // 设备ID
|
||||
type: 102; // 电梯类型标识
|
||||
status: ElevatorStatus; // 电梯状态
|
||||
floor?: number; // 当前楼层
|
||||
isConnected: boolean; // 连接状态
|
||||
id: string; // 设备ID
|
||||
type: 102; // 电梯类型标识
|
||||
elevatorFloor: number; // 电梯楼层(0-未知)
|
||||
elevatorDirection: number; // 电梯方向(0-未知,1-停止,2-向上,3-向下)
|
||||
elevatorFrontDoorStatus: number; // 电梯前门状态(0-未知,1-关,2-正在开关,3-开)
|
||||
isConnected: boolean; // 连接状态
|
||||
}
|
||||
|
||||
// 电梯点映射信息
|
||||
@ -54,49 +48,9 @@ export const useElevatorStore = defineStore('elevator', () => {
|
||||
const elevatorPoints = ref<Map<string, ElevatorPoint>>(new Map());
|
||||
const editorService = ref<EditorService | null>(null);
|
||||
|
||||
|
||||
|
||||
// ========== 计算属性 ==========
|
||||
|
||||
// 获取所有电梯数据
|
||||
const allElevators = computed(() => Array.from(elevators.value.values()));
|
||||
|
||||
// 获取在线电梯数量
|
||||
const onlineElevatorsCount = computed(() =>
|
||||
allElevators.value.filter(e => e.isConnected).length
|
||||
);
|
||||
|
||||
// 获取离线电梯数量
|
||||
const offlineElevatorsCount = computed(() =>
|
||||
allElevators.value.filter(e => !e.isConnected).length
|
||||
);
|
||||
|
||||
// 获取故障电梯数量
|
||||
const faultElevatorsCount = computed(() =>
|
||||
allElevators.value.filter(e => e.status === ElevatorStatus.FAULT).length
|
||||
);
|
||||
|
||||
// 按状态分组的电梯
|
||||
const elevatorsByStatus = computed(() => {
|
||||
const groups: Record<ElevatorStatus, ElevatorData[]> = {} as any;
|
||||
|
||||
// 初始化所有状态
|
||||
Object.values(ElevatorStatus).forEach(status => {
|
||||
if (typeof status === 'number') {
|
||||
groups[status] = [];
|
||||
}
|
||||
});
|
||||
|
||||
// 分组
|
||||
allElevators.value.forEach(elevator => {
|
||||
groups[elevator.status].push(elevator);
|
||||
});
|
||||
|
||||
return groups;
|
||||
});
|
||||
|
||||
// ========== 方法 ==========
|
||||
|
||||
|
||||
/**
|
||||
* 设置编辑器服务
|
||||
* 注意:此方法只保存编辑器引用,不会立即构建映射
|
||||
@ -151,7 +105,13 @@ export const useElevatorStore = defineStore('elevator', () => {
|
||||
* 处理WebSocket推送的电梯数据
|
||||
*/
|
||||
const handleElevatorWebSocketData = (data: ElevatorWebSocketData) => {
|
||||
const { id: deviceId, status, floor, isConnected } = data;
|
||||
const {
|
||||
id: deviceId,
|
||||
elevatorFloor,
|
||||
elevatorDirection,
|
||||
elevatorFrontDoorStatus,
|
||||
isConnected
|
||||
} = data;
|
||||
|
||||
// 获取或创建电梯数据
|
||||
let elevatorData = elevators.value.get(deviceId);
|
||||
@ -160,16 +120,19 @@ export const useElevatorStore = defineStore('elevator', () => {
|
||||
if (!elevatorData) {
|
||||
elevatorData = {
|
||||
deviceId,
|
||||
status: ElevatorStatus.IDLE,
|
||||
elevatorFloor: 0,
|
||||
elevatorDirection: 0, // 未知
|
||||
elevatorFrontDoorStatus: 0, // 未知
|
||||
isConnected: false,
|
||||
lastUpdate: Date.now(),
|
||||
penId: elevatorPoint?.penId
|
||||
};
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
elevatorData.status = isConnected ? status : ElevatorStatus.OFFLINE;
|
||||
elevatorData.floor = floor;
|
||||
// 更新数据(直接使用后端字段)
|
||||
elevatorData.elevatorFloor = elevatorFloor;
|
||||
elevatorData.elevatorDirection = elevatorDirection;
|
||||
elevatorData.elevatorFrontDoorStatus = elevatorFrontDoorStatus;
|
||||
elevatorData.isConnected = isConnected;
|
||||
elevatorData.lastUpdate = Date.now();
|
||||
|
||||
@ -180,6 +143,7 @@ export const useElevatorStore = defineStore('elevator', () => {
|
||||
|
||||
// 存储到Map
|
||||
elevators.value.set(deviceId, elevatorData);
|
||||
|
||||
// 更新画布上的电梯点显示
|
||||
if (elevatorData.penId && editorService.value) {
|
||||
updateElevatorPointDisplay(elevatorData);
|
||||
@ -187,124 +151,152 @@ export const useElevatorStore = defineStore('elevator', () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新画布上电梯点的显示
|
||||
* 更新画布上电梯点的显示 - 增强版
|
||||
*/
|
||||
const updateElevatorPointDisplay = (elevatorData: ElevatorData) => {
|
||||
if (!editorService.value || !elevatorData.penId) return;
|
||||
|
||||
const penId = elevatorData.penId;
|
||||
|
||||
// 获取状态对应的颜色和图标
|
||||
const { color, iconPath } = getElevatorDisplay(elevatorData.status, elevatorData.isConnected);
|
||||
// 获取状态对应的颜色、图标和动画配置
|
||||
const displayConfig = getElevatorDisplay(elevatorData);
|
||||
|
||||
// 基础更新数据
|
||||
const updateData: any = {
|
||||
// 设置图标
|
||||
image: iconPath,
|
||||
image: displayConfig.iconPath,
|
||||
// 更新点位信息
|
||||
point: {
|
||||
...((editorService.value.getPenById(penId) as any)?.point || {}),
|
||||
elevatorStatus: elevatorData.status,
|
||||
isConnected: elevatorData.isConnected,
|
||||
currentFloor: elevatorData.floor,
|
||||
currentFloor: elevatorData.elevatorFloor,
|
||||
lastUpdate: elevatorData.lastUpdate,
|
||||
// 使用颜色区分状态
|
||||
color
|
||||
}
|
||||
// 保存原始后端数据
|
||||
elevatorDirection: elevatorData.elevatorDirection,
|
||||
elevatorFrontDoorStatus: elevatorData.elevatorFrontDoorStatus,
|
||||
// 使用新的颜色系统
|
||||
color: displayConfig.color,
|
||||
// 动画配置
|
||||
animationClass: displayConfig.animationClass,
|
||||
priority: displayConfig.priority
|
||||
},
|
||||
// 增强的样式配置
|
||||
statusStyle: displayConfig.color
|
||||
};
|
||||
|
||||
// 更新电梯点
|
||||
editorService.value.updatePen(penId, updateData, false);
|
||||
try {
|
||||
// 更新电梯点
|
||||
editorService.value.updatePen(penId, updateData, false);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error(`🛗 更新电梯点显示失败 (${elevatorData.deviceId}):`, error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取电梯显示配置(颜色、图标)
|
||||
* 基于后端字段直接判断,使用现代化设计系统
|
||||
*/
|
||||
const getElevatorDisplay = (status: ElevatorStatus, isConnected: boolean) => {
|
||||
const getElevatorDisplay = (elevatorData: ElevatorData) => {
|
||||
// 生成图标路径的辅助函数
|
||||
const getIconPath = (theme: string) => `${import.meta.env.BASE_URL}/point/elevator-${theme}.svg`;
|
||||
|
||||
// 如果离线,显示灰色
|
||||
if (!isConnected) {
|
||||
// 现代化颜色配置系统
|
||||
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: '#999999',
|
||||
color: colors.offline,
|
||||
iconPath: getIconPath('offline'),
|
||||
text: '离线'
|
||||
text: '离线',
|
||||
priority: 'high',
|
||||
animationClass: 'pulse-error'
|
||||
};
|
||||
}
|
||||
|
||||
const statusMap = {
|
||||
[ElevatorStatus.IDLE]: {
|
||||
color: '#1890FF',
|
||||
iconPath: getIconPath('idle'),
|
||||
text: '静止'
|
||||
},
|
||||
[ElevatorStatus.OPENING]: {
|
||||
color: '#52C41A',
|
||||
iconPath: getIconPath('opening'),
|
||||
text: '开门中'
|
||||
},
|
||||
[ElevatorStatus.CLOSING]: {
|
||||
color: '#FA8C16',
|
||||
iconPath: getIconPath('closing'),
|
||||
text: '关门中'
|
||||
},
|
||||
[ElevatorStatus.MOVING_UP]: {
|
||||
color: '#722ED1',
|
||||
iconPath: getIconPath('up'),
|
||||
text: '上行中'
|
||||
},
|
||||
[ElevatorStatus.MOVING_DOWN]: {
|
||||
color: '#13C2C2',
|
||||
iconPath: getIconPath('down'),
|
||||
text: '下行中'
|
||||
},
|
||||
[ElevatorStatus.DOOR_OPEN]: {
|
||||
color: '#52C41A',
|
||||
iconPath: getIconPath('door-open'),
|
||||
text: '门已开'
|
||||
},
|
||||
[ElevatorStatus.DOOR_CLOSED]: {
|
||||
color: '#1890FF',
|
||||
iconPath: getIconPath('door-closed'),
|
||||
text: '门已关'
|
||||
},
|
||||
[ElevatorStatus.FAULT]: {
|
||||
color: '#FF4D4F',
|
||||
iconPath: getIconPath('fault'),
|
||||
text: '故障'
|
||||
},
|
||||
// 根据后端字段判断显示状态
|
||||
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'
|
||||
};
|
||||
|
||||
return statusMap[status] || statusMap[ElevatorStatus.IDLE];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取单个电梯数据
|
||||
*/
|
||||
const getElevatorById = (deviceId: string): ElevatorData | undefined => {
|
||||
return elevators.value.get(deviceId);
|
||||
};
|
||||
|
||||
/**
|
||||
* 手动更新电梯状态
|
||||
*/
|
||||
const updateElevatorStatus = (
|
||||
deviceId: string,
|
||||
status: ElevatorStatus,
|
||||
isConnected: boolean = true
|
||||
) => {
|
||||
const mockData: ElevatorWebSocketData = {
|
||||
id: deviceId,
|
||||
type: 102,
|
||||
status,
|
||||
isConnected
|
||||
};
|
||||
|
||||
handleElevatorWebSocketData(mockData);
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新电梯映射(场景变化时调用)
|
||||
@ -320,29 +312,8 @@ export const useElevatorStore = defineStore('elevator', () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除所有电梯数据
|
||||
*/
|
||||
const clearAllData = () => {
|
||||
elevators.value.clear();
|
||||
console.log('🛗 电梯数据已清除');
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取电梯统计信息
|
||||
*/
|
||||
const getStatistics = () => {
|
||||
return {
|
||||
total: allElevators.value.length,
|
||||
online: onlineElevatorsCount.value,
|
||||
offline: offlineElevatorsCount.value,
|
||||
fault: faultElevatorsCount.value,
|
||||
byStatus: Object.entries(elevatorsByStatus.value).reduce((acc, [status, list]) => {
|
||||
acc[Number(status)] = list.length;
|
||||
return acc;
|
||||
}, {} as Record<number, number>)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
@ -351,22 +322,10 @@ export const useElevatorStore = defineStore('elevator', () => {
|
||||
elevators,
|
||||
elevatorPoints,
|
||||
editorService,
|
||||
|
||||
// 计算属性
|
||||
allElevators,
|
||||
onlineElevatorsCount,
|
||||
offlineElevatorsCount,
|
||||
faultElevatorsCount,
|
||||
elevatorsByStatus,
|
||||
|
||||
// 方法
|
||||
setEditorService,
|
||||
handleElevatorWebSocketData,
|
||||
getElevatorById,
|
||||
updateElevatorStatus,
|
||||
refreshMapping,
|
||||
clearAllData,
|
||||
getStatistics,
|
||||
getElevatorDisplay
|
||||
};
|
||||
});
|
||||
@ -1,129 +0,0 @@
|
||||
/**
|
||||
* 电梯状态模拟工具
|
||||
* 用于开发和测试电梯状态功能
|
||||
*/
|
||||
|
||||
import { ElevatorStatus,useElevatorStore } from '../stores/elevator.store';
|
||||
|
||||
export const createElevatorMockData = () => {
|
||||
const elevatorStore = useElevatorStore();
|
||||
|
||||
// 模拟的设备ID列表(与API返回的设备ID格式保持一致)
|
||||
const mockDeviceIds = [
|
||||
'2',
|
||||
'4',
|
||||
'6',
|
||||
'8'
|
||||
];
|
||||
|
||||
// 缓存上次的状态,避免无意义的更新
|
||||
const lastStates = new Map<string, { status: ElevatorStatus; floor: number; isConnected: boolean }>();
|
||||
|
||||
// 生成模拟数据
|
||||
const generateMockData = () => {
|
||||
mockDeviceIds.forEach((deviceId, index) => {
|
||||
// 为每个电梯生成不同的状态
|
||||
let status: ElevatorStatus;
|
||||
let floor: number;
|
||||
|
||||
// 70%概率保持当前状态,30%概率改变状态
|
||||
if (Math.random() > 0.7) {
|
||||
switch (index % 4) {
|
||||
case 0:
|
||||
status = ElevatorStatus.MOVING_UP;
|
||||
break;
|
||||
case 1:
|
||||
status = ElevatorStatus.MOVING_DOWN;
|
||||
break;
|
||||
case 2:
|
||||
status = ElevatorStatus.OPENING;
|
||||
break;
|
||||
default:
|
||||
status = ElevatorStatus.IDLE;
|
||||
}
|
||||
} else {
|
||||
// 保持当前状态
|
||||
const last = lastStates.get(deviceId);
|
||||
status = last ? last.status : (index % 4 === 0 ? ElevatorStatus.MOVING_UP : index % 4 === 1 ? ElevatorStatus.MOVING_DOWN : index % 4 === 2 ? ElevatorStatus.OPENING : ElevatorStatus.IDLE);
|
||||
}
|
||||
|
||||
// 95%概率在线,5%概率离线
|
||||
const isConnected = Math.random() > 0.05;
|
||||
|
||||
// 只有在线时才更新楼层
|
||||
if (isConnected && Math.random() > 0.5) {
|
||||
// 30%概率改变楼层
|
||||
const last = lastStates.get(deviceId);
|
||||
if (Math.random() > 0.7) {
|
||||
floor = last ? Math.min(10, Math.max(1, last.floor + (Math.random() > 0.5 ? 1 : -1))) : Math.floor(Math.random() * 10) + 1;
|
||||
} else {
|
||||
floor = last ? last.floor : Math.floor(Math.random() * 10) + 1;
|
||||
}
|
||||
} else {
|
||||
floor = lastStates.get(deviceId)?.floor || Math.floor(Math.random() * 10) + 1;
|
||||
}
|
||||
|
||||
// 检查状态是否真的发生了变化
|
||||
const lastState = lastStates.get(deviceId);
|
||||
if (lastState &&
|
||||
lastState.status === status &&
|
||||
lastState.floor === floor &&
|
||||
lastState.isConnected === isConnected) {
|
||||
// 状态没有变化,跳过更新
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
lastStates.set(deviceId, { status, floor, isConnected });
|
||||
|
||||
// 偶尔模拟故障(5%概率)
|
||||
if (Math.random() > 0.95 && isConnected) {
|
||||
status = ElevatorStatus.FAULT;
|
||||
}
|
||||
|
||||
elevatorStore.handleElevatorWebSocketData({
|
||||
id: deviceId,
|
||||
type: 102,
|
||||
status: isConnected ? status : ElevatorStatus.OFFLINE,
|
||||
floor,
|
||||
isConnected
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 初始生成一次数据
|
||||
generateMockData();
|
||||
|
||||
// 设置定时器,每8-12秒更新一次数据(降低频率)
|
||||
const interval = setInterval(() => {
|
||||
generateMockData();
|
||||
}, Math.random() * 4000 + 8000); // 8-12秒随机间隔
|
||||
|
||||
console.log('🛗 电梯模拟数据已启动,设备ID:', mockDeviceIds);
|
||||
|
||||
// 返回停止函数
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
lastStates.clear();
|
||||
console.log('🛗 电梯模拟数据已停止');
|
||||
};
|
||||
};
|
||||
|
||||
// 创建单个电梯的测试数据
|
||||
export const createSingleElevatorData = (deviceId: string, status?: ElevatorStatus) => {
|
||||
const elevatorStore = useElevatorStore();
|
||||
|
||||
elevatorStore.handleElevatorWebSocketData({
|
||||
id: deviceId,
|
||||
type: 102,
|
||||
status: status || ElevatorStatus.IDLE,
|
||||
floor: Math.floor(Math.random() * 10) + 1,
|
||||
isConnected: true
|
||||
});
|
||||
};
|
||||
|
||||
// 清除所有电梯数据
|
||||
export const clearElevatorData = () => {
|
||||
const elevatorStore = useElevatorStore();
|
||||
elevatorStore.clearAllData();
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user