feat: 扩展自动门点功能,新增光圈绘制与状态更新机制,优化性能并引入映射缓存以提升数据处理效率
This commit is contained in:
parent
94dccba404
commit
26af765dc8
@ -630,3 +630,149 @@ export class EditorService extends Meta2d {
|
||||
5. **状态验证不足**: 缺乏对接收数据的有效性验证
|
||||
|
||||
通过实施全局状态管理、WebSocket连接复用、状态缓存机制和坐标转换优化等解决方案,可以有效解决这些问题,确保多页面间机器人位置的一致性。
|
||||
|
||||
## 8. 自动门点光圈功能扩展
|
||||
|
||||
### 8.1 功能概述
|
||||
|
||||
在机器人光圈绘制的基础上,新增了自动门点的光圈绘制功能。当WebSocket推送自动门点状态数据时,系统会根据设备状态自动绘制相应颜色的光圈。
|
||||
|
||||
### 8.2 数据结构
|
||||
|
||||
WebSocket推送的自动门点数据格式:
|
||||
|
||||
```typescript
|
||||
{
|
||||
"gid": "",
|
||||
"id": "172.31.57.55-502-17", // 设备ID,用于匹配地图中的自动门点
|
||||
"label": "AutoD01",
|
||||
"type": 99, // 标识为自动门点
|
||||
"deviceStatus": 0, // 设备状态:0=关门,1=开门
|
||||
"active": true,
|
||||
// ... 其他字段
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 实现细节
|
||||
|
||||
#### 主题颜色配置
|
||||
|
||||
在 `editor-dark.json` 和 `editor-light.json` 中添加自动门点颜色配置:
|
||||
|
||||
```json
|
||||
"autoDoor": {
|
||||
"stroke-closed": "#FF4D4F99", // 关门状态边框(红色)
|
||||
"fill-closed": "#FF4D4F33", // 关门状态填充(红色)
|
||||
"stroke-open": "#1890FF99", // 开门状态边框(蓝色)
|
||||
"fill-open": "#1890FF33" // 开门状态填充(蓝色)
|
||||
}
|
||||
```
|
||||
|
||||
#### 数据模型扩展
|
||||
|
||||
在 `MapPointInfo` 接口中新增字段:
|
||||
|
||||
```typescript
|
||||
interface MapPointInfo {
|
||||
// ... 现有字段
|
||||
deviceStatus?: number; // 设备状态(0=关门,1=开门)
|
||||
active?: boolean; // 是否激活状态,控制光圈显示
|
||||
}
|
||||
```
|
||||
|
||||
#### 绘制函数修改
|
||||
|
||||
在 `drawPoint` 函数中添加自动门点光圈绘制逻辑:
|
||||
|
||||
```typescript
|
||||
// 为自动门点绘制光圈
|
||||
if (type === MapPointType.自动门点 && pointActive && deviceStatus !== undefined) {
|
||||
const ox = x + w / 2;
|
||||
const oy = y + h / 2;
|
||||
const haloRadius = Math.max(w, h) / 2 + 10;
|
||||
|
||||
ctx.ellipse(ox, oy, haloRadius, haloRadius, 0, 0, Math.PI * 2);
|
||||
|
||||
// 根据设备状态选择颜色
|
||||
if (deviceStatus === 0) {
|
||||
// 关门状态 - 红色
|
||||
ctx.fillStyle = get(theme, 'autoDoor.fill-closed') ?? '#FF4D4F33';
|
||||
ctx.strokeStyle = get(theme, 'autoDoor.stroke-closed') ?? '#FF4D4F99';
|
||||
} else {
|
||||
// 开门状态 - 蓝色
|
||||
ctx.fillStyle = get(theme, 'autoDoor.fill-open') ?? '#1890FF33';
|
||||
ctx.strokeStyle = get(theme, 'autoDoor.stroke-open') ?? '#1890FF99';
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
```
|
||||
|
||||
#### 状态更新方法
|
||||
|
||||
新增 `updateAutoDoorByDeviceId` 方法:
|
||||
|
||||
```typescript
|
||||
public updateAutoDoorByDeviceId(deviceId: string, deviceStatus: number, active = true): void {
|
||||
// 查找匹配的自动门点
|
||||
const autoDoorPoint = this.data().pens.find(
|
||||
(pen) => pen.name === 'point' &&
|
||||
(pen as MapPen).point?.type === MapPointType.自动门点 &&
|
||||
(pen as MapPen).point?.deviceId === deviceId
|
||||
) as MapPen | undefined;
|
||||
|
||||
if (!autoDoorPoint?.id || !autoDoorPoint.point) return;
|
||||
|
||||
// 更新自动门点状态
|
||||
this.updatePen(autoDoorPoint.id, {
|
||||
point: {
|
||||
...autoDoorPoint.point,
|
||||
deviceStatus,
|
||||
active,
|
||||
},
|
||||
}, false);
|
||||
}
|
||||
```
|
||||
|
||||
#### WebSocket数据处理
|
||||
|
||||
修改运动监控组件的WebSocket处理逻辑:
|
||||
|
||||
```typescript
|
||||
ws.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data || '{}');
|
||||
|
||||
// 判断数据类型:type=99为自动门点,其他为机器人
|
||||
if (data.type === 99) {
|
||||
// 自动门点数据处理
|
||||
const { id: deviceId, deviceStatus, active = true } = data;
|
||||
if (deviceId && deviceStatus !== undefined) {
|
||||
latestAutoDoorData.set(deviceId, { deviceId, deviceStatus, active });
|
||||
}
|
||||
} else {
|
||||
// 机器人数据处理
|
||||
const robotData = data as RobotRealtimeInfo;
|
||||
latestRobotData.set(robotData.id, robotData);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 8.4 使用方式
|
||||
|
||||
1. **地图编辑**:在场景编辑器中创建自动门点,并设置对应的设备ID
|
||||
2. **状态监控**:系统自动接收WebSocket推送的自动门点状态数据
|
||||
3. **视觉反馈**:
|
||||
- 关门状态(deviceStatus=0):显示红色光圈
|
||||
- 开门状态(deviceStatus=1):显示蓝色光圈
|
||||
- 无状态数据时:不显示光圈
|
||||
|
||||
### 8.5 技术优势
|
||||
|
||||
1. **复用机器人架构**:充分利用现有的渲染和状态管理机制
|
||||
2. **高性能处理**:采用相同的时间分片和数据缓冲策略
|
||||
3. **类型安全**:完整的TypeScript类型支持
|
||||
4. **主题适配**:支持深色和浅色主题
|
||||
5. **实时性**:与机器人监控相同的实时更新能力
|
||||
|
||||
这种设计展现了系统架构的可扩展性,为未来支持更多设备类型(如电梯、传感器等)奠定了良好的基础。
|
||||
|
@ -31,6 +31,8 @@ export interface MapPointInfo {
|
||||
associatedStorageLocations?: string[]; // 库位名称
|
||||
deviceId?: string; // 设备ID
|
||||
enabled?: 0 | 1; // 是否启用(仅停靠点使用,0=禁用,1=启用)
|
||||
deviceStatus?: number; // 设备状态(仅自动门点使用,0=关门,1=开门)
|
||||
active?: boolean; // 是否激活状态,用于控制光圈显示
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
@ -51,5 +51,11 @@
|
||||
"fill-warning": "#FF851B33",
|
||||
"stroke-fault": "#FF4D4F99",
|
||||
"fill-fault": "#FF4D4F33"
|
||||
},
|
||||
"autoDoor": {
|
||||
"stroke-closed": "#FF4D4F99",
|
||||
"fill-closed": "#FF4D4F33",
|
||||
"stroke-open": "#1890FF99",
|
||||
"fill-open": "#1890FF33"
|
||||
}
|
||||
}
|
||||
|
@ -51,5 +51,11 @@
|
||||
"fill-warning": "#FF851B33",
|
||||
"stroke-fault": "#FF4D4F99",
|
||||
"fill-fault": "#FF4D4F33"
|
||||
},
|
||||
"autoDoor": {
|
||||
"stroke-closed": "#FF4D4F99",
|
||||
"fill-closed": "#FF4D4F33",
|
||||
"stroke-open": "#1890FF99",
|
||||
"fill-open": "#1890FF33"
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import { isNil } from 'lodash-es';
|
||||
import { computed, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { autoDoorSimulationService, type AutoDoorWebSocketData } from '../services/auto-door-simulation.service';
|
||||
|
||||
const EDITOR_KEY = Symbol('editor-key');
|
||||
|
||||
type Props = {
|
||||
@ -84,7 +86,7 @@ const monitorScene = async () => {
|
||||
const frameBudget = 8; // 每一帧的渲染预算,单位:毫秒。保守设置为8ms,为其他任务留出时间。
|
||||
const startTime = performance.now();
|
||||
|
||||
// 在时间预算内,持续处理数据
|
||||
// 在时间预算内,持续处理机器人数据
|
||||
while (performance.now() - startTime < frameBudget && latestRobotData.size > 0) {
|
||||
// 获取并移除 Map 中的第一条数据
|
||||
const entry = latestRobotData.entries().next().value;
|
||||
@ -109,6 +111,9 @@ const monitorScene = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理缓冲的自动门点数据
|
||||
autoDoorSimulationService.processBufferedData(frameBudget, startTime);
|
||||
|
||||
// 请求浏览器在下一次重绘之前再次调用 renderLoop,形成动画循环。
|
||||
// 即使本帧没有处理完所有数据,下一帧也会继续处理剩余的数据。
|
||||
animationFrameId = requestAnimationFrame(renderLoop);
|
||||
@ -117,12 +122,20 @@ const monitorScene = async () => {
|
||||
/**
|
||||
* WebSocket 的 onmessage 事件处理器。
|
||||
* 这个函数只做一件事:接收数据并将其快速存入缓冲区。
|
||||
* 这种“数据与渲染分离”的设计是避免UI阻塞的关键。
|
||||
* 这种"数据与渲染分离"的设计是避免UI阻塞的关键。
|
||||
*/
|
||||
ws.onmessage = (e) => {
|
||||
const data = <RobotRealtimeInfo>JSON.parse(e.data || '{}');
|
||||
// 将最新数据存入 Map,如果已有相同ID的数据,则会被自动覆盖。
|
||||
latestRobotData.set(data.id, data);
|
||||
const data = JSON.parse(e.data || '{}');
|
||||
|
||||
// 判断数据类型:type=99为自动门点,其他为机器人
|
||||
if (data.type === 99) {
|
||||
// 自动门点数据处理
|
||||
autoDoorSimulationService.handleWebSocketData(data as AutoDoorWebSocketData);
|
||||
} else {
|
||||
// 机器人数据处理
|
||||
const robotData = data as RobotRealtimeInfo;
|
||||
latestRobotData.set(robotData.id, robotData);
|
||||
}
|
||||
};
|
||||
|
||||
client.value = ws;
|
||||
@ -158,11 +171,29 @@ onMounted(async () => {
|
||||
storageLocationService.value?.startMonitoring({ interval: 1 });
|
||||
// 自动保存和恢复视图状态
|
||||
await handleAutoSaveAndRestoreViewState();
|
||||
|
||||
// 设置编辑器服务
|
||||
if (editor.value) {
|
||||
autoDoorSimulationService.setEditorService(editor.value);
|
||||
|
||||
// 注释掉模拟逻辑,使用真实WebSocket数据
|
||||
// autoDoorSimulationService.startSimulation({
|
||||
// deviceId: 'test01',
|
||||
// label: 'TestAutoDoor01',
|
||||
// interval: 3000,
|
||||
// initialStatus: 0,
|
||||
// enableLogging: true,
|
||||
// });
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
client.value?.close();
|
||||
storageLocationService.value?.destroy();
|
||||
// 清理自动门点服务(清空缓冲数据)
|
||||
autoDoorSimulationService.clearBufferedData();
|
||||
// 注释掉模拟相关的清理,只保留WebSocket数据处理的清理
|
||||
// autoDoorSimulationService.stopAllSimulations();
|
||||
});
|
||||
//#endregion
|
||||
|
||||
|
388
src/services/auto-door-simulation.service.ts
Normal file
388
src/services/auto-door-simulation.service.ts
Normal file
@ -0,0 +1,388 @@
|
||||
/**
|
||||
* 自动门点服务
|
||||
* 提供自动门点的WebSocket数据处理功能
|
||||
* 注:模拟数据功能已被注释,现在主要用于处理真实WebSocket推送数据
|
||||
*/
|
||||
|
||||
import { type MapPen, MapPointType } from '@api/map';
|
||||
|
||||
import type { EditorService } from './editor.service';
|
||||
|
||||
/**
|
||||
* 自动门点模拟数据配置
|
||||
*/
|
||||
export interface AutoDoorSimulationConfig {
|
||||
/** 设备ID,需要与地图中自动门点的deviceId匹配 */
|
||||
deviceId: string;
|
||||
/** 设备标签 */
|
||||
label?: string;
|
||||
/** 状态切换间隔(毫秒),默认3000ms */
|
||||
interval?: number;
|
||||
/** 初始状态,0=关门,1=开门,默认0 */
|
||||
initialStatus?: 0 | 1;
|
||||
/** 是否启用控制台日志 */
|
||||
enableLogging?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动门点状态数据结构(模拟WebSocket推送的数据格式)
|
||||
*/
|
||||
interface AutoDoorStatusData {
|
||||
gid: string;
|
||||
id: string;
|
||||
label: string;
|
||||
brand: null;
|
||||
type: 99; // 自动门点类型标识
|
||||
ip: null;
|
||||
battery: number;
|
||||
isConnected: boolean;
|
||||
state: number;
|
||||
canOrder: boolean;
|
||||
canStop: null;
|
||||
canControl: boolean;
|
||||
targetPoint: null;
|
||||
deviceStatus: 0 | 1; // 设备状态:0=关门,1=开门
|
||||
x: number;
|
||||
y: number;
|
||||
active: boolean;
|
||||
angle: number;
|
||||
isWaring: null;
|
||||
isFault: null;
|
||||
isLoading: null;
|
||||
path: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket推送的自动门点数据接口
|
||||
*/
|
||||
export interface AutoDoorWebSocketData {
|
||||
gid: string;
|
||||
id: string; // 设备ID
|
||||
label: string;
|
||||
brand: null;
|
||||
type: 99; // 自动门点类型标识
|
||||
ip: null;
|
||||
battery: number;
|
||||
isConnected: boolean;
|
||||
state: number;
|
||||
canOrder: boolean;
|
||||
canStop: null;
|
||||
canControl: boolean;
|
||||
targetPoint: null;
|
||||
deviceStatus: 0 | 1; // 设备状态:0=关门,1=开门
|
||||
x: number;
|
||||
y: number;
|
||||
active: boolean;
|
||||
angle: number;
|
||||
isWaring: null;
|
||||
isFault: null;
|
||||
isLoading: null;
|
||||
path: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动门点服务
|
||||
* 主要用于WebSocket数据处理,模拟功能保留但默认不使用
|
||||
*/
|
||||
export class AutoDoorService {
|
||||
private timers = new Map<string, NodeJS.Timeout>();
|
||||
private statusMap = new Map<string, 0 | 1>();
|
||||
private editorService: EditorService | null = null;
|
||||
private latestAutoDoorData = new Map<string, { deviceId: string; deviceStatus: number; active: boolean }>();
|
||||
|
||||
// 设备ID到自动门点ID的映射缓存,避免每次都遍历查找
|
||||
private deviceIdToPointIdMap = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* 设置编辑器服务实例并初始化自动门点映射
|
||||
* @param editor 编辑器服务实例
|
||||
*/
|
||||
setEditorService(editor: EditorService): void {
|
||||
this.editorService = editor;
|
||||
// 初始化设备ID到点位ID的映射关系
|
||||
this.initializeDeviceMapping();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化设备ID到自动门点ID的映射关系
|
||||
* 在场景加载后调用,建立高效的查找缓存
|
||||
*/
|
||||
private initializeDeviceMapping(): void {
|
||||
if (!this.editorService) {
|
||||
console.warn('⚠️ 编辑器服务未设置,无法初始化自动门点映射');
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空现有映射
|
||||
this.deviceIdToPointIdMap.clear();
|
||||
|
||||
// 遍历所有点位,找出自动门点并建立映射
|
||||
const pens = this.editorService.data().pens;
|
||||
let autoDoorCount = 0;
|
||||
|
||||
pens.forEach((pen) => {
|
||||
if (pen.name === 'point' && (pen as MapPen).point?.type === MapPointType.自动门点) {
|
||||
const deviceId = (pen as MapPen).point?.deviceId;
|
||||
if (deviceId && pen.id) {
|
||||
this.deviceIdToPointIdMap.set(deviceId, pen.id);
|
||||
autoDoorCount++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`🚪 自动门点映射初始化完成: 共找到 ${autoDoorCount} 个自动门点`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动刷新设备映射(当场景发生变化时调用)
|
||||
*/
|
||||
refreshDeviceMapping(): void {
|
||||
this.initializeDeviceMapping();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动自动门点模拟(已停用,保留接口用于兼容性)
|
||||
* 注:当前使用真实WebSocket数据,此方法仅在测试时启用
|
||||
* @param config 模拟配置
|
||||
*/
|
||||
startSimulation(config: AutoDoorSimulationConfig): void {
|
||||
const { deviceId, interval = 3000, initialStatus = 0, enableLogging = true } = config;
|
||||
|
||||
// 如果已经存在相同设备ID的模拟,先停止它
|
||||
this.stopSimulation(deviceId);
|
||||
|
||||
if (enableLogging) {
|
||||
console.log(`🚪 启动自动门点模拟: ${deviceId}`);
|
||||
}
|
||||
|
||||
// 设置初始状态
|
||||
this.statusMap.set(deviceId, initialStatus);
|
||||
|
||||
// 创建定时器
|
||||
const timer = setInterval(() => {
|
||||
const currentStatus = this.statusMap.get(deviceId) ?? 0;
|
||||
const newStatus = currentStatus === 0 ? 1 : 0;
|
||||
|
||||
// 更新状态
|
||||
this.statusMap.set(deviceId, newStatus);
|
||||
|
||||
// 注意:这里不需要创建完整的模拟数据对象,只需要更新状态
|
||||
|
||||
if (enableLogging) {
|
||||
console.log(`🚪 自动门点状态更新: ${newStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`);
|
||||
}
|
||||
|
||||
// 更新编辑器中的自动门点状态
|
||||
if (this.editorService) {
|
||||
this.editorService.updateAutoDoorByDeviceId(deviceId, newStatus, true);
|
||||
} else {
|
||||
console.warn('⚠️ 编辑器服务未设置,无法更新自动门点状态');
|
||||
}
|
||||
}, interval);
|
||||
|
||||
// 保存定时器引用
|
||||
this.timers.set(deviceId, timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止指定设备的模拟(已停用,保留接口用于兼容性)
|
||||
* @param deviceId 设备ID
|
||||
*/
|
||||
stopSimulation(deviceId: string): void {
|
||||
const timer = this.timers.get(deviceId);
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
this.timers.delete(deviceId);
|
||||
this.statusMap.delete(deviceId);
|
||||
console.log(`🚪 停止自动门点模拟: ${deviceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止所有模拟(已停用,保留接口用于兼容性)
|
||||
*/
|
||||
stopAllSimulations(): void {
|
||||
for (const deviceId of this.timers.keys()) {
|
||||
this.stopSimulation(deviceId);
|
||||
}
|
||||
console.log('🚪 停止所有自动门点模拟');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前正在模拟的设备列表
|
||||
*/
|
||||
getActiveSimulations(): string[] {
|
||||
return Array.from(this.timers.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定设备的当前状态
|
||||
* @param deviceId 设备ID
|
||||
*/
|
||||
getCurrentStatus(deviceId: string): 0 | 1 | undefined {
|
||||
return this.statusMap.get(deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动设置设备状态
|
||||
* @param deviceId 设备ID
|
||||
* @param status 设备状态
|
||||
*/
|
||||
setDeviceStatus(deviceId: string, status: 0 | 1): void {
|
||||
this.statusMap.set(deviceId, status);
|
||||
|
||||
if (this.editorService) {
|
||||
this.editorService.updateAutoDoorByDeviceId(deviceId, status, true);
|
||||
console.log(`🚪 手动设置自动门点状态: ${status === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动多个自动门点模拟
|
||||
* @param configs 多个模拟配置
|
||||
*/
|
||||
startMultipleSimulations(configs: AutoDoorSimulationConfig[]): void {
|
||||
configs.forEach((config) => this.startSimulation(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理WebSocket推送的自动门点数据
|
||||
* @param data WebSocket推送的数据
|
||||
*/
|
||||
handleWebSocketData(data: AutoDoorWebSocketData): void {
|
||||
const { id: deviceId, deviceStatus, active = true } = data;
|
||||
|
||||
if (!deviceId || deviceStatus === undefined) {
|
||||
console.warn('⚠️ 自动门点数据格式不正确', data);
|
||||
return;
|
||||
}
|
||||
|
||||
// 缓存最新数据
|
||||
this.latestAutoDoorData.set(deviceId, { deviceId, deviceStatus, active });
|
||||
|
||||
console.log(
|
||||
`🚪 收到自动门点WebSocket数据: ${deviceStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理缓冲的自动门点数据(在渲染循环中调用)
|
||||
* @param frameBudget 帧预算(毫秒)
|
||||
* @param startTime 开始时间
|
||||
* @returns 是否还有待处理的数据
|
||||
*/
|
||||
processBufferedData(frameBudget: number, startTime: number): boolean {
|
||||
// 在时间预算内,持续处理自动门点数据
|
||||
while (performance.now() - startTime < frameBudget && this.latestAutoDoorData.size > 0) {
|
||||
// 获取并移除 Map 中的第一条自动门点数据
|
||||
const entry = this.latestAutoDoorData.entries().next().value;
|
||||
if (!entry) break;
|
||||
|
||||
const [deviceId, data] = entry;
|
||||
this.latestAutoDoorData.delete(deviceId);
|
||||
|
||||
// 使用映射缓存快速查找点位ID
|
||||
const pointId = this.deviceIdToPointIdMap.get(data.deviceId);
|
||||
|
||||
if (!pointId) {
|
||||
console.warn(`⚠️ 未找到设备ID ${data.deviceId} 对应的自动门点,可能需要刷新映射`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 更新自动门点状态(使用pointId直接更新,避免查找)
|
||||
if (this.editorService) {
|
||||
this.editorService.updateAutoDoorByDeviceId(data.deviceId, data.deviceStatus, data.active, pointId);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回是否还有待处理的数据
|
||||
return this.latestAutoDoorData.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前缓冲数据数量
|
||||
*/
|
||||
getBufferedDataCount(): number {
|
||||
return this.latestAutoDoorData.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓冲数据
|
||||
*/
|
||||
clearBufferedData(): void {
|
||||
this.latestAutoDoorData.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取映射关系数量
|
||||
*/
|
||||
getMappingCount(): number {
|
||||
return this.deviceIdToPointIdMap.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查设备ID是否有对应的自动门点
|
||||
*/
|
||||
hasDeviceMapping(deviceId: string): boolean {
|
||||
return this.deviceIdToPointIdMap.has(deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已映射的设备ID列表
|
||||
*/
|
||||
getMappedDeviceIds(): string[] {
|
||||
return Array.from(this.deviceIdToPointIdMap.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模拟数据
|
||||
* @param deviceId 设备ID
|
||||
* @param label 设备标签
|
||||
* @param deviceStatus 设备状态
|
||||
*/
|
||||
private createMockData(deviceId: string, label: string, deviceStatus: 0 | 1): AutoDoorStatusData {
|
||||
return {
|
||||
gid: '',
|
||||
id: deviceId,
|
||||
label,
|
||||
brand: null,
|
||||
type: 99,
|
||||
ip: null,
|
||||
battery: 0,
|
||||
isConnected: true,
|
||||
state: 0,
|
||||
canOrder: false,
|
||||
canStop: null,
|
||||
canControl: false,
|
||||
targetPoint: null,
|
||||
deviceStatus,
|
||||
x: 0,
|
||||
y: 0,
|
||||
active: true,
|
||||
angle: 0,
|
||||
isWaring: null,
|
||||
isFault: null,
|
||||
isLoading: null,
|
||||
path: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁服务,清理所有资源
|
||||
*/
|
||||
destroy(): void {
|
||||
this.stopAllSimulations();
|
||||
this.clearBufferedData();
|
||||
this.deviceIdToPointIdMap.clear();
|
||||
this.editorService = null;
|
||||
console.log('🚪 自动门点服务已销毁');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动门点服务单例
|
||||
*/
|
||||
export const autoDoorService = new AutoDoorService();
|
||||
|
||||
// 保持向后兼容
|
||||
export const autoDoorSimulationService = autoDoorService;
|
@ -839,6 +839,55 @@ export class EditorService extends Meta2d {
|
||||
this.updatePen(pointId, { statusStyle: color }, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备ID更新自动门点状态
|
||||
* @param deviceId 设备ID
|
||||
* @param deviceStatus 设备状态(0=关门,1=开门)
|
||||
* @param active 是否显示光圈
|
||||
* @param pointId 可选的点位ID,如果提供则直接使用,避免查找
|
||||
*/
|
||||
public updateAutoDoorByDeviceId(deviceId: string, deviceStatus: number, active = true, pointId?: string): void {
|
||||
let autoDoorPointId = pointId;
|
||||
|
||||
// 如果没有提供pointId,则通过deviceId查找
|
||||
if (!autoDoorPointId) {
|
||||
const autoDoorPoint = this.data().pens.find(
|
||||
(pen) =>
|
||||
pen.name === 'point' &&
|
||||
(pen as MapPen).point?.type === MapPointType.自动门点 &&
|
||||
(pen as MapPen).point?.deviceId === deviceId,
|
||||
) as MapPen | undefined;
|
||||
|
||||
if (!autoDoorPoint?.id) {
|
||||
console.warn(`未找到设备ID为 ${deviceId} 的自动门点`);
|
||||
return;
|
||||
}
|
||||
|
||||
autoDoorPointId = autoDoorPoint.id;
|
||||
}
|
||||
|
||||
// 通过pointId获取点位对象
|
||||
const autoDoorPoint = this.getPenById(autoDoorPointId) as MapPen | undefined;
|
||||
|
||||
if (!autoDoorPoint?.point) {
|
||||
console.warn(`自动门点 ${autoDoorPointId} 不存在或数据异常`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新自动门点状态
|
||||
this.updatePen(
|
||||
autoDoorPointId,
|
||||
{
|
||||
point: {
|
||||
...autoDoorPoint.point,
|
||||
deviceStatus,
|
||||
active,
|
||||
},
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
public updatePointLockIcon(pointId: string, show: boolean): void {
|
||||
const pointPen = this.getPenById(pointId);
|
||||
if (!pointPen || pointPen.name !== 'point') return;
|
||||
@ -1301,10 +1350,33 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const theme = sTheme.editor;
|
||||
const { active, iconSize: r = 0, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {};
|
||||
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
|
||||
const { type } = pen.point ?? {};
|
||||
const { type, deviceStatus, active: pointActive } = pen.point ?? {};
|
||||
const { label = '', statusStyle } = pen ?? {};
|
||||
|
||||
ctx.save();
|
||||
|
||||
// 为自动门点绘制光圈
|
||||
if (type === MapPointType.自动门点 && pointActive && deviceStatus !== undefined) {
|
||||
const ox = x + w / 2;
|
||||
const oy = y + h / 2;
|
||||
console.log(ox, oy);
|
||||
const haloRadius = Math.max(w, h) / 2 + 6; // 光圈半径比点位本身大一些
|
||||
|
||||
ctx.ellipse(ox, oy, haloRadius, haloRadius, 0, 0, Math.PI * 2);
|
||||
|
||||
// 根据设备状态选择颜色:0=关门(红色),1=开门(蓝色)
|
||||
if (deviceStatus === 0) {
|
||||
ctx.fillStyle = get(theme, 'autoDoor.fill-closed') ?? '#FF4D4F33';
|
||||
ctx.strokeStyle = get(theme, 'autoDoor.stroke-closed') ?? '#FF4D4F99';
|
||||
} else {
|
||||
ctx.fillStyle = get(theme, 'autoDoor.fill-open') ?? '#1890FF33';
|
||||
ctx.strokeStyle = get(theme, 'autoDoor.stroke-open') ?? '#1890FF99';
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MapPointType.普通点:
|
||||
case MapPointType.等待点:
|
||||
|
Loading…
x
Reference in New Issue
Block a user