feat: 长期计划(目标→每日任务物化+进度条) / 详情页Markdown范围导出 / 记账与详情页改内容优先弹窗新增
This commit is contained in:
+111
-5
@@ -1,7 +1,7 @@
|
||||
// 数据仓库:全部业务数据只有服务器一份权威副本,本地 Dexie 是实时缓存。
|
||||
// 约定:每个写函数修改本地缓存后立即调用 commit() —— 推送差异给服务器,
|
||||
// 并以服务器返回的权威快照重建缓存;断网时抛出错误(纯在线模式)。
|
||||
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 { db, DEFAULT_LOCATIONS, SCHEMA_VERSION, type Attendance, type Credential, type LogItem, type Memo, type Goal, type PlanItem, type Project, type Transaction, type WeeklyNote, type WorkLog } from './schema'
|
||||
import { addDays, nowLocalISO, toDateStr } from '../lib/date'
|
||||
import { flushAfterWrite } from '../sync/engine'
|
||||
|
||||
@@ -438,6 +438,108 @@ export async function stopPlanTimer(id: number): Promise<void> {
|
||||
await commit()
|
||||
}
|
||||
|
||||
// ---------- 长期计划(目标) ----------
|
||||
|
||||
export interface GoalInput {
|
||||
title: string
|
||||
kind: string
|
||||
start_date: string
|
||||
end_date: string
|
||||
note: string
|
||||
}
|
||||
|
||||
const MAX_MATERIALIZE_DAYS = 366
|
||||
|
||||
function eachDate(start: string, end: string): string[] {
|
||||
const out: string[] = []
|
||||
let cur = start
|
||||
let guard = 0
|
||||
while (cur <= end && guard < MAX_MATERIALIZE_DAYS) {
|
||||
out.push(cur)
|
||||
cur = addDays(cur, 1)
|
||||
guard++
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export async function getGoalProgress(): Promise<{ goalId: number; done: number; total: number }[]> {
|
||||
const goals = await db.goal.toArray()
|
||||
const out: { goalId: number; done: number; total: number }[] = []
|
||||
for (const g of goals) {
|
||||
const items = await db.plan.where('goal_id').equals(g.id as number).toArray()
|
||||
out.push({ goalId: g.id as number, done: items.filter(i => i.status === 'done').length, total: items.length })
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export async function listGoals(): Promise<Goal[]> {
|
||||
const rows = await db.goal.toArray()
|
||||
return rows.sort((a, b) => (a.start_date < b.start_date ? -1 : 1))
|
||||
}
|
||||
|
||||
// 新建长期计划并物化每一天的任务(落实每日打卡与计时)
|
||||
export async function addGoal(input: GoalInput): Promise<void> {
|
||||
const now = nowLocalISO()
|
||||
const gid = await db.goal.add({
|
||||
title: input.title.trim(),
|
||||
kind: input.kind.trim() || '其他',
|
||||
start_date: input.start_date,
|
||||
end_date: input.end_date,
|
||||
note: input.note || null,
|
||||
uuid: '',
|
||||
created_at: now,
|
||||
updated_at: now
|
||||
} as never)
|
||||
const goal = await db.goal.get(gid)
|
||||
const dates = eachDate(input.start_date, input.end_date)
|
||||
for (const d of dates) {
|
||||
await db.plan.add({
|
||||
date: d,
|
||||
start_time: '08:00',
|
||||
end_time: '09:00',
|
||||
title: input.title.trim(),
|
||||
kind: input.kind.trim() || '其他',
|
||||
note: input.note || null,
|
||||
status: 'todo',
|
||||
actual_minutes: 0,
|
||||
timer_started_at: null,
|
||||
goal_id: gid,
|
||||
uuid: '',
|
||||
created_at: now,
|
||||
updated_at: now
|
||||
} as never)
|
||||
}
|
||||
void goal
|
||||
await commit()
|
||||
}
|
||||
|
||||
// 编辑目标:标题/类别级联到其每日任务(已完成项不改动)
|
||||
export async function updateGoal(id: number, patch: Partial<GoalInput>): Promise<void> {
|
||||
const goal = await db.goal.get(id)
|
||||
if (!goal) return
|
||||
const next = { ...patch, updated_at: nowLocalISO() }
|
||||
await db.goal.update(id, next)
|
||||
const items = await db.plan.where('goal_id').equals(id).toArray()
|
||||
for (const it of items) {
|
||||
if (it.status === 'done') continue
|
||||
await db.plan.update(it.id as number, {
|
||||
...(patch.title !== undefined ? { title: patch.title.trim() } : {}),
|
||||
...(patch.kind !== undefined ? { kind: patch.kind } : {}),
|
||||
...(patch.note !== undefined ? { note: patch.note || null } : {}),
|
||||
updated_at: nowLocalISO()
|
||||
})
|
||||
}
|
||||
await commit()
|
||||
}
|
||||
|
||||
// 删除目标:连同其每日任务一起删除(前端二次确认)
|
||||
export async function deleteGoal(id: number): Promise<void> {
|
||||
const items = await db.plan.where('goal_id').equals(id).toArray()
|
||||
for (const it of items) await db.plan.delete(it.id as number)
|
||||
await db.goal.delete(id)
|
||||
await commit()
|
||||
}
|
||||
|
||||
// ---------- 备忘录 ----------
|
||||
|
||||
export async function listMemos(): Promise<Memo[]> {
|
||||
@@ -536,7 +638,8 @@ export async function exportAll(): Promise<string> {
|
||||
db.memo.toArray(),
|
||||
db.credential.toArray(),
|
||||
db.table('transaction').toArray(),
|
||||
db.plan.toArray()
|
||||
db.plan.toArray(),
|
||||
db.goal.toArray()
|
||||
])
|
||||
return JSON.stringify({
|
||||
schema_version: SCHEMA_VERSION,
|
||||
@@ -551,7 +654,8 @@ export async function exportAll(): Promise<string> {
|
||||
memo: tables[6],
|
||||
credential: tables[7],
|
||||
transaction: tables[8],
|
||||
plan: tables[9]
|
||||
plan: tables[9],
|
||||
goal: tables[10]
|
||||
}
|
||||
}, null, 2)
|
||||
}
|
||||
@@ -570,11 +674,12 @@ export async function importAll(jsonText: string): Promise<void> {
|
||||
const dl = db.table('log_item')
|
||||
const dt = db.table('transaction')
|
||||
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 () => {
|
||||
const dg = db.goal
|
||||
await db.transaction('rw', [db.project, db.work_log, dl, db.attendance, db.weekly_note, db.setting, db.memo, db.credential, dt, dg, 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(), dp.clear()
|
||||
db.memo.clear(), db.credential.clear(), dt.clear(), dg.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[])
|
||||
@@ -585,6 +690,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.goal && d.goal.length) await dg.bulkAdd(d.goal as never[])
|
||||
if (d.plan && d.plan.length) await dp.bulkAdd(d.plan as never[])
|
||||
})
|
||||
// 导入即推送:以导入内容覆盖服务器(缺失行自动生成墓碑清除),随后以快照回读
|
||||
|
||||
Reference in New Issue
Block a user