外观
状态与持久化
目标:分清应该保留的数据、可重新获取的数据和仅当前页面有效的状态。
状态归属
| 状态 | 所有者 | 持久化位置 |
|---|---|---|
| token | 内核 token 工具与 session store | uadmin_token |
| 当前用户与导航 | session / nav store | 会话启动时重新获取 |
| 主题、布局、方向 | prefs store | uadmin_prefs |
| 标签页及显示偏好 | tabs store | uadmin_tabs、uadmin_tabs_ui |
| 语言 | core i18n | uadmin_locale |
| 演示业务记录 | Mock 注册表 | uadmin_demo_v1_state |
| 演示文件 | 文件存储模块 | uadmin_demo_v1_files IndexedDB |
token 恢复不等于用户资料已经加载。使用 session.viewer 时允许初始 null,不要把存储里有没有 token 当作业务数据已就绪。
页面状态保持局部
筛选、分页、loading 和表单草稿优先由页面或 hook 管理。任务列表通过 apps/demo/src/hooks/useRemoteList.ts 和 useTaskList.ts 管理请求与刷新,没有为每个列表再建一个全局 store。
确实需要多页面共享时才新增 Pinia store;持久化字段应显式挑选,避免保存 loading、错误对象、组件实例或临时 Blob URL。
ts
import { usePrefsStore } from '@uadmin/app'
const prefs = usePrefsStore()
prefs.setThemeMode('dark')
prefs.resetAppearance()上述操作会沿内核现有持久化流程更新,不需要额外调用 localStorage。
浏览器 Mock 的提交边界
packages/modules/src/mock/storage.ts 将已登记状态合成一个版本化 JSON 快照。adapter 串行执行请求,持久化失败会恢复原状态。文件独立保存在 IndexedDB,记录中保存稳定文件 ID,而非跨刷新失效的 Blob URL。
浏览器数据按 origin 隔离。同域不同路径通常共享 localStorage,因此不要把路径当作租户隔离;演示也不提供多标签页数据库级并发事务。真实业务状态应由 API 与服务端数据库管理。
清理与重置
退出账号使用会话流程,演示重置使用 apps/demo/mock/browser.ts 的 resetDemoData()。后者等待请求结束、清理演示文件与版本化数据、移除 token/标签页并刷新,不会使用 localStorage.clear() 清空同域其他应用。
外观和语言并不属于业务种子,重置演示不意味着清除所有偏好。测试需要初始外观时使用 prefs 的方法或明确的专属存储键。
排障
写入后刷新丢失: 检查存储异常、是否处于 Node 开发 Mock、是否使用不同 origin。退出后仍看到旧菜单: 检查 nav 是否清理。上传预览刷新后失效: 检查保存的是文件 ID 还是 Blob URL。浏览器禁用存储: 应显示失败并允许重试,不将未持久化的变更提示为已保存。