体验修复:概览看当日具体任务+模板实时对账+打卡圈缩小+孩子我的记录
- 家长学习概览日历点击某天可查看当日具体任务及完成状态(1.1) - 今日任务快照改为实时对账:模板删除则移除未完成快照、新增则补上, 解决"模板无任务但孩子端仍显示"(1.2);历史日保持不变 - 孩子端打卡选中圈由 size-12 缩小到 size-9,更精致(2.1) - 孩子端新增"我的记录":累计打卡天数 + 最近7天已完成任务(2.2) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
11c2997409
commit
053f818158
+78
-34
@@ -2,7 +2,7 @@
|
||||
import { and, asc, eq, inArray } from "drizzle-orm";
|
||||
import { db } from "@/db";
|
||||
import { photos, taskInstances, taskTemplates } from "@/db/schema";
|
||||
import { dowOf, mondayOf } from "@/lib/dates";
|
||||
import { dowOf, mondayOf, todayStr, daysAgo } from "@/lib/dates";
|
||||
import { viewUrl } from "@/lib/oss";
|
||||
|
||||
// 模板是否覆盖某一天(weekly/date/range/week 四种生效方式)
|
||||
@@ -18,6 +18,83 @@ function coversDate(
|
||||
return false;
|
||||
}
|
||||
|
||||
// 生成/对账某天任务快照。历史日:只生成一次(保历史);今天:实时对账,
|
||||
// 模板删了→移除未完成快照,模板加了→补上,保证孩子端与家长模板一致。
|
||||
export async function ensureDay(childId: number, date: string) {
|
||||
const isToday = date === todayStr();
|
||||
await db.transaction(async (tx) => {
|
||||
const all = await tx
|
||||
.select()
|
||||
.from(taskTemplates)
|
||||
.where(eq(taskTemplates.childUserId, childId))
|
||||
.orderBy(asc(taskTemplates.sortOrder));
|
||||
const templates = all.filter((t) => coversDate(t, date));
|
||||
const instances = await tx
|
||||
.select()
|
||||
.from(taskInstances)
|
||||
.where(and(eq(taskInstances.childUserId, childId), eq(taskInstances.date, date)));
|
||||
|
||||
if (!isToday) {
|
||||
if (instances.length > 0) return;
|
||||
if (templates.length > 0)
|
||||
await tx.insert(taskInstances).values(
|
||||
templates.map((t) => ({
|
||||
childUserId: childId,
|
||||
date,
|
||||
title: t.title,
|
||||
subject: t.subject,
|
||||
requiresPhoto: t.requiresPhoto,
|
||||
sortOrder: t.sortOrder,
|
||||
sourceTemplateId: t.id,
|
||||
})),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 今天:对账
|
||||
const tIds = new Set(templates.map((t) => t.id));
|
||||
for (const inst of instances) {
|
||||
if (
|
||||
inst.status === "pending" &&
|
||||
inst.sourceTemplateId != null &&
|
||||
!tIds.has(inst.sourceTemplateId)
|
||||
) {
|
||||
await tx.delete(taskInstances).where(eq(taskInstances.id, inst.id));
|
||||
}
|
||||
}
|
||||
const haveIds = new Set(instances.map((i) => i.sourceTemplateId));
|
||||
for (const t of templates) {
|
||||
if (!haveIds.has(t.id)) {
|
||||
await tx.insert(taskInstances).values({
|
||||
childUserId: childId,
|
||||
date,
|
||||
title: t.title,
|
||||
subject: t.subject,
|
||||
requiresPhoto: t.requiresPhoto,
|
||||
sortOrder: t.sortOrder,
|
||||
sourceTemplateId: t.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 孩子打卡统计:累计打卡天数 + 最近 n 天已完成任务
|
||||
export async function childCheckStats(childId: number, n = 7) {
|
||||
const rows = await db
|
||||
.select({ date: taskInstances.date, status: taskInstances.status, title: taskInstances.title })
|
||||
.from(taskInstances)
|
||||
.where(eq(taskInstances.childUserId, childId));
|
||||
const doneDates = new Set(rows.filter((r) => r.status === "done").map((r) => r.date));
|
||||
const recent: { date: string; tasks: string[] }[] = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
const d = daysAgo(i);
|
||||
const done = rows.filter((r) => r.date === d && r.status === "done").map((r) => r.title);
|
||||
if (done.length > 0) recent.push({ date: d, tasks: done });
|
||||
}
|
||||
return { totalDays: doneDates.size, recent };
|
||||
}
|
||||
|
||||
export type TaskView = {
|
||||
id: number;
|
||||
title: string;
|
||||
@@ -28,39 +105,6 @@ export type TaskView = {
|
||||
photoUrls: string[];
|
||||
};
|
||||
|
||||
// 确保某天的任务快照存在:第一次访问当天时由模板生成,之后读快照
|
||||
export async function ensureDay(childId: number, date: string) {
|
||||
await db.transaction(async (tx) => {
|
||||
const existing = await tx
|
||||
.select({ id: taskInstances.id })
|
||||
.from(taskInstances)
|
||||
.where(and(eq(taskInstances.childUserId, childId), eq(taskInstances.date, date)))
|
||||
.limit(1);
|
||||
if (existing.length > 0) return;
|
||||
|
||||
const all = await tx
|
||||
.select()
|
||||
.from(taskTemplates)
|
||||
.where(eq(taskTemplates.childUserId, childId))
|
||||
.orderBy(asc(taskTemplates.sortOrder));
|
||||
const templates = all.filter((t) => coversDate(t, date));
|
||||
|
||||
if (templates.length > 0) {
|
||||
await tx.insert(taskInstances).values(
|
||||
templates.map((t) => ({
|
||||
childUserId: childId,
|
||||
date,
|
||||
title: t.title,
|
||||
subject: t.subject,
|
||||
requiresPhoto: t.requiresPhoto,
|
||||
sortOrder: t.sortOrder,
|
||||
sourceTemplateId: t.id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 孩子端当日任务视图(含照片签名地址)
|
||||
export async function dayTasks(childId: number, date: string): Promise<TaskView[]> {
|
||||
await ensureDay(childId, date);
|
||||
|
||||
Reference in New Issue
Block a user