diff --git a/src/apis/map/type.ts b/src/apis/map/type.ts index 3aa7e18..25403dd 100644 --- a/src/apis/map/type.ts +++ b/src/apis/map/type.ts @@ -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 diff --git a/src/components/card/point-detail-card.vue b/src/components/card/point-detail-card.vue index 14213ff..fb9c869 100644 --- a/src/components/card/point-detail-card.vue +++ b/src/components/card/point-detail-card.vue @@ -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>; @@ -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'; // 默认 - 蓝色 }; @@ -218,14 +238,14 @@ const getElevatorStatusColor = (status: ElevatorStatus, isConnected: boolean): s {{ $t('当前楼层') }} {{ point.currentFloor }}F - + {{ $t('电梯状态') }} - {{ getElevatorStatusText(point.elevatorStatus) }} + {{ getElevatorStatusText(point) }} diff --git a/src/pages/movement-supervision.vue b/src/pages/movement-supervision.vue index 74085c1..2f98d7d 100644 --- a/src/pages/movement-supervision.vue +++ b/src/pages/movement-supervision.vue @@ -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 }); diff --git a/src/services/editor/editor-drawers.ts b/src/services/editor/editor-drawers.ts index 711e4f5..8042f31 100644 --- a/src/services/editor/editor-drawers.ts +++ b/src/services/editor/editor-drawers.ts @@ -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: diff --git a/src/stores/elevator.store.ts b/src/stores/elevator.store.ts index f61960f..59b5882 100644 --- a/src/stores/elevator.store.ts +++ b/src/stores/elevator.store.ts @@ -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>(new Map()); const editorService = ref(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 = {} 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) - }; - }; + @@ -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 }; }); \ No newline at end of file diff --git a/src/utils/elevator-mock.ts b/src/utils/elevator-mock.ts deleted file mode 100644 index 36aa4fa..0000000 --- a/src/utils/elevator-mock.ts +++ /dev/null @@ -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(); - - // 生成模拟数据 - 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(); -}; \ No newline at end of file