新增积分系统:打卡/测试达标赚积分,纯累计可配置

- point_rule 规则表 + point_log 流水表(0008 迁移),唯一键防刷:同一事件首次达标才发放,重复静默返回 0
- 打卡分值随任务设置(模板/实例 points 字段,默认 10);测试类 6 种事件按规则表取值,各有默认分(单词 20/数学课 15/语法点 15/周测 50×2/考试通过 100)
- 发放埋点 6 处:打卡、单词检测、数学课、语法知识点、英语/数学周测、考试通过;周测达标联动打卡实例不重复计分
- 孩子端:/kid 总积分徽章,新增 /points 积分页(总积分+流水),测验结果页即时显示「+N 积分」,周测结果页刷新后从流水取回已发积分
- 家长端:功能管理新增「积分管理」Section(规则分值配置+每孩子总积分/最近流水);任务管理内联编辑任务分值
- 修复:drizzle 将 mysql2 重复键错误包进 cause,沿错误链识别 ER_DUP_ENTRY

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jacobxu666
2026-08-18 17:58:53 +08:00
co-authored by Claude Opus 4.8
parent 841f937b8e
commit 7a11a89031
27 changed files with 592 additions and 41 deletions
+7 -3
View File
@@ -9,9 +9,10 @@ import { recordWrong } from "@/lib/wrongbook";
import { db } from "@/db";
import { taskInstances, weeklyTest } from "@/db/schema";
import { todayStr } from "@/lib/dates";
import { awardRulePoints } from "@/lib/points";
export type WeeklyResult =
| { score: number; passed: boolean; correct: number; total: number; detail: boolean[] }
| { score: number; passed: boolean; correct: number; total: number; detail: boolean[]; pointsEarned: number }
| { error: string };
// 提交每周能力检测:判分、记录、错题入库、达标关联当日英语打卡
@@ -43,8 +44,10 @@ export async function takeWeekly(answers: AnswerValue[]): Promise<WeeklyResult>
})
.onDuplicateKeyUpdate({ set: { score: g.score, correct: g.correct, total: g.total, passed: g.passed } });
// 达标关联当日英语打卡
// 达标关联当日英语打卡,并发积分(首次达标才发)
let pointsEarned = 0;
if (g.passed) {
pointsEarned = await awardRulePoints(user.id, "grammar_weekly", `week-${wk}`, `英语周检测 ${wk}`);
const today = todayStr();
const title = `英语周检测 · ${wk}`;
const [ex] = await db
@@ -72,5 +75,6 @@ export async function takeWeekly(answers: AnswerValue[]): Promise<WeeklyResult>
revalidatePath("/kid");
revalidatePath("/parent");
revalidatePath("/wrongbook");
return g;
revalidatePath("/points");
return { ...g, pointsEarned };
}