新增: Backend 持久化抽象层 + LocalBackend 实现(P3)
- 定义 Backend 接口: deck/文库/会话/模板/主题/配置的读写 - LocalBackend 实现(localStorage,Web版当前模式) - getBackend()/setBackend() 管理活动后端实例 - store 内部 localStorage 调用暂不变(LocalBackend 是其包装) - B2 迁移时统一改为 getBackend().xxx(),B4 实现 TauriBackend - 为桌面版(Rust 文件系统)和云端版(HTTP API)预留接口
This commit is contained in:
173
src/core/backend.ts
Normal file
173
src/core/backend.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/* =====================================================================
|
||||
* 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