diff --git a/src/pages/scene-editor.vue b/src/pages/scene-editor.vue
index 699b7c8..7111fa1 100644
--- a/src/pages/scene-editor.vue
+++ b/src/pages/scene-editor.vue
@@ -256,18 +256,139 @@ const toPush = () => {
};
// --- Import/Update Logic ---
-const importScene = async () => {
- const file = await selectFile('.scene');
- if (!file) return;
- const jsonString = await decodeTextFile(file);
- if (!jsonString) {
- message.error('文件内容为空或无法读取');
- return;
+const importModalVisible = ref(false);
+const importMode = ref<'normal' | 'floor'>('normal');
+const normalImportFileList = ref([]);
+const normalImportKeepProperties = ref(true);
+const floorImportList = ref([{ name: 'F1', fileList: [], keepProperties: true }]);
+
+const fileTableColumns = [
+ { title: '文件名', dataIndex: 'name', key: 'name' },
+ { title: '格式', dataIndex: 'format', key: 'format', width: 100 },
+ { title: '文件大小', dataIndex: 'size', key: 'size', width: 120 },
+];
+
+const handleNormalFileChange = (info: any) => {
+ // 限制只上传一个文件
+ let fileList = [...info.fileList];
+ fileList = fileList.slice(-1);
+ normalImportFileList.value = fileList;
+};
+
+const handleFloorFileChange = (info: any, index: number) => {
+ floorImportList.value[index].fileList = [...info.fileList];
+};
+
+const addFloor = () => {
+ floorImportList.value.push({
+ name: `F${floorImportList.value.length + 1}`,
+ fileList: [],
+ keepProperties: true,
+ });
+};
+
+const mergeSceneObjects = (scenes: any[]): any => {
+ if (!scenes || scenes.length === 0) {
+ return {};
}
- try {
- const sceneData = JSON.parse(jsonString);
+ 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;
+};
+
+const handleImportConfirm = async () => {
+ try {
+ if (importMode.value === 'normal') {
+ if (normalImportFileList.value.length === 0) {
+ message.warn('请选择一个文件');
+ return;
+ }
+ const file = normalImportFileList.value[0].originFileObj as File;
+ const fileName = file.name.toLowerCase();
+ let sceneJsonString: string | null = null;
+
+ if (fileName.endsWith('.smap')) {
+ // SMAP 文件转换逻辑
+ if (normalImportKeepProperties.value) {
+ // 更新模式
+ const currentSceneJson = editor.value?.save();
+ if (!currentSceneJson) {
+ message.error('无法获取当前场景数据,请确保场景不为空');
+ return;
+ }
+ const sceneBlob = new Blob([currentSceneJson], { type: 'application/json' });
+ const sceneFile = new File([sceneBlob], `${title.value || 'current'}.scene`, {
+ type: 'application/json',
+ });
+ sceneJsonString = await convertSmapToScene(file, sceneFile);
+ } else {
+ // 创建模式
+ sceneJsonString = await convertSmapToScene(file);
+ }
+ } else if (fileName.endsWith('.scene')) {
+ // SCENE 文件直接读取
+ sceneJsonString = await decodeTextFile(file);
+ } else {
+ message.error('不支持的文件格式。请选择 .scene 或 .smap 文件。');
+ return;
+ }
+
+ if (!sceneJsonString) {
+ message.error('文件内容为空或转换失败');
+ return;
+ }
+ await processAndLoadSceneData(JSON.parse(sceneJsonString));
+ } else if (importMode.value === 'floor') {
+ if (floorImportList.value.some((f) => f.fileList.length === 0)) {
+ message.warn('每个楼层都需要选择至少一个文件');
+ return;
+ }
+ const floorJsons = await Promise.all(
+ floorImportList.value.map(async (floor) => {
+ const sceneObjectsForFloor = await Promise.all(
+ floor.fileList.map(async (fileInfo: any) => {
+ const file = fileInfo.originFileObj as File;
+ const jsonString = await decodeTextFile(file);
+ if (!jsonString) {
+ throw new Error(`楼层 ${floor.name} 中的文件 ${file.name} 为空`);
+ }
+ return JSON.parse(jsonString);
+ }),
+ );
+ return mergeSceneObjects(sceneObjectsForFloor);
+ }),
+ );
+ await processAndLoadSceneData(floorJsons);
+ }
+
+ importModalVisible.value = false;
+ // 清理状态
+ normalImportFileList.value = [];
+ floorImportList.value = [{ name: 'F1', fileList: [], keepProperties: true }];
+ } catch (error) {
+ console.error('导入失败:', error);
+ message.error(`导入失败: ${(error as Error).message || '请检查文件格式或内容。'}`);
+ }
+};
+
+const processAndLoadSceneData = async (sceneData: any) => {
+ try {
// 检查导入的数据是数组(多楼层)还是对象(单楼层)
if (Array.isArray(sceneData)) {
requireImportTransform.value = true;
@@ -304,8 +425,10 @@ const importScene = async () => {
await editor.value?.load('{}', editable.value, undefined, requireImportTransform.value);
}
} catch (error) {
- console.error('场景文件解析失败:', error);
- message.error('导入失败:文件不是有效的JSON格式。');
+ console.error('场景文件解析或加载失败:', error);
+ message.error('导入失败:文件不是有效的JSON格式或数据无法加载。');
+ // 抛出错误以确保上层可以捕获
+ throw error;
}
};
@@ -489,7 +612,7 @@ const handleFloorChange = async (value: any) => {
{{ $t('推送') }}
-
+
{{ $t('导入') }}
@@ -570,6 +693,99 @@ const handleFloorChange = async (value: any) => {
+
+
+
+
+
+
+
+
+
+ 点击或拖拽上传文件
+
+
+
+
+ {{ record.name }}
+
+
+ {{ record.name.split('.').pop() }}
+
+
+ {{ (record.size / 1024).toFixed(2) }}kb
+
+
+
+
保留原属性
+
+
+
+
+
+
handleFloorFileChange(info, index)"
+ :before-upload="() => false"
+ >
+
+
+
+ 点击或拖拽上传文件
+
+
+
+
+ {{ record.name }}
+
+
+ {{ record.name.split('.').pop() }}
+
+
+ {{ (record.size / 1024).toFixed(2) }}kb
+
+
+
+
保留原属性
+
+
+ 添加楼层
+
+
+
+
+
+
{
:deep(.ant-tabs-tab) {
padding: 14px 12px !important;
}
+
+.import-modal-content {
+ .mode-switcher {
+ display: flex;
+ margin-bottom: 16px;
+ .mode-tab {
+ padding: 8px 16px;
+ cursor: pointer;
+ border: 1px solid #d9d9d9;
+ background-color: #fafafa;
+ &:first-child {
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ }
+ &:last-child {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-left: none;
+ }
+ &.active {
+ background-color: #0dbb8a;
+ color: white;
+ border-color: #0dbb8a;
+ }
+ }
+ }
+
+ .core-content {
+ min-height: 200px;
+ /* 后续添加具体内容样式 */
+ }
+
+ .action-buttons {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 16px;
+ .hint-text {
+ color: red;
+ font-size: 12px;
+ }
+ }
+}
+
+.floor-uploader-item {
+ border: 1px solid #f0f0f0;
+ padding: 16px;
+ border-radius: 4px;
+ margin-bottom: 16px;
+ &:last-child {
+ margin-bottom: 0;
+ }
+}