大改,使用pinia传值,indexdb做持久化

This commit is contained in:
zwgczx committed 2026-03-11 11:06:04 +08:00
1 parent 3ad7bae1a9
commit 9a045cfe86
20 files changed
+1527 -545

No files matched your search

+124
View File
@@ -0,0 +1,124 @@
import type { PiniaPluginContext } from 'pinia'
import localforage from 'localforage'
export type PersistOption = boolean | {
key?: string
debounce?: number
}
type PersistStoreExt = {
$persistNow?: () => Promise<void>
$clearPersisted?: () => Promise<void>
}
type PiniaStorageConfig = LocalForageOptions & {
mode?: 'single' | 'multiple'
debounce?: number
}
const baseConfig: PiniaStorageConfig = {
driver: localforage.INDEXEDDB,
name: 'DB',
version: 1,
description: 'pinia persisted storage',
storeName: 'pinia-storage',
debounce: 300
}
let singleStore: LocalForage | null = null
const multipleStores = new Map<string, LocalForage>()
const getStore = (mode: 'single' | 'multiple', storeName: string, config: LocalForageOptions) => {
if (mode === 'single') {
if (!singleStore) {
singleStore = localforage.createInstance({ ...baseConfig, ...config })
}
return singleStore
}
if (!multipleStores.has(storeName)) {
multipleStores.set(
storeName,
localforage.createInstance({ ...baseConfig, ...config, storeName })
)
}
return multipleStores.get(storeName) as LocalForage
}
const resolvePersistOptions = (persist: PersistOption | undefined) => {
if (!persist) return null
if (persist === true) return {}
return persist
}
export default (config?: PiniaStorageConfig) => {
const finalConfig = { ...baseConfig, ...(config || {}) }
const { mode = 'single', debounce = 300, ...forageConfig } = finalConfig
return (context: PiniaPluginContext) => {
const persistOptions = resolvePersistOptions(context.options.persist as PersistOption | undefined)
if (!persistOptions) return
const storeId = context.store.$id
const baseStoreName = forageConfig.storeName || baseConfig.storeName || 'pinia-storage'
const resolvedStoreName = mode === 'multiple' ? `${baseStoreName}-${storeId}` : baseStoreName
const key = persistOptions.key || (mode === 'single' ? `${baseStoreName}-${storeId}` : resolvedStoreName)
const lf = getStore(mode, resolvedStoreName, forageConfig)
const persistDebounce = persistOptions.debounce ?? debounce
let timer: ReturnType<typeof setTimeout> | null = null
let hydrating = false
let userMutatedBeforeHydrate = false
const writeState = (state: unknown) => lf.setItem(key, JSON.parse(JSON.stringify(state)))
const storeExt = context.store as typeof context.store & PersistStoreExt
storeExt.$persistNow = async () => {
if (timer) {
clearTimeout(timer)
timer = null
}
try {
await writeState(context.store.$state)
} catch (error) {
console.error('pinia persist failed:', error)
}
}
storeExt.$clearPersisted = async () => {
if (timer) {
clearTimeout(timer)
timer = null
}
try {
await lf.removeItem(key)
} catch (error) {
console.error('pinia clear persisted failed:', error)
}
}
context.store.$subscribe(
(_mutation, state) => {
if (!hydrating) userMutatedBeforeHydrate = true
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
void writeState(state).catch(error => {
console.error('pinia persist failed:', error)
})
}, Math.max(0, persistDebounce))
},
{ detached: true }
)
void lf.getItem<Record<string, unknown>>(key)
.then(state => {
if (!state || typeof state !== 'object') return
// 若在异步hydrate返回前,store已被用户修改(如removeTab),不再回填旧缓存覆盖当前状态。
if (userMutatedBeforeHydrate) return
hydrating = true
context.store.$patch(state as any)
hydrating = false
})
.catch(error => {
hydrating = false
console.error('pinia hydrate failed:', error)
})
}
}