# 网页粒子系统 · 阶段九 > 阶段定位:**撤回与快捷键、编辑器配置持久化、可复用预设体系定版** > 完成日期:2026-08-30 > 工程目录:`/Users/tianmokeji/Desktop/SpineParticle` > 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Spine Runtime 4.2 + Vite --- ## 一、阶段结论 阶段九在阶段八场景编辑、Spine 骨骼跟随和对象复制能力的基础上,补齐了编辑器日常使用所需的撤回、快捷键、工程保存、工程加载和预设加载流程。 编辑器现在可以把粒子系统、碰撞体、路径、Spine、系统设置、画布视图和动画归属等数据保存为可移植 JSON,并能把 Spine 骨架、Atlas 与多张纹理一并内嵌。配置重新加载后不需要再次选择 Spine 原始文件,画布、动画列表和骨骼树可以直接恢复。 本阶段主要完成: 1. 实现默认 20 步、最大 200 步的撤回历史; 2. 在系统设置中增加最大撤回次数设置; 3. 支持 `Ctrl/Cmd + Z` 撤回; 4. 增加画布工具、时间轴和保存配置快捷键; 5. 实现完整编辑器配置 JSON 下载保存; 6. 实现本地 JSON 配置加载和数据恢复; 7. 将 Spine 原始资源以内嵌 Data URL 形式纳入配置; 8. 修复 Spine 配置恢复后的运行时资源注册和渲染; 9. 实现 `public/Preset/` 预设目录与自动索引; 10. 实现预设列表、空状态和一键加载; 11. 建立纯粒子、路径、碰撞体、Spine 和空场景测试预设; 12. 修复包含 `+` 的预设文件名无法通过开发服务器读取的问题; 13. 为预设加载错误增加文件命名检查提示; 14. 修复同一配置第二次加载后停在第 0 帧的问题; 15. 完成普通配置和含 Spine 配置的保存—加载往返验证。 --- ## 二、撤回功能 ### 2.1 历史模型 编辑器通过快照记录可撤回数据。默认保留 20 步,用户可以在系统设置中修改,允许范围为 1~200 步。 快照覆盖: - 全部粒子系统及其面板参数; - 全部碰撞体; - 全部路径与路径点; - 全部 Spine 场景对象; - 所属动画列表; - 除撤回上限自身以外的系统设置; - 对象显隐、背景图数据及背景偏移和透明度。 以下内容不作为普通撤回快照保存: - 粒子逐帧录制缓存; - PixiJS `Texture` 实例; - 拖尾运行时纹理实例; - 当前时间轴帧和正在播放状态; - 撤回上限本身。 这些数据属于运行时缓存或撤回系统自身配置,恢复快照后由编辑器重新计算或重建。 ### 2.2 操作合并 参数连续拖动或连续输入不会为每个微小变化创建独立历史。编辑器在最后一次变化后等待约 250ms,再把连续变化合并为一步操作。 该设计适用于: - 滑块拖动; - 数值连续输入; - 曲线调整; - 场景对象变换; - 背景图偏移和透明度调整。 ### 2.3 Spine 资源安全 Spine 场景对象的可序列化属性保存在快照中,已解析的 SkeletonData 和纹理通过资源注册表引用计数保留。 撤回删除、复制或资源替换操作时: - 快照仍引用的 Spine 资源不会被提前销毁; - 恢复快照时重新注册对应 Spine ID 的运行时资源; - 超出撤回上限的旧快照会释放自己持有的资源引用; - 粒子录制缓存清空并重新生成。 ### 2.4 撤回后的状态修复 执行撤回后会: 1. 恢复场景对象和设置; 2. 修复当前选择,避免选择已不存在的对象; 3. 恢复粒子系统所属动画; 4. 清空旧录制帧; 5. 重新计算时间轴总长度; 6. 更新撤回按钮的剩余步数和可用状态。 当前阶段只实现“撤回”,尚未实现“重做”。 --- ## 三、快捷键 ### 3.1 快捷键表 | 快捷键 | 作用 | 生效范围 | |---|---|---| | `Ctrl/Cmd + Z` | 撤回上一步 | 全局 | | `Ctrl/Cmd + S` | 下载保存当前编辑器配置 | 全局 | | `Q` | 切换为画布拖动模式 | 画布已选中 | | `W` | 切换位移工具 | 画布已选中 | | `E` | 切换旋转工具 | 画布已选中 | | `R` | 切换缩放工具 | 画布已选中 | | `D` | 播放/暂停时间轴 | 全局非输入状态 | | `S` | 回到时间轴第 0 帧 | 全局非输入状态 | ### 3.2 浏览器快捷键覆盖 `Ctrl/Cmd + S` 和 `Ctrl/Cmd + Z` 会阻止浏览器默认行为,分别交给编辑器保存和撤回,不再弹出浏览器网页保存或执行浏览器输入历史撤销。 ### 3.3 输入控件保护 当焦点位于以下控件时,普通字母快捷键不会触发: - `input`; - `textarea`; - `select`; - `contenteditable` 内容。 因此输入名称、搜索骨骼或编辑参数时,可以正常键入 `q/w/e/r/d/s`。带 `Ctrl/Cmd` 的保存与撤回仍按编辑器全局快捷键处理。 --- ## 四、编辑器配置保存 ### 4.1 保存入口 场景对象面板上方增加: - `保存配置`; - `加载配置`; - `选择预设`。 点击“保存配置”或按 `Ctrl/Cmd + S`,浏览器会下载: ```text SpineParticle-YYYYMMDD-HHMMSS.json ``` ### 4.2 文件格式 当前格式标识与版本: ```text format: SpineParticle.EditorConfig version: 1 ``` 顶层数据结构: ```text editor 画布缩放与平移 settings 系统设置、背景图及撤回上限 timeline FPS、循环状态和所属动画列表 selection 当前场景对象类型与 ID scene 粒子、碰撞体、路径和 Spine 数组 savedAt 保存时间 ``` ### 4.3 粒子与图片资源 粒子系统保存全部可编辑参数,包括: - 发射模式、持续时间和延迟; - 发射器形状和根变换; - 生命周期、速度、缩放、旋转与曲线; - 颜色、透明度、混合模式; - 修改器; - 碰撞、路径和骨骼跟随; - 图片资源与拖尾资源; - 对象显隐和所属动画。 PixiJS 纹理实例和粒子逐帧缓存不会写入 JSON。图片资源通过自身的预览 URL 或 Data URL 保存,配置加载时重新创建纹理。 ### 4.4 Spine 原始资源内嵌 Spine 对象除属性、动画选择和骨骼元数据外,还保存原始资源副本: ```text sourceBundle.skeleton sourceBundle.atlas sourceBundle.textures[] ``` 每个资源同时保存原文件名和 Data URL。恢复时重新创建同名 `File`: - JSON 使用原 JSON 文件名; - SKEL 使用原二进制文件名; - Atlas 保留原 Atlas 文件名和内容; - 纹理保留 Atlas 中引用的准确文件名。 保持文件名一致是 Atlas 再次匹配多张纹理的关键。Data URL 只是文件内容载体,不会替代资源之间的文件名引用关系。 Spine 面板在首次加载成功后异步生成 `sourceBundle`,避免内嵌资源转换干扰当前画布中的实时 Spine 解析结果。 --- ## 五、编辑器配置加载 ### 5.1 校验 加载本地文件时只接受 `.json`,并依次检查: 1. 是否为有效 JSON; 2. `format` 是否为 `SpineParticle.EditorConfig`; 3. 配置版本是否为 1; 4. 场景是否包含粒子、碰撞体、路径和 Spine 数组。 失败时保持当前编辑器状态,并显示具体错误。 ### 5.2 原子恢复流程 配置加载先在临时数据中完成资源准备: 1. 深度复制配置数据; 2. 补齐旧粒子参数默认值; 3. 重建粒子和拖尾纹理; 4. 还原 Spine 原始文件; 5. 解析骨架、Atlas 和多纹理; 6. 全部解析成功后再替换当前场景; 7. 注册新的 Spine 运行时资源; 8. 修复对象选择和所属动画; 9. 重新计算时间轴长度。 如果内嵌 Spine 资源解析失败,已临时创建的纹理会释放,不会把半完成场景覆盖到编辑器中。 ### 5.3 Spine 运行时刷新 重复加载配置时,Spine 对象 ID 可能相同。加载流程会提升 `assetVersion`,确保运行时层识别为新资源并重新创建 Spine 显示对象,而不是继续使用旧实例。 资源加载完成后会恢复: - Spine 画布渲染; - 动画列表和动画时长; - 当前选择的 Spine 动画; - 骨骼层级树; - 当前选中骨骼; - 粒子系统保存的 Spine ID 与骨骼名称绑定。 ### 5.4 加载后自动播放 配置和预设加载完成后统一执行: ```text frame = 0 recorded = false playing = true ``` 因此首次加载和连续加载同一份配置都会从头自动播放,不再依赖场景深度监听是否检测到数据变化。 --- ## 六、预设体系 ### 6.1 预设目录 预设文件放置于: ```text public/Preset/ ``` 每个预设都是与“保存配置”完全相同的 `SpineParticle.EditorConfig` JSON,因此预设和本地工程配置共用一套解析、校验和资源恢复流程。 ### 6.2 自动索引 Vite 插件会扫描预设目录中的全部 `.json` 文件并自动生成: ```text public/Preset/index.json ``` 索引生成时: - 排除 `index.json` 自身; - 按中文文件名排序; - 使用文件名去掉 `.json` 作为界面名称; - 开发服务器监听文件新增和删除并刷新列表; - 生产构建开始前重新生成索引。 目录中没有配置文件时,下拉框显示“无预设文件”并置灰。 ### 6.3 当前预设 当前项目包含: - `纯粒子.json`; - `纯粒子-路径.json`; - `纯粒子-碰撞.json`; - `纯粒子-spine.json`; - `空场景.json`; - `默认粒子.json`。 带 Spine 的预设内嵌 `bingo.json`、`bingo.atlas`、`bingo.png` 和 `bingo_2.png`,加载后可直接显示 Spine、动画列表和骨骼树。 ### 6.4 文件命名规则 阶段测试发现,文件名包含 `+` 时,经 URL 编码后开发服务器会返回应用 HTML,而不是目标 JSON,最终表现为“配置文件不是有效的 JSON”。 为确保开发环境和静态托管环境行为一致,组合预设统一使用连字符: ```text 推荐:纯粒子-路径.json 避免:纯粒子+路径.json ``` 预设加载失败时,界面在原始错误后追加: ```text 请检查预设文件命名 ``` --- ## 七、代码结构 本阶段新增或重点调整: | 文件 | 职责 | |---|---| | `src/editor/editorClone.ts` | 安全深度复制编辑器可序列化数据 | | `src/editor/editorHistory.ts` | 撤回快照、合并、上限和 Spine 资源引用 | | `src/editor/useEditorShortcuts.ts` | 全局快捷键、画布焦点和输入保护 | | `src/editor/editorConfig.ts` | 配置创建、下载、校验、加载和资源恢复 | | `src/spine/spineSourceBundle.ts` | Spine 原始文件 Data URL 内嵌与还原 | | `src/spine/spineAssetRegistry.ts` | Spine 配置恢复和撤回所需的资源生命周期 | | `src/views/ParticlePanel.vue` | 保存、加载、预设列表和结果提示 | | `src/views/Stage.vue` | 快捷键接入、撤回按钮和加载后的运行时刷新 | | `vite.config.ts` | 预设索引自动生成和 Spine 依赖去重 | 配置、历史与快捷键逻辑已从主画布和主属性面板中拆分,避免继续扩大单文件职责。 --- ## 八、验证结果 已完成以下实际验证: - `Ctrl/Cmd + S` 可以下载编辑器配置; - 配置文件名按时间生成且 JSON 格式有效; - 修改粒子名称后保存,再次修改并加载,名称恢复为保存值; - 系统设置、背景图数据、画布视图和场景对象写入配置; - 含 Spine 的配置保存后文件约 11MB,资源数据完整内嵌; - 含 Spine 配置重新加载后,Spine 名称恢复; - Spine 画布图像正常显示; - 14 个 Spine 动画及“无”选项正常恢复; - 骨骼层级树正常恢复; - 页面未出现资源解析或渲染错误; - `纯粒子.json` 加载成功; - `纯粒子-路径.json` 加载成功,场景包含粒子与路径; - `纯粒子-碰撞.json` 加载成功,场景包含粒子与碰撞体; - `纯粒子-spine.json` 加载成功,场景包含粒子与 Spine; - `空场景.json` 可以加载为零对象场景; - `默认粒子.json` 加载成功; - 同一 Spine 预设连续加载两次,两次都从第 0 帧自动播放; - 第二次加载后帧数从 40 推进到 59,播放按钮保持“暂停”状态; - 浏览器控制台无错误和警告; - `npm run build` 生产构建通过。 构建仍存在 Vite 主产物超过 500kB 的提示,不影响功能和构建结果。 --- ## 九、当前限制 1. 当前只支持单向撤回,尚未实现重做; 2. 撤回历史只存在于当前页面内存中,刷新页面后清空; 3. 撤回不保存当前播放帧、播放状态、画布选择和粒子录制缓存; 4. 配置格式当前为版本 1,尚未建立跨版本迁移器; 5. Spine 和本地图片使用 Data URL 内嵌,资源较大时配置文件体积明显增加; 6. 保存配置始终下载新文件,尚未提供覆盖保存、最近文件和自动保存; 7. 浏览器安全限制下无法记住原本地文件路径; 8. 预设依赖构建时生成的索引,纯静态部署后新增文件需要重新构建; 9. 预设命名目前通过规避 `+` 保证兼容,尚未建立完整的文件名字符校验; 10. 配置导入目前只接受 JSON,尚未支持 YAML; 11. 配置文件没有压缩,包含 Spine 多纹理时下载和解析成本较高; 12. 主包仍包含 Spine 运行时和主要编辑器模块,尚未动态拆包。 --- ## 十、阶段十建议 1. 增加重做功能和 `Ctrl/Cmd + Shift + Z` 快捷键; 2. 增加配置格式迁移器,支持未来版本平滑升级; 3. 为预设文件名建立创建、校验和错误定位工具; 4. 提供自动保存、最近配置和页面异常恢复; 5. 支持压缩工程包,把 JSON 与 Spine、粒子图片资源拆分存储; 6. 增加配置导入前预览、差异对比和合并模式; 7. 为配置与预设建立自动化往返测试; 8. 对无效 ID、丢失骨骼、缺失纹理和旧字段提供迁移报告; 9. 增加工程名称、作者、备注和自定义元数据; 10. 开始设计正式导出结构,把所属动画、粒子系统和 Spine 骨骼绑定映射到导出数据; 11. 对 Spine 和编辑器主模块进行动态加载,降低初始包体积; 12. 增加配置资源大小统计和大文件保存提示。