优化: 移除死代码(backend/useStore)与过期类型依赖,引入 marked/dompurify/vitest
This commit is contained in:
@@ -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()
|
||||
@@ -1,173 +0,0 @@
|
||||
/* =====================================================================
|
||||
* backend.ts — 持久化后端抽象层
|
||||
*
|
||||
* store 只管内存状态 + Op Log,持久化通过 Backend 接口抽象。
|
||||
* 三个实现:
|
||||
* - LocalBackend (localStorage,Web 版当前模式)
|
||||
* - TauriBackend (Rust 文件系统,桌面版,B4 实现)
|
||||
* - CloudBackend (HTTP API,云端版,C0 实现)
|
||||
*
|
||||
* 设计原则:
|
||||
* - 接口只管读写数据,不管状态逻辑(状态逻辑在 store)
|
||||
* - 所有方法返回 Promise(TauriBackend/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
|
||||
}
|
||||
Reference in New Issue
Block a user