import type { Table } from 'dexie' import type { RecipeItem } from '~/types' import Dexie from 'dexie' export interface DbRecipeItem extends RecipeItem { id?: number } interface RecipeMetaBase { total: number version: number } interface RecipeMetaSingle extends RecipeMetaBase { chunked: false } interface RecipeMetaChunked extends RecipeMetaBase { chunked: true chunkSize: number chunks: { index: number, hash: string, count: number, ids?: number[] }[] } type RecipeMeta = RecipeMetaSingle | RecipeMetaChunked const CHUNK_HASH_STORAGE_KEY = 'cook:chunk-hashes' // 预注册分片文件,Vite 会在构建时处理这些 glob 导入 const chunkModules = import.meta.glob<{ default: RecipeItem[] }>('../../../../packages/data/recipe-chunk-*.json') export class MySubClassedDexie extends Dexie { recipes!: Table constructor() { super('cook-db') this.version(1).stores({ recipes: '++id, name, stuff, bv, difficulty, tags, methods, tools, link, description', // Primary key and indexed props }) } } export const db = new MySubClassedDexie() /** * 加载已记录的分片 hash(用于增量更新判断) */ function loadChunkHashes(): Record { try { const raw = localStorage.getItem(CHUNK_HASH_STORAGE_KEY) return raw ? JSON.parse(raw) : {} } catch { return {} } } /** * 保存分片 hash 记录 */ function saveChunkHashes(hashes: Record) { localStorage.setItem(CHUNK_HASH_STORAGE_KEY, JSON.stringify(hashes)) } /** * 主键使用 recipe.csv 的固定 id(旧数据没有 id 时退回到位置); * 菜谱删掉后 id 不再出现,收藏它的记录自然失效,其他菜谱的收藏不受影响 */ function withId(item: RecipeItem, fallback: number): DbRecipeItem { return { id: fallback, ...item } } /** * 单文件模式:全量加载 recipe.json,先清空以去掉已删除的菜谱 */ async function loadSingleFile() { const { default: recipeData } = await import('@cook/data/recipe.json') return db.transaction('rw', db.recipes, async () => { await db.recipes.clear() await db.recipes.bulkPut((recipeData as RecipeItem[]).map(withId)) }) } /** * 分片模式:增量加载变化的分片 */ async function loadChunks(meta: RecipeMetaChunked) { const savedHashes = loadChunkHashes() const newHashes = { ...savedHashes } // 找出需要加载的分片(hash 变化或新增的) const chunksToLoad = meta.chunks.filter( chunk => savedHashes[String(chunk.index)] !== chunk.hash, ) if (chunksToLoad.length === 0) { return } // 并行加载所有需要更新的分片 await Promise.all( chunksToLoad.map(async (chunk) => { const modulePath = `../../../../packages/data/recipe-chunk-${chunk.index}.json` const loader = chunkModules[modulePath] if (!loader) { console.warn(`Chunk module not found: ${modulePath}`) return } const { default: chunkData } = await loader() const offset = chunk.index * meta.chunkSize await db.recipes.bulkPut( (chunkData as RecipeItem[]).map((item, i) => withId(item, offset + i)), ) newHashes[String(chunk.index)] = chunk.hash }), ) // 删除已不在任何分片里的菜谱 if (meta.chunks.every(chunk => chunk.ids)) { const valid = meta.chunks.flatMap(chunk => chunk.ids!) const stale = await db.recipes.where(':id').noneOf(valid).primaryKeys() await db.recipes.bulkDelete(stale) } saveChunkHashes(newHashes) } export async function initDb() { const { default: meta } = await import('@cook/data/recipe-meta.json') as { default: RecipeMeta } if (meta.chunked) { return loadChunks(meta) } else { return loadSingleFile() } }