fix: all data storage uniformly uses indexed.

This commit is contained in:
Zyronon 2026-04-24 02:35:05 +08:00 committed by GitHub
parent c2f05714db
commit d58f598a9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 65 additions and 38 deletions

View File

@ -87,10 +87,10 @@ function getSyncClient(client?: SupabaseClient | null): SupabaseClient | null {
async function getLocalPersistMeta(type: SyncDataType): Promise<LocalPersistMeta | null> {
if (type === SyncDataType.practice_word) {
return getPracticeWordCacheLocalWithMeta()
return await getPracticeWordCacheLocalWithMeta()
}
if (type === SyncDataType.practice_article) {
return getPracticeArticleCacheLocalWithMeta()
return await getPracticeArticleCacheLocalWithMeta()
}
const raw = await get(getPersistKey(type))
try {
@ -102,11 +102,11 @@ async function getLocalPersistMeta(type: SyncDataType): Promise<LocalPersistMeta
async function persistLocalState(type: SyncDataType, val: unknown, updated_at?: string): Promise<void> {
if (type === SyncDataType.practice_word) {
setPracticeWordCacheLocal(val as PracticeWordCacheStored, updated_at)
await setPracticeWordCacheLocal(val as PracticeWordCacheStored, updated_at)
return
}
if (type === SyncDataType.practice_article) {
setPracticeArticleCacheLocal(val as PracticeArticleCache, updated_at)
await setPracticeArticleCacheLocal(val as PracticeArticleCache, updated_at)
return
}
await set(
@ -327,10 +327,8 @@ export async function saveHashSnapshot(currentHash: string, previousHash: string
data: {
dict: await get(SAVE_DICT_KEY.key),
setting: await get(SAVE_SETTING_KEY.key),
//@ts-ignore
[PRACTICE_WORD_CACHE.key]: import.meta.client ? localStorage.getItem(PRACTICE_WORD_CACHE.key) : null,
//@ts-ignore
[PRACTICE_ARTICLE_CACHE.key]: import.meta.client ? localStorage.getItem(PRACTICE_ARTICLE_CACHE.key) : null,
[PRACTICE_WORD_CACHE.key]: await get(PRACTICE_WORD_CACHE.key) ?? null,
[PRACTICE_ARTICLE_CACHE.key]: await get(PRACTICE_ARTICLE_CACHE.key) ?? null,
},
}
if (!snapshot.data.dict) {
@ -569,9 +567,9 @@ export function useDataSyncPersistence() {
await saveLocalAndSync(SyncDataType.dict, data, { ...options, canSyncRemote })
}
function getLocalCompactDataByType(type: SyncDataType) {
if (type === SyncDataType.practice_word) return getPracticeWordCacheLocal()
if (type === SyncDataType.practice_article) return getPracticeArticleCacheLocal()
async function getLocalCompactDataByType(type: SyncDataType) {
if (type === SyncDataType.practice_word) return await getPracticeWordCacheLocal()
if (type === SyncDataType.practice_article) return await getPracticeArticleCacheLocal()
if (type === SyncDataType.dict) return shakeCommonDict(store.$state)
if (type === SyncDataType.setting) return settingStore.$state
}

View File

@ -137,7 +137,7 @@ export function usePracticeWordPersistence() {
async function load(): Promise<PracticeWordCache | null> {
const res = await fetch()
return res ?? restorePracticeWordCache(getPracticeWordCacheLocal())
return res ?? restorePracticeWordCache(await getPracticeWordCacheLocal())
}
async function fetch(): Promise<PracticeWordCache | null> {
@ -150,7 +150,7 @@ export function usePracticeWordPersistence() {
}
async function getLocalDataCompact(): Promise<PracticeWordCacheStored> {
return getPracticeWordCacheLocal()
return await getPracticeWordCacheLocal()
}
async function save(data: PracticeWordCache | null) {
@ -170,11 +170,11 @@ export function usePracticeArticlePersistence() {
async function load(): Promise<PracticeArticleCache | null> {
const res = await fetch()
return res ?? getPracticeArticleCacheLocal()
return res ?? await getPracticeArticleCacheLocal()
}
async function getLocalDataCompact(): Promise<PracticeArticleCache | null> {
return getPracticeArticleCacheLocal()
return await getPracticeArticleCacheLocal()
}
async function fetch(): Promise<PracticeArticleCache | null> {
@ -195,4 +195,4 @@ export function usePracticeArticlePersistence() {
}
return { load, save, clear, fetch, getLocalDataCompact }
}
}

View File

@ -1,5 +1,6 @@
import type { PracticeData, TaskWords } from '../types'
import type { PracticeState } from '../stores'
import { get, set } from 'idb-keyval'
type CacheConfig = { key: string; version: number }
@ -47,52 +48,80 @@ export type PracticeArticleCache = {
export type LocalCacheResult<T> = { val: T; updated_at?: string; version: number }
function getLocal<T>(config: CacheConfig): T | null {
const result = getLocalWithMeta<T>(config)
return result?.val ?? null
}
/** 返回带 updated_at 的本地缓存,供同步时比较时间戳用;无数据或解析失败返回 null */
function getLocalWithMeta<T>(config: CacheConfig): LocalCacheResult<T> | null {
const d = localStorage.getItem(config.key)
if (!d) return null
/**
* localStorage IndexedDB
* idb localStorage localStorage key
* JSON idb
*/
async function migrateFromLocalStorage<T>(config: CacheConfig): Promise<LocalCacheResult<T> | null> {
try {
return JSON.parse(d)
} catch {
const raw = localStorage.getItem(config.key)
if (!raw) return null
const parsed = JSON.parse(raw) as LocalCacheResult<T>
// 迁移到 idb直接存对象不再序列化字符串
await set(config.key, parsed)
// 删除 localStorage 中的老数据
localStorage.removeItem(config.key)
console.log(`[cache] migrated ${config.key} from localStorage to idb`)
return parsed
} catch {
return null
}
}
function setLocal<T>(config: CacheConfig, val: T | null, updated_at: string): void {
const payload: { version: number; val: T; updated_at?: string } = {
/** 从 idb 读取带 meta 的缓存;无数据或解析失败返回 null */
async function getLocalWithMeta<T>(config: CacheConfig): Promise<LocalCacheResult<T> | null> {
const raw = await get(config.key)
if (raw) {
// 兼容旧版本写入的 JSON 字符串格式
if (typeof raw === 'string') {
try {
return JSON.parse(raw) as LocalCacheResult<T>
} catch {
return null
}
}
return raw as LocalCacheResult<T>
}
// idb 中没有数据,尝试从 localStorage 迁移(兼容老数据)
return migrateFromLocalStorage<T>(config)
}
async function getLocal<T>(config: CacheConfig): Promise<T | null> {
const result = await getLocalWithMeta<T>(config)
return result?.val ?? null
}
async function setLocal<T>(config: CacheConfig, val: T | null, updated_at: string): Promise<void> {
// idb 原生支持对象存储,直接存对象,无需 JSON.stringify
const payload: LocalCacheResult<T> = {
version: config.version,
val,
updated_at,
}
localStorage.setItem(config.key, JSON.stringify(payload))
await set(config.key, payload)
}
export function getPracticeWordCacheLocal(): PracticeWordCacheStored | null {
export async function getPracticeWordCacheLocal(): Promise<PracticeWordCacheStored | null> {
return getLocal<PracticeWordCacheStored>(PRACTICE_WORD_CACHE)
}
export function getPracticeWordCacheLocalWithMeta(): LocalCacheResult<PracticeWordCacheStored> | null {
export async function getPracticeWordCacheLocalWithMeta(): Promise<LocalCacheResult<PracticeWordCacheStored> | null> {
return getLocalWithMeta<PracticeWordCacheStored>(PRACTICE_WORD_CACHE)
}
export function setPracticeWordCacheLocal(cache: PracticeWordCacheStored | null, updated_at?: string): void {
setLocal(PRACTICE_WORD_CACHE, cache, updated_at)
export async function setPracticeWordCacheLocal(cache: PracticeWordCacheStored | null, updated_at?: string): Promise<void> {
await setLocal(PRACTICE_WORD_CACHE, cache, updated_at)
}
export function getPracticeArticleCacheLocal(): PracticeArticleCache | null {
export async function getPracticeArticleCacheLocal(): Promise<PracticeArticleCache | null> {
return getLocal<PracticeArticleCache>(PRACTICE_ARTICLE_CACHE)
}
export function getPracticeArticleCacheLocalWithMeta(): LocalCacheResult<PracticeArticleCache> | null {
export async function getPracticeArticleCacheLocalWithMeta(): Promise<LocalCacheResult<PracticeArticleCache> | null> {
return getLocalWithMeta<PracticeArticleCache>(PRACTICE_ARTICLE_CACHE)
}
export function setPracticeArticleCacheLocal(cache: PracticeArticleCache | null, updated_at?: string): void {
setLocal(PRACTICE_ARTICLE_CACHE, cache, updated_at)
export async function setPracticeArticleCacheLocal(cache: PracticeArticleCache | null, updated_at?: string): Promise<void> {
await setLocal(PRACTICE_ARTICLE_CACHE, cache, updated_at)
}