feat(auto-door): 新增自动门设备状态模拟功能,优化门区域与路段的状态同步逻辑
This commit is contained in:
parent
a0e33b2e86
commit
11cdde454c
@ -12,7 +12,18 @@ type Props = {
|
||||
const props = defineProps<Props>();
|
||||
const editor = inject(props.token)!;
|
||||
|
||||
const pen = computed<MapPen | undefined>(() => editor.value.getPenById(props.current));
|
||||
// 订阅区域集合变化(含设备状态/连接状态),用于触发详情面板的响应式刷新
|
||||
const areasTick = computed<string>(() =>
|
||||
editor.value.areas.value
|
||||
.map((v: any) => `${v.id}:${v?.area?.deviceStatus ?? ''}:${v?.area?.isConnected ?? ''}`)
|
||||
.join('|'),
|
||||
);
|
||||
|
||||
const pen = computed<MapPen | undefined>(() => {
|
||||
// 引用 areasTick 以建立依赖关系
|
||||
void areasTick.value;
|
||||
return editor.value.getPenById(props.current);
|
||||
});
|
||||
const area = computed<MapAreaInfo | null>(() => {
|
||||
const v = pen.value?.area;
|
||||
if (!v?.type) return null;
|
||||
@ -61,6 +72,8 @@ const ruleText = computed(() => {
|
||||
if (area.value?.inoutflag === 2) return '后进先出';
|
||||
return '';
|
||||
});
|
||||
|
||||
// 无日志版:不输出调试信息
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -106,6 +119,22 @@ const ruleText = computed(() => {
|
||||
<a-typography-text>{{ area.doorDeviceId || $t('无') }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="area && (area.type as any) === DOOR_AREA_TYPE">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('连接状态') }}</a-typography-text>
|
||||
<a-typography-text>{{
|
||||
area.isConnected === true ? $t('已连接') : area.isConnected === false ? $t('未连接') : $t('无')
|
||||
}}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="area && (area.type as any) === DOOR_AREA_TYPE">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('门状态') }}</a-typography-text>
|
||||
<a-typography-text>
|
||||
{{ area.deviceStatus === 1 ? $t('开门') : area.deviceStatus === 0 ? $t('关门') : $t('无') }}
|
||||
</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="area && (area.type as any) === DOOR_AREA_TYPE">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('已绑定路段') }}</a-typography-text>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { DOOR_AREA_TYPE } from '@api/map/door-area';
|
||||
import { LockState } from '@meta2d/core';
|
||||
import { message } from 'ant-design-vue';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
@ -76,6 +77,9 @@ const container = shallowRef<HTMLDivElement>();
|
||||
const editor = shallowRef<EditorService>();
|
||||
const storageLocationService = shallowRef<StorageLocationService>();
|
||||
const client = shallowRef<WebSocket>();
|
||||
// 模拟门设备WS推送(仅开发调试使用)
|
||||
let doorMockTimer: number | undefined;
|
||||
let doorMockStatus: 0 | 1 = 0;
|
||||
// 左侧边栏元素引用(用于 Ctrl/Cmd+F 聚焦搜索框)
|
||||
const leftSiderEl = shallowRef<HTMLElement>();
|
||||
const isPlaybackControllerVisible = ref<boolean>(true);
|
||||
@ -379,6 +383,59 @@ const monitorScene = async () => {
|
||||
originalOnClose.call(ws, event);
|
||||
}
|
||||
};
|
||||
|
||||
// 开发/开启DOOR_MOCK时,添加模拟门设备WS推送
|
||||
try {
|
||||
// DEV 或显式开启 localStorage.DOOR_MOCK === '1' 时启用模拟
|
||||
const enableMock =
|
||||
(typeof localStorage !== 'undefined' && localStorage.getItem('DOOR_MOCK') === '1') ||
|
||||
import.meta?.env?.DEV === true;
|
||||
if (enableMock) {
|
||||
if (doorMockTimer) window.clearInterval(doorMockTimer);
|
||||
|
||||
// 优先使用当前场景中第一个“门区域”的设备ID作为模拟目标;找不到则回退 door1
|
||||
let mockDeviceId = 'door1';
|
||||
try {
|
||||
const areas: any[] = (editor.value?.find('area') || []) as any[];
|
||||
const doorAreas = areas.filter((a: any) => (a.area?.type as any) === (DOOR_AREA_TYPE as any));
|
||||
const first = doorAreas[0];
|
||||
const did = first?.area?.doorDeviceId;
|
||||
if (did) mockDeviceId = did;
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
doorMockTimer = window.setInterval(() => {
|
||||
doorMockStatus = doorMockStatus === 0 ? 1 : 0;
|
||||
const mock: AutoDoorWebSocketData = {
|
||||
gid: 'mock-gid',
|
||||
id: mockDeviceId,
|
||||
label: mockDeviceId,
|
||||
brand: null,
|
||||
type: 99,
|
||||
ip: null,
|
||||
battery: 100,
|
||||
isConnected: true,
|
||||
state: 0,
|
||||
canOrder: true,
|
||||
canStop: null,
|
||||
canControl: true,
|
||||
targetPoint: null,
|
||||
deviceStatus: doorMockStatus,
|
||||
x: 0,
|
||||
y: 0,
|
||||
active: true,
|
||||
angle: 0,
|
||||
isWaring: null,
|
||||
isFault: null,
|
||||
isLoading: null,
|
||||
path: [],
|
||||
};
|
||||
autoDoorSimulationService.handleWebSocketData(mock);
|
||||
}, 2000);
|
||||
}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
};
|
||||
//#endregion
|
||||
|
||||
@ -389,6 +446,16 @@ onMounted(() => {
|
||||
// 将 editor 存储到 store 中
|
||||
editorStore.setEditor(editor as ShallowRef<EditorService>);
|
||||
|
||||
// 提前注入自动门服务的编辑器实例,确保WS/模拟数据到达时可立即落盘到区域/路段
|
||||
try {
|
||||
if (editor.value) {
|
||||
console.log('[DoorWS] bind editor to door service');
|
||||
autoDoorSimulationService.setEditorService(editor.value);
|
||||
}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
|
||||
storageLocationService.value = new StorageLocationService(editor.value, props.sid);
|
||||
container.value?.addEventListener('pointerdown', handleCanvasPointerDown, true);
|
||||
});
|
||||
@ -424,7 +491,6 @@ onMounted(async () => {
|
||||
contextMenuState.value = state;
|
||||
});
|
||||
|
||||
|
||||
// 添加全局点击事件监听器,用于关闭右键菜单
|
||||
document.addEventListener('click', handleGlobalClick);
|
||||
document.addEventListener('keydown', handleGlobalKeydown);
|
||||
@ -438,6 +504,10 @@ onUnmounted(() => {
|
||||
storageLocationService.value?.destroy();
|
||||
// 清理自动门点服务(清空缓冲数据)
|
||||
autoDoorSimulationService.clearBufferedData();
|
||||
if (doorMockTimer) {
|
||||
window.clearInterval(doorMockTimer);
|
||||
doorMockTimer = undefined;
|
||||
}
|
||||
|
||||
// 移除EditorService事件监听器
|
||||
if (editor.value) {
|
||||
|
||||
@ -100,12 +100,6 @@ type SaveScenePayload = {
|
||||
png?: string;
|
||||
};
|
||||
|
||||
type FloorViewState = {
|
||||
scale: number;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
const syncDoorDeviceToRoutes = () => {
|
||||
const pens = editor.value?.data()?.pens || [];
|
||||
pens.forEach((pen: any) => {
|
||||
@ -126,7 +120,11 @@ const syncDoorDeviceToRoutes = () => {
|
||||
};
|
||||
const saveScene = async (payload?: SaveScenePayload) => {
|
||||
// 保存前,同步门设备到绑定的路段,确保路段也带有 deviceId 等信息
|
||||
try { syncDoorDeviceToRoutes(); } catch {}
|
||||
try {
|
||||
syncDoorDeviceToRoutes();
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
const currentJson = payload?.json ?? editor.value?.save();
|
||||
if (currentJson) {
|
||||
try {
|
||||
@ -320,26 +318,6 @@ const addFloor = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const mergeSceneObjects = (scenes: any[]): any => {
|
||||
if (!scenes || scenes.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return scenes.reduce((merged, currentScene) => {
|
||||
const newMerged = { ...merged, ...currentScene };
|
||||
if (currentScene.pens && Array.isArray(currentScene.pens)) {
|
||||
const penMap = new Map(merged.pens?.map((p: any) => [p.id, p]) ?? []);
|
||||
currentScene.pens.forEach((pen: any) => {
|
||||
if (pen.id) {
|
||||
penMap.set(pen.id, pen);
|
||||
}
|
||||
});
|
||||
newMerged.pens = Array.from(penMap.values());
|
||||
}
|
||||
return newMerged;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const openImportModal = () => {
|
||||
importMode.value = 'normal';
|
||||
importModalVisible.value = true;
|
||||
@ -417,7 +395,9 @@ const handleImportConfirm = async () => {
|
||||
console.log('[MapConverter] Request payload (object):', payload);
|
||||
console.log('[MapConverter] Request payload (JSON):', JSON.stringify(payload, null, 2));
|
||||
message.info('已在控制台打印请求参数');
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
|
||||
// 3) 调用转换服务
|
||||
const result = await postProcessJsonList(payload);
|
||||
|
||||
@ -153,6 +153,19 @@ export interface AutoDoorWebSocketData {
|
||||
*/
|
||||
|
||||
export class AutoDoorService {
|
||||
// 日志开关:默认关闭;可通过 localStorage.DOOR_LOG='1' 或 VITE_LOG_DOOR='1' 开启
|
||||
private readonly DEBUG = (() => {
|
||||
try {
|
||||
const ls = typeof localStorage !== 'undefined' && localStorage.getItem('DOOR_LOG') === '1';
|
||||
const env = typeof import.meta !== 'undefined' && (import.meta as any)?.env?.VITE_LOG_DOOR === '1';
|
||||
return !!(ls || env);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
private debugLog = (...args: any[]) => {
|
||||
if (this.DEBUG) console.log('[DoorWS]', ...args);
|
||||
};
|
||||
private timers = new Map<string, NodeJS.Timeout>();
|
||||
|
||||
private statusMap = new Map<string, 0 | 1>();
|
||||
@ -182,6 +195,7 @@ export class AutoDoorService {
|
||||
// 初始化设备ID到点位ID的映射关系
|
||||
|
||||
this.initializeDeviceMapping();
|
||||
this.debugLog('setEditorService -> editor attached, mapping initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -224,6 +238,12 @@ export class AutoDoorService {
|
||||
}
|
||||
}
|
||||
});
|
||||
this.debugLog(
|
||||
'initializeDeviceMapping -> found autoDoor points:',
|
||||
autoDoorCount,
|
||||
'map keys:',
|
||||
Array.from(this.deviceIdToPointIdMap.keys()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -391,7 +411,11 @@ export class AutoDoorService {
|
||||
*/
|
||||
|
||||
handleWebSocketData(data: AutoDoorWebSocketData): void {
|
||||
const { label: deviceId, deviceStatus, active = true, isConnected = true } = data;
|
||||
// 兼容不同字段:优先使用 id 作为设备ID,退回 label
|
||||
const deviceId = (data as any)?.id || (data as any)?.label;
|
||||
const deviceStatus = (data as any)?.deviceStatus;
|
||||
const active = (data as any)?.active ?? true;
|
||||
const isConnected = (data as any)?.isConnected ?? true;
|
||||
|
||||
if (!deviceId || deviceStatus === undefined) {
|
||||
console.warn('⚠️ 自动门点数据格式不正确', data);
|
||||
@ -402,6 +426,13 @@ export class AutoDoorService {
|
||||
// 缓存最新数据
|
||||
|
||||
this.latestAutoDoorData.set(deviceId, { deviceId, deviceStatus, active, isConnected });
|
||||
this.debugLog('handleWebSocketData -> buffered', {
|
||||
deviceId,
|
||||
deviceStatus,
|
||||
active,
|
||||
isConnected,
|
||||
bufferSize: this.latestAutoDoorData.size,
|
||||
});
|
||||
|
||||
// console.log(
|
||||
|
||||
@ -431,6 +462,8 @@ export class AutoDoorService {
|
||||
|
||||
// 在时间预算内,智能处理自动门点数据
|
||||
|
||||
if (this.latestAutoDoorData.size > 0)
|
||||
this.debugLog('processBufferedData -> start, buffer size:', this.latestAutoDoorData.size);
|
||||
while (
|
||||
performance.now() - startTime < frameBudget &&
|
||||
this.latestAutoDoorData.size > 0 &&
|
||||
@ -447,69 +480,73 @@ export class AutoDoorService {
|
||||
// 使用映射缓存快速查找点位ID列表
|
||||
|
||||
const pointIds = this.deviceIdToPointIdMap.get(data.deviceId);
|
||||
this.debugLog('processBufferedData -> processing', data, 'mapped pointIds:', pointIds);
|
||||
|
||||
if (!pointIds || pointIds.length === 0) {
|
||||
console.warn(`⚠️ 未找到设备ID ${data.deviceId} 对应的自动门点,可能需要刷新映射`);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// 批量更新该设备对应的所有自动门点状态
|
||||
// 批量更新该设备对应的所有自动门点状态(若存在自动门点)
|
||||
|
||||
if (this.editorService) {
|
||||
// 使用批量更新减少渲染调用
|
||||
|
||||
const updates = pointIds.map((pid) => ({
|
||||
id: pid,
|
||||
|
||||
deviceId: data.deviceId,
|
||||
|
||||
deviceStatus: data.deviceStatus,
|
||||
|
||||
isConnected: data.isConnected,
|
||||
|
||||
active: data.active,
|
||||
}));
|
||||
|
||||
// 批量执行更新
|
||||
|
||||
updates.forEach((update) => {
|
||||
this.editorService!.updateAutoDoorByDeviceId(
|
||||
update.deviceId,
|
||||
|
||||
update.deviceStatus,
|
||||
|
||||
update.isConnected,
|
||||
|
||||
update.active,
|
||||
|
||||
update.id,
|
||||
);
|
||||
});
|
||||
if (!pointIds || pointIds.length === 0) {
|
||||
console.warn(`⚠️ 未找到设备ID ${data.deviceId} 对应的自动门点,跳过点位更新,但会同步更新路段与门区域`);
|
||||
} else {
|
||||
// 使用批量更新减少渲染调用
|
||||
const updates = pointIds.map((pid) => ({
|
||||
id: pid,
|
||||
deviceId: data.deviceId,
|
||||
deviceStatus: data.deviceStatus,
|
||||
isConnected: data.isConnected,
|
||||
active: data.active,
|
||||
}));
|
||||
// 批量执行更新
|
||||
updates.forEach((update) => {
|
||||
this.editorService!.updateAutoDoorByDeviceId(
|
||||
update.deviceId,
|
||||
update.deviceStatus,
|
||||
update.isConnected,
|
||||
update.active,
|
||||
update.id,
|
||||
);
|
||||
this.debugLog('update point by deviceId ->', update);
|
||||
});
|
||||
}
|
||||
|
||||
// 同步更新:将门设备状态写入绑定该设备的路段与门区域
|
||||
|
||||
try {
|
||||
const routes = this.editorService.find('route').filter((r: any) => r.route?.deviceId === data.deviceId);
|
||||
this.debugLog('sync to routes -> count:', routes.length);
|
||||
|
||||
routes.forEach((r: any) => {
|
||||
const merged = { ...(r.route || {}), deviceStatus: data.deviceStatus, isConnected: data.isConnected };
|
||||
|
||||
this.editorService!.setValue({ id: r.id, route: merged }, { render: true, history: false, doEvent: false });
|
||||
const patch = { deviceStatus: data.deviceStatus, isConnected: data.isConnected } as any;
|
||||
this.editorService!.updateRoute(r.id, patch);
|
||||
this.debugLog('route updated ->', r.id, patch);
|
||||
});
|
||||
|
||||
const areas = this.editorService
|
||||
// 兼容匹配:
|
||||
// 1) doorDeviceId === deviceId
|
||||
// 2) 或绑定路段中存在 route.deviceId === deviceId
|
||||
const allDoorAreas = this.editorService
|
||||
.find('area')
|
||||
.filter((a: any) => (a.area?.type as any) === (15 as any) && a.area?.doorDeviceId === data.deviceId);
|
||||
areas.forEach((a: any) => {
|
||||
const mergedArea = { ...(a.area || {}), deviceStatus: data.deviceStatus, isConnected: data.isConnected };
|
||||
.filter((a: any) => (a.area?.type as any) === (15 as any));
|
||||
|
||||
this.editorService!.setValue(
|
||||
{ id: a.id, area: mergedArea },
|
||||
{ render: true, history: false, doEvent: false },
|
||||
);
|
||||
const areas = allDoorAreas.filter((a: any) => {
|
||||
if (a.area?.doorDeviceId === data.deviceId) return true;
|
||||
const rids: string[] = a.area?.routes || [];
|
||||
for (const rid of rids) {
|
||||
const rpen: any = this.editorService!.getPenById(rid);
|
||||
if (rpen?.route?.deviceId === data.deviceId) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
} catch {}
|
||||
|
||||
this.debugLog('sync to door areas -> count:', areas.length, 'deviceId:', data.deviceId);
|
||||
areas.forEach((a: any) => {
|
||||
const patch = { deviceStatus: data.deviceStatus, isConnected: data.isConnected } as any;
|
||||
this.editorService!.updateArea(a.id, patch);
|
||||
this.debugLog('door area updated ->', a.id, patch);
|
||||
});
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user