新增: 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