diff --git a/precise_multi_floor.scene b/precise_multi_floor.scene new file mode 100644 index 0000000..1d867ee --- /dev/null +++ b/precise_multi_floor.scene @@ -0,0 +1,75 @@ +[ + { + "points": [ + { + "id": "p_charge_01", + "name": "充电桩-01", + "desc": "靠近东门的充电桩", + "x": -150.123, + "y": 80.456, + "type": 11, + "robots": [], + "enabled": 1, + "properties": {} + } + ], + "routes": [], + "areas": [], + "robotGroups": [], + "robots": [], + "robotLabels": [], + "scale": 1, + "origin": { "x": 0, "y": 0 }, + "width": 1920, + "height": 1080, + "ratio": 1, + "mapInfo": { + "name": "一楼" + }, + "colorConfig": {} + }, + { + "points": [ + { + "id": "f2_p_charge_01", + "name": "二楼充电桩", + "x": 88.000, + "y": -120.000, + "type": 11, + "enabled": 1, + "properties": {} + }, + { + "id": "f2_p_action_01", + "name": "二楼动作点", + "x": 88.000, + "y": -50.000, + "type": 12, + "associatedStorageLocations": ["F2-A-01"], + "properties": {} + } + ], + "routes": [ + { + "id": "f2_route_1", + "from": "f2_p_charge_01", + "to": "f2_p_action_01", + "type": 0, + "pass": 0 + } + ], + "areas": [], + "robotGroups": [], + "robots": [], + "robotLabels": [], + "scale": 1, + "origin": { "x": 0, "y": 0 }, + "width": 1920, + "height": 1080, + "ratio": 1, + "mapInfo": { + "name": "二楼" + }, + "colorConfig": {} + } +] diff --git a/precise_single_floor.scene b/precise_single_floor.scene new file mode 100644 index 0000000..b4f7640 --- /dev/null +++ b/precise_single_floor.scene @@ -0,0 +1,91 @@ +{ + "points": [ + { + "id": "p_charge_01", + "name": "充电桩-01", + "desc": "靠近东门的充电桩", + "x": -150.123, + "y": 80.456, + "type": 11, + "robots": [], + "enabled": 1, + "properties": {} + }, + { + "id": "p_station_01", + "name": "工作站-A", + "x": 50.789, + "y": 80.456, + "type": 10, + "properties": {} + }, + { + "id": "p_action_01", + "name": "动作点-货架1", + "x": 50.789, + "y": -20.000, + "type": 12, + "associatedStorageLocations": [ + "A-01-01", + "A-01-02" + ], + "properties": {} + } + ], + "routes": [ + { + "id": "route_1", + "from": "p_charge_01", + "to": "p_station_01", + "type": 0, + "pass": 0, + "maxSpeed": 1.2, + "properties": {} + }, + { + "id": "route_2", + "from": "p_station_01", + "to": "p_action_01", + "type": 1, + "c1": { + "x": 60.000, + "y": 30.000 + }, + "pass": 1, + "properties": {} + } + ], + "areas": [ + { + "id": "area_storage_A", + "name": "A库区", + "x": 20.111, + "y": -40.222, + "w": 100.000, + "h": 80.000, + "type": 0, + "points": [ + "动作点-货架1" + ], + "storageLocations": [ + { + "动作点-货架1": [ + "A-01-01", + "A-01-02" + ] + } + ], + "inoutflag": 2, + "properties": {} + } + ], + "robotGroups": [], + "robots": [], + "robotLabels": [], + "scale": 1, + "origin": { "x": 0, "y": 0 }, + "width": 1920, + "height": 1080, + "ratio": 1, + "colorConfig": {} +} diff --git a/src/components/card/point-detail-card.vue b/src/components/card/point-detail-card.vue index acab732..53b4c2a 100644 --- a/src/components/card/point-detail-card.vue +++ b/src/components/card/point-detail-card.vue @@ -17,7 +17,7 @@ const editor = inject(props.token)!; const pen = computed(() => editor.value.getPenById(props.current)); const point = computed(() => { const v = pen.value?.point; - if (!v?.type) return null; + if (isNil(v?.type)) return null; return v; }); const rect = computed>(() => { @@ -105,7 +105,9 @@ const binTaskData = computed(() => { - {{ $t(MapPointType[point.type]) }} + {{ + $t(MapPointType[point.type]) + }} diff --git a/src/components/editor-toolbar.vue b/src/components/editor-toolbar.vue index dd684af..36bddf4 100644 --- a/src/components/editor-toolbar.vue +++ b/src/components/editor-toolbar.vue @@ -6,11 +6,17 @@ import { isEmpty } from 'lodash-es'; import { computed } from 'vue'; import { inject, type InjectionKey, ref, type ShallowRef, watch } from 'vue'; +type SavePayload = { + json: string; + png?: string; +}; + type Props = { token: InjectionKey>; editable?: boolean; sid?: string; id: string; + customSave?: (payload: SavePayload) => Promise; }; const props = defineProps(); const editor = inject(props.token)!; @@ -19,12 +25,17 @@ const editor = inject(props.token)!; const updateScene = async () => { const json = editor.value.save(); if (!json) return; + if (props.customSave) { + const png = editor.value.toPng(8, undefined, true); + await props.customSave({ json, png }); + return; + } if (props.sid) { await saveSceneByGroupId(props.id, props.sid, json); - } else { - const png = editor.value.toPng(8, undefined, true); - await saveSceneById(props.id, json, png); + return; } + const png = editor.value.toPng(8, undefined, true); + await saveSceneById(props.id, json, png); }; //#endregion diff --git a/src/pages/movement-supervision.vue b/src/pages/movement-supervision.vue index df9dae5..a82b8b3 100644 --- a/src/pages/movement-supervision.vue +++ b/src/pages/movement-supervision.vue @@ -396,6 +396,7 @@ onMounted(() => { //#region 生命周期管理 onMounted(async () => { + console.log('Current route in movement-supervision.vue:', window.location.href); if (mode.value === 'live') { await readScene(); await monitorScene(); diff --git a/src/pages/scene-editor.vue b/src/pages/scene-editor.vue index 6e36e3e..3b60660 100644 --- a/src/pages/scene-editor.vue +++ b/src/pages/scene-editor.vue @@ -32,45 +32,71 @@ const readScene = async () => { title.value = res?.label ?? ''; let sceneJson = res?.json; + requireImportTransform.value = false; // 适配不同的场景数据格式 if (Array.isArray(sceneJson)) { if (sceneJson.length > 0) { // 多楼层 floorScenes.value = sceneJson; currentFloorIndex.value = 0; - editor.value?.load(floorScenes.value[0], editable.value); + previousFloorIndex.value = 0; + editor.value?.load(floorScenes.value[0], editable.value, undefined, requireImportTransform.value); } else { // 空数组 message.warn('场景文件为空数组'); floorScenes.value = [{}]; // 创建一个默认的空楼层 currentFloorIndex.value = 0; - editor.value?.load('{}', editable.value); + previousFloorIndex.value = 0; + editor.value?.load('{}', editable.value, undefined, requireImportTransform.value); } } else if (sceneJson && Object.keys(sceneJson).length > 0) { // 单楼层,统一为多楼层数组格式 floorScenes.value = [sceneJson]; currentFloorIndex.value = 0; - editor.value?.load(floorScenes.value[0], editable.value); + previousFloorIndex.value = 0; + editor.value?.load(floorScenes.value[0], editable.value, undefined, requireImportTransform.value); } else { message.warn('场景文件为空或格式不正确'); floorScenes.value = [{}]; // 创建一个默认的空楼层 currentFloorIndex.value = 0; - editor.value?.load('{}', editable.value); + previousFloorIndex.value = 0; + editor.value?.load('{}', editable.value, undefined, requireImportTransform.value); } }; -const saveScene = async () => { - // 保存前,先将当前编辑器的内容更新到 floorScenes 中 - const currentJson = editor.value?.save(); +type SaveScenePayload = { + json?: string; + png?: string; +}; + +const saveScene = async (payload?: SaveScenePayload) => { + const currentJson = payload?.json ?? editor.value?.save(); if (currentJson) { - floorScenes.value[currentFloorIndex.value] = JSON.parse(currentJson); + try { + const parsed = typeof currentJson === 'string' ? JSON.parse(currentJson) : currentJson; + floorScenes.value[currentFloorIndex.value] = parsed; + } catch (error) { + console.error('解析楼层数据失败:', error); + floorScenes.value[currentFloorIndex.value] = currentJson as any; + } } - // 根据楼层数量决定保存单层对象还是多层数组 - const dataToSave = floorScenes.value.length > 1 ? floorScenes.value : floorScenes.value[0]; + let dataToSave: string | undefined; + if (floorScenes.value.length > 1) { + dataToSave = JSON.stringify(floorScenes.value); + } else { + const singleScene = floorScenes.value[0]; + if (typeof singleScene === 'string') { + dataToSave = singleScene; + } else if (singleScene) { + dataToSave = JSON.stringify(singleScene); + } else { + dataToSave = '{}'; + } + } if (!dataToSave) return Promise.reject('无法获取场景数据'); - const res = await saveSceneById(props.id, dataToSave); + const res = await saveSceneById(props.id, dataToSave, payload?.png); if (!res) return Promise.reject('保存失败'); if (editor.value?.store) { @@ -89,6 +115,10 @@ const pushScene = async () => { return Promise.resolve(); }; +const handleToolbarSave = async (payload: SaveScenePayload) => { + await saveScene(payload); +}; + //#endregion const title = ref(''); @@ -96,9 +126,11 @@ const title = ref(''); const floorScenes = ref([]); // 新增:当前楼层的索引 const currentFloorIndex = ref(0); +const previousFloorIndex = ref(0); // 新增:判断是否为多楼层模式 const isMultiFloor = computed(() => floorScenes.value.length > 1); +const requireImportTransform = ref(false); onMounted(() => { document.title = '场景编辑器'; @@ -199,8 +231,54 @@ const toPush = () => { const importScene = async () => { const file = await selectFile('.scene'); if (!file) return; - const json = await decodeTextFile(file); - editor.value?.load(json, editable.value, undefined, true); + const jsonString = await decodeTextFile(file); + if (!jsonString) { + message.error('文件内容为空或无法读取'); + return; + } + + try { + const sceneData = JSON.parse(jsonString); + + // 检查导入的数据是数组(多楼层)还是对象(单楼层) + if (Array.isArray(sceneData)) { + requireImportTransform.value = true; + // 多楼层逻辑 + if (sceneData.length > 0) { + floorScenes.value = sceneData; + currentFloorIndex.value = 0; + previousFloorIndex.value = 0; + // 加载第一个楼层到编辑器 + await editor.value?.load(floorScenes.value[0], editable.value, undefined, requireImportTransform.value); + message.success(`成功导入 ${sceneData.length} 个楼层,当前显示第一层。`); + } else { + message.warn('导入的场景文件是一个空数组,已加载空场景。'); + floorScenes.value = [{}]; + currentFloorIndex.value = 0; + previousFloorIndex.value = 0; + await editor.value?.load('{}', editable.value, undefined, requireImportTransform.value); + } + } else if (sceneData && typeof sceneData === 'object' && Object.keys(sceneData).length > 0) { + // 单楼层逻辑,统一为多楼层数组格式 + requireImportTransform.value = true; + floorScenes.value = [sceneData]; + currentFloorIndex.value = 0; + previousFloorIndex.value = 0; + // 直接加载(editor.load可以接受对象或字符串) + await editor.value?.load(sceneData, editable.value, undefined, requireImportTransform.value); + message.success('成功导入单楼层场景。'); + } else { + message.error('导入失败,场景文件为空或格式不正确。'); + // 可选:加载一个空场景作为回退 + floorScenes.value = [{}]; + currentFloorIndex.value = 0; + previousFloorIndex.value = 0; + await editor.value?.load('{}', editable.value, undefined, requireImportTransform.value); + } + } catch (error) { + console.error('场景文件解析失败:', error); + message.error('导入失败:文件不是有效的JSON格式。'); + } }; const importSmapModalVisible = ref(false); @@ -225,7 +303,8 @@ const handleImportSmapConfirm = async ({ smapFile, keepProperties }: { smapFile: } if (sceneJson) { - editor.value?.load(sceneJson, editable.value, undefined, true); + requireImportTransform.value = true; + editor.value?.load(sceneJson, editable.value, undefined, requireImportTransform.value); } }; @@ -332,17 +411,28 @@ const handleAutoCreateStorageCancel = () => { const handleFloorChange = async (value: any) => { const newFloorIndex = value as number; + const prevFloorIndex = previousFloorIndex.value; if (editor.value && floorScenes.value[newFloorIndex]) { // 切换前,先保存当前楼层的状态 const currentJson = editor.value.save(); - floorScenes.value[currentFloorIndex.value] = JSON.parse(currentJson); + if (currentJson) { + floorScenes.value[prevFloorIndex] = JSON.parse(currentJson); + } // 加载新楼层 currentFloorIndex.value = newFloorIndex; - await editor.value.load(floorScenes.value[newFloorIndex], editable.value); + previousFloorIndex.value = newFloorIndex; + await editor.value.load( + floorScenes.value[newFloorIndex], + editable.value, + undefined, + requireImportTransform.value, + ); + } else { + currentFloorIndex.value = newFloorIndex; + previousFloorIndex.value = newFloorIndex; } }; -