import Dexie, { type Table } from 'dexie' export interface Project { id?: number name: string archived: number created_at: string } export interface WorkLog { id?: number date: string created_at: string } export interface LogItem { id?: number work_log_id: number content: string project_id: number | null category: string minutes: number | null is_milestone: number needs_followup: number takeaway: string | null remark: string | null sort_order: number created_at: string } export interface Attendance { id?: number date: string clock_in_at: string | null clock_out_at: string | null kind: string note: string | null location?: string | null created_at: string } export interface WeeklyNote { id?: number week_start: string highlights: string | null challenges: string | null plan_next: string | null created_at: string } export interface Goal { id?: number title: string // 长期计划,如:学完产品文档 / 本月健身 20 次 kind: string start_date: string // YYYY-MM-DD end_date: string note: string | null uuid: string created_at: string updated_at: string } export interface PlanItem { id?: number date: string // YYYY-MM-DD start_time: string // HH:mm end_time: string // HH:mm title: string // 计划内容,如:产品学习 / 健身 kind: string // 类别:学习 / 健身 / 自律 / 其他自定义 note: string | null status: string // todo | done | skip actual_minutes: number // 计时时钟累计的实际投入分钟 timer_started_at: string | null goal_id: number | null // 来源长期计划(本地 id) uuid: string created_at: string updated_at: string } export interface Memo { id?: number title: string content: string created_at: string updated_at: string } export interface Credential { id?: number system: string account: string password: string remark: string created_at: string updated_at: string } export interface Transaction { id?: number type: string // income | expense category: string amount: number // 单位:分 date: string // YYYY-MM-DD note: string reimbursable: number // 支出是否需要报销 reimbursed: number // 是否已报销 created_at: string } // 同步镜像:记录上次同步时每条数据的哈希,用于增量差异检测 export interface SyncMirror { id?: number table: string uuid: string hash: string } // 删除墓碑:同步时告知其他端该记录已删除 export interface SyncTombstone { id?: number table: string uuid: string deleted_at: string } export class WorklogDB extends Dexie { project!: Table work_log!: Table log_item!: Table attendance!: Table weekly_note!: Table setting!: Table<{ key: string; value: string }, string> memo!: Table credential!: Table // 注意:transaction 表通过 db.table('transaction') 访问, // 因为 db.transaction 会与 Dexie 内置的 transaction() 方法冲突 sync_mirror!: Table tombstone!: Table plan!: Table goal!: Table constructor(dbName = 'intern_worklog') { super(dbName) this.version(1).stores({ project: '++id, &name, archived', work_log: '++id, &date', log_item: '++id, work_log_id, project_id, category', attendance: '++id, &date', weekly_note: '++id, &week_start', setting: 'key' }) this.version(2).stores({ memo: '++id, updated_at', credential: '++id, system, updated_at' }) this.version(3).stores({ transaction: '++id, date, type, category' }) this.version(4).stores({ sync_mirror: '++id, table, uuid', tombstone: '++id, table, uuid, deleted_at' }) this.version(5).stores({ plan: '++id, date, status' }).upgrade(async tx => { // 旧固定分类 key 迁移为用户自定义分类标签(v0.4.0 起分类为用户数据) const map: Record = { dev: '项目实操', test: '项目实操', bugfix: '售后服务', learning: '产品学习', meeting: '打杂', doc: '打杂', other: '打杂' } await tx.table('log_item').toCollection().modify((item: { category?: string }) => { if (item.category && map[item.category]) item.category = map[item.category] }) }) this.version(6).stores({ plan: '++id, date, status, goal_id', goal: '++id, start_date' }) } } // 当前登录用户绑定的数据库实例:不同用户物理隔离不同库,登录后由 auth 模块注入 export let db: WorklogDB | undefined export function bindDatabase(instance: WorklogDB): void { db = instance } export function unbindDatabase(): void { if (db) db.close() db = undefined } export const SCHEMA_VERSION = 6 export const EXPENSE_CATEGORIES: string[] = ['餐饮', '交通', '住房', '通讯', '学习', '娱乐', '其他'] export const INCOME_CATEGORIES: string[] = ['工资', '报销', '其他'] export const DEFAULT_LOCATIONS: string[] = ['公司', '客户现场', '出差', '居家办公'] // 旧版固定分类 key → 默认标签的映射(仅用于历史数据迁移与展示兜底) export const LEGACY_CATEGORY_LABELS: Record = { dev: '项目实操', test: '项目实操', bugfix: '售后服务', learning: '产品学习', meeting: '打杂', doc: '打杂', other: '打杂' } // v0.4.0 起分类即用户自定义标签本身;历史 key 在此直通转换 export function categoryLabel(key: string): string { return LEGACY_CATEGORY_LABELS[key] || key }