第一期:家庭自学打卡系统

- 家长手机号登录 + 孩子设备一次性激活(长期会话)
- 周模板自动生成每日任务快照,支持拍照凭证直传对象存储
- 连击计算(空白日自动达成)、家长补卡带标记
- 家长端三标签:今日概览 / 任务模板 / 设备管理
- Docker 独立输出,经 Traefik 接入 jump.hn12301.net

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jacobxu666
2026-08-02 23:19:13 +08:00
co-authored by Claude Opus 4.8
commit af7a144d58
57 changed files with 8875 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
import { Flame } from "lucide-react";
import { redirect } from "next/navigation";
import { todayStr } from "@/lib/dates";
import { getSessionUser } from "@/lib/auth";
import { streakOf } from "@/lib/streak";
import { dayTasks } from "@/lib/tasks";
import { TaskCard } from "./task-card";
export default async function KidPage() {
const user = await getSessionUser();
if (!user) redirect("/join");
if (user.role !== "child") redirect("/parent");
const today = todayStr();
const [tasks, streak] = await Promise.all([
dayTasks(user.id, today),
streakOf(user.id),
]);
const doneCount = tasks.filter((t) => t.status === "done").length;
const allDone = tasks.length > 0 && doneCount === tasks.length;
return (
<main className="mx-auto max-w-lg space-y-5 p-4 pb-10">
<header className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">你好,{user.displayName}</h1>
<p className="text-sm text-muted-foreground">{today}</p>
</div>
<div className="flex items-center gap-1 rounded-full bg-orange-100 px-4 py-2 text-orange-600">
<Flame className="size-6" fill="currentColor" />
<span className="text-xl font-bold">{streak}</span>
<span className="text-sm">天</span>
</div>
</header>
{tasks.length === 0 ? (
<div className="rounded-2xl bg-muted p-8 text-center">
<p className="text-2xl">🎉</p>
<p className="mt-2 text-lg font-medium">今天没有任务</p>
<p className="mt-1 text-sm text-muted-foreground">连击自动保持,去玩吧!</p>
</div>
) : (
<>
<p className="text-sm text-muted-foreground">
已完成 {doneCount} / {tasks.length} 项
</p>
<div className="space-y-3">
{tasks.map((t) => (
<TaskCard key={t.id} task={t} />
))}
</div>
{allDone && (
<div className="rounded-2xl bg-green-100 p-6 text-center">
<p className="text-2xl">💪</p>
<p className="mt-2 text-lg font-bold text-green-700">今天全部完成,太棒了!</p>
</div>
)}
</>
)}
</main>
);
}