feat: 计划模式(时间段+打卡+计时时钟) / 工作详情范围导出 / 工作与记账分类自定义(含历史迁移)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// 数据仓库:全部业务数据只有服务器一份权威副本,本地 Dexie 是实时缓存。
|
||||
// 约定:每个写函数修改本地缓存后立即调用 commit() —— 推送差异给服务器,
|
||||
// 并以服务器返回的权威快照重建缓存;断网时抛出错误(纯在线模式)。
|
||||
import { db, DEFAULT_LOCATIONS, SCHEMA_VERSION, type Attendance, type Credential, type LogItem, type Memo, type Project, type Transaction, type WeeklyNote, type WorkLog } from './schema'
|
||||
import { db, DEFAULT_LOCATIONS, SCHEMA_VERSION, type Attendance, type Credential, type LogItem, type Memo, type PlanItem, type Project, type Transaction, type WeeklyNote, type WorkLog } from './schema'
|
||||
import { addDays, nowLocalISO, toDateStr } from '../lib/date'
|
||||
import { flushAfterWrite } from '../sync/engine'
|
||||
|
||||
@@ -360,6 +360,84 @@ export async function listWeeklyNotesBetween(start: string, end: string): Promis
|
||||
return db.weekly_note.where('week_start').between(start, end, true, true).toArray()
|
||||
}
|
||||
|
||||
// ---------- 计划模式 ----------
|
||||
|
||||
export interface PlanInput {
|
||||
date: string
|
||||
start_time: string
|
||||
end_time: string
|
||||
title: string
|
||||
kind: string
|
||||
note: string
|
||||
}
|
||||
|
||||
export async function listPlansByDate(date: string): Promise<PlanItem[]> {
|
||||
const rows = await db.plan.where('date').equals(date).toArray()
|
||||
return rows.sort((a, b) => (a.start_time < b.start_time ? -1 : 1))
|
||||
}
|
||||
|
||||
export async function addPlan(input: PlanInput): Promise<void> {
|
||||
const now = nowLocalISO()
|
||||
await db.plan.add({
|
||||
date: input.date,
|
||||
start_time: input.start_time,
|
||||
end_time: input.end_time,
|
||||
title: input.title.trim(),
|
||||
kind: input.kind.trim() || '其他',
|
||||
note: input.note || null,
|
||||
status: 'todo',
|
||||
actual_minutes: 0,
|
||||
timer_started_at: null,
|
||||
uuid: '',
|
||||
created_at: now,
|
||||
updated_at: now
|
||||
} as never)
|
||||
await commit()
|
||||
}
|
||||
|
||||
export async function updatePlan(id: number, patch: Partial<PlanItem>): Promise<void> {
|
||||
await db.plan.update(id, { ...patch, updated_at: nowLocalISO() })
|
||||
await commit()
|
||||
}
|
||||
|
||||
export async function deletePlan(id: number): Promise<void> {
|
||||
await db.plan.delete(id)
|
||||
await commit()
|
||||
}
|
||||
|
||||
// 打卡:完成 / 跳过 / 恢复
|
||||
export async function setPlanStatus(id: number, status: 'todo' | 'done' | 'skip'): Promise<void> {
|
||||
const row = await db.plan.get(id)
|
||||
if (!row) return
|
||||
if (status !== 'todo' && row.timer_started_at) {
|
||||
await stopPlanTimer(id)
|
||||
}
|
||||
await db.plan.update(id, { status, updated_at: nowLocalISO() })
|
||||
await commit()
|
||||
}
|
||||
|
||||
// 计时时钟:同一时刻只允许一个条目在计时,开始新计时会自动停止上一个
|
||||
export async function startPlanTimer(id: number): Promise<void> {
|
||||
const running = await db.plan.filter(p => !!p.timer_started_at).toArray()
|
||||
for (const p of running) {
|
||||
if (p.id !== id) await stopPlanTimer(p.id as number)
|
||||
}
|
||||
await db.plan.update(id, { timer_started_at: nowLocalISO(), updated_at: nowLocalISO() })
|
||||
await commit()
|
||||
}
|
||||
|
||||
export async function stopPlanTimer(id: number): Promise<void> {
|
||||
const row = await db.plan.get(id)
|
||||
if (!row || !row.timer_started_at) return
|
||||
const elapsed = Math.max(0, Math.round((Date.now() - new Date(row.timer_started_at).getTime()) / 60000))
|
||||
await db.plan.update(id, {
|
||||
actual_minutes: (row.actual_minutes || 0) + elapsed,
|
||||
timer_started_at: null,
|
||||
updated_at: nowLocalISO()
|
||||
})
|
||||
await commit()
|
||||
}
|
||||
|
||||
// ---------- 备忘录 ----------
|
||||
|
||||
export async function listMemos(): Promise<Memo[]> {
|
||||
@@ -457,7 +535,8 @@ export async function exportAll(): Promise<string> {
|
||||
db.setting.toArray(),
|
||||
db.memo.toArray(),
|
||||
db.credential.toArray(),
|
||||
db.table('transaction').toArray()
|
||||
db.table('transaction').toArray(),
|
||||
db.plan.toArray()
|
||||
])
|
||||
return JSON.stringify({
|
||||
schema_version: SCHEMA_VERSION,
|
||||
@@ -471,7 +550,8 @@ export async function exportAll(): Promise<string> {
|
||||
setting: tables[5],
|
||||
memo: tables[6],
|
||||
credential: tables[7],
|
||||
transaction: tables[8]
|
||||
transaction: tables[8],
|
||||
plan: tables[9]
|
||||
}
|
||||
}, null, 2)
|
||||
}
|
||||
@@ -483,17 +563,18 @@ export async function importAll(jsonText: string): Promise<void> {
|
||||
} catch {
|
||||
throw new Error('不是有效的 JSON 文件')
|
||||
}
|
||||
if (!parsed || (parsed.schema_version !== 1 && parsed.schema_version !== 2 && parsed.schema_version !== 3) || !parsed.data) {
|
||||
if (!parsed || (parsed.schema_version !== 1 && parsed.schema_version !== 2 && parsed.schema_version !== 3 && parsed.schema_version !== 4 && parsed.schema_version !== 5) || !parsed.data) {
|
||||
throw new Error('文件格式或 schema 版本不匹配')
|
||||
}
|
||||
const d = parsed.data
|
||||
const dl = db.table('log_item')
|
||||
const dt = db.table('transaction')
|
||||
await db.transaction('rw', [db.project, db.work_log, dl, db.attendance, db.weekly_note, db.setting, db.memo, db.credential, dt], async () => {
|
||||
const dp = db.plan
|
||||
await db.transaction('rw', [db.project, db.work_log, dl, db.attendance, db.weekly_note, db.setting, db.memo, db.credential, dt, dp], async () => {
|
||||
await Promise.all([
|
||||
db.project.clear(), db.work_log.clear(), dl.clear(),
|
||||
db.attendance.clear(), db.weekly_note.clear(), db.setting.clear(),
|
||||
db.memo.clear(), db.credential.clear(), dt.clear()
|
||||
db.memo.clear(), db.credential.clear(), dt.clear(), dp.clear()
|
||||
])
|
||||
if (d.project && d.project.length) await db.project.bulkAdd(d.project as never[])
|
||||
if (d.work_log && d.work_log.length) await db.work_log.bulkAdd(d.work_log as never[])
|
||||
@@ -504,6 +585,7 @@ export async function importAll(jsonText: string): Promise<void> {
|
||||
if (d.memo && d.memo.length) await db.memo.bulkAdd(d.memo as never[])
|
||||
if (d.credential && d.credential.length) await db.credential.bulkAdd(d.credential as never[])
|
||||
if (d.transaction && d.transaction.length) await dt.bulkAdd(d.transaction as never[])
|
||||
if (d.plan && d.plan.length) await dp.bulkAdd(d.plan as never[])
|
||||
})
|
||||
// 导入即推送:以导入内容覆盖服务器(缺失行自动生成墓碑清除),随后以快照回读
|
||||
await commit()
|
||||
|
||||
Reference in New Issue
Block a user