web-map/src/stores/elevator.store.ts

372 lines
9.6 KiB
TypeScript
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.

/**
* 电梯状态管理 Store
* 使用 Pinia 管理电梯的全局状态
*/
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';
// 电梯状态枚举
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, // 离线
}
// 电梯数据接口
export interface ElevatorData {
deviceId: string; // 设备ID
status: ElevatorStatus; // 当前状态
floor?: number; // 当前楼层
isConnected: boolean; // 是否连接
lastUpdate: number; // 最后更新时间戳
penId?: string; // 对应的画布点位ID
}
// WebSocket推送的电梯数据接口
export interface ElevatorWebSocketData {
id: string; // 设备ID
type: 102; // 电梯类型标识
status: ElevatorStatus; // 电梯状态
floor?: number; // 当前楼层
isConnected: boolean; // 连接状态
}
// 电梯点映射信息
export interface ElevatorPoint {
penId: string;
deviceId: string;
pointType: MapPointType.电梯点;
}
export const useElevatorStore = defineStore('elevator', () => {
// ========== 状态定义 ==========
const elevators = ref<Map<string, ElevatorData>>(new Map());
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;
});
// ========== 方法 ==========
/**
* 设置编辑器服务
* 注意:此方法只保存编辑器引用,不会立即构建映射
* 需要在场景加载完成后手动调用 buildElevatorMapping() 或 refreshMapping()
*/
const setEditorService = (editor: EditorService) => {
editorService.value = markRaw(editor);
console.log('🛗 电梯Store: 编辑器服务已设置,等待场景加载后构建映射');
};
/**
* 构建设备ID到电梯点的映射
*/
const buildElevatorMapping = () => {
if (!editorService.value) return;
elevatorPoints.value.clear();
const pens = editorService.value.data().pens;
pens.forEach((pen: MapPen) => {
if (
pen.name === 'point' &&
pen.point?.type === MapPointType. &&
pen.point?.deviceId
) {
const elevatorPoint: ElevatorPoint = {
penId: pen.id,
deviceId: pen.point.deviceId,
pointType: MapPointType.电梯点
};
elevatorPoints.value.set(pen.point.deviceId, elevatorPoint);
}
});
// 打印详细的映射列表
const mappingList = Array.from(elevatorPoints.value.entries()).map(([deviceId, point]) => ({
设备ID: deviceId,
点位ID: point.penId,
: '电梯点'
}));
console.log('🛗 电梯映射构建完成:', {
映射总数: elevatorPoints.value.size,
设备ID列表: Array.from(elevatorPoints.value.keys())
});
console.table(mappingList);
};
/**
* 处理WebSocket推送的电梯数据
*/
const handleElevatorWebSocketData = (data: ElevatorWebSocketData) => {
const { id: deviceId, status, floor, isConnected } = data;
// 获取或创建电梯数据
let elevatorData = elevators.value.get(deviceId);
const elevatorPoint = elevatorPoints.value.get(deviceId);
if (!elevatorData) {
elevatorData = {
deviceId,
status: ElevatorStatus.IDLE,
isConnected: false,
lastUpdate: Date.now(),
penId: elevatorPoint?.penId
};
}
// 更新数据
elevatorData.status = isConnected ? status : ElevatorStatus.OFFLINE;
elevatorData.floor = floor;
elevatorData.isConnected = isConnected;
elevatorData.lastUpdate = Date.now();
// 如果找到对应的点位也更新penId
if (elevatorPoint && !elevatorData.penId) {
elevatorData.penId = elevatorPoint.penId;
}
// 存储到Map
elevators.value.set(deviceId, elevatorData);
// 更新画布上的电梯点显示
if (elevatorData.penId && editorService.value) {
updateElevatorPointDisplay(elevatorData);
}
};
/**
* 更新画布上电梯点的显示
*/
const updateElevatorPointDisplay = (elevatorData: ElevatorData) => {
if (!editorService.value || !elevatorData.penId) return;
const penId = elevatorData.penId;
// 获取状态对应的颜色和图标
const { color, iconPath } = getElevatorDisplay(elevatorData.status, elevatorData.isConnected);
// 基础更新数据
const updateData: any = {
// 设置图标
image: iconPath,
// 更新点位信息
point: {
...((editorService.value.getPenById(penId) as any)?.point || {}),
elevatorStatus: elevatorData.status,
isConnected: elevatorData.isConnected,
currentFloor: elevatorData.floor,
lastUpdate: elevatorData.lastUpdate,
// 使用颜色区分状态
color
}
};
// 更新电梯点
editorService.value.updatePen(penId, updateData, false);
};
/**
* 获取电梯显示配置(颜色、图标)
*/
const getElevatorDisplay = (status: ElevatorStatus, isConnected: boolean) => {
// 生成图标路径的辅助函数
const getIconPath = (theme: string) => `${import.meta.env.BASE_URL}/point/elevator-${theme}.svg`;
// 如果离线,显示灰色
if (!isConnected) {
return {
color: '#999999',
iconPath: getIconPath('offline'),
text: '离线'
};
}
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: '故障'
},
};
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);
};
/**
* 刷新电梯映射(场景变化时调用)
*/
const refreshMapping = () => {
buildElevatorMapping();
// 重新更新所有电梯的显示
elevators.value.forEach(elevatorData => {
if (elevatorData.penId) {
updateElevatorPointDisplay(elevatorData);
}
});
};
/**
* 清除所有电梯数据
*/
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>)
};
};
return {
// 状态
elevators,
elevatorPoints,
editorService,
// 计算属性
allElevators,
onlineElevatorsCount,
offlineElevatorsCount,
faultElevatorsCount,
elevatorsByStatus,
// 方法
setEditorService,
handleElevatorWebSocketData,
getElevatorById,
updateElevatorStatus,
refreshMapping,
clearAllData,
getStatistics,
getElevatorDisplay
};
});