import { defineStore, acceptHMRUpdate } from 'pinia' import { debounce } from 'lodash-es' import { store } from '@/store' import { getCurrentUserId, imStorage, setQuietly, StorageKeys } from '../../utils/storage' import { getConversationKey } from '../../utils/conversation' /** * 草稿快照 * - html:editor 是 contenteditable,含 @-token /
,回填编辑器用整段 innerHTML 才能还原 * - plain:纯文本预览,给会话列表 [草稿] 文案展示用,避免列表渲染时再 strip HTML */ export interface DraftSnapshot { html: string plain: string } /** 草稿持久化整桶结构:Record<会话 key, 快照>;草稿量级小(每会话至多几百字节),整桶整写够用 */ type DraftBucket = Record /** 写盘 debounce:用户连续敲键盘时合并写入,避免高频 IDB 写放大 */ const PERSIST_DEBOUNCE_MS = 500 /** 合并连续输入的 IDB 写;setQuietly 内部 toRaw 拆 reactive proxy,避免 structuredClone 抛错 */ const persistBucket = debounce((userId: number, bucket: DraftBucket) => { setQuietly(StorageKeys.drafts(userId), bucket, '[IM draftStore] persist 失败') }, PERSIST_DEBOUNCE_MS) export const useDraftStore = defineStore('imDraft', { state: () => ({ /** 内存草稿表:key = getConversationKey */ drafts: {} as DraftBucket }), actions: { /** * 启动时从 IDB 加载已有草稿;失败仅打日志(草稿丢失可以接受,不阻断 IM 初始化) * * 进入即清空内存:账号切换 / 重进 IM 时 store 实例可能保留上一次的 drafts, * 同 targetId 的不同账号会话会串显示 [草稿] */ async loadDrafts(): Promise { this.drafts = {} const userId = getCurrentUserId() if (!userId) { return } try { const bucket = await imStorage.getItem(StorageKeys.drafts(userId)) if (bucket && typeof bucket === 'object') { this.drafts = bucket } } catch (e) { console.warn('[IM draftStore] loadDrafts 失败', e) } }, /** 取草稿;返回 undefined 表示该会话无草稿 */ getDraft(conversation: { type: number; targetId: number }): DraftSnapshot | undefined { return this.drafts[getConversationKey(conversation)] }, /** * 写草稿 + debounce 落盘 * * plain 为空(仅含
/ 空白)按 clear 处理:避免列表残留 [草稿] 与编辑器实际为空对不上 */ setDraft( conversation: { type: number; targetId: number }, snapshot: DraftSnapshot ): void { if (!snapshot.plain.trim()) { this.clearDraft(conversation) return } this.drafts[getConversationKey(conversation)] = snapshot this.schedulePersist() }, /** 清草稿:发送成功 / 编辑器清空 / 会话被软删除时调用 */ clearDraft(conversation: { type: number; targetId: number }): void { const key = getConversationKey(conversation) if (!(key in this.drafts)) { return } delete this.drafts[key] this.schedulePersist() }, /** 调度 debounce 写盘;未登录时直接跳过(无主 key 不写) */ schedulePersist(): void { const userId = getCurrentUserId() if (!userId) { return } persistBucket(userId, this.drafts) }, /** 立即落盘待写的草稿;beforeunload 时调,避免最后一次输入卡在 debounce 队列里丢失 */ flushPersist(): void { persistBucket.flush() } } }) export const useDraftStoreWithOut = () => { return useDraftStore(store) } // dev: 让 Pinia 的 actions / state 改动支持 HMR,避免每次改 store 都得硬刷 if (import.meta.hot) { import.meta.hot.accept(acceptHMRUpdate(useDraftStore, import.meta.hot)) }