优化: 移除死代码(backend/useStore)与过期类型依赖,引入 marked/dompurify/vitest

This commit is contained in:
lxy
2026-08-24 02:13:14 +08:00
parent cf2de7d64d
commit 01f0f45007
4 changed files with 401 additions and 256 deletions
-73
View File
@@ -1,73 +0,0 @@
/* =====================================================================
* useStore.ts — 统一 Store 接口(兼容 Tauri 和 Web 双模式)
*
* 设计:
* - 检测 __TAURI__ 环境,决定走 invoke 还是直接调 TS store
* - 暴露与现有 store 相同的方法签名(88 处组件调用零改动)
* - 返回 reactive ref(与 Vue 响应式兼容)
*
* Web 模式:直接 return store(现有行为,零改动)
* Tauri 模式:返回封装对象,内部 invoke Rust 命令
* ===================================================================== */
import type { Deck, Slide, SlideElement, LibItem, PageTemplate, ChatMessage, AiCfg } from '../core/types'
import { store as tsStore } from '../core/store'
import { invoke } from '../core/bridge'
/** 是否运行在 Tauri 桌面环境中 */
export const isTauri = typeof window !== 'undefined' && '__TAURI__' in window
/** 根据环境创建 store 接口 */
function createStore() {
// Web 模式:直接返回现有 TS store
if (!isTauri) return tsStore
// Tauri 模式:封装 invoke 调用
// 注意:所有写操作通过 exec_op 统一走 Op Log
const CLIENT_ID = 'client-' + Math.random().toString(36).slice(2, 10)
/** 生成一个 Op 并发送到 Rust 后端 */
async function execOp(type: string, payload: Record<string, unknown>) {
try {
const result = await invoke<{ success: boolean; deck: Deck; error?: string }>('exec_op', {
opJson: { type, clientId: CLIENT_ID, timestamp: Date.now(), ...payload }
})
if (result?.deck) {
// 更新本地 ref 状态(由 TauriBridge watcher 处理)
}
} catch (e) {
console.error('exec_op 失败:', e)
}
}
// 返回与 TS store 类型兼容的对象
// 关键属性通过 computed ref 暴露
return {
...tsStore,
// 读操作暂时委托给 TS store(未来Rust端会emit状态同步)
// 写操作改为 invoke
setTheme: async (name: string) => {
await execOp('set_theme', { theme: name })
},
addSlide: async (atIndex?: number) => {
const slide: Slide = { id: 's-' + Date.now(), background: 'bg', elements: [] }
const idx = atIndex != null ? atIndex + 1 : tsStore.getCount()
await execOp('add_slide', { atIndex: idx, slide })
tsStore.addSlide(atIndex)
},
updateElement: async (id: string, patch: Record<string, unknown>) => {
await execOp('update_element', { slideIdx: tsStore.getCurrentIndex(), elementId: id, patch })
},
delElement: async (id: string) => {
const el = tsStore.findElement(id)
await execOp('del_element', { slideIdx: tsStore.getCurrentIndex(), elementId: id, deletedElement: el ? { ...el } : undefined })
},
undo: async () => {
await invoke('undo')
},
redo: async () => {
await invoke('redo')
},
}
}
export const store = createStore()
-173
View File
@@ -1,173 +0,0 @@
/* =====================================================================
* backend.ts — 持久化后端抽象层
*
* store 只管内存状态 + Op Log,持久化通过 Backend 接口抽象。
* 三个实现:
* - LocalBackend localStorageWeb 版当前模式)
* - TauriBackend Rust 文件系统,桌面版,B4 实现)
* - CloudBackend HTTP API,云端版,C0 实现)
*
* 设计原则:
* - 接口只管读写数据,不管状态逻辑(状态逻辑在 store)
* - 所有方法返回 PromiseTauriBackend/CloudBackend 天然异步)
* - LocalBackend 内部用 localStorage 但包装为 Promise(统一接口)
* ===================================================================== */
import type { Deck, LibItem, PageTemplate, Theme, ChatMessage, AiCfg } from './types'
export interface Backend {
// ---- 当前 deck ----
loadDeck(): Promise<Deck | null>
saveDeck(deck: Deck): Promise<void>
loadBackup(): Promise<Deck | null>
saveBackup(deck: Deck): Promise<void>
clearBackup(): Promise<void>
// ---- 文库 ----
loadLibrary(): Promise<LibItem[]>
saveLibrary(lib: LibItem[]): Promise<void>
getActiveLibId(): Promise<string | null>
setActiveLibId(id: string | null): Promise<void>
// ---- 会话(按 chatId 隔离) ----
loadChat(chatId: string): Promise<ChatMessage[]>
saveChat(chatId: string, msgs: ChatMessage[]): Promise<void>
clearChat(chatId: string): Promise<void>
// ---- 用户模板 ----
loadTemplates(): Promise<PageTemplate[]>
saveTemplates(tpls: PageTemplate[]): Promise<void>
// ---- 自定义主题 ----
loadThemes(): Promise<Record<string, Theme>>
saveThemes(themes: Record<string, Theme>): Promise<void>
// ---- AI 配置 ----
loadCfg(): Promise<Partial<AiCfg> | null>
saveCfg(cfg: AiCfg): Promise<void>
}
/* ============================================================
* LocalBackend — localStorage 实现(Web 版当前模式)
* ============================================================ */
const KEYS = {
deck: 'u-ppt.deck.v1',
deckBak: 'u-ppt.deck.v1.bak',
lib: 'u-ppt.library.v1',
activeLib: 'u-ppt.activeLib.v1',
chatPrefix: 'u-ppt.chat.',
templates: 'u-ppt.templates.v1',
themes: 'u-ppt.themes.v1',
cfg: 'u-ppt.ai-cfg.v1',
}
function lsGet(key: string): string | null {
try { return localStorage.getItem(key) } catch (e) { return null }
}
function lsSet(key: string, val: string): void {
try { localStorage.setItem(key, val) } catch (e) { /* 配额满等忽略 */ }
}
function lsDel(key: string): void {
try { localStorage.removeItem(key) } catch (e) {}
}
export class LocalBackend implements Backend {
async loadDeck(): Promise<Deck | null> {
const raw = lsGet(KEYS.deck)
if (!raw) return null
try { return JSON.parse(raw) } catch (e) { return null }
}
async saveDeck(deck: Deck): Promise<void> {
lsSet(KEYS.deck, JSON.stringify(deck))
}
async loadBackup(): Promise<Deck | null> {
const raw = lsGet(KEYS.deckBak)
if (!raw) return null
try { return JSON.parse(raw) } catch (e) { return null }
}
async saveBackup(deck: Deck): Promise<void> {
lsSet(KEYS.deckBak, JSON.stringify(deck))
}
async clearBackup(): Promise<void> {
lsDel(KEYS.deckBak)
}
async loadLibrary(): Promise<LibItem[]> {
const raw = lsGet(KEYS.lib)
if (!raw) return []
try { return JSON.parse(raw) || [] } catch (e) { return [] }
}
async saveLibrary(lib: LibItem[]): Promise<void> {
lsSet(KEYS.lib, JSON.stringify(lib))
}
async getActiveLibId(): Promise<string | null> {
return lsGet(KEYS.activeLib)
}
async setActiveLibId(id: string | null): Promise<void> {
if (id) lsSet(KEYS.activeLib, id)
else lsDel(KEYS.activeLib)
}
async loadChat(chatId: string): Promise<ChatMessage[]> {
const raw = lsGet(KEYS.chatPrefix + chatId)
if (!raw) return []
try { return JSON.parse(raw) || [] } catch (e) { return [] }
}
async saveChat(chatId: string, msgs: ChatMessage[]): Promise<void> {
lsSet(KEYS.chatPrefix + chatId, JSON.stringify(msgs))
}
async clearChat(chatId: string): Promise<void> {
lsDel(KEYS.chatPrefix + chatId)
}
async loadTemplates(): Promise<PageTemplate[]> {
const raw = lsGet(KEYS.templates)
if (!raw) return []
try { return JSON.parse(raw) || [] } catch (e) { return [] }
}
async saveTemplates(tpls: PageTemplate[]): Promise<void> {
// 只存用户模板(category === 'user'
lsSet(KEYS.templates, JSON.stringify(tpls.filter(t => t.category === 'user')))
}
async loadThemes(): Promise<Record<string, Theme>> {
const raw = lsGet(KEYS.themes)
if (!raw) return {}
try { return JSON.parse(raw) || {} } catch (e) { return {} }
}
async saveThemes(themes: Record<string, Theme>): Promise<void> {
lsSet(KEYS.themes, JSON.stringify(themes))
}
async loadCfg(): Promise<Partial<AiCfg> | null> {
const raw = lsGet(KEYS.cfg)
if (!raw) return null
try { return JSON.parse(raw) } catch (e) { return null }
}
async saveCfg(cfg: AiCfg): Promise<void> {
lsSet(KEYS.cfg, JSON.stringify(cfg))
}
}
/* ---------- 当前活动的 Backend 实例 ---------- */
let activeBackend: Backend = new LocalBackend()
export function getBackend(): Backend {
return activeBackend
}
export function setBackend(b: Backend): void {
activeBackend = b
}