将编辑器修改位Monaco并且修复bug
This commit is contained in:
50
package-lock.json
generated
50
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
392
src/App.vue
392
src/App.vue
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
Reference in New Issue
Block a user