将编辑器修改位Monaco并且修复bug

This commit is contained in:
nnwang
2025-12-04 17:31:18 +08:00
parent 231349b24c
commit fc3df13dd9
5 changed files with 252 additions and 243 deletions

50
package-lock.json generated
View File

@@ -12,6 +12,8 @@
"@tailwindcss/vite": "^4.1.14", "@tailwindcss/vite": "^4.1.14",
"axios": "^1.12.2", "axios": "^1.12.2",
"daisyui": "^5.3.7", "daisyui": "^5.3.7",
"monaco-editor": "^0.55.1",
"monaco-editor-vue3": "^1.0.4",
"tailwindcss": "^4.1.14", "tailwindcss": "^4.1.14",
"vue": "^3.5.22" "vue": "^3.5.22"
}, },
@@ -1109,6 +1111,13 @@
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz",
@@ -1310,6 +1319,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dompurify": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1857,6 +1875,18 @@
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
} }
}, },
"node_modules/marked": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -1908,6 +1938,26 @@
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/monaco-editor": {
"version": "0.55.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
"license": "MIT",
"dependencies": {
"dompurify": "3.2.7",
"marked": "14.0.0"
}
},
"node_modules/monaco-editor-vue3": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/monaco-editor-vue3/-/monaco-editor-vue3-1.0.4.tgz",
"integrity": "sha512-gaIMBdhUGorOAX0kBvWul9QCQ+6J+MjZgqkieDECv3rjXsRbI07XNNrnD3IBC1jnGRF9+aTZ9CNhJ8Uv06z1uw==",
"license": "MIT",
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"vue": "^3"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",

View File

