开发中途存档
This commit is contained in:
1 parent
a2de23a789
commit
a80575ef5e
19 files changed
+1998
-91
No files matched your search
@@ -4,6 +4,17 @@
|
||||
<span>粒子系统 · 编辑器</span>
|
||||
</div>
|
||||
|
||||
<div class="config-toolbar">
|
||||
<button title="保存全部编辑器配置 (Ctrl+S)" @click="saveConfig">保存配置</button>
|
||||
<button title="加载本地编辑器配置" @click="configFileInput?.click()">加载配置</button>
|
||||
<select v-model="selectedPreset" aria-label="预设列表" :disabled="!presetOptions.length || presetLoading" @pointerdown="refreshPresets" @focus="refreshPresets" @change="loadSelectedPreset">
|
||||
<option value="">{{ presetOptions.length ? '选择预设' : '无预设文件' }}</option>
|
||||
<option v-for="preset in presetOptions" :key="preset.file" :value="preset.file">{{ preset.name }}</option>
|
||||
</select>
|
||||
<input ref="configFileInput" hidden type="file" accept=".json,application/json" @change="onConfigFileChange" />
|
||||
</div>
|
||||
<div v-if="configMessage" class="config-message" :class="{ error: configError }">{{ configMessage }}</div>
|
||||
|
||||
<!-- 1. 场景对象:创建入口始终可用,即使当前没有粒子系统。 -->
|
||||
<div class="group">
|
||||
<div class="group-head scene-head" @click="toggle('scene')">
|
||||
@@ -768,7 +779,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, markRaw, reactive, ref, watchEffect } from 'vue'
|
||||
import { computed, markRaw, onMounted, reactive, ref, watchEffect } from 'vue'
|
||||
import { Texture } from 'pixi.js'
|
||||
import { useParticleStore } from '../store/particleStore'
|
||||
import { defaultConfig, ensureEmitterConfig } from '../core/particleEmitter'
|
||||
@@ -778,11 +789,76 @@ import ParticleAttributeControl from './ParticleAttributeControl.vue'
|
||||
import ColorGradientEditor from './ColorGradientEditor.vue'
|
||||
import ImageResourceCard from './ImageResourceCard.vue'
|
||||
import SpineObjectPanel from '../spine/SpineObjectPanel.vue'
|
||||
import { downloadEditorConfig, loadEditorConfigFile, loadEditorConfigText } from '../editor/editorConfig'
|
||||
|
||||
const store = useParticleStore()
|
||||
const configFileInput = ref<HTMLInputElement | null>(null)
|
||||
const selectedPreset = ref('')
|
||||
const presetOptions = ref<Array<{ name: string; file: string }>>([])
|
||||
const presetLoading = ref(false)
|
||||
const configMessage = ref('')
|
||||
const configError = ref(false)
|
||||
// 默认场景始终有一个可编辑的粒子系统;热更新保留现有系统时不重复创建。
|
||||
if (!store.systems.length) store.addSystem()
|
||||
|
||||
function saveConfig() {
|
||||
downloadEditorConfig(store)
|
||||
configError.value = false
|
||||
configMessage.value = '配置已保存到本地'
|
||||
}
|
||||
|
||||
async function onConfigFileChange(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
if (!file) return
|
||||
configMessage.value = '正在加载配置…'
|
||||
configError.value = false
|
||||
try {
|
||||
await loadEditorConfigFile(store, file)
|
||||
configMessage.value = `已加载:${file.name}`
|
||||
} catch (error) {
|
||||
configError.value = true
|
||||
configMessage.value = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
input.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshPresets() {
|
||||
try {
|
||||
const response = await fetch(`/Preset/index.json?t=${Date.now()}`, { cache: 'no-store' })
|
||||
if (!response.ok) throw new Error('无法读取预设列表')
|
||||
const presets = await response.json()
|
||||
presetOptions.value = Array.isArray(presets)
|
||||
? presets.filter((item): item is { name: string; file: string } => !!item && typeof item.name === 'string' && typeof item.file === 'string')
|
||||
: []
|
||||
} catch {
|
||||
presetOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSelectedPreset() {
|
||||
const file = selectedPreset.value
|
||||
if (!file) return
|
||||
presetLoading.value = true
|
||||
configError.value = false
|
||||
configMessage.value = '正在加载预设…'
|
||||
try {
|
||||
const response = await fetch(`/Preset/${encodeURIComponent(file)}`, { cache: 'no-store' })
|
||||
if (!response.ok) throw new Error(`无法读取预设:${file}`)
|
||||
await loadEditorConfigText(store, await response.text())
|
||||
configMessage.value = `已加载预设:${file.replace(/\.json$/i, '')}`
|
||||
} catch (error) {
|
||||
configError.value = true
|
||||
configMessage.value = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
presetLoading.value = false
|
||||
selectedPreset.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(refreshPresets)
|
||||
|
||||
const activeIdx = computed(() => {
|
||||
const i = store.systems.findIndex((s) => s.id === store.activeId)
|
||||
return i >= 0 ? i : 0
|
||||
@@ -1434,6 +1510,17 @@ function toggleResourceLock(resourceId: number) {
|
||||
.panel::-webkit-scrollbar-thumb:hover { background: #3a3a55; }
|
||||
.panel { scrollbar-width: thin; scrollbar-color: #2e2e44 transparent; }
|
||||
.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-weight: 700; color: #ccd; }
|
||||
.config-toolbar { display: grid; grid-template-columns: auto auto minmax(0,1fr); gap: 6px; margin-bottom: 10px; }
|
||||
.config-toolbar button,.config-toolbar select {
|
||||
min-width: 0; height: 30px; box-sizing: border-box; border: 1px solid #3b4864; border-radius: 6px;
|
||||
background: #263149; color: #d6def0; font-size: 11px;
|
||||
}
|
||||
.config-toolbar button { padding: 0 9px; cursor: pointer; }
|
||||
.config-toolbar button:hover { border-color: #7169dc; background: #343d68; color: #fff; }
|
||||
.config-toolbar select { padding: 0 7px; overflow: hidden; text-overflow: ellipsis; }
|
||||
.config-toolbar select:disabled { color: #68738a; background: #1b2130; }
|
||||
.config-message { margin: -4px 0 9px; color: #83b9a2; font-size: 10px; }
|
||||
.config-message.error { color: #e8959e; }
|
||||
.empty, .panel-hint { color: #667; font-size: 12px; padding: 8px 0; }
|
||||
.group { border: 1px solid #2a2a3c; border-radius: 8px; margin-bottom: 10px; overflow: hidden; }
|
||||
.group-head {
|
||||
|
||||
Reference in new issue
Block a user