@@ -13,6 +13,8 @@
"@tailwindcss/vite": "^4.1.14", "@tailwindcss/vite": "^4.1.14",
"axios": "^1.12.2", "axios": "^1.12.2",
"daisyui": "^5.3.7", "daisyui": "^5.3.7",
"monaco-editor": "^0.55.1",
"monaco-editor-vue3": "^1.0.4",
"tailwindcss": "^4.1.14", "tailwindcss": "^4.1.14",
"vue": "^3.5.22" "vue": "^3.5.22"
}, },

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="container mx-auto max-w-6xl p-4"> <div class="">
<div class="card bg-base-200 shadow-xl"> <div class="card bg-base-200 shadow-xl">
<div class="card-body"> <div class="card-body">
<div class="text-center mb-6"> <div class="text-center mb-6">
@@ -137,12 +137,15 @@
</span> </span>
</div> </div>
<textarea <div class="h-96 border rounded-lg overflow-hidden">
class="textarea textarea-bordered w-full h-96 mt-2 font-mono" <CodeEditor
v-model="displayedJson" v-model:value="displayedJson"
placeholder="蓝图JSON内容将显示在这里..." language="json"
theme="vs"
:options="editorOptions"
:disabled="!isParsed" :disabled="!isParsed"
></textarea> />
</div>
<div class="flex flex-wrap gap-3 mt-4"> <div class="flex flex-wrap gap-3 mt-4">
<button <button
@@ -534,8 +537,8 @@
</div> </div>
<!-- 进阶编辑区域 - 使用第一个文件的编辑器 --> <!-- 进阶编辑区域 - 使用第一个文件的编辑器 -->
<div v-show="activeVisualTab === 'advanced' && showObjects"> <div v-show="activeVisualTab === 'advanced'">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 lg:grid-cols-4 gap-1">
<!-- 左侧实体列表 --> <!-- 左侧实体列表 -->
<div class="lg:col-span-1"> <div class="lg:col-span-1">
<div class="card bg-base-100 shadow-xl"> <div class="card bg-base-100 shadow-xl">
@@ -549,8 +552,8 @@
:class="['p-3 mb-2 rounded-lg cursor-pointer hover:bg-base-300 transition', :class="['p-3 mb-2 rounded-lg cursor-pointer hover:bg-base-300 transition',
selectedEntityIndex === index ? 'bg-primary text-primary-content' : 'bg-base-200']" selectedEntityIndex === index ? 'bg-primary text-primary-content' : 'bg-base-200']"
> >
<div class="font-bold truncate">{{ entity.instanceName || '未命名实体' }}</div> <div class="font-bold truncate">{{ getDisplayName(entity.instanceName) || '未命名实体' }}</div>
<div class="text-sm opacity-75 truncate">{{ entity.typePath || '未知类型' }}</div> <div class="text-sm opacity-75 truncate">{{ getTypeName(entity.typePath) || '未知类型' }}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -562,7 +565,7 @@
<div v-if="selectedEntity" class="card bg-base-100 shadow-xl"> <div v-if="selectedEntity" class="card bg-base-100 shadow-xl">
<div class="card-body"> <div class="card-body">
<h2 class="card-title flex items-center"> <h2 class="card-title flex items-center">
<span class="truncate">{{ selectedEntity.instanceName || '未命名实体' }}</span> <span class="truncate">{{ getDisplayName(selectedEntity.instanceName) || '未命名实体' }}</span>
<span class="badge badge-primary ml-2">{{ selectedEntity.type || '未知类型' }}</span> <span class="badge badge-primary ml-2">{{ selectedEntity.type || '未知类型' }}</span>
</h2> </h2>
@@ -581,10 +584,14 @@
<!-- 原始JSON标签页 --> <!-- 原始JSON标签页 -->
<div v-if="activeTab === 'raw'"> <div v-if="activeTab === 'raw'">
<h3 class="text-lg font-semibold mb-3">原始JSON数据</h3> <h3 class="text-lg font-semibold mb-3">原始JSON数据</h3>
<textarea <div class="h-96 border rounded-lg overflow-hidden">
v-model="entityJson" <CodeEditor
class="textarea textarea-bordered w-full h-96 font-mono" v-model:value="entityJson"
></textarea> language="json"
theme="vs"
:options="editorOptions"
/>
</div>
<div class="mt-3 flex justify-end"> <div class="mt-3 flex justify-end">
<button @click="updateEntityFromJson" class="btn btn-primary">更新实体</button> <button @click="updateEntityFromJson" class="btn btn-primary">更新实体</button>
</div> </div>
@@ -697,30 +704,28 @@
> >
</td> </td>
<td> <td>
<select v-model="value.ueType" class="select select-bordered select-sm"> <input
<option value="FloatProperty">FloatProperty</option> type="text"
<option value="IntProperty">IntProperty</option> v-model="value.ueType"
<option value="ObjectProperty">ObjectProperty</option> class="input input-bordered input-sm"
<option value="StructProperty">StructProperty</option> placeholder="输入类型"
</select> >
</td> </td>
<td> <td>
<input
v-if="value.ueType === 'FloatProperty' || value.ueType === 'IntProperty'" <div v-if="isComplexValue(value.value)" class="h-20 border rounded-lg overflow-hidden">
type="number" <CodeEditor
v-model.number="value.value" v-model:value="objectProperties[key]"
class="input input-bordered input-sm" language="json"
> theme="vs"
<textarea :options="smallEditorOptions"
v-else-if="value.ueType === 'ObjectProperty' || value.ueType === 'StructProperty'" />
v-model="objectProperties[key]" </div>
class="textarea textarea-bordered textarea-sm w-full"
rows="3"
></textarea>
<input <input
v-else v-else
type="text" type="text"
v-model="value.value" :value="getPropertyDisplayValue(value)"
@input="setPropertyValue(value, $event.target.value)"
class="input input-bordered input-sm" class="input input-bordered input-sm"
> >
</td> </td>
@@ -739,26 +744,13 @@
<input type="text" v-model="newProperty.name" placeholder="属性名称" class="input input-bordered"> <input type="text" v-model="newProperty.name" placeholder="属性名称" class="input input-bordered">
</div> </div>
<div class="form-control"> <div class="form-control">
<select v-model="newProperty.type" class="select select-bordered"> <input type="text" v-model="newProperty.type" placeholder="属性类型" class="input input-bordered">
<option value="FloatProperty">FloatProperty</option>
<option value="IntProperty">IntProperty</option>
<option value="ObjectProperty">ObjectProperty</option>
<option value="StructProperty">StructProperty</option>
</select>
</div> </div>
<div class="form-control"> <div class="form-control">
<input <input
v-if="newProperty.type === 'FloatProperty' || newProperty.type === 'IntProperty'"
type="number"
v-model.number="newProperty.value"
placeholder="值"
class="input input-bordered"
>
<input
v-else
type="text" type="text"
v-model="newProperty.value" v-model="newProperty.value"
placeholder="值" placeholder="属性值"
class="input input-bordered" class="input input-bordered"
> >
</div> </div>
@@ -772,10 +764,14 @@
<!-- 组件标签页 --> <!-- 组件标签页 -->
<div v-if="activeTab === 'components'"> <div v-if="activeTab === 'components'">
<h3 class="text-lg font-semibold mb-3">组件编辑</h3> <h3 class="text-lg font-semibold mb-3">组件编辑</h3>
<textarea <div class="h-96 border rounded-lg overflow-hidden">
v-model="componentsJson" <CodeEditor
class="textarea textarea-bordered w-full h-96 font-mono" v-model:value="componentsJson"
></textarea> language="json"
theme="vs"
:options="editorOptions"
/>
</div>
<div class="mt-3 flex justify-end"> <div class="mt-3 flex justify-end">
<button @click="updateComponents" class="btn btn-primary">更新组件</button> <button @click="updateComponents" class="btn btn-primary">更新组件</button>
</div> </div>
@@ -793,14 +789,18 @@
> >
<input type="checkbox" /> <input type="checkbox" />
<div class="collapse-title font-semibold"> <div class="collapse-title font-semibold">
{{ comp.instanceName ? comp.instanceName.split('.').pop() : '未命名组件' }} {{ getDisplayName(comp.instanceName) || '未命名组件' }}
<span class="badge badge-sm badge-neutral ml-2">{{ comp.typePath ? comp.typePath.split('/').pop() : '未知类型' }}</span> <span class="badge badge-sm badge-neutral ml-2">{{ getTypeName(comp.typePath) || '未知类型' }}</span>
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<textarea <div class="h-64 border rounded-lg overflow-hidden mb-4">
v-model="attachedComponentJson[index]" <CodeEditor
class="textarea textarea-bordered w-full h-64 font-mono mb-4" v-model:value="attachedComponentJson[index]"
></textarea> language="json"
theme="vs"
:options="editorOptions"
/>
</div>
<div class="flex justify-end"> <div class="flex justify-end">
<button @click="updateAttachedComponent(index)" class="btn btn-primary">更新组件</button> <button @click="updateAttachedComponent(index)" class="btn btn-primary">更新组件</button>
</div> </div>
@@ -836,9 +836,13 @@
<script> <script>
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import { Parser } from '@etothepii/satisfactory-file-parser'; import { Parser } from '@etothepii/satisfactory-file-parser';
import { CodeEditor } from 'monaco-editor-vue3';
export default { export default {
name: 'BlueprintEditor', name: 'BlueprintEditor',
components: {
CodeEditor
},
setup() { setup() {
// 文件输入引用 // 文件输入引用
const fileInput = ref(null); const fileInput = ref(null);
@@ -855,6 +859,32 @@ export default {
const activeVisualTab = ref('basic'); const activeVisualTab = ref('basic');
const isParsed = ref(false); const isParsed = ref(false);
// 默认的 sbpcfg 十六进制数据
const defaultSbpcfgHex = '04000000e5ffffff5300610074006900730066006100630074006f00720079002000dd84fe56167f918f6856d89ea48b73006200700063006600670000000e0300000000803f0000803f0000803f0000803f300000002f47616d652f466163746f727947616d652f2d5368617265642f426c75657072696e742f49636f6e4c696272617279000c00000049636f6e4c69627261727900010000000000000000000000000000000000000000';
// 编辑器选项 - 调整行号宽度
const editorOptions = ref({
automaticLayout: true,
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontSize: 14,
lineNumbers: 'on',
folding: true,
tabSize: 2,
scrollbar: {
verticalScrollbarSize: 8,
horizontalScrollbarSize: 8
}
});
const smallEditorOptions = ref({
...editorOptions.value,
fontSize: 12,
automaticLayout: true,
minimap: { enabled: false },
tabSize: 2,
});
// 蓝图数据对象 // 蓝图数据对象
const blueprintData = ref({ const blueprintData = ref({
name: '', name: '',
@@ -897,8 +927,8 @@ export default {
}); });
// Objects 数据 // Objects 数据
const rawObjects = ref([]); // 原始读取到的Objects const rawObjects = ref([]);
const newObjects = ref([]); // 编辑后的Objects const newObjects = ref([]);
// 使用轻量级对象存储文件信息 // 使用轻量级对象存储文件信息
const fileInfo = ref({ const fileInfo = ref({
@@ -927,7 +957,7 @@ export default {
// 计算属性 // 计算属性
const canParse = computed(() => { const canParse = computed(() => {
return fileInfo.value.mainFileName; // 只需要主文件存在 return fileInfo.value.mainFileName;
}); });
const mainFileName = computed(() => fileInfo.value.mainFileName); const mainFileName = computed(() => fileInfo.value.mainFileName);
@@ -982,10 +1012,8 @@ export default {
get: () => { get: () => {
if (!isParsed.value) return ''; if (!isParsed.value) return '';
// 创建当前JSON的副本
const jsonObj = { const jsonObj = {
...blueprintData.value, ...blueprintData.value,
// 根据showObjects决定是否包含objects
objects: showObjects.value ? newObjects.value : undefined objects: showObjects.value ? newObjects.value : undefined
}; };
@@ -993,10 +1021,69 @@ export default {
}, },
set: (value) => { set: (value) => {
// 仅存储JSON字符串不自动更新数据模型 // 仅存储JSON字符串不自动更新数据模型
// 用户需要手动点击"更新数据"按钮来应用更改
} }
}); });
// 辅助函数检查是否为复杂对象需要JSON编辑器
const isComplexValue = (value) => {
if (value === null || value === undefined) return false;
return typeof value === 'object' && !(value instanceof Array);
};
// 辅助函数:获取属性显示类型
const getPropertyDisplayType = (property) => {
if (!property) return '';
return property.ueType || property.type || '';
};
// 辅助函数:获取属性值用于显示
const getPropertyDisplayValue = (property) => {
if (!property) return '';
if (isComplexValue(property.value)) {
return JSON.stringify(property.value, null, 2);
}
// 处理嵌套的value对象如ByteProperty的情况
if (property.value && typeof property.value === 'object' && 'value' in property.value) {
return property.value.value;
}
return property.value;
};
// 辅助函数:设置属性值
const setPropertyValue = (property, newValue) => {
if (!property) return;
try {
// 如果是复杂对象尝试解析JSON
if (typeof newValue === 'string' && newValue.trim().startsWith('{')) {
property.value = JSON.parse(newValue);
} else if (property.value && typeof property.value === 'object' && 'value' in property.value) {
// 处理嵌套value对象的情况如ByteProperty
property.value.value = newValue;
} else {
property.value = newValue;
}
} catch (e) {
// 如果JSON解析失败保持原值
console.warn('JSON解析失败保持原值:', e);
}
};
// 方法:处理显示名称
const getDisplayName = (instanceName) => {
if (!instanceName) return '';
return instanceName.replace(/^Persistent_Level:PersistentLevel\./, '');
};
// 方法:处理类型名称
const getTypeName = (typePath) => {
if (!typePath) return '';
return typePath.split('/').pop() || typePath;
};
// 方法 // 方法
const triggerFileInput = () => { const triggerFileInput = () => {
fileInput.value.click(); fileInput.value.click();
@@ -1024,7 +1111,6 @@ export default {
return; return;
} }
// 只存储文件名,不存储文件内容
const sbpFile = validFiles.find(f => f.name.endsWith('.sbp')); const sbpFile = validFiles.find(f => f.name.endsWith('.sbp'));
const sbpcfgFile = validFiles.find(f => f.name.endsWith('.sbpcfg')); const sbpcfgFile = validFiles.find(f => f.name.endsWith('.sbpcfg'));
@@ -1034,7 +1120,6 @@ export default {
configFileName: sbpcfgFile ? sbpcfgFile.name : '' configFileName: sbpcfgFile ? sbpcfgFile.name : ''
}; };
// 只存储文件引用,不存储内容
uploadedFiles.value = validFiles; uploadedFiles.value = validFiles;
}; };
@@ -1064,7 +1149,6 @@ export default {
if (!canParse.value || isParsing.value) return; if (!canParse.value || isParsing.value) return;
try { try {
// 重置状态
isParsing.value = true; isParsing.value = true;
statusMessages.value = []; statusMessages.value = [];
progress.value = 0; progress.value = 0;
@@ -1074,11 +1158,9 @@ export default {
rawObjects.value = []; rawObjects.value = [];
newObjects.value = []; newObjects.value = [];
// 获取主文件
const sbpFile = uploadedFiles.value.find(f => f.name.endsWith('.sbp')); const sbpFile = uploadedFiles.value.find(f => f.name.endsWith('.sbp'));
const sbpcfgFile = uploadedFiles.value.find(f => f.name.endsWith('.sbpcfg')); const sbpcfgFile = uploadedFiles.value.find(f => f.name.endsWith('.sbpcfg'));
// 读取文件为ArrayBuffer
const readFileAsArrayBuffer = (file) => { const readFileAsArrayBuffer = (file) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
@@ -1095,13 +1177,18 @@ export default {
sbpcfgBuffer = await readFileAsArrayBuffer(sbpcfgFile); sbpcfgBuffer = await readFileAsArrayBuffer(sbpcfgFile);
statusMessages.value.push('检测到配置文件,将一起解析...'); statusMessages.value.push('检测到配置文件,将一起解析...');
} else { } else {
statusMessages.value.push('未检测到配置文件,将仅解析主文件...'); statusMessages.value.push('未检测到配置文件,将使用默认配置...');
const hexString = defaultSbpcfgHex;
const buffer = new ArrayBuffer(hexString.length / 2);
const view = new Uint8Array(buffer);
for (let i = 0; i < hexString.length; i += 2) {
view[i / 2] = parseInt(hexString.substring(i, i + 2), 16);
}
sbpcfgBuffer = buffer;
} }
// 更新状态
statusMessages.value.push('开始解析蓝图文件...'); statusMessages.value.push('开始解析蓝图文件...');
// 解析蓝图
const blueprint = await Parser.ParseBlueprintFiles( const blueprint = await Parser.ParseBlueprintFiles(
fileInfo.value.blueprintName, fileInfo.value.blueprintName,
sbpBuffer, sbpBuffer,
@@ -1114,13 +1201,9 @@ export default {
} }
); );
// 存储原始objects数据
rawObjects.value = blueprint.objects || []; rawObjects.value = blueprint.objects || [];
// 初始化newObjects为rawObjects的副本
newObjects.value = JSON.parse(JSON.stringify(rawObjects.value)); newObjects.value = JSON.parse(JSON.stringify(rawObjects.value));
// 提取蓝图数据
blueprintData.value = { blueprintData.value = {
name: blueprint.name || '', name: blueprint.name || '',
compressionInfo: { compressionInfo: {
@@ -1183,20 +1266,32 @@ export default {
isExporting.value = true; isExporting.value = true;
exportStatus.value = '开始导出蓝图文件...'; exportStatus.value = '开始导出蓝图文件...';
// 创建当前JSON对象 // 创建当前时间戳
const currentJson = { const timestamp = Date.now().toString();
...blueprintData.value,
// 根据showObjects状态决定使用哪个objects数据 // 准备导出的数据 - 深拷贝避免修改原始数据
objects: showObjects.value ? newObjects.value : rawObjects.value const exportData = JSON.parse(JSON.stringify({
}; ...blueprintData.value,
objects: newObjects.value
}));
// 确保 lastEditedBy 数组存在
if (!exportData.config.lastEditedBy) {
exportData.config.lastEditedBy = [];
}
// 在数组尾部添加新的编辑者信息(第二位)
exportData.config.lastEditedBy.push({
accountId: timestamp,
displayName: "LinXingLuo",
platformName: "Satisfactory-edit"
});
// 准备导出变量
let mainFileHeader = null; let mainFileHeader = null;
const mainFileBodyChunks = []; const mainFileBodyChunks = [];
// 导出蓝图
const summary = await Parser.WriteBlueprintFiles( const summary = await Parser.WriteBlueprintFiles(
currentJson, exportData,
header => { header => {
mainFileHeader = header; mainFileHeader = header;
}, },
@@ -1205,13 +1300,11 @@ export default {
} }
); );
// 组合主文件
const mainFileData = new Uint8Array([ const mainFileData = new Uint8Array([
...mainFileHeader, ...mainFileHeader,
...mainFileBodyChunks.flatMap(chunk => [...chunk]) ...mainFileBodyChunks.flatMap(chunk => [...chunk])
]); ]);
// 创建下载链接
const downloadFile = (data, filename, type) => { const downloadFile = (data, filename, type) => {
const blob = new Blob([data], { type }); const blob = new Blob([data], { type });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@@ -1224,11 +1317,10 @@ export default {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}; };
// 下载文件
downloadFile(mainFileData, `${blueprintName.value}.sbp`, 'application/octet-stream'); downloadFile(mainFileData, `${blueprintName.value}.sbp`, 'application/octet-stream');
downloadFile(summary.configFileBinary, `${blueprintName.value}.sbpcfg`, 'application/octet-stream'); downloadFile(summary.configFileBinary, `${blueprintName.value}.sbpcfg`, 'application/octet-stream');
exportStatus.value = '蓝图文件导出成功!'; exportStatus.value = '蓝图文件导出成功!已添加编辑者信息。';
} catch (err) { } catch (err) {
console.error('导出错误:', err); console.error('导出错误:', err);
@@ -1247,9 +1339,7 @@ export default {
} }
}; };
// 刷新JSON显示
const refreshJson = () => { const refreshJson = () => {
// 创建一个新的JSON对象根据showObjects状态决定是否包含objects
const jsonObj = { const jsonObj = {
...blueprintData.value, ...blueprintData.value,
objects: showObjects.value ? newObjects.value : undefined objects: showObjects.value ? newObjects.value : undefined
@@ -1258,19 +1348,16 @@ export default {
displayedJson.value = JSON.stringify(jsonObj, null, 2); displayedJson.value = JSON.stringify(jsonObj, null, 2);
}; };
// 从JSON更新数据
const updateDataFromJson = () => { const updateDataFromJson = () => {
try { try {
const editedObj = JSON.parse(displayedJson.value); const editedObj = JSON.parse(displayedJson.value);
// 更新blueprintData除了objects
for (const key in editedObj) { for (const key in editedObj) {
if (key !== 'objects') { if (key !== 'objects') {
blueprintData.value[key] = editedObj[key]; blueprintData.value[key] = editedObj[key];
} }
} }
// 如果JSON中包含objects字段则更新newObjects
if (editedObj.objects) { if (editedObj.objects) {
newObjects.value = editedObj.objects; newObjects.value = editedObj.objects;
} }
@@ -1293,7 +1380,6 @@ export default {
const updateEntityFromJson = () => { const updateEntityFromJson = () => {
try { try {
const updatedEntity = JSON.parse(entityJson.value); const updatedEntity = JSON.parse(entityJson.value);
// 找到实体在原始数组中的位置
const originalIndex = newObjects.value.findIndex( const originalIndex = newObjects.value.findIndex(
e => e.instanceName === selectedEntity.value.instanceName e => e.instanceName === selectedEntity.value.instanceName
); );
@@ -1344,17 +1430,6 @@ export default {
const addProperty = () => { const addProperty = () => {
if (!newProperty.value.name || !selectedEntity.value) return; if (!newProperty.value.name || !selectedEntity.value) return;
// 处理ObjectProperty类型
let value = newProperty.value.value;
if (newProperty.value.type === 'ObjectProperty') {
try {
value = JSON.parse(newProperty.value.value);
} catch (e) {
value = { levelName: "", pathName: "" };
}
}
// 确保properties对象存在
if (!selectedEntity.value.properties) { if (!selectedEntity.value.properties) {
selectedEntity.value.properties = {}; selectedEntity.value.properties = {};
} }
@@ -1363,15 +1438,13 @@ export default {
type: newProperty.value.type, type: newProperty.value.type,
ueType: newProperty.value.type, ueType: newProperty.value.type,
name: newProperty.value.name, name: newProperty.value.name,
value: value value: newProperty.value.value
}; };
// 初始化新属性的JSON字符串 if (isComplexValue(newProperty.value.value)) {
if (newProperty.value.type === 'ObjectProperty' || newProperty.value.type === 'StructProperty') { objectProperties.value[newProperty.value.name] = JSON.stringify(newProperty.value.value, null, 2);
objectProperties.value[newProperty.value.name] = JSON.stringify(value, null, 2);
} }
// 重置表单
newProperty.value = { newProperty.value = {
name: '', name: '',
type: 'FloatProperty', type: 'FloatProperty',
@@ -1385,7 +1458,7 @@ export default {
objectProperties.value = {}; objectProperties.value = {};
for (const key in selectedEntity.value.properties) { for (const key in selectedEntity.value.properties) {
const prop = selectedEntity.value.properties[key]; const prop = selectedEntity.value.properties[key];
if (prop.ueType === 'ObjectProperty' || prop.ueType === 'StructProperty') { if (isComplexValue(prop.value)) {
objectProperties.value[key] = JSON.stringify(prop.value, null, 2); objectProperties.value[key] = JSON.stringify(prop.value, null, 2);
} }
} }
@@ -1422,7 +1495,6 @@ export default {
// 监听newObjects变化 // 监听newObjects变化
watch(newObjects, () => { watch(newObjects, () => {
// 当newObjects变化时刷新附属组件
if (selectedEntity.value) { if (selectedEntity.value) {
initAttachedComponentJson(); initAttachedComponentJson();
} }
@@ -1447,6 +1519,8 @@ export default {
mainFileName, mainFileName,
configFileName, configFileName,
isParsed, isParsed,
editorOptions,
smallEditorOptions,
// 第一个文件编辑器相关 // 第一个文件编辑器相关
filteredEntities, filteredEntities,
@@ -1461,6 +1535,12 @@ export default {
componentsJson, componentsJson,
attachedComponents, attachedComponents,
// 辅助函数
isComplexValue,
getPropertyDisplayType,
getPropertyDisplayValue,
setPropertyValue,
// 方法 // 方法
triggerFileInput, triggerFileInput,
handleFileChange, handleFileChange,
@@ -1475,6 +1555,8 @@ export default {
removeRecipe, removeRecipe,
addItemCost, addItemCost,
removeItemCost, removeItemCost,
getDisplayName,
getTypeName,
// 第一个文件编辑器方法 // 第一个文件编辑器方法
selectEntity, selectEntity,
@@ -1487,85 +1569,3 @@ export default {
} }
} }
</script> </script>
<style scoped>
.container {
min-height: 100vh;
}
.card-title {
margin-bottom: 1rem;
}
.progress {
transition: width 0.3s ease;
}
.json-editor {
min-height: 400px;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.5;
}
.alert {
margin-top: 1rem;
}
.badge {
margin-left: 0.5rem;
}
.large-file-warning {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(255, 0, 0, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0); }
}
.dimension-input {
width: 80px;
}
.color-preview {
width: 30px;
height: 30px;
border-radius: 4px;
display: inline-block;
vertical-align: middle;
margin-right: 10px;
}
.recipe-item {
border-radius: 4px;
padding: 8px;
margin-bottom: 8px;
}
.item-cost {
border-radius: 4px;
padding: 8px;
margin-bottom: 8px;
}
.editor-tabs .tab {
padding: 12px 20px;
}
.tab[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.object-list-item {
transition: all 0.2s;
}
.object-list-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
</style>

View File

@@ -1,43 +0,0 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>