新增数学思维自学板块与考试板块(9模块154课+初一上5套真实试卷客观题)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3473ecbf2b
commit
bc53e2b2a5
@@ -0,0 +1,75 @@
|
||||
// xuchao: 考试答题页——复用 QuizBlock,判分走考试 action,可重考取最近成绩
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { BackgroundBlobs } from "@/components/background-blobs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { EXAM_TYPE_LABEL, getExamRecord, getPaper, paperQuestions } from "@/lib/exam";
|
||||
import { getSessionUser } from "@/lib/auth";
|
||||
import { QuizBlock } from "@/app/learn/quiz-block";
|
||||
import { takeExam } from "../actions";
|
||||
|
||||
export default async function ExamTakePage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const user = await getSessionUser();
|
||||
if (!user) redirect("/join");
|
||||
if (user.role !== "child") redirect("/parent");
|
||||
|
||||
const paperId = (await params).id;
|
||||
const paper = await getPaper(paperId);
|
||||
if (!paper) redirect("/exam");
|
||||
|
||||
const questions = paperQuestions(paper);
|
||||
const record = await getExamRecord(user.id, paperId);
|
||||
|
||||
return (
|
||||
<main className="relative mx-auto max-w-2xl space-y-5 p-4 pb-12">
|
||||
<BackgroundBlobs />
|
||||
<header className="space-y-4">
|
||||
<Link href="/exam" className="text-sm font-medium text-indigo-600">
|
||||
← 考试中心
|
||||
</Link>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge className="bg-sky-100 text-sky-700 hover:bg-sky-100">
|
||||
{EXAM_TYPE_LABEL[paper.examType] ?? paper.examType}
|
||||
</Badge>
|
||||
{paper.duration && <Badge variant="secondary">{paper.duration} 分钟</Badge>}
|
||||
{paper.fullScore && <Badge variant="secondary">满分 {paper.fullScore}</Badge>}
|
||||
{record && (
|
||||
<Badge variant="secondary" className={record.passed ? "text-emerald-700" : "text-rose-600"}>
|
||||
上次 {record.score} 分
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<h1 className="mt-1 text-2xl font-extrabold leading-snug tracking-tight text-slate-900">{paper.title}</h1>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{[paper.year, paper.region].filter(Boolean).join(" · ")}
|
||||
{paper.sourceUrl && (
|
||||
<>
|
||||
{" · "}
|
||||
<a href={paper.sourceUrl} target="_blank" rel="noreferrer" className="underline">
|
||||
来源
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
{paper.summary && <p className="mt-1 text-indigo-600">📋 {paper.summary}</p>}
|
||||
</header>
|
||||
|
||||
{questions.length === 0 ? (
|
||||
<div className="rounded-xl border border-slate-100 bg-white p-8 text-center shadow-soft">
|
||||
<p className="text-lg font-bold">试卷暂无题目</p>
|
||||
</div>
|
||||
) : (
|
||||
<QuizBlock
|
||||
pointId={paperId}
|
||||
questions={questions}
|
||||
submitter={takeExam.bind(null, paperId)}
|
||||
title="开始答题"
|
||||
subtitle={`共 ${questions.length} 题,做完提交判分;≥80 分达标,可重考刷新成绩。`}
|
||||
passText="考试达标 🎉 已计入今日数学打卡"
|
||||
backHref="/exam"
|
||||
backLabel="返回考试中心"
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
"use server";
|
||||
|
||||
// xuchao: 考试判分——按卷面题判分,错题进错题本,达标联动数学打卡
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { getSessionUser } from "@/lib/auth";
|
||||
import { gradeAnswers, type AnswerValue } from "@/lib/grammar";
|
||||
import { getPaper, paperQuestions, recordExam } from "@/lib/exam";
|
||||
import { recordWrong } from "@/lib/wrongbook";
|
||||
|
||||
export type ExamResult =
|
||||
| { score: number; passed: boolean; correct: number; total: number; detail: boolean[] }
|
||||
| { error: string };
|
||||
|
||||
// 提交试卷:服务端判分、记录成绩、错题入库;可重考,取最近一次成绩
|
||||
export async function takeExam(paperId: string, answers: AnswerValue[]): Promise<ExamResult> {
|
||||
const user = await getSessionUser();
|
||||
if (!user || user.role !== "child") return { error: "会话无效,请重新激活设备" };
|
||||
|
||||
const paper = await getPaper(paperId);
|
||||
if (!paper) return { error: "试卷不存在" };
|
||||
|
||||
const questions = paperQuestions(paper);
|
||||
if (questions.length === 0) return { error: "试卷暂无题目" };
|
||||
|
||||
const g = gradeAnswers(questions, answers);
|
||||
questions.forEach((q, i) => {
|
||||
if (!g.detail[i]) void recordWrong(user.id, "exam", paperId, paper.title, q, answers[i]);
|
||||
});
|
||||
await recordExam(user.id, paperId, paper.title, g.score, g.correct, g.total, g.passed, answers);
|
||||
|
||||
revalidatePath("/exam");
|
||||
revalidatePath(`/exam/${paperId}`);
|
||||
revalidatePath("/kid");
|
||||
revalidatePath("/parent");
|
||||
revalidatePath("/wrongbook");
|
||||
return g;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// xuchao: 考试中心——按月度分组的试卷列表,首期初一上学期数学
|
||||
import Link from "next/link";
|
||||
import { ScrollText } from "lucide-react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { BackgroundBlobs } from "@/components/background-blobs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { EXAM_TYPE_LABEL, listPapers, recordsByPaper } from "@/lib/exam";
|
||||
import { getSessionUser } from "@/lib/auth";
|
||||
|
||||
// xuchao: 月份分组展示顺序与标签(初一上:9月~次年1月)
|
||||
const MONTH_LABEL: Record<number, string> = {
|
||||
9: "9 月 · 有理数",
|
||||
10: "10 月 · 整式的加减",
|
||||
11: "11 月 · 一元一次方程",
|
||||
12: "12 月 · 几何图形初步",
|
||||
1: "1 月 · 期末全册",
|
||||
};
|
||||
const MONTH_ORDER = [9, 10, 11, 12, 1];
|
||||
|
||||
export default async function ExamHome() {
|
||||
const user = await getSessionUser();
|
||||
if (!user) redirect("/join");
|
||||
if (user.role !== "child") redirect("/parent");
|
||||
|
||||
const papers = await listPapers("g7", "s1");
|
||||
const records = await recordsByPaper(user.id);
|
||||
|
||||
return (
|
||||
<main className="relative mx-auto max-w-2xl space-y-5 p-4 pb-10">
|
||||
<BackgroundBlobs />
|
||||
<header className="flex items-center justify-between">
|
||||
<Link href="/kid" className="text-sm font-medium text-indigo-600">
|
||||
← 返回打卡
|
||||
</Link>
|
||||
<span className="text-sm text-muted-foreground">初一上学期 · 数学</span>
|
||||
</header>
|
||||
|
||||
<div className="rounded-2xl bg-gradient-to-br from-sky-600 to-indigo-600 p-6 text-white shadow-btn">
|
||||
<span className="inline-block rounded-full bg-white/20 px-3 py-1 text-xs font-semibold">考试中心</span>
|
||||
<h1 className="mt-3 text-2xl font-extrabold leading-tight">真实月考 / 期中 / 期末卷</h1>
|
||||
<p className="mt-1 text-sm opacity-90">按月度分类,与真实试卷格式一致;≥80 分达标,错题自动进错题本。</p>
|
||||
</div>
|
||||
|
||||
{papers.length === 0 ? (
|
||||
<div className="rounded-xl border border-slate-100 bg-white p-8 text-center shadow-soft">
|
||||
<p className="text-3xl">📄</p>
|
||||
<p className="mt-2 text-lg font-bold">试卷还在整理中</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">稍后再来看看吧</p>
|
||||
</div>
|
||||
) : (
|
||||
MONTH_ORDER.map((m) => {
|
||||
const group = papers.filter((p) => p.month === m);
|
||||
if (group.length === 0) return null;
|
||||
return (
|
||||
<section key={m} className="rounded-xl border border-slate-100 bg-white p-4 shadow-soft">
|
||||
<header className="mb-3">
|
||||
<h2 className="text-lg font-extrabold text-slate-900">{MONTH_LABEL[m] ?? `${m} 月`}</h2>
|
||||
<p className="text-xs text-muted-foreground">{group.length} 套试卷</p>
|
||||
</header>
|
||||
<div className="space-y-3">
|
||||
{group.map((p) => {
|
||||
const rec = records.get(p.paperId);
|
||||
return (
|
||||
<Link
|
||||
key={p.paperId}
|
||||
href={`/exam/${p.paperId}`}
|
||||
className="block rounded-xl border border-slate-200 bg-white p-4 transition-all duration-200 hover:-translate-y-0.5 hover:border-indigo-300"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<Badge className="bg-sky-100 text-sky-700 hover:bg-sky-100">
|
||||
{EXAM_TYPE_LABEL[p.examType] ?? p.examType}
|
||||
</Badge>
|
||||
{p.duration && <Badge variant="secondary">{p.duration} 分钟</Badge>}
|
||||
{p.fullScore && <Badge variant="secondary">满分 {p.fullScore}</Badge>}
|
||||
</div>
|
||||
<p className="mt-2 line-clamp-2 text-sm font-bold text-slate-900">{p.title}</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{[p.year, p.region].filter(Boolean).join(" · ")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="shrink-0 text-right">
|
||||
{rec ? (
|
||||
<>
|
||||
<span
|
||||
className={cn(
|
||||
"block text-xl font-extrabold",
|
||||
rec.passed ? "text-emerald-600" : "text-rose-500",
|
||||
)}
|
||||
>
|
||||
{rec.score}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">{rec.passed ? "已达标" : "未达标"}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ScrollText className="ml-auto size-6 text-slate-300" />
|
||||
<span className="mt-1 block text-xs text-muted-foreground">未作答</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
+46
-1
@@ -1,4 +1,4 @@
|
||||
import { BookOpen, Flame, Languages } from "lucide-react";
|
||||
import { BookOpen, Calculator, Flame, Languages } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
@@ -12,6 +12,8 @@ import { getSessionUser } from "@/lib/auth";
|
||||
import { streakOf } from "@/lib/streak";
|
||||
import { dayTasks, childCheckStats } from "@/lib/tasks";
|
||||
import { listPoints, passedByPoint } from "@/lib/grammar";
|
||||
import { listMathPoints, mathPassedByPoint } from "@/lib/math";
|
||||
import { recordsByPaper } from "@/lib/exam";
|
||||
import { TaskCard } from "./task-card";
|
||||
|
||||
export default async function KidPage() {
|
||||
@@ -41,6 +43,16 @@ export default async function KidPage() {
|
||||
.where(and(eq(wordProgress.childUserId, user.id), eq(wordProgress.passed, true)));
|
||||
const wDone = wd.n;
|
||||
|
||||
// 数学思维入口:下一课标题 + 已点亮数
|
||||
const mPassed = await mathPassedByPoint(user.id);
|
||||
const mPoints = await listMathPoints();
|
||||
const mGoal = mPoints.find((p) => !mPassed.has(p.pointId));
|
||||
const mDone = mPassed.size;
|
||||
|
||||
// 考试中心入口:已考卷数
|
||||
const examRecords = await recordsByPaper(user.id);
|
||||
const examDone = examRecords.size;
|
||||
|
||||
return (
|
||||
<main className="relative mx-auto min-h-dvh max-w-lg space-y-5 p-4 pb-10">
|
||||
<BackgroundBlobs />
|
||||
@@ -86,7 +98,31 @@ export default async function KidPage() {
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/math" className="block">
|
||||
<div className="flex items-center gap-3 rounded-xl bg-gradient-to-r from-orange-500 to-amber-500 p-4 text-white shadow-btn transition-transform duration-200 hover:-translate-y-0.5">
|
||||
<div className="flex size-11 shrink-0 items-center justify-center rounded-lg bg-white/20">
|
||||
<Calculator className="size-6" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-xs font-medium opacity-90">数学思维 · 已点亮 {mDone} 课</p>
|
||||
<p className="truncate text-base font-bold">
|
||||
{mGoal ? mGoal.title : "全部点亮,真棒!"}
|
||||
</p>
|
||||
</div>
|
||||
<span className="shrink-0 text-lg">→</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<Link href="/exam" className="block">
|
||||
<div className="flex items-center gap-2 rounded-xl border border-slate-100 bg-white p-3 shadow-soft transition-transform duration-200 hover:-translate-y-0.5">
|
||||
<span className="text-xl">📄</span>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-slate-900">考试中心</p>
|
||||
<p className="text-xs text-muted-foreground">真实月考/期中/期末卷{examDone > 0 ? ` · 已考 ${examDone} 卷` : ""}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="/weekly" className="block">
|
||||
<div className="flex items-center gap-2 rounded-xl border border-slate-100 bg-white p-3 shadow-soft transition-transform duration-200 hover:-translate-y-0.5">
|
||||
<span className="text-xl">📝</span>
|
||||
@@ -96,6 +132,15 @@ export default async function KidPage() {
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="/math/weekly" className="block">
|
||||
<div className="flex items-center gap-2 rounded-xl border border-slate-100 bg-white p-3 shadow-soft transition-transform duration-200 hover:-translate-y-0.5">
|
||||
<span className="text-xl">🧮</span>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-slate-900">数学每周检测</p>
|
||||
<p className="text-xs text-muted-foreground">看看这周数学掌握得怎样</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="/wrongbook" className="block">
|
||||
<div className="flex items-center gap-2 rounded-xl border border-slate-100 bg-white p-3 shadow-soft transition-transform duration-200 hover:-translate-y-0.5">
|
||||
<span className="text-xl">📒</span>
|
||||
|
||||
@@ -3,16 +3,45 @@
|
||||
import { Md } from "./md";
|
||||
import { QuizBlock } from "./quiz-block";
|
||||
import { extractQuiz, type QuizQ } from "@/lib/quiz-parse";
|
||||
import type { AnswerValue } from "@/lib/grammar";
|
||||
import type { QuizResult } from "./actions";
|
||||
|
||||
// 课文 = 前段 markdown + ⑤即时练习(quiz)+ 后段 markdown(小结/知识卡)
|
||||
export function LessonBody({ md, pointId }: { md: string; pointId: string }) {
|
||||
// submitter 可选:英语课用默认提交,数学课传入自己的服务端判分函数
|
||||
export function LessonBody({
|
||||
md,
|
||||
pointId,
|
||||
submitter,
|
||||
quizTitle,
|
||||
passText,
|
||||
backHref,
|
||||
backLabel,
|
||||
}: {
|
||||
md: string;
|
||||
pointId: string;
|
||||
submitter?: (a: AnswerValue[]) => Promise<QuizResult>;
|
||||
quizTitle?: string;
|
||||
passText?: string;
|
||||
backHref?: string;
|
||||
backLabel?: string;
|
||||
}) {
|
||||
const parts = md.split(/```quiz\s*\n[\s\S]*?```/);
|
||||
const questions: QuizQ[] = extractQuiz(md);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Md text={parts[0] ?? ""} />
|
||||
{questions.length > 0 && <QuizBlock pointId={pointId} questions={questions} />}
|
||||
{questions.length > 0 && (
|
||||
<QuizBlock
|
||||
pointId={pointId}
|
||||
questions={questions}
|
||||
submitter={submitter}
|
||||
title={quizTitle}
|
||||
passText={passText}
|
||||
backHref={backHref}
|
||||
backLabel={backLabel}
|
||||
/>
|
||||
)}
|
||||
{parts.slice(1).join("\n").trim() !== "" && <Md text={parts.slice(1).join("\n")} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,14 +12,25 @@ import { submitQuiz, type QuizResult } from "./actions";
|
||||
type AV = AnswerValue | null;
|
||||
|
||||
// 结课检验:选择/判断/填空/连词成句/连线,提交后服务端判分,达标点亮并联动打卡
|
||||
// 文案可配置:数学课/考试卷复用此组件时传入各自的标题、打卡文案与返回地址
|
||||
export function QuizBlock({
|
||||
pointId,
|
||||
questions,
|
||||
submitter,
|
||||
title = "⑤ 即时练习",
|
||||
subtitle = "做完提交,马上判分;每题有解说,达标即点亮本课。",
|
||||
passText = "本课点亮 🎉 已计入今日英语打卡",
|
||||
backHref = "/learn",
|
||||
backLabel = "返回课程地图",
|
||||
}: {
|
||||
pointId: string;
|
||||
questions: QuizQ[];
|
||||
submitter?: (a: AnswerValue[]) => Promise<QuizResult>;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
passText?: string;
|
||||
backHref?: string;
|
||||
backLabel?: string;
|
||||
}) {
|
||||
const [answers, setAnswers] = useState<AV[]>(questions.map(() => null));
|
||||
const [result, setResult] = useState<Extract<QuizResult, { score: number }> | null>(null);
|
||||
@@ -53,8 +64,8 @@ export function QuizBlock({
|
||||
|
||||
return (
|
||||
<div className="my-4 rounded-2xl border border-slate-100 bg-white p-5 shadow-soft">
|
||||
<h3 className="text-base font-extrabold text-slate-900">⑤ 即时练习</h3>
|
||||
<p className="mb-3 text-sm text-muted-foreground">做完提交,马上判分;每题有解说,达标即点亮本课。</p>
|
||||
<h3 className="text-base font-extrabold text-slate-900">{title}</h3>
|
||||
<p className="mb-3 text-sm text-muted-foreground">{subtitle}</p>
|
||||
<div className="space-y-4">
|
||||
{questions.map((q, qi) => {
|
||||
const a = answers[qi];
|
||||
@@ -151,7 +162,7 @@ export function QuizBlock({
|
||||
<p className="text-4xl font-extrabold">{result.score} 分</p>
|
||||
<p className="mt-1 text-sm opacity-90">
|
||||
答对 {result.correct} / {result.total}
|
||||
{result.passed ? " · 本课点亮 🎉 已计入今日英语打卡" : " · 看看解说,再试一次"}
|
||||
{result.passed ? ` · ${passText}` : " · 看看解说,再试一次"}
|
||||
</p>
|
||||
<div className="mt-4 flex justify-center gap-3">
|
||||
{!result.passed && (
|
||||
@@ -159,8 +170,8 @@ export function QuizBlock({
|
||||
重做练习
|
||||
</Button>
|
||||
)}
|
||||
<Link href="/learn">
|
||||
<Button variant="secondary" size="lg">{result.passed ? "返回课程地图" : "先看看其它课"}</Button>
|
||||
<Link href={backHref}>
|
||||
<Button variant="secondary" size="lg">{result.passed ? backLabel : "先看看其它课"}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// xuchao: 数学课时页(结构同英语课,判分走数学 action)
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { BackgroundBlobs } from "@/components/background-blobs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { getMathPoint, mathPassedByPoint } from "@/lib/math";
|
||||
import { getSessionUser } from "@/lib/auth";
|
||||
import { LessonBody } from "@/app/learn/lesson-body";
|
||||
import { submitMathQuiz } from "../actions";
|
||||
|
||||
export default async function MathLessonPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const user = await getSessionUser();
|
||||
if (!user) redirect("/join");
|
||||
if (user.role !== "child") redirect("/parent");
|
||||
|
||||
const pointId = (await params).id;
|
||||
const point = await getMathPoint(pointId);
|
||||
if (!point) redirect("/math");
|
||||
|
||||
const passed = await mathPassedByPoint(user.id);
|
||||
const alreadyPassed = passed.has(pointId);
|
||||
|
||||
return (
|
||||
<main className="relative mx-auto max-w-2xl space-y-5 p-4 pb-12">
|
||||
<BackgroundBlobs />
|
||||
<header className="space-y-4">
|
||||
<Link href="/math" className="text-sm font-medium text-orange-600">
|
||||
← 数学课程地图
|
||||
</Link>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge className="bg-orange-100 text-orange-700 hover:bg-orange-100">{point.moduleName}</Badge>
|
||||
{point.grade && <Badge variant="secondary">{point.grade}</Badge>}
|
||||
<Badge variant="secondary">难度 {"★".repeat(point.difficulty)}</Badge>
|
||||
{alreadyPassed && (
|
||||
<Badge className="bg-emerald-100 text-emerald-700 hover:bg-emerald-100">已点亮</Badge>
|
||||
)}
|
||||
</div>
|
||||
<h1 className="mt-1 text-2xl font-extrabold leading-snug tracking-tight text-slate-900">
|
||||
{point.title}
|
||||
</h1>
|
||||
{point.summary && <p className="mt-1 text-orange-600">🎯 {point.summary}</p>}
|
||||
</header>
|
||||
|
||||
<div className="rounded-2xl border border-slate-100 bg-white p-5 shadow-soft">
|
||||
<LessonBody
|
||||
md={point.md}
|
||||
pointId={point.pointId}
|
||||
submitter={submitMathQuiz.bind(null, pointId)}
|
||||
quizTitle="④ 即时练习"
|
||||
passText="本课点亮 🎉 已计入今日数学打卡"
|
||||
backHref="/math"
|
||||
backLabel="返回数学地图"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
"use server";
|
||||
|
||||
// xuchao: 数学课即时练习判分(同英语 submitQuiz,打卡联动数学)
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { getSessionUser } from "@/lib/auth";
|
||||
import { completeMathPoint, extractQuiz, getMathPoint, gradeAnswers, type AnswerValue } from "@/lib/math";
|
||||
import { recordWrong } from "@/lib/wrongbook";
|
||||
|
||||
export type QuizResult =
|
||||
| { score: number; passed: boolean; correct: number; total: number; detail: boolean[] }
|
||||
| { error: string };
|
||||
|
||||
// 提交数学课练习:服务端判分,达标记进度并联动数学打卡
|
||||
export async function submitMathQuiz(pointId: string, answers: AnswerValue[]): Promise<QuizResult> {
|
||||
const user = await getSessionUser();
|
||||
if (!user || user.role !== "child") return { error: "会话无效,请重新激活设备" };
|
||||
|
||||
const point = await getMathPoint(pointId);
|
||||
if (!point) return { error: "课程不存在" };
|
||||
|
||||
const questions = extractQuiz(point.md);
|
||||
if (questions.length === 0) return { error: "本课暂无练习题" };
|
||||
|
||||
const g = gradeAnswers(questions, answers);
|
||||
// 错题进错题本(带详细讲解)
|
||||
questions.forEach((q, i) => {
|
||||
if (!g.detail[i]) void recordWrong(user.id, "mlesson", pointId, point.title, q, answers[i]);
|
||||
});
|
||||
await completeMathPoint(user.id, pointId, point.title, g.score, g.passed);
|
||||
|
||||
revalidatePath("/math");
|
||||
revalidatePath(`/math/${pointId}`);
|
||||
revalidatePath("/kid");
|
||||
revalidatePath("/parent");
|
||||
revalidatePath("/wrongbook");
|
||||
return g;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// xuchao: 数学思维课程地图(结构同英语 /learn,按七大模块分组)
|
||||
import Link from "next/link";
|
||||
import { Calculator, Check } from "lucide-react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { BackgroundBlobs } from "@/components/background-blobs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MODULES, listMathPoints, mathPassedByPoint, totalMathPoints } from "@/lib/math";
|
||||
import { getSessionUser } from "@/lib/auth";
|
||||
|
||||
export default async function MathHome() {
|
||||
const user = await getSessionUser();
|
||||
if (!user) redirect("/join");
|
||||
if (user.role !== "child") redirect("/parent");
|
||||
|
||||
const points = await listMathPoints();
|
||||
const passed = await mathPassedByPoint(user.id);
|
||||
const total = await totalMathPoints();
|
||||
const done = passed.size;
|
||||
const pct = total ? Math.round((done / total) * 100) : 0;
|
||||
const next = points.find((p) => !passed.has(p.pointId));
|
||||
|
||||
return (
|
||||
<main className="relative mx-auto max-w-2xl space-y-5 p-4 pb-10">
|
||||
<BackgroundBlobs />
|
||||
<header className="flex items-center justify-between">
|
||||
<Link href="/kid" className="text-sm font-medium text-indigo-600">
|
||||
← 返回打卡
|
||||
</Link>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
已点亮 {done} / {total}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
{next ? (
|
||||
<div className="rounded-2xl bg-gradient-to-br from-orange-500 to-amber-500 p-6 text-white shadow-btn">
|
||||
<span className="inline-block rounded-full bg-white/20 px-3 py-1 text-xs font-semibold">
|
||||
推荐下一课 · {next.moduleName}
|
||||
</span>
|
||||
<h1 className="mt-3 text-2xl font-extrabold leading-tight">{next.title}</h1>
|
||||
<p className="mt-1 text-sm opacity-90">每天一课约 40 分钟,学完做即时练习,达标即点亮。</p>
|
||||
<Link href={`/math/${next.pointId}`} className="mt-4 block">
|
||||
<Button variant="secondary" size="lg" className="w-full">
|
||||
{done === 0 ? "开始第一课" : "继续学习"} →
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-2xl bg-gradient-to-br from-emerald-500 to-emerald-600 p-6 text-center text-white shadow-btn">
|
||||
<p className="text-3xl">🏆</p>
|
||||
<p className="mt-2 text-xl font-extrabold">全部数学课都点亮了!</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="rounded-xl border border-slate-100 bg-white p-4 shadow-soft">
|
||||
<div className="mb-1 flex justify-between text-sm">
|
||||
<span className="font-medium text-slate-700">总进度</span>
|
||||
<span className="font-bold text-orange-600">{pct}%</span>
|
||||
</div>
|
||||
<div className="h-2.5 w-full overflow-hidden rounded-full bg-slate-100">
|
||||
<div
|
||||
className="h-full rounded-full bg-gradient-to-r from-orange-400 to-amber-500"
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{MODULES.map((mod) => {
|
||||
const group = points.filter((p) => p.module === mod.id);
|
||||
if (group.length === 0) return null;
|
||||
const modDone = group.filter((p) => passed.has(p.pointId)).length;
|
||||
return (
|
||||
<section key={mod.id} className="rounded-xl border border-slate-100 bg-white p-4 shadow-soft">
|
||||
<header className="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg font-extrabold text-slate-900">
|
||||
{mod.name}
|
||||
<span className="ml-1 text-amber-500">{"★".repeat(mod.stars)}</span>
|
||||
</h2>
|
||||
<p className="text-xs text-muted-foreground">{mod.desc}</p>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-orange-600">
|
||||
{modDone}/{group.length}
|
||||
</span>
|
||||
</header>
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
{group.map((p) => {
|
||||
const doneIt = passed.has(p.pointId);
|
||||
return (
|
||||
<Link
|
||||
key={p.pointId}
|
||||
href={`/math/${p.pointId}`}
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-lg border px-3 py-2 text-sm transition-all duration-200 hover:-translate-y-0.5",
|
||||
doneIt
|
||||
? "border-emerald-200 bg-emerald-50 text-emerald-700"
|
||||
: "border-slate-200 bg-white text-slate-700 hover:border-orange-300",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"flex size-5 shrink-0 items-center justify-center rounded-full",
|
||||
doneIt ? "bg-emerald-500 text-white" : "border border-slate-300",
|
||||
)}
|
||||
>
|
||||
{doneIt && <Check className="size-3.5" strokeWidth={3} />}
|
||||
</span>
|
||||
<span className="truncate">{p.title}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
<p className="flex items-center justify-center gap-1 text-center text-xs text-muted-foreground">
|
||||
<Calculator className="size-3.5" /> 每课学完做即时练习,≥80 分点亮;每周还有数学周检测
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
"use server";
|
||||
|
||||
// xuchao: 数学每周检测判分(同英语 takeWeekly,打卡联动数学)
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { getSessionUser } from "@/lib/auth";
|
||||
import { gradeAnswers, type AnswerValue } from "@/lib/grammar";
|
||||
import { buildMathWeeklyQuestions, currentMathWeekKey, getMathWeeklyRecord } from "@/lib/math-weekly";
|
||||
import { recordWrong } from "@/lib/wrongbook";
|
||||
import { db } from "@/db";
|
||||
import { taskInstances, mathWeeklyTest } from "@/db/schema";
|
||||
import { todayStr } from "@/lib/dates";
|
||||
|
||||
export type MathWeeklyResult =
|
||||
| { score: number; passed: boolean; correct: number; total: number; detail: boolean[] }
|
||||
| { error: string };
|
||||
|
||||
// 提交数学每周检测:判分、记录、错题入库、达标关联当日数学打卡
|
||||
export async function takeMathWeekly(answers: AnswerValue[]): Promise<MathWeeklyResult> {
|
||||
const user = await getSessionUser();
|
||||
if (!user || user.role !== "child") return { error: "会话无效,请重新激活设备" };
|
||||
const wk = currentMathWeekKey();
|
||||
const existing = await getMathWeeklyRecord(user.id, wk);
|
||||
if (existing) return { error: "本周已完成数学检测,下周再来。" };
|
||||
|
||||
const questions = await buildMathWeeklyQuestions(user.id, wk);
|
||||
if (questions.length === 0) return { error: "暂无可组卷的题目,先学几课吧。" };
|
||||
|
||||
const g = gradeAnswers(questions, answers);
|
||||
questions.forEach((q, i) => {
|
||||
if (!g.detail[i]) void recordWrong(user.id, "mweekly", wk, `数学周检测 ${wk}`, q, answers[i]);
|
||||
});
|
||||
|
||||
await db
|
||||
.insert(mathWeeklyTest)
|
||||
.values({
|
||||
childUserId: user.id,
|
||||
weekKey: wk,
|
||||
score: g.score,
|
||||
correct: g.correct,
|
||||
total: g.total,
|
||||
passed: g.passed,
|
||||
questions: JSON.stringify(questions),
|
||||
})
|
||||
.onDuplicateKeyUpdate({ set: { score: g.score, correct: g.correct, total: g.total, passed: g.passed } });
|
||||
|
||||
// 达标关联当日数学打卡
|
||||
if (g.passed) {
|
||||
const today = todayStr();
|
||||
const title = `数学周检测 · ${wk}`;
|
||||
const [ex] = await db
|
||||
.select({ id: taskInstances.id, status: taskInstances.status })
|
||||
.from(taskInstances)
|
||||
.where(and(eq(taskInstances.childUserId, user.id), eq(taskInstances.date, today), eq(taskInstances.title, title)))
|
||||
.limit(1);
|
||||
if (ex) {
|
||||
if (ex.status !== "done")
|
||||
await db.update(taskInstances).set({ status: "done", doneAt: sql`CURRENT_TIMESTAMP` }).where(eq(taskInstances.id, ex.id));
|
||||
} else {
|
||||
await db.insert(taskInstances).values({
|
||||
childUserId: user.id,
|
||||
date: today,
|
||||
title,
|
||||
subject: "math",
|
||||
status: "done",
|
||||
doneAt: new Date(),
|
||||
sortOrder: 95,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
revalidatePath("/math/weekly");
|
||||
revalidatePath("/kid");
|
||||
revalidatePath("/parent");
|
||||
revalidatePath("/wrongbook");
|
||||
return g;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// xuchao: 数学每周检测页(结构同英语 /weekly)
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { BackgroundBlobs } from "@/components/background-blobs";
|
||||
import { getSessionUser } from "@/lib/auth";
|
||||
import { buildMathWeeklyQuestions, currentMathWeekKey, getMathWeeklyRecord } from "@/lib/math-weekly";
|
||||
import { QuizBlock } from "@/app/learn/quiz-block";
|
||||
import { takeMathWeekly } from "./actions";
|
||||
|
||||
export default async function MathWeeklyPage() {
|
||||
const user = await getSessionUser();
|
||||
if (!user) redirect("/join");
|
||||
if (user.role !== "child") redirect("/parent");
|
||||
|
||||
const wk = currentMathWeekKey();
|
||||
const record = await getMathWeeklyRecord(user.id, wk);
|
||||
const questions = record ? [] : await buildMathWeeklyQuestions(user.id, wk);
|
||||
|
||||
return (
|
||||
<main className="relative mx-auto max-w-2xl space-y-5 p-4 pb-12">
|
||||
<BackgroundBlobs />
|
||||
<header className="flex items-center justify-between">
|
||||
<Link href="/math" className="text-sm font-medium text-orange-600">
|
||||
← 返回数学
|
||||
</Link>
|
||||
<h1 className="text-xl font-extrabold text-slate-900">数学每周检测</h1>
|
||||
<span className="text-xs text-muted-foreground">{wk}</span>
|
||||
</header>
|
||||
<p className="rounded-lg border-l-4 border-orange-400 bg-orange-50 px-3 py-2 text-sm text-orange-800">
|
||||
从你已点亮的数学课里抽 10 题,判断这周的掌握情况;达标自动计入今日数学打卡,错题进错题本。
|
||||
</p>
|
||||
|
||||
{record ? (
|
||||
<div
|
||||
className={
|
||||
record.passed
|
||||
? "rounded-2xl bg-gradient-to-r from-emerald-500 to-emerald-600 p-6 text-center text-white shadow-btn"
|
||||
: "rounded-2xl bg-gradient-to-r from-rose-500 to-rose-600 p-6 text-center text-white shadow-btn"
|
||||
}
|
||||
>
|
||||
<p className="text-4xl font-extrabold">{record.score} 分</p>
|
||||
<p className="mt-1 text-sm opacity-90">
|
||||
答对 {record.correct} / {record.total} · {record.passed ? "本周掌握良好 🎉" : "看看错题本,下周再战"}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<QuizBlock
|
||||
pointId=""
|
||||
questions={questions}
|
||||
submitter={takeMathWeekly}
|
||||
title="数学周检测"
|
||||
subtitle="10 题,做完提交;每题有解说,达标计入今日数学打卡。"
|
||||
passText="周检测达标 🎉 已计入今日数学打卡"
|
||||
backHref="/math"
|
||||
backLabel="返回数学地图"
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"papers": [
|
||||
{
|
||||
"id": "g7s1-m01-gongyi",
|
||||
"grade": "g7",
|
||||
"semester": "s1",
|
||||
"month": 1,
|
||||
"examType": "final",
|
||||
"title": "2025-2026学年第一学期期末考试试卷七年级数学(客观题)",
|
||||
"region": "河南郑州",
|
||||
"year": 2026,
|
||||
"duration": 100,
|
||||
"fullScore": 39,
|
||||
"sourceUrl": "https://www.zxxk.com/soft/58892845.html",
|
||||
"summary": "据学科网原卷转录,仅含选择题与填空题客观题部分,解答题未收录;客观题合计 39 分。",
|
||||
"questions": [
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "下列各数中,绝对值最小的数是()",
|
||||
"options": ["A. -3", "B. -1/2", "C. 0", "D. 1"],
|
||||
"answer": 2,
|
||||
"explain": "|-3|=3,|-1/2|=1/2,|0|=0,|1|=1,其中0最小,故绝对值最小的数是0。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "2024年中秋假期第二天,据成都东部新区世园会运营部统计,全天累计入园达62100人次.62100用科学记数法表示为()",
|
||||
"options": ["A. 6.21×10⁴", "B. 6.21×10⁵", "C. 0.621×10⁴", "D. 0.621×10⁵"],
|
||||
"answer": 0,
|
||||
"explain": "62100=6.21×10⁴(小数点左移4位,n=4),故选A。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如图所示的几何体是由5个大小相同的小立方块搭成(下层4个、上层1个,上层立方块位于下层左侧一列的上方),从前面看得到的图形是()",
|
||||
"options": [
|
||||
"A. 竖排三个正方形,中间正方形右侧还有一个正方形",
|
||||
"B. 横排三个正方形,最左正方形上方还有一个正方形",
|
||||
"C. 横排两个正方形,左边正方形上方还有一个正方形",
|
||||
"D. 竖排三个正方形,最上正方形右侧还有一个正方形"
|
||||
],
|
||||
"answer": 2,
|
||||
"explain": "从前面看左右两列:左列两层、右列一层,即上层一个正方形(居左)、下层两个正方形,为C。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "若x=-2是方程2x+a=1的解,则a的值为()",
|
||||
"options": ["A. 3", "B. 5", "C. -3", "D. -5"],
|
||||
"answer": 1,
|
||||
"explain": "把x=-2代入方程,得2×(-2)+a=1,即-4+a=1,解得a=5。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如图,正方体的表面展开图上写有“我们热爱中国”六个字(上排“我”“们”,中排“热”“中”“国”,其中“国”在“我”正下方,下排“爱”在“中”正下方),还原成正方体后“我”的对面的字是()",
|
||||
"options": ["A. 热", "B. 爱", "C. 中", "D. 国"],
|
||||
"answer": 1,
|
||||
"explain": "正方体展开图中相对面中间必隔一个正方形:“我”对“爱”,“们”对“中”,“热”对“国”。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "已知ax=ay,下列等式变形不一定成立的是()",
|
||||
"options": ["A. b+ax=b+ay", "B. x=y", "C. x-ax=x-ay", "D. ax/(a²+1)=ay/(a²+1)"],
|
||||
"answer": 1,
|
||||
"explain": "等式两边同加同减仍成立(A、C);a²+1≠0,两边同除以a²+1成立(D);B需两边同除以a,当a=0时不能除,x=y不一定成立。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "有理数a、b在数轴上的位置如图所示(a在原点左侧、b在原点右侧,且a到原点的距离大于b到原点的距离),下列式子错误的是()",
|
||||
"options": ["A. a<b", "B. -a>b", "C. a²<b²", "D. b-a>0"],
|
||||
"answer": 2,
|
||||
"explain": "由图a<0<b且|a|>|b|:A正确;-a=|a|>|b|=b,B正确;a²=|a|²>|b|²=b²,故a²<b²错误,C错;b-a=b+|a|>0,D正确。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "一把直尺和一块三角板(含30°,60°角)如图所示摆放,三角板直角顶点为C,∠BAC=60°,∠B=30°,直尺一边与三角板的两直角边分别交于A,D两点,另一边与三角板的两直角边分别交于E,F两点,且∠CAD=50°,那么∠BAD的大小是()",
|
||||
"options": ["A. 10°", "B. 20°", "C. 30°", "D. 40°"],
|
||||
"answer": 0,
|
||||
"explain": "∠BAD=∠BAC-∠CAD=60°-50°=10°。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "中国古代人民很早就在生产生活中发现了许多有趣的数学问题,其中《孙子算经》中有个问题:今有四人共车,一车空;二人共车,八人步,问人与车各几何?这道题的意思是:今有若干人乘车,若每4人乘一车,则最终剩余1辆车;若每2人乘一车,则最终剩余8人无车可乘,问有多少人,多少辆车?如果我们设有x个人,那么可列方程为()",
|
||||
"options": ["A. 4(x-1)=2x+8", "B. 4(x+1)=2x-8", "C. x/4+1=(x+8)/2", "D. x/4+1=(x-8)/2"],
|
||||
"answer": 3,
|
||||
"explain": "车数不变:每4人一车空1辆,车数为x/4+1;每2人一车余8人步行,车数为(x-8)/2,故x/4+1=(x-8)/2。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "单项式-3πx²y/2的系数是___________.",
|
||||
"answers": ["-3π/2", "-(3/2)π", "-1.5π"],
|
||||
"explain": "系数是单项式中的数字因数,π是常数属于系数,故系数为-3π/2。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "将13转换为二进制数为___________.",
|
||||
"answers": ["1101", "1101₂", "(1101)₂"],
|
||||
"explain": "13=8+4+1=1×2³+1×2²+0×2¹+1×2⁰,故二进制为1101。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "已知x-3y=2,那么代数式1+2x-6y的值是___________.",
|
||||
"answers": ["5"],
|
||||
"explain": "1+2x-6y=1+2(x-3y)=1+2×2=5。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "一个角的补角是125°,则这个角的余角为___________.",
|
||||
"answers": ["35°", "35"],
|
||||
"explain": "这个角=180°-125°=55°,其余角=90°-55°=35°。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"papers": [
|
||||
{
|
||||
"id": "g7s1-m09-zhengzhou4",
|
||||
"grade": "g7",
|
||||
"semester": "s1",
|
||||
"month": 9,
|
||||
"examType": "monthly",
|
||||
"title": "2025-2026学年河南省郑州四中七年级(上)月考数学试卷(10月份)(客观题)",
|
||||
"region": "河南郑州",
|
||||
"year": 2025,
|
||||
"fullScore": 39,
|
||||
"sourceUrl": "https://www.zxxk.com/soft/55491209.html",
|
||||
"summary": "据学科网原卷转录,仅含选择题与填空题客观题部分,解答题未收录;客观题合计 39 分。",
|
||||
"questions": [
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "-5的相反数是()",
|
||||
"options": ["A. -1/5", "B. 1/5", "C. 5", "D. -5"],
|
||||
"answer": 2,
|
||||
"explain": "只有符号不同的两个数互为相反数,-5的相反数是5。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如图,图中属于柱体的个数是()(图中从左到右依次为:正方体、圆锥、长方体、圆柱、球、三棱柱)",
|
||||
"options": ["A. 3", "B. 4", "C. 5", "D. 6"],
|
||||
"answer": 1,
|
||||
"explain": "柱体分为圆柱和棱柱:正方体、长方体、圆柱、三棱柱属于柱体,共4个;圆锥是锥体、球是球体。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "下列说法中正确的是()",
|
||||
"options": ["A. 正有理数和负有理数统称为有理数", "B. 整数和小数统称有理数", "C. 整数、零和分数统称为有理数", "D. 整数和分数统称为有理数"],
|
||||
"answer": 3,
|
||||
"explain": "有理数包括整数和分数(或正有理数、0、负有理数):A漏了0;B中无限不循环小数不是有理数;C中零已包含在整数内,分类重复说法不当;D正确。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "分别用一个平面去截如图所示的几何体,能得到三角形截面的几何体共有()(图中从左到右依次为:长方体、圆柱、三棱柱、圆锥、球)",
|
||||
"options": ["A. 2个", "B. 3个", "C. 4个", "D. 5个"],
|
||||
"answer": 1,
|
||||
"explain": "用平面截长方体、三棱柱、圆锥都能得到三角形截面;截圆柱和球不能得到三角形截面,共3个。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如图是正方体表面展开图(中间一行4个正方形;最左边正方形上方有1个正方形,其左上顶点为A;中间一行从左数第3个正方形下方有1个正方形,该下方正方形的左上、右上、左下、右下顶点依次为B、D、C、E)。将其折叠成正方体后,距顶点A最近的点是()",
|
||||
"options": ["A. B点", "B. C点", "C. D点", "D. E点"],
|
||||
"answer": 2,
|
||||
"explain": "将展开图折叠成正方体后,点E与顶点A重合,B、C、D中与A距离最近的是D点。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "比-3又1/3大且比2又1/3小的所有整数的和为()",
|
||||
"options": ["A. 4", "B. 0", "C. 3", "D. -3"],
|
||||
"answer": 3,
|
||||
"explain": "满足条件的整数有-3,-2,-1,0,1,2,它们的和为(-3)+(-2)+(-1)+0+1+2=-3。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "纽约与北京的时差为-13h(负数表示同一时刻纽约比北京晚的时数),小明10月1日在北京乘坐早晨8:00的航班飞行约20h到达纽约,此时纽约时间是()",
|
||||
"options": ["A. 10月1日7时", "B. 10月1日15时", "C. 10月2日15时", "D. 10月2日7时"],
|
||||
"answer": 1,
|
||||
"explain": "8+20+(-13)=15,即到达时纽约时间是10月1日15时。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如图,由若干相同大小的小正方体组成的几何体,从不同方向看到的图形如图所示(从正面看:下层一行3个正方形,最右一列上方1个正方形;从左面看:下层一行2个正方形,左列上方1个正方形),组成该几何体最少需要m个小正方体,最多需要n个小正方体,则m,n分别为()",
|
||||
"options": ["A. 3,7", "B. 4,7", "C. 3,8", "D. 4,8"],
|
||||
"answer": 1,
|
||||
"explain": "从正面看三列高为1、1、2,从左面看前后两行高为2、1。最多时每个位置高度取两方向高度的较小值:(1+1)+(1+1)+(2+1)=7个;最少时只需每列、每行的最大高度被满足:一摞高2加两个高1,共4个。故m=4,n=7。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "在朱自清的《春》中描写春雨“像牛毛、像花针、像细丝,密密麻麻地斜织着”的语句,这里把雨看成了线,这种生活现象可以反映的数学原理是__________.",
|
||||
"answers": ["线动成面"],
|
||||
"explain": "雨被看成线,密密麻麻斜织成片(面),反映的数学原理是线动成面。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "若|m-3|与|2n+4|互为相反数,则2m-3n=______.",
|
||||
"answers": ["12"],
|
||||
"explain": "绝对值都非负,两非负数互为相反数则都为0:m-3=0,2n+4=0,得m=3,n=-2,故2m-3n=6+6=12。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "一个直三棱柱,它的底面边长都相等,侧棱长12cm,侧面积是144cm²,那么它的底面边长是______.",
|
||||
"answers": ["4cm", "4"],
|
||||
"explain": "直三棱柱侧面积=底面周长×侧棱长=3a×12=144,解得底面边长a=4cm。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "正六边形ABCDEF(六条边相等)在数轴上的位置如图所示,点A,F对应的数分别为1和0(F在0处、A在1处,B、C、D、E依次在右、右上、左上、左方)。若正六边形绕顶点顺时针方向在数轴上连续翻转,翻转1次后,点B所对应的数为2;…按此规律继续翻转下去,数轴上数2026所对应的顶点是______.",
|
||||
"answers": ["D", "点D", "顶点D"],
|
||||
"explain": "数k对应的顶点以6为周期:0→F,1→A,2→B,3→C,4→D,5→E。2026÷6=337余4,故2026对应的顶点是D。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "长方形纸片上有一数轴,剪下16个单位长度(从-4到12)的一条线段,并把这条线段沿某点折叠,然后在重叠部分某处剪一刀得到三条线段(如图所示).若这三条线段的长度之比为1:1:2,则折痕处对应的点所表示的数可能是_____",
|
||||
"answers": ["2或4或6", "2、4、6"],
|
||||
"explain": "三线段长为4、4、8。设两剪点为p、q(p<q),折痕为(p+q)/2。三种排列(4,4,8)、(4,8,4)、(8,4,4)分别得剪点0与4、0与8、4与8,折痕分别为2、4、6,经验证剪点均落在重叠部分内。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"papers": [
|
||||
{
|
||||
"id": "g7s1-m10-bashu",
|
||||
"grade": "g7",
|
||||
"semester": "s1",
|
||||
"month": 10,
|
||||
"examType": "monthly",
|
||||
"title": "重庆市鲁能巴蜀中学2025-2026学年上学期七年级第二次月考数学试卷(客观题)",
|
||||
"region": "重庆",
|
||||
"year": 2025,
|
||||
"fullScore": 44,
|
||||
"sourceUrl": "https://www.zxxk.com/soft/55893523.html",
|
||||
"summary": "据学科网原卷转录,仅含选择题与填空题客观题部分,解答题未收录;客观题合计 44 分。",
|
||||
"questions": [
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "-2的倒数是()",
|
||||
"options": ["A. -2", "B. -1/2", "C. 1/2", "D. 2"],
|
||||
"answer": 1,
|
||||
"explain": "乘积为1的两个数互为倒数,-2×(-1/2)=1,故-2的倒数是-1/2。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如图是一幅几何体素描作品(图中有一个正方体、一个竖放的长方体、一个球体和一个四棱锥),则该作品中不存在的几何体是()",
|
||||
"options": ["A. 棱柱", "B. 棱锥", "C. 圆柱", "D. 球体"],
|
||||
"answer": 2,
|
||||
"explain": "正方体、长方体属于棱柱,图中还有棱锥、球体,没有圆柱。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "数轴上点A到表示-3的点的距离是10,那么点A表示的数是()",
|
||||
"options": ["A. 7", "B. -13", "C. 7或-13", "D. 13或-7"],
|
||||
"answer": 2,
|
||||
"explain": "点A可在-3的右侧或左侧:-3+10=7,-3-10=-13,故点A表示7或-13。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "若关于x的方程2x+a=3的解是x=-1,则a的值为()",
|
||||
"options": ["A. 1", "B. -1", "C. 5", "D. -5"],
|
||||
"answer": 2,
|
||||
"explain": "把x=-1代入方程,得2×(-1)+a=3,即-2+a=3,解得a=5。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "单项式-2πx³y²/5的系数和次数分别为()",
|
||||
"options": ["A. -2π/5,5", "B. -2/5,6", "C. 2π/5,5", "D. 2/5,6"],
|
||||
"answer": 0,
|
||||
"explain": "π是常数不是字母,系数为-2π/5;次数为所有字母指数和3+2=5。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "下列说法错误的是()",
|
||||
"options": ["A. 两点之间的线段的长度叫做这两点之间的距离", "B. 射线AB与射线BA对应同一图形", "C. 过一点可画无数条直线", "D. 两点之间线段最短"],
|
||||
"answer": 1,
|
||||
"explain": "射线AB以A为端点、射线BA以B为端点,端点和方向都不同,不是同一图形,B错误;A、C、D均正确。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如图,蜂窝结构在工程学中有广泛应用,图①由4个房孔组成,图②由7个房孔组成,图③由10个房孔组成……,按照这样的规律,第⑨个图形中房孔的个数是()",
|
||||
"options": ["A. 27", "B. 31", "C. 36", "D. 28"],
|
||||
"answer": 3,
|
||||
"explain": "4,7,10,…每次增加3,第n个图形有(3n+1)个房孔,第⑨个为3×9+1=28。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "甲乙两车从A地到B地同时出发同向而行,已知甲车速度为100km/h,乙车速度比甲车速度的1.5倍少10km/h,且甲车比乙车晚到1h,若设乙车的行驶时间为xh,可列方程()",
|
||||
"options": ["A. 100(x-1)=(1.5×100-10)x", "B. 100(x-1)=(1.5×100+10)x", "C. 100(x+1)=(1.5×100-10)x", "D. 100(x+1)=(1.5×100+10)x"],
|
||||
"answer": 2,
|
||||
"explain": "乙车速度为(1.5×100-10)km/h;甲车比乙车晚到1h,甲车用时(x+1)h,两车路程相等,得100(x+1)=(1.5×100-10)x。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "已知整式M:aₙxⁿ+aₙ₋₁xⁿ⁻¹+…+a₁x+a₀,其中n,aₙ是正整数,其余系数均为自然数,且满足n+max{aₙ,aₙ₋₁,…,a₀}=b,其中max{aₙ,aₙ₋₁,…,a₀}表示aₙ,aₙ₋₁,…,a₀中最大的数,b是正整数,下列说法:①当b=5时满足条件的整式中只有4个单项式;②当b=4时在所有满足条件的整式中,整式的系数和的最大值为6;③当n≤2且b=4时,满足条件的整式共有20个。其中正确的个数是()",
|
||||
"options": ["A. 3", "B. 2", "C. 0", "D. 1"],
|
||||
"answer": 0,
|
||||
"explain": "①单项式即M=aₙxⁿ,n+aₙ=5,(n,aₙ)可取(1,4),(2,3),(3,2),(4,1)共4个,正确;②b=4时,n=1、max=3可取a₁=a₀=3,系数和为6(n=2时最大2+2+2=6,n=3时最大4),正确;③n=1时max=3,共6个;n=2时max=2,共2×3×3-1×2×2=14个,合计20个,正确。三个都对,选A。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "比较大小:-2/3_______-3/4(填“>”或“<”).",
|
||||
"answers": [">"],
|
||||
"explain": "通分:-2/3=-8/12,-3/4=-9/12,-8/12>-9/12,故-2/3>-3/4。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "若m-2n=2,则2m-4n+5的值是___________.",
|
||||
"answers": ["9"],
|
||||
"explain": "2m-4n+5=2(m-2n)+5=2×2+5=9。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"papers": [
|
||||
{
|
||||
"id": "g7s1-m11-bincheng",
|
||||
"grade": "g7",
|
||||
"semester": "s1",
|
||||
"month": 11,
|
||||
"examType": "midterm",
|
||||
"title": "2025—2026学年度第一学期期中教学质量检测七年级数学试题(A)(客观题)",
|
||||
"region": "山东滨州",
|
||||
"year": 2025,
|
||||
"duration": 120,
|
||||
"fullScore": 42,
|
||||
"sourceUrl": "https://www.zxxk.com/soft/55249928.html",
|
||||
"summary": "据学科网原卷转录,仅含选择题与填空题客观题部分,解答题未收录;客观题合计 42 分。",
|
||||
"questions": [
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "某地今年1月1日至1月4日每天的最高气温与最低气温如下表:1月1日最高气温5℃、最低气温0℃;1月2日最高气温4℃、最低气温-2℃;1月3日最高气温0℃、最低气温-4℃;1月4日最高气温4℃、最低气温-3℃。其中温差最大的是()",
|
||||
"options": ["A. 1月1日", "B. 1月2日", "C. 1月3日", "D. 1月4日"],
|
||||
"answer": 3,
|
||||
"explain": "温差=最高气温-最低气温:1月1日5-0=5℃,1月2日4-(-2)=6℃,1月3日0-(-4)=4℃,1月4日4-(-3)=7℃,故温差最大的是1月4日。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "截至2025年5月,国家智慧教育平台注册用户已突破1.64亿,成为世界第一大教育资源数字化中心和平台。将1.64亿用科学记数法表示应为()",
|
||||
"options": ["A. 16.4×10⁷", "B. 0.164×10⁹", "C. 1.64×10⁸", "D. 1.64×10⁹"],
|
||||
"answer": 2,
|
||||
"explain": "1.64亿=164000000=1.64×10⁸。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "把8-(+4)+(-6)-(-5)写成省略括号和加号的形式是()",
|
||||
"options": ["A. 8+(-4)+(-6)+5", "B. 8-4-6-5", "C. 8-4-6+5", "D. 8+4-6-5"],
|
||||
"answer": 2,
|
||||
"explain": "8-(+4)+(-6)-(-5)=8-4-6+5:减去+4得-4,加上-6得-6,减去-5得+5。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "a,b是有理数,它们在数轴上的对应点的位置如图所示(b在-1左侧,a在0与1之间),则下列各式正确的是()",
|
||||
"options": ["A. -b<-a<a<b", "B. -a<-b<a<b", "C. b<-b<a<-a", "D. b<-a<a<-b"],
|
||||
"answer": 3,
|
||||
"explain": "由数轴知b<-1<0<a<1,则-a<0<-b,且|b|>|a|,故b<-a<a<-b。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "下列问题情境,不能用加法算式-2+10表示的是()",
|
||||
"options": ["A. 数轴上表示10与-2的两个点之间的距离", "B. 某日最低气温为-2℃,温差为10℃,求该日的最高气温", "C. 用10元纸币购买2元文具后找回的零钱", "D. 水位先下降2cm,再上升10cm后的水位变化情况"],
|
||||
"answer": 0,
|
||||
"explain": "数轴上10与-2两点间的距离为10-(-2)=12,而-2+10=8,不能用-2+10表示;B、C、D均可化为-2+10。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "有理数-3²,(-3)²,|-3³|,-1/3按从小到大的顺序排列是()",
|
||||
"options": ["A. -1/3 < -3² < (-3)² < |-3³|", "B. -3² < -1/3 < (-3)² < |-3³|", "C. |-3³| < -3² < -1/3 < (-3)²", "D. -1/3 < -3² < |-3³| < (-3)²"],
|
||||
"answer": 1,
|
||||
"explain": "-3²=-9,(-3)²=9,|-3³|=27,-1/3≈-0.33,故-3² < -1/3 < (-3)² < |-3³|。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "下列说法正确的是()",
|
||||
"options": ["A. 任意有理数的绝对值都是非负数", "B. 若|a|=-a,则a必为负数", "C. 绝对值不大于3的整数有6个,分别是±1,±2,±3", "D. 若|a|=a,则a>0;反之,若a>0,则|a|=a"],
|
||||
"answer": 0,
|
||||
"explain": "绝对值都是非负数,A正确;B中a还可为0;C中还有0,共7个;D中|a|=a时a也可为0。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "用长度相同的木棍按如图所示的规律拼图案(第①个图案为1个正方形与1个六边形拼接、相邻图形共用一条边,第②个为1个正方形与2个六边形拼接,第③个为1个正方形与3个六边形拼接,第④个为1个正方形与4个六边形拼接),其中第①个图案用了9根木棍,第②个图案用了14根木棍,第③个图案用了19根木棍,第④个图案用了24根木棍,…,按此规律排列下去,若第n个图案中有y根木棍,则y与n之间的关系式是()",
|
||||
"options": ["A. y=5n+4", "B. y=5n-1", "C. y=2n+1", "D. y=2n-1"],
|
||||
"answer": 0,
|
||||
"explain": "每个图案比前一个多5根木棍:9,14,19,24,…,故y=9+5(n-1)=5n+4。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "月球表面温度白天最高130℃,记为+130℃;则夜间最低零下175℃,记为______℃。",
|
||||
"answers": ["-175"],
|
||||
"explain": "零上记为正,则零下记为负,零下175℃记为-175℃。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "用“>”、“<”、“=”填空:① -0.02 ______ -4/5;② -3/4 ______ -(-3/4);③ -[+(-0.75)] ______ 4/7。",
|
||||
"answers": ["①> ②< ③>", ">,<,>"],
|
||||
"explain": "① -4/5=-0.8,-0.02>-0.8;② -(-3/4)=3/4为正,-3/4<3/4;③ -[+(-0.75)]=0.75=21/28>4/7=16/28。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "已知代数式x+2y的值是3,则代数式2x+4y+2025的值是______。",
|
||||
"answers": ["2031"],
|
||||
"explain": "2x+4y+2025=2(x+2y)+2025=2×3+2025=2031。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "在50,6.3,2025,-12,19%,-3.12(12循环),-11,-5%中,正整数有a个,负有理数有b个,则代数式(a-b)³的值为______。",
|
||||
"answers": ["-8"],
|
||||
"explain": "正整数为50、2025,a=2;负有理数为-12、-3.12(循环)、-11、-5%,b=4;(a-b)³=(2-4)³=(-2)³=-8。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "如果路程为s,速度为v,时间为t,那么t与s、v之间的关系可以用式子表示为______,v与t之间的关系是______关系(填“正比例”或“反比例”)。",
|
||||
"answers": ["t=s/v,反比例"],
|
||||
"explain": "时间=路程÷速度,即t=s/v;路程s一定时,v与t的乘积为定值s,成反比例关系。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "上海举办过第十四届国际数学教育大会(简称ICME-14),会徽的主题图案(如图)有着丰富的数学元素,展现了中国古代数学的灿烂成就,图案中右下方的图形是用中国古代的计数符号写出的八进制数字3745。我们常用的数是十进制数,如4657=4×10³+6×10²+5×10¹+7×10⁰。(规定:当a≠0时,a⁰=1)八进制是以8作为进位基数的数字系统,有8个基本数字0~7。八进制数(3745)₈换算成十进制数是3×8³+7×8²+4×8¹+5×8⁰=2021。将八进制数(2037)₈换算成十进制数为______。",
|
||||
"answers": ["1055"],
|
||||
"explain": "(2037)₈=2×8³+0×8²+3×8¹+7×8⁰=1024+0+24+7=1055。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"papers": [
|
||||
{
|
||||
"id": "g7s1-m12-bj101",
|
||||
"grade": "g7",
|
||||
"semester": "s1",
|
||||
"month": 12,
|
||||
"examType": "monthly",
|
||||
"title": "七年级数学(客观题)",
|
||||
"region": "北京",
|
||||
"year": 2025,
|
||||
"fullScore": 39,
|
||||
"sourceUrl": "https://www.zxxk.com/soft/55926833.html",
|
||||
"summary": "据学科网原卷转录,仅含选择题与填空题客观题部分,解答题未收录;客观题合计 39 分。",
|
||||
"questions": [
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "-2的倒数是()",
|
||||
"options": ["A. -2", "B. -1/2", "C. 1/2", "D. 2"],
|
||||
"answer": 1,
|
||||
"explain": "乘积为1的两个数互为倒数,-2×(-1/2)=1,故-2的倒数是-1/2。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "为助力数字经济发展,北京积极推进多个公共算力中心的建设。北京数字经济算力中心日前已部署上架和调试的设备的算力为4×10¹⁷ Flops(Flops是计算机系统算力的一种度量单位),整体投产后,累计实现的算力将是日前已部署上架和调试的设备的算力的5倍,达到m Flops,则m的值为()",
|
||||
"options": ["A. 8×10¹⁶", "B. 2×10¹⁷", "C. 5×10¹⁷", "D. 2×10¹⁸"],
|
||||
"answer": 3,
|
||||
"explain": "m=4×10¹⁷×5=20×10¹⁷=2×10¹⁸。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "若{x=2, y=-1}是关于x、y的二元一次方程mx+y=3的解,则m的值为()",
|
||||
"options": ["A. 1", "B. -1", "C. -2", "D. 2"],
|
||||
"answer": 3,
|
||||
"explain": "把x=2,y=-1代入mx+y=3,得2m-1=3,解得m=2。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "下列正方体的展开图上每个面上都有一个汉字。其中,手的对面是口的是()",
|
||||
"options": [
|
||||
"A. 上排自左至右为勤、洗、手,下排自左至右为戴、口、罩,其中戴在手的正下方",
|
||||
"B. 中排自左至右为洗、手、戴、口,勤在洗的正上方,罩在口的正下方",
|
||||
"C. 上排自左至右为勤、洗,中排自左至右为手、戴(手在洗的正下方),下排自左至右为口、罩(口在戴的正下方)",
|
||||
"D. 中排自左至右为洗、手、戴,勤在洗的正上方,口在手的正下方,罩在口的正下方"
|
||||
],
|
||||
"answer": 1,
|
||||
"explain": "正方体展开图中相对的面之间一定相隔一个正方形:A中手的对面是勤,C中手的对面是罩,D中手的对面是罩,只有B中手的对面是口。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如图,长和宽为a、b的长方形的周长为14,面积为10,则ab(a+b)的值为()",
|
||||
"options": ["A. 140", "B. 70", "C. 35", "D. 24"],
|
||||
"answer": 1,
|
||||
"explain": "由周长得2(a+b)=14,即a+b=7;由面积得ab=10;所以ab(a+b)=10×7=70。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如图所示,已知O是直线AB上一点,射线OC、OD在直线同侧(OC在左、OD在右),∠1即∠AOC,∠2即∠BOD,∠1=40°,OD平分∠BOC,则∠2的度数是()",
|
||||
"options": ["A. 20°", "B. 25°", "C. 30°", "D. 70°"],
|
||||
"answer": 3,
|
||||
"explain": "∠BOC=180°-∠1=140°;OD平分∠BOC,∠2=BOC÷2=70°。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "如果一个角的度数比它的补角的度数2倍多30°,那么这个角的度数是()",
|
||||
"options": ["A. 50°", "B. 70°", "C. 130°", "D. 160°"],
|
||||
"answer": 2,
|
||||
"explain": "设这个角为x,则x=2(180°-x)+30°,解得3x=390°,x=130°。"
|
||||
},
|
||||
{
|
||||
"type": "choice",
|
||||
"stem": "已知数a、b在数轴上的位置如图所示(a在原点左侧,b在原点右侧,且a到原点的距离大于b到原点的距离),下列结论:①ab<0;②a+b>0;③a³>b³;④(a-b)³>0;⑤a<-b<b<-a;⑥|b-a|-|a|=b。其中正确的结论有()",
|
||||
"options": ["A. 2个", "B. 3个", "C. 4个", "D. 5个"],
|
||||
"answer": 1,
|
||||
"explain": "由图知a<0<b且|a|>|b|:①ab<0正确;②a+b<0错误;③a³<b³错误;④a-b<0,(a-b)³<0错误;⑤正确;⑥|b-a|-|a|=(b-a)-(-a)=b正确。共3个正确。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "如图,小亮为将一根木条固定在墙上,他在木条两端各用一个钉子进行固定,能够解释这一操作原因的数学基本事实是________。",
|
||||
"answers": ["两点确定一条直线", "经过两点有且只有一条直线"],
|
||||
"explain": "两个钉子相当于两个点,木条相当于经过这两点的直线,由“两点确定一条直线”可知木条被固定。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "若多项式xy^(m+n)+(n-2)x²y²+1是关于x、y的三次多项式,则mn=____。",
|
||||
"answers": ["0"],
|
||||
"explain": "三次多项式不能含四次项,故n-2=0,n=2;又xy^(m+n)的次数1+(m+n)=3,得m=0,所以mn=0。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "一项工程,甲单独做需10天完成,乙单独做需6天完成。现由甲先做2天,乙再加入合作,完成这项工程共需多少天?若设完成这项工程共需x天,依题意可列方程___________。",
|
||||
"answers": ["x/10+(x-2)/6=1", "(x-2)/6+x/10=1"],
|
||||
"explain": "甲共做x天,完成x/10;乙做(x-2)天,完成(x-2)/6;两者合计为整个工程1。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "用⊕表示一种运算,它的含义是:A⊕B=1/(A+B)+x/((A+1)(B+1)),如果2⊕1=5/3,那么3⊕4=______。",
|
||||
"answers": ["19/35"],
|
||||
"explain": "由2⊕1=1/3+x/6=5/3,解得x=8;于是3⊕4=1/7+8/(4×5)=1/7+2/5=5/35+14/35=19/35。"
|
||||
},
|
||||
{
|
||||
"type": "fill",
|
||||
"stem": "如图,将一副三角尺叠放在一起,使直角顶点重合于点O(两块三角尺的直角分别为∠AOB和∠COD,即∠AOB=∠COD=90°),绕点O任意转动其中一个三角尺,则与∠AOD始终相等的角是________。",
|
||||
"answers": ["∠BOC"],
|
||||
"explain": "∠AOD=∠AOC+∠COD=∠AOC+90°,∠BOC=∠AOC+∠AOB=∠AOC+90°,故∠AOD=∠BOC,与转动位置无关。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"module": "calc",
|
||||
"moduleName": "计算与计数",
|
||||
"points": [
|
||||
{
|
||||
"id": "calc-50",
|
||||
"title": "数一数(1~10)与比多少",
|
||||
"grade": "一年级",
|
||||
"difficulty": 1,
|
||||
"summary": "学会手口一致地点数物品,用一一对应比多少",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n数数是数学学习的起点。本课要学会两样本领:手口一致地点数物品、数到几就是几;用一一对应的方法比较两组物品的多少。\n\n## ② 知识精讲\n### 概念/方法 1:数一数(1~10)\n数数四步法:\n1. 看:看清要数哪些东西\n2. 指:用手指着一个一个点\n3. 数:嘴里跟着念 1、2、3……\n4. 说:最后一个数是多少就是总数\n\n数数约定:从左到右、从上到下,不跳着数。\n数数儿歌:一二三四五,上山打老虎;六七八九十,数数要数齐。\n\n**例1** 数一数:★★★☆☆☆☆☆,★有几个?☆有几个?\n【思路】只数★,用手指着一个一个点着数,不数☆。\n【解答】★:1、2、3,共3个;☆:1、2、3、4、5,共5个。\n【提醒】手口一致:手指点到哪个,嘴里就数哪个,最后一个数就是个数。\n\n**例2** 一排花 🌷🌷🌷🌷🌷🌷🌷,共有几朵?\n【思路】从左到右一个一个点着数。\n【解答】1、2、3、4、5、6、7,共7朵花。\n【验算】再从右往左数一遍,还是7朵,说明没数错。\n\n### 概念/方法 2:比多少(一一对应)\n排队法:把两组物品一个对一个地配对,谁有剩余,谁就多。\n口诀:排排队,手拉手,谁没朋友谁就多。\n\n**例3** ●●● 和 ★★★★★,谁多?多几个?\n【思路】一个●对一个★配对。\n【解答】配了3对后,★还多出2个。●有3个,★有5个,5>3,所以★比●多2,●比★少2。\n【提醒】\"多几\"和\"少几\"说的是同一件事:★比●多2,也就是●比★少2。\n\n**例4** 妈妈有4个苹果,爸爸有4个苹果,谁的多?\n【思路】一一对应配对,看有没有剩余。\n【解答】每人都有朋友,没有剩余,一样多,4=4。\n\n## ③ 常见错误与提醒\n- 数数时跳着数或重复数:必须从左到右、从上到下按顺序数\n- 嘴里数到5、手指却点到第6个:手口要一致\n- 比较多少时只凭眼睛看:两组物品排列不齐时容易看错,要一一对应配对\n- 只说出\"谁多\",忘记回答\"多几个\"\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"fill\",\"stem\":\"数一数:★★★☆☆☆☆☆ 中,★有几个?\",\"answers\":[\"3\"],\"explain\":\"一个一个点着数★:1、2、3,共3个。\"},{\"type\":\"fill\",\"stem\":\"数一数:★★★☆☆☆☆☆ 中,☆有几个?\",\"answers\":[\"5\"],\"explain\":\"一个一个点着数☆:1、2、3、4、5,共5个。\"},{\"type\":\"fill\",\"stem\":\"数一数:△△△△△ 中,△有几个?\",\"answers\":[\"5\"],\"explain\":\"手口一致点着数,共5个△。\"},{\"type\":\"choice\",\"stem\":\"下面有一排花:🌷🌷🌷🌷🌷🌷🌷,共有几朵花?\",\"options\":[\"6朵\",\"7朵\",\"8朵\"],\"answer\":1,\"explain\":\"从左到右一个一个数:1、2、3、4、5、6、7,共7朵。\"},{\"type\":\"choice\",\"stem\":\"●●● 和 ★★★★★ 一一对应后,下面的说法正确的是?\",\"options\":[\"★比●多2\",\"●比★多2\",\"一样多\"],\"answer\":0,\"explain\":\"一个●对一个★,3对之后★还多2个。●有3个,★有5个,5>3,所以★比●多2。\"},{\"type\":\"fill\",\"stem\":\"△△△△ 和 ○○○ 一一对应后,△比○多( )个。\",\"answers\":[\"1\"],\"explain\":\"一个△对一个○,配对3组后△还剩1个,所以△比○多1个(○比△少1个)。\"},{\"type\":\"judge\",\"stem\":\"妈妈有4个苹果,爸爸有4个苹果,一一对应后两人一样多。\",\"answer\":0,\"explain\":\"对。一一对应,每人都有朋友、没有剩余,所以一样多,4=4。\"},{\"type\":\"choice\",\"stem\":\"小兔子有5只萝卜,小松鼠有3个松果,谁的多?多几个?\",\"options\":[\"小兔子的多,多2个\",\"小松鼠的多,多2个\",\"一样多\"],\"answer\":0,\"explain\":\"5>3,小兔子的萝卜多,5-3=2,多2个。\"},{\"type\":\"choice\",\"stem\":\"在 7○9 的○里应填哪个比较符号?\",\"options\":[\">\",\"<\",\"=\"],\"answer\":1,\"explain\":\"7比9小,所以填<,即7<9。\"},{\"type\":\"judge\",\"stem\":\"数数时可以跳着数,这样数得更快。\",\"answer\":1,\"explain\":\"错。数数要按从左到右、从上到下的顺序一个一个数,跳着数容易多数或漏数。\"}]}\n```\n\n## ⑤ 本课小结\n- 数数四步法:看、指、数、说,按顺序数不跳跃\n- 最后一个数到的数就是总数\n- 比多少用一一对应排队法:谁没朋友(有剩余)谁就多\n- 一方多几,另一方就少几\n- 比大小用 >、<、= 填空"
|
||||
},
|
||||
{
|
||||
"id": "calc-51",
|
||||
"title": "数100以内的数",
|
||||
"grade": "一年级",
|
||||
"difficulty": 1,
|
||||
"summary": "突破拐弯数,理解几十几由几个十和几个一组成",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n上节课我们学会了点数 1~10。这节课要把数数范围扩大到 100,重点突破拐弯数,并理解几十几的组成。\n\n## ② 知识精讲\n### 概念/方法 1:从1数到100与拐弯数\n从 1 数到 100,最难的是拐弯的地方:\n- 29 后面是 30(二九过了是三零)\n- 39 后面是 40,49 后面是 50\n- 99 后面是 100\n\n建议每天从 1 数到 100,坚持一周就能数熟。\n\n数数游戏:\n- 拍七:轮到 7 和 7 的倍数时拍手不说话\n- 跳数:2、4、6、8……(双数);1、3、5、7……(单数)\n\n**例1** 填数:38、( )、40、41、( )、( )\n【思路】按顺序一个一个往后数。\n【解答】39、42、43。\n【验算】连起来读一遍:38、39、40、41、42、43,顺不顺口。\n\n**例2** 填数:66、67、( )、( )、70、( )\n【解答】68、69、71。\n【提醒】69 后面是 70,拐弯处个位满十,十位加 1。\n\n**例3** 从72数到83,写出中间的数。\n【解答】73、74、75、76、77、78、79、80、81、82,共10个数。\n\n### 概念/方法 2:几十几的组成\n几个十和几个一组成几十几。例如 53 由 5 个十和 3 个一组成。\n\n**例4** 5个十和3个一组成几?7个十是多少?\n【解答】5个十和3个一组成53;7个十是70。\n【提醒】分清几个十和几个一:7个十是70,不是17。\n\n## ③ 常见错误与提醒\n- 拐弯数卡壳:29 后面说成\"二十十\"。记住个位满十就变成下一个整十数\n- 倒数时漏数:从 10 倒数回 1 也要一个一个倒着数\n- 混淆几个十和几个一:7个十是 70,7个一是 7\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"fill\",\"stem\":\"填数:38、( )、40、41,括号里填几?\",\"answers\":[\"39\"],\"explain\":\"按顺序数,38后面是39,39后面是40。\"},{\"type\":\"fill\",\"stem\":\"填数:66、67、( )、69、70,括号里填几?\",\"answers\":[\"68\"],\"explain\":\"67后面是68,68后面是69。\"},{\"type\":\"fill\",\"stem\":\"拐弯数:29后面是几?\",\"answers\":[\"30\"],\"explain\":\"二九过了是三零,个位满十就向十位进一,29后面是30。\"},{\"type\":\"fill\",\"stem\":\"拐弯数:49后面是几?\",\"answers\":[\"50\"],\"explain\":\"49的个位满十,十位加1,后面是50。\"},{\"type\":\"fill\",\"stem\":\"拐弯数:99后面是几?\",\"answers\":[\"100\"],\"explain\":\"99的个位、十位都满十,后面是100。\"},{\"type\":\"fill\",\"stem\":\"5个十和3个一组成( )。\",\"answers\":[\"53\"],\"explain\":\"几个十和几个一组成几十几:5个十是50,再加3个一,是53。\"},{\"type\":\"choice\",\"stem\":\"7个十是多少?\",\"options\":[\"7\",\"17\",\"70\"],\"answer\":2,\"explain\":\"7个十就是70。\"},{\"type\":\"choice\",\"stem\":\"从72数到83,中间(不含72和83)有几个数?\",\"options\":[\"9\",\"10\",\"11\"],\"answer\":1,\"explain\":\"中间的数是73、74、75、76、77、78、79、80、81、82,共10个。\"},{\"type\":\"choice\",\"stem\":\"100以内,数字5出现在几个数里?\",\"options\":[\"18个\",\"19个\",\"20个\"],\"answer\":1,\"explain\":\"个位是5的有5、15、25、35、45、55、65、75、85、95共10个;十位是5的有50~59共10个;其中55被数了两次,所以共10+10-1=19个。\"},{\"type\":\"judge\",\"stem\":\"跳数 2、4、6、8……数出的都是双数。\",\"answer\":0,\"explain\":\"对。2、4、6、8……是双数;1、3、5、7……是单数。\"}]}\n```\n\n## ⑤ 本课小结\n- 从1数到100,拐弯数(29→30、39→40、99→100)是关键\n- 几个十和几个一组成几十几\n- 双数:2、4、6、8……;单数:1、3、5、7……\n- 按顺序数数,正着数、倒着数都要熟练"
|
||||
},
|
||||
{
|
||||
"id": "calc-52",
|
||||
"title": "分类整理与正字计数",
|
||||
"grade": "一年级",
|
||||
"difficulty": 1,
|
||||
"summary": "按不同标准分类,用正字记录并统计数据",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n生活中经常要把杂乱的东西整理清楚。本课学习按不同标准分类,并学会用正字记录和统计数据。\n\n## ② 知识精讲\n### 概念/方法 1:按不同标准分类\n分类就是把事物按共同特征分组。注意:分类标准不同,结果也不同。要\"先想好按什么分,再动手分\"。\n\n**例1** 把红色方块、蓝色圆圈、红色三角、蓝色方块、红色圆圈分类。\n【思路】可以按颜色分,也可以按形状分。\n【解答】按颜色分:红色3个(红色方块、红色三角、红色圆圈),蓝色2个(蓝色圆圈、蓝色方块);按形状分:方块2个、圆圈2个、三角1个。\n【提醒】同一堆物品,按不同标准分,结果不同。\n\n**例2** 整理书包:书放一层,本子放一层,文具盒放一层。\n【解答】这就是分类在生活中的应用——把物品按类别分开摆放。\n\n### 概念/方法 2:正字计数与统计表\n一个正字有5画,一画代表1个。记录时一笔一笔地画正字,最后数有几个正字、几画,就知道总数。\n\n**例3** 统计一个路口5分钟内通过的车辆:小轿车记作正正,公交车记作正,摩托车记作正丅,各有多少辆?\n【思路】一个正字是5,正字数完再数零头笔画。\n【解答】小轿车:两个正字,5+5=10辆;公交车:一个正字,5辆;摩托车:一个正字加2画,5+2=7辆。\n【验算】一笔一笔地数,结果相同。\n\n## ③ 常见错误与提醒\n- 画正字时多画、漏画笔画:一画代表1个,要画一笔数一个\n- 只数正字的个数,忘记加上剩余的零头笔画\n- 分类标准不统一,分着分着换了标准\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"choice\",\"stem\":\"把红色方块、蓝色圆圈、红色三角、蓝色方块、红色圆圈按颜色分,红色的有几个?\",\"options\":[\"2个\",\"3个\",\"5个\"],\"answer\":1,\"explain\":\"红色的有:红色方块、红色三角、红色圆圈,共3个;蓝色的有2个。\"},{\"type\":\"choice\",\"stem\":\"把上面这些物品按形状分,可以分成几类?\",\"options\":[\"2类\",\"3类\",\"4类\"],\"answer\":1,\"explain\":\"按形状分成方块、圆圈、三角共3类:方块2个、圆圈2个、三角1个。\"},{\"type\":\"judge\",\"stem\":\"分类的标准不同,分类的结果也不同。\",\"answer\":0,\"explain\":\"对。同一堆物品,按颜色分和按形状分,结果不一样。\"},{\"type\":\"fill\",\"stem\":\"一个正字有( )画,用正字计数时一画代表1个。\",\"answers\":[\"5\"],\"explain\":\"正字共5画,所以一个正字代表5个。\"},{\"type\":\"fill\",\"stem\":\"用正字计数,两个正字代表( )个。\",\"answers\":[\"10\"],\"explain\":\"一个正字代表5,两个正字是5+5=10。\"},{\"type\":\"choice\",\"stem\":\"统计路口车辆,小轿车记作正正,小轿车有几辆?\",\"options\":[\"5辆\",\"10辆\",\"20辆\"],\"answer\":1,\"explain\":\"一个正字5辆,两个正字是10辆。\"},{\"type\":\"choice\",\"stem\":\"摩托车记作正丅(一个正字加2画),摩托车有几辆?\",\"options\":[\"6辆\",\"7辆\",\"8辆\"],\"answer\":1,\"explain\":\"一个正字是5,再加2画,5+2=7辆。\"},{\"type\":\"fill\",\"stem\":\"用正字记录:★★★★★★★★ 共有( )个。\",\"answers\":[\"8\"],\"explain\":\"8个★记作一个正字加3画,5+3=8。\"},{\"type\":\"fill\",\"stem\":\"用正字记录:◆◆◆◆◆◆ 共有( )个。\",\"answers\":[\"6\"],\"explain\":\"6个◆记作一个正字加1画,5+1=6。\"},{\"type\":\"judge\",\"stem\":\"整理书包时把书、本子、文具盒分层放置,就用到了分类的思想。\",\"answer\":0,\"explain\":\"对。把不同的物品按类别分层放好,这就是分类。\"}]}\n```\n\n## ⑤ 本课小结\n- 分类:把事物按共同特征分组,先想好标准再动手\n- 分类标准不同,结果不同\n- 正字计数:一个正字5画,一画代表1个\n- 统计数据可以整理成统计表,一目了然"
|
||||
},
|
||||
{
|
||||
"id": "calc-53",
|
||||
"title": "枚举法:有序列举不重不漏",
|
||||
"grade": "二~三年级",
|
||||
"difficulty": 1,
|
||||
"summary": "按顺序枚举所有可能性,做到不重复不遗漏",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n可能性不多的时候,可以一个一个列举出来。本课学习有序枚举:把所有可能性按顺序列举出来,不重复、不遗漏。\n\n## ② 知识精讲\n### 概念/方法 1:有序枚举\n枚举口诀:先分类后枚举,按顺序不重复。\n\n**例1** 用1、2、3能组成几个不同的两位数?\n【思路】按十位分类,固定十位再换十位,依次枚举。\n【解答】十位是1:12、13;十位是2:21、23;十位是3:31、32。共6个。\n【验算】十位有3种选择,个位各有2种选择,3×2=6,正好对上,说明没重没漏。\n【提醒】12 和 21 数字相同但位置不同,是两个不同的两位数,都要算。\n\n**例2** 用0、1、2能组成几个不同的两位数?\n【思路】注意0不能做十位!十位只能是1或2。\n【解答】十位是1:10、12;十位是2:20、21。共4个。\n【提醒】数字里有0时,要先排除0做首位的情况。\n\n### 概念/方法 2:列表法与简单搭配\n**例3** 小明有2件上衣(红、蓝)和3条裤子(黑、白、灰),一共有几种搭配方法?\n【思路】先固定上衣,再依次枚举裤子。\n【解答】红上衣配黑裤、白裤、灰裤,3种;蓝上衣配黑裤、白裤、灰裤,3种。共3+3=6种。\n【验算】2×3=6,结果一致。\n\n**例4** 从A、B、C三个地点中选两个去旅游,有几种选法?\n【解答】AB、AC、BC,共3种。选地点不分先后,AB和BA算同一种选法。\n\n## ③ 常见错误与提醒\n- 枚举没有顺序,想到哪写到哪,造成重复或遗漏\n- 含0的数字问题中,把0做首位的情况也算进去\n- 分不清顺序是否影响结果:组两位数时12和21不同,选地点时AB和BA相同\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"fill\",\"stem\":\"用1、2、3能组成( )个不同的两位数。\",\"answers\":[\"6\"],\"explain\":\"有序枚举:12、13、21、23、31、32,共6个。\"},{\"type\":\"fill\",\"stem\":\"用1、2、3组成的两位数中,最大的是( )。\",\"answers\":[\"32\"],\"explain\":\"枚举出的6个两位数是12、13、21、23、31、32,最大的是32。\"},{\"type\":\"fill\",\"stem\":\"小明有2件上衣(红、蓝)和3条裤子(黑、白、灰),一共有( )种搭配方法。\",\"answers\":[\"6\"],\"explain\":\"红上衣配黑、白、灰3种,蓝上衣配黑、白、灰3种,3+3=6种。\"},{\"type\":\"choice\",\"stem\":\"从A、B、C三个地点中选两个去旅游,有几种选法?\",\"options\":[\"2种\",\"3种\",\"6种\"],\"answer\":1,\"explain\":\"枚举:AB、AC、BC,共3种(选地点不分先后)。\"},{\"type\":\"fill\",\"stem\":\"用0、1、2能组成( )个不同的两位数。\",\"answers\":[\"4\"],\"explain\":\"0不能做十位:十位是1有10、12,十位是2有20、21,共4个。\"},{\"type\":\"choice\",\"stem\":\"下面哪个不是0、1、2组成的两位数?\",\"options\":[\"10\",\"02\",\"20\"],\"answer\":1,\"explain\":\"0不能做十位,02不是两位数。\"},{\"type\":\"judge\",\"stem\":\"有序枚举的关键是按一定顺序列举,做到不重复、不遗漏。\",\"answer\":0,\"explain\":\"对。枚举口诀:先分类后枚举,按顺序不重复。\"},{\"type\":\"judge\",\"stem\":\"12和21由相同的数字组成,所以是同一个两位数。\",\"answer\":1,\"explain\":\"错。数字的位置不同,数就不同,12和21是两个不同的两位数。\"},{\"type\":\"fill\",\"stem\":\"用1、2、3组成的两位数中,十位是1的有( )个。\",\"answers\":[\"2\"],\"explain\":\"十位是1的有12、13,共2个。\"},{\"type\":\"choice\",\"stem\":\"用1、2、3组成的两位数中,十位上的数比个位上的数大的有几个?\",\"options\":[\"2个\",\"3个\",\"4个\"],\"answer\":1,\"explain\":\"枚举6个两位数12、13、21、23、31、32,十位大于个位的有21、31、32,共3个。\"}]}\n```\n\n## ⑤ 本课小结\n- 有序枚举:按一定顺序列举所有可能性,不重复、不遗漏\n- 先分类后枚举:先固定一个位置,再换下一个\n- 数字中有0时,0不能做最高位\n- 列表法能把枚举结果整理得清楚整齐"
|
||||
},
|
||||
{
|
||||
"id": "calc-54",
|
||||
"title": "加法原理:分类用加法",
|
||||
"grade": "三~四年级",
|
||||
"difficulty": 2,
|
||||
"summary": "任务分类完成时,把各类方法数相加",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n枚举法要一个一个列出来,可能性多了就很麻烦。从这节课起学习\"不用数就能算\"的计数原理,先学加法原理。\n\n## ② 知识精讲\n### 概念/方法 1:加法原理\n完成一件事有 n 类方法,每类分别有 m₁、m₂……种,总方法数 = m₁+m₂+…。\n区分关键:分类用加法(\"要么……要么……\")。\n\n**例1** 从A地到B地可以坐火车、汽车或飞机,一共有几种走法?\n【思路】坐任何一种交通工具都能到达目的地,这是分类完成。\n【解答】3类方法,共3种走法。\n【提醒】\"分类\"的意思是:做其中任何一件,都算完成了这件事。\n\n**例2** 书架上有5本故事书、4本科技书、3本漫画书,借一本,有几种选法?\n【思路】任务是借\"一本\",随便拿哪一本都算完成,分三类。\n【解答】5+4+3=12种。\n【验算】三类分别数:5、4、3,加起来12。\n\n### 概念/方法 2:识别\"分类\"\n**例3** 班里有男生10人、女生12人,任选一名同学当旗手,有几种选法?\n【思路】选一名就够,男生、女生是两类。\n【解答】10+12=22种。\n【提醒】先问自己:任务要求做几件事?只做一件就完成,用加法。\n\n## ③ 常见错误与提醒\n- 把分类的任务当成乘法来算(乘法用于分步任务,下节课学)\n- 分类时出现重叠,同一件东西被算进两类\n- 漏数某一类的方法数\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"choice\",\"stem\":\"加法原理:完成一件事有若干类方法,总方法数等于?\",\"options\":[\"各类方法数相加\",\"各类方法数相乘\",\"最多那一类的方法数\"],\"answer\":0,\"explain\":\"完成一件事有n类方法,每类分别有m₁、m₂……种,总数=m₁+m₂+…,分类用加法。\"},{\"type\":\"fill\",\"stem\":\"从A地到B地可以坐火车、汽车或飞机,一共有( )种走法。\",\"answers\":[\"3\"],\"explain\":\"三类交通工具,任选一种都能到达,3种走法,用加法原理。\"},{\"type\":\"fill\",\"stem\":\"书架上有5本故事书、4本科技书、3本漫画书,借一本,有( )种选法。\",\"answers\":[\"12\"],\"explain\":\"借一本,随便拿哪本都算完成任务,分类相加:5+4+3=12。\"},{\"type\":\"choice\",\"stem\":\"小明有3个红球、2个蓝球,任取一个,有几种取法?\",\"options\":[\"5种\",\"6种\",\"1种\"],\"answer\":0,\"explain\":\"取一个球就完成,分类用加法:3+2=5种。\"},{\"type\":\"choice\",\"stem\":\"加法原理适用的任务,关键词是?\",\"options\":[\"分类\",\"分步\",\"排队\",\"搭配\"],\"answer\":0,\"explain\":\"分类用加法(要么……要么……);分步才用乘法(先……再……)。\"},{\"type\":\"judge\",\"stem\":\"加法原理用于需要分几个步骤才能完成的任务。\",\"answer\":1,\"explain\":\"错。分步完成的任务用乘法原理;分类完成的任务才用加法原理。\"},{\"type\":\"fill\",\"stem\":\"班里男生有10人、女生有12人,任选一名同学当旗手,有( )种选法。\",\"answers\":[\"22\"],\"explain\":\"任选一名,分类相加:10+12=22种。\"},{\"type\":\"fill\",\"stem\":\"文具店有6种铅笔、4种钢笔,买一支笔,有( )种买法。\",\"answers\":[\"10\"],\"explain\":\"买一支笔就完成,6+4=10种。\"},{\"type\":\"judge\",\"stem\":\"用加法原理时,总方法数等于各类方法数相乘。\",\"answer\":1,\"explain\":\"错。加法原理是各类方法数相加;相乘是乘法原理。\"},{\"type\":\"choice\",\"stem\":\"下面哪个任务用加法原理?\",\"options\":[\"选一件上衣和一条裤子\",\"从书架上借一本书\",\"三类书各借一本\"],\"answer\":1,\"explain\":\"借一本书,拿一本就完成,分类用加法。另外两个都要分两步(或三步)完成,用乘法。\"}]}\n```\n\n## ⑤ 本课小结\n- 加法原理:完成任务分 n 类,总方法数 = 各类方法数相加\n- 关键词:分类、\"要么……要么……\"、\"任取一个\"\n- 判断标准:做任何一件都算完成任务,就用加法\n- 注意各类之间不能重叠"
|
||||
},
|
||||
{
|
||||
"id": "calc-55",
|
||||
"title": "乘法原理:分步用乘法",
|
||||
"grade": "三~四年级",
|
||||
"difficulty": 2,
|
||||
"summary": "任务分步完成时,把各步方法数相乘",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n上节课学了分类用加法。本课学分步用乘法——乘法原理,并学会区分两个原理。\n\n## ② 知识精讲\n### 概念/方法 1:乘法原理\n完成一件事需要分成 n 个步骤,每步分别有 m₁、m₂……种方法,总方法数 = m₁×m₂×…。\n区分关键:分步用乘法(\"先……再……\")。\n\n**例1** 选一件上衣和一条裤子,上衣有3种、裤子有4种,有几种选法?\n【思路】这件事要分两步:先选上衣,再选裤子。\n【解答】3×4=12种。\n\n**例2** 从学校到电影院有3条路,从电影院到公园有2条路,从学校经电影院到公园有几种走法?\n【思路】分两步走:第一步学校→电影院,第二步电影院→公园。\n【解答】3×2=6种。\n【验算】把6种走法一一列举出来,正好6种,不多不少。\n\n**例3** 早餐店有4种主食(包子、馒头、油条、面包)和3种饮料(豆浆、牛奶、果汁),选一种主食和一种饮料,有几种搭配?\n【解答】4×3=12种。\n\n### 概念/方法 2:区分加法原理与乘法原理\n分类用加法(\"要么……要么……\"),分步用乘法(\"先……再……\")。\n\n**例4** 书架上有5本故事书、4本科技书、3本漫画书。\n- 借一本:拿一本就完成,分类用加法,5+4+3=12种;\n- 三类各借一本:要分三步才完成,分步用乘法,5×4×3=60种。\n【提醒】同样的书架,一字之差(\"一本\"与\"各一本\"),方法完全不同。审题先问:做几件事才算完成?\n\n## ③ 常见错误与提醒\n- 分类用了乘法、分步用了加法:先判断任务性质\n- 步骤数漏数:要所有步骤都做完才算完成任务\n- 混淆\"借一本\"与\"各借一本\"\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"choice\",\"stem\":\"乘法原理:完成一件事需要分成几个步骤,总方法数等于?\",\"options\":[\"各步方法数相乘\",\"各步方法数相加\",\"最后一步的方法数\"],\"answer\":0,\"explain\":\"完成一件事有n个步骤,每步分别有m₁、m₂……种,总数=m₁×m₂×…,分步用乘法。\"},{\"type\":\"fill\",\"stem\":\"从学校到电影院有3条路,从电影院到公园有2条路,从学校经电影院到公园有( )种走法。\",\"answers\":[\"6\"],\"explain\":\"分两步:先学校→电影院(3种),再电影院→公园(2种),3×2=6。\"},{\"type\":\"fill\",\"stem\":\"早餐店有4种主食和3种饮料,选一种主食和一种饮料,有( )种搭配。\",\"answers\":[\"12\"],\"explain\":\"先选主食4种,再选饮料3种,4×3=12。\"},{\"type\":\"fill\",\"stem\":\"书架上有5本故事书、4本科技书、3本漫画书,三类各借一本,有( )种借法。\",\"answers\":[\"60\"],\"explain\":\"分三步:故事书5种、科技书4种、漫画书3种,5×4×3=60。\"},{\"type\":\"choice\",\"stem\":\"上衣有3种、裤子有4种,选一件上衣和一条裤子,有几种选法?\",\"options\":[\"7种\",\"12种\",\"24种\"],\"answer\":1,\"explain\":\"先选上衣再选裤子,分步用乘法:3×4=12。\"},{\"type\":\"choice\",\"stem\":\"乘法原理适用的任务,关键词是?\",\"options\":[\"分类\",\"分步\",\"至少\",\"相同\"],\"answer\":1,\"explain\":\"分步用乘法(先……再……);分类才用加法。\"},{\"type\":\"judge\",\"stem\":\"从书架上借一本书这个任务,要用乘法原理解决。\",\"answer\":1,\"explain\":\"错。借一本,拿一本就完成,是分类任务,用加法原理。\"},{\"type\":\"fill\",\"stem\":\"从3件上衣中选一件、2条裤子中选一条、2顶帽子中选一顶搭配,有( )种搭法。\",\"answers\":[\"12\"],\"explain\":\"分三步:3×2×2=12。\"},{\"type\":\"judge\",\"stem\":\"区分加法原理和乘法原理,要看任务是分类完成还是分步完成。\",\"answer\":0,\"explain\":\"对。分类用加法(要么……要么……),分步用乘法(先……再……)。\"},{\"type\":\"fill\",\"stem\":\"一张桌子配一把椅子为一套,桌子有4种、椅子有3种,有( )种配法。\",\"answers\":[\"12\"],\"explain\":\"先选桌子再选椅子,4×3=12。\"}]}\n```\n\n## ⑤ 本课小结\n- 乘法原理:完成任务分 n 步,总方法数 = 各步方法数相乘\n- 关键词:分步、\"先……再……\"\n- 分类用加法,分步用乘法\n- 审题第一步:弄清做几件事才算完成任务"
|
||||
},
|
||||
{
|
||||
"id": "calc-56",
|
||||
"title": "排列初步:排列与阶乘",
|
||||
"grade": "四年级",
|
||||
"difficulty": 2,
|
||||
"summary": "认识与顺序有关的排列,学会阶乘的意义与计算",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n乘法原理告诉我们分步相乘。本课正式研究\"顺序\"问题:什么是排列,什么是阶乘。\n\n## ② 知识精讲\n### 概念/方法 1:排列的概念\n从 n 个不同元素中取出 m 个(m≤n),按一定顺序排成一列,叫做排列。\n关键特征:顺序有关,交换位置算不同结果。\n例:从A、B、C中取2个排列:AB、BA、AC、CA、BC、CB,共6种。\n\n生活例子区分:\n- 排队站位(AB和BA不同)→ 排列\n- 选人组队(AB和BA相同)→ 组合(后面的课学)\n\n计算排列用\"填空法\":画几个空位,每个空位有几种选择,依次乘起来。\n\n**例1** 甲、乙、丙3人排成一排照相,有几种排法?\n【思路】3个位置:□□□,逐个填空。\n【解答】第1个位置3种选择,第2个位置2种,第3个位置1种,共3×2×1=3!=6种。\n【验算】枚举:甲乙丙、甲丙乙、乙甲丙、乙丙甲、丙甲乙、丙乙甲,正好6种。\n\n**例2** 甲、乙、丙、丁4人坐一排4把椅子,甲必须坐最左边,有几种坐法?\n【思路】有约束的元素先安排:先把甲固定在最左边,再排其余3人。\n【解答】3×2×1=6种。\n【提醒】有条件限制时,先安排受限制的元素。\n\n### 概念/方法 2:阶乘\nn! = n×(n-1)×(n-2)×…×3×2×1,特别规定 0!=1。\n从小数字记起:1!=1,2!=2,3!=6,4!=24,5!=120,6!=720。\n全排列:n 个人全部排队 = n!,如 A(4,4)=4!=24。\n\n**例3** 计算 3! 和 5!。\n【解答】3!=3×2×1=6;5!=5×4×3×2×1=120。\n【提醒】阶乘要一直乘到1,不能半途停下。\n\n## ③ 常见错误与提醒\n- 排列与组合混淆:排列讲顺序,组合不讲顺序,审题时要判断\n- 阶乘计算漏乘:把5!算成5×4×3=60(少了×2×1)\n- 有约束条件(某人必须站某处)时没有先安排受约束的元素\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"choice\",\"stem\":\"排列的关键特征是?\",\"options\":[\"顺序有关\",\"顺序无关\",\"元素必须相同\",\"不能重复选取\"],\"answer\":0,\"explain\":\"排列讲顺序,交换位置算不同结果,如AB和BA是两种排法。\"},{\"type\":\"fill\",\"stem\":\"从A、B、C中取2个排成一列,共有( )种排法。\",\"answers\":[\"6\"],\"explain\":\"枚举:AB、BA、AC、CA、BC、CB,共6种。\"},{\"type\":\"fill\",\"stem\":\"3! = ( )\",\"answers\":[\"6\"],\"explain\":\"3!=3×2×1=6。\"},{\"type\":\"fill\",\"stem\":\"5! = ( )\",\"answers\":[\"120\"],\"explain\":\"5!=5×4×3×2×1=120,要一直乘到1。\"},{\"type\":\"fill\",\"stem\":\"按约定,0! = ( )\",\"answers\":[\"1\"],\"explain\":\"特别规定0!=1。\"},{\"type\":\"fill\",\"stem\":\"甲、乙、丙3人排成一排照相,有( )种排法。\",\"answers\":[\"6\"],\"explain\":\"填空法:第一个位置3种,第二个2种,第三个1种,3×2×1=6。\"},{\"type\":\"fill\",\"stem\":\"4个小朋友排成一排做游戏,有( )种排法。\",\"answers\":[\"24\"],\"explain\":\"4×3×2×1=4!=24。\"},{\"type\":\"choice\",\"stem\":\"甲、乙、丙、丁4人坐一排4把椅子,甲必须坐最左边,有几种坐法?\",\"options\":[\"6种\",\"12种\",\"24种\"],\"answer\":0,\"explain\":\"先固定甲在最左边,剩下3人在3个位置上全排列:3×2×1=6种。\"},{\"type\":\"judge\",\"stem\":\"AB和BA的元素相同,所以它们是同一种排法。\",\"answer\":1,\"explain\":\"错。排列讲顺序,AB和BA位置不同,是两种不同的排法。\"},{\"type\":\"judge\",\"stem\":\"5! = 5×4×3 = 60。\",\"answer\":1,\"explain\":\"错。5!=5×4×3×2×1=120,漏乘了2和1。\"}]}\n```\n\n## ⑤ 本课小结\n- 排列:从n个不同元素中取m个按顺序排成一列,顺序有关\n- 填空法:画空位,数每位的选法,依次相乘\n- n!=n×(n-1)×…×2×1,0!=1\n- n个元素全排列有n!种"
|
||||
},
|
||||
{
|
||||
"id": "calc-57",
|
||||
"title": "排列数公式A(n,m)",
|
||||
"grade": "四年级",
|
||||
"difficulty": 2,
|
||||
"summary": "用填空法与排列数公式计数,处理首位含0问题",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n上节课认识了排列和阶乘。本课学习排列数公式 A(n,m),用填空法解决各类排列计数问题。\n\n## ② 知识精讲\n### 概念/方法 1:排列数公式\n从 n 个不同元素中取 m 个的排列数,记为 A(n,m) 或 P(n,m):\nA(n,m) = n×(n-1)×(n-2)×…(共 m 个连续递减的数相乘)\n全排列:A(n,n) = n!,例如 A(4,4)=4!=24。\n\n**例1** 计算 A(5,3)。\n【思路】从5开始,连续写3个递减的数相乘。\n【解答】A(5,3)=5×4×3=60。\n【提醒】一共乘 m 个数,不多不少。\n\n### 概念/方法 2:填空法解应用题\n**例2** 从5名同学中选3人分别担任班长、学委、体委,有几种选法?\n【思路】三个职位不同(有顺序)→ 排列问题。画3个职位空位逐个填。\n【解答】班长:5人选1,5种;学委:剩4人,4种;体委:剩3人,3种。共5×4×3=A(5,3)=60种。\n\n**例3** 用1、2、3、4能组成多少个没有重复数字的三位数?\n【思路】百位、十位、个位三个空位,数字不能重复。\n【解答】百位4种,十位3种,个位2种,共4×3×2=A(4,3)=24个。\n\n**例4** 用0、1、2、3能组成多少个没有重复数字的三位数?\n【思路】0不能做百位!百位先定,其余位再放开。\n【解答】百位只能是1、2、3:3种;十位从剩下3个数(含0)中选:3种;个位:2种。共3×3×2=18个。\n【提醒】含0的数字问题,首位要单独处理。\n\n## ③ 常见错误与提醒\n- 排列与组合混淆:\"分别担任\"\"排队\"\"排列\"等词提示顺序有关,是排列\n- 含0的排列中忘记排除0做首位的情况\n- A(n,m) 相乘的个数数错:应是 m 个连续递减的数\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"fill\",\"stem\":\"计算:A(5,3) = ( )\",\"answers\":[\"60\"],\"explain\":\"A(5,3)=5×4×3=60,共3个连续递减的数相乘。\"},{\"type\":\"fill\",\"stem\":\"计算:A(6,2) = ( )\",\"answers\":[\"30\"],\"explain\":\"A(6,2)=6×5=30。\"},{\"type\":\"fill\",\"stem\":\"计算:A(5,4) = ( )\",\"answers\":[\"120\"],\"explain\":\"A(5,4)=5×4×3×2=120。\"},{\"type\":\"fill\",\"stem\":\"计算:6! = ( )\",\"answers\":[\"720\"],\"explain\":\"6!=6×5×4×3×2×1=720。\"},{\"type\":\"fill\",\"stem\":\"计算:7!/5! = ( )\",\"answers\":[\"42\"],\"explain\":\"7!/5!=(7×6×5!)/5!=7×6=42。\"},{\"type\":\"fill\",\"stem\":\"从5名同学中选3人分别担任班长、学委、体委,有( )种选法。\",\"answers\":[\"60\"],\"explain\":\"职位不同、顺序有关,是排列:5×4×3=A(5,3)=60。\"},{\"type\":\"fill\",\"stem\":\"用1、2、3、4能组成( )个没有重复数字的三位数。\",\"answers\":[\"24\"],\"explain\":\"百位4种、十位3种、个位2种,4×3×2=A(4,3)=24。\"},{\"type\":\"fill\",\"stem\":\"用0、1、2、3能组成( )个没有重复数字的三位数。\",\"answers\":[\"18\"],\"explain\":\"0不能做百位:百位3种(1、2、3),十位从剩下3个数里选有3种,个位2种,3×3×2=18。\"},{\"type\":\"fill\",\"stem\":\"从7名运动员中选3人分别参加100米、200米、400米比赛,有( )种选法。\",\"answers\":[\"210\"],\"explain\":\"项目不同、顺序有关:A(7,3)=7×6×5=210。\"},{\"type\":\"judge\",\"stem\":\"全排列 A(n,n) = n!。\",\"answer\":0,\"explain\":\"对。n个元素全部排列,n×(n-1)×…×1正好是n!。\"}]}\n```\n\n## ⑤ 本课小结\n- A(n,m)=n×(n-1)×…(共m个数相乘),A(n,n)=n!\n- 填空法:画出空位,逐位数选法,依次相乘\n- 职位不同、位置不同,都属于排列问题\n- 数字含0时,最高位要单独处理"
|
||||
},
|
||||
{
|
||||
"id": "calc-58",
|
||||
"title": "组合初步:组合与组合数",
|
||||
"grade": "四~五年级",
|
||||
"difficulty": 2,
|
||||
"summary": "认识顺序无关的组合,掌握组合数公式C(n,m)",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n排队站位讲顺序,选人组队不讲顺序。本课学习组合与组合数公式,并分清它和排列的区别。\n\n## ② 知识精讲\n### 概念/方法 1:组合的概念\n从 n 个不同元素中取出 m 个(m≤n)组成一组,叫做组合。\n关键特征:顺序无关,交换位置算同一种。\n例:从A、B、C中取2个组合:AB、AC、BC,共3种。\n\n用\"选人\"场景区分:\n- 从5个人中选2人打扫教室:选甲乙和选乙甲一样 → 组合\n- 从5个人中选正、副班长:甲正乙副 ≠ 甲副乙正 → 排列\n\n口诀:排列有序组合乱,排列除阶乘变组合。\n\n### 概念/方法 2:组合数公式\nC(n,m) = A(n,m) ÷ m!,也就是:\nC(n,m) = n×(n-1)×…×(n-m+1) ÷ m×(m-1)×…×1\n\n为什么要除以 m!? 因为排列把每一组重复算了 m! 次:\n- 选2人:每对被算了2!=2次 → C(n,2)=A(n,2)÷2\n- 选3人:每组被算了3!=6次 → C(n,3)=A(n,3)÷6\n\n**例1** 从5名同学中选2人参加朗诵比赛,有几种选法?\n【思路】选2人组队,不分先后 → 组合。\n【解答】C(5,2)=(5×4)/(2×1)=20÷2=10种。\n\n**例2** 计算 C(6,3)。\n【解答】C(6,3)=(6×5×4)/(3×2×1)=120÷6=20。\n【提醒】分子从6开始连乘3个数,分母是3!,都别漏。\n\n## ③ 常见错误与提醒\n- 排列组合判断错误:审题关键是看\"交换后结果是否不同\"\n- 组合计算时忘记除以m!,直接用排列数当组合数\n- 关键词提示:\"选取\"\"组队\"\"一组\" → 组合;\"排列\"\"排队\"\"分别担任\" → 排列\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"choice\",\"stem\":\"组合的关键特征是?\",\"options\":[\"顺序无关\",\"顺序有关\",\"必须站成一排\",\"元素要重复选取\"],\"answer\":0,\"explain\":\"组合不讲顺序,交换位置算同一种,如选甲乙和选乙甲一样。\"},{\"type\":\"fill\",\"stem\":\"从A、B、C中取2个组成一组,共有( )种取法。\",\"answers\":[\"3\"],\"explain\":\"枚举:AB、AC、BC,共3种。\"},{\"type\":\"fill\",\"stem\":\"计算:C(5,2) = ( )\",\"answers\":[\"10\"],\"explain\":\"C(5,2)=(5×4)/(2×1)=20/2=10。\"},{\"type\":\"fill\",\"stem\":\"计算:C(6,3) = ( )\",\"answers\":[\"20\"],\"explain\":\"C(6,3)=(6×5×4)/(3×2×1)=120/6=20。\"},{\"type\":\"fill\",\"stem\":\"计算:C(6,2) = ( )\",\"answers\":[\"15\"],\"explain\":\"C(6,2)=(6×5)/(2×1)=30/2=15。\"},{\"type\":\"choice\",\"stem\":\"根据 C(n,m)=C(n,n-m),C(7,2) 等于下面哪个?\",\"options\":[\"C(7,5)\",\"C(7,3)\",\"C(5,2)\"],\"answer\":0,\"explain\":\"选2个等于留下5个,所以C(7,2)=C(7,5)。\"},{\"type\":\"fill\",\"stem\":\"从5名同学中选2人参加朗诵比赛,有( )种选法。\",\"answers\":[\"10\"],\"explain\":\"选2人组队不分先后,是组合:C(5,2)=(5×4)/(2×1)=10。\"},{\"type\":\"fill\",\"stem\":\"从8本书中选3本借阅,有( )种选法。\",\"answers\":[\"56\"],\"explain\":\"选书不分顺序:C(8,3)=(8×7×6)/(3×2×1)=56。\"},{\"type\":\"fill\",\"stem\":\"从4男3女中选2男1女参加活动,有( )种选法。\",\"answers\":[\"18\"],\"explain\":\"分两类人选:选2男C(4,2)=6种,选1女C(3,1)=3种,再相乘:6×3=18。\"},{\"type\":\"judge\",\"stem\":\"C(5,2) 与 A(5,2) 的计算结果相同。\",\"answer\":1,\"explain\":\"错。A(5,2)=5×4=20,C(5,2)=20÷2=10,组合要在排列基础上除以m!。\"}]}\n```\n\n## ⑤ 本课小结\n- 组合:顺序无关,交换位置算同一种\n- C(n,m)=A(n,m)÷m!:排列数除以重复次数\n- 判断依据:交换后结果是否不同\n- C(n,m)=C(n,n-m);C(n,0)=1,C(n,1)=n,C(n,n)=1"
|
||||
},
|
||||
{
|
||||
"id": "calc-59",
|
||||
"title": "组合的应用:握手、线段与循环赛",
|
||||
"grade": "四~五年级",
|
||||
"difficulty": 3,
|
||||
"summary": "用组合数解决握手、连线段、单循环比赛问题",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n上节课学会了组合数公式。本课用组合数解决握手、连线段、单循环比赛等经典问题,并认识组合数的性质。\n\n## ② 知识精讲\n### 概念/方法 1:组合数的性质\n- C(n,m) = C(n,n-m):选 m 个 = 留下 n-m 个\n- C(n,0)=1,C(n,1)=n,C(n,n)=1\n\n### 概念/方法 2:\"每两个一次\"的经典模型\n握手、连线段、单循环比赛,都是\"从 n 个里每次取 2 个、不分顺序\",用 C(n,2)=n×(n-1)÷2。\n\n**例1** 平面上有6个点(任意3点不共线),可以连成几条线段?\n【思路】每2个点连一条线段,线段AB和BA是同一条 → 组合。\n【解答】C(6,2)=(6×5)/(2×1)=15条。\n\n**例2** 10个人进行乒乓球单打比赛,每两人赛一场,共需赛几场?\n【思路】每2人赛一场,不分先后 → 组合。\n【解答】C(10,2)=(10×9)/2=45场。\n\n**例3** 一个班有45人,每两人握一次手,共握手几次?\n【解答】C(45,2)=45×44÷2=990次。\n【提醒】45×44 把每次握手都数了两遍(甲握乙、乙握甲),所以要除以2。\n\n### 概念/方法 3:取3个的模型与分组去重\n**例4** 一条直线上有8个点,可以组成多少条线段?\n【解答】C(8,2)=8×7÷2=28条。\n延伸:若平面内有8个点、任意3点不共线,可组成多少个三角形?每3点定一个三角形,C(8,3)=56个。\n【提醒】源练习中说\"一条直线上8个点组成三角形\",但三点在一条直线上是围不成三角形的;三角形问题应按\"平面内8个点、任意3点不共线\"来理解。\n\n**例5**(分组去重)把6人平均分成2组。\n【解答】先选3人出来 C(6,3)=20,但两组没有名称、先后之分,每种分法被数了两次,要再除以2!,答案是20÷2=10种。\n【提醒】平均分成无区别的组,要除以组数的阶乘去掉重复。\n\n## ③ 常见错误与提醒\n- 排列组合判断错误:看交换后结果是否不同\n- 握手问题忘记除以2,把次数算成 n×(n-1)\n- 分组问题中重复计数:平均分组要再除以组数的阶乘\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"fill\",\"stem\":\"平面上有6个点(任意3点不共线),可以连成( )条线段。\",\"answers\":[\"15\"],\"explain\":\"每2个点连一条线段,无序:C(6,2)=(6×5)/(2×1)=15。\"},{\"type\":\"fill\",\"stem\":\"10个人进行乒乓球单打比赛,每两人赛一场,共需赛( )场。\",\"answers\":[\"45\"],\"explain\":\"每2人赛一场,无序:C(10,2)=(10×9)/2=45场。\"},{\"type\":\"fill\",\"stem\":\"一个班有45人,每两人握一次手,共握手( )次。\",\"answers\":[\"990\"],\"explain\":\"C(45,2)=45×44÷2=990次。\"},{\"type\":\"fill\",\"stem\":\"一条直线上有8个点,可以组成( )条线段。\",\"answers\":[\"28\"],\"explain\":\"每2个点确定一条线段:C(8,2)=8×7÷2=28。\"},{\"type\":\"choice\",\"stem\":\"平面内有8个点,任意3点不共线,可以组成多少个三角形?\",\"options\":[\"28个\",\"56个\",\"336个\"],\"answer\":1,\"explain\":\"每3个点组成一个三角形:C(8,3)=(8×7×6)/(3×2×1)=56个。\"},{\"type\":\"choice\",\"stem\":\"组合数的性质:C(n,m) 等于?\",\"options\":[\"C(n,n-m)\",\"C(m,n)\",\"n×m\"],\"answer\":0,\"explain\":\"选m个留下,等价于留下n-m个,所以C(n,m)=C(n,n-m)。\"},{\"type\":\"fill\",\"stem\":\"C(10,1) = ( )\",\"answers\":[\"10\"],\"explain\":\"性质C(n,1)=n:从10个里选1个,有10种。\"},{\"type\":\"fill\",\"stem\":\"C(9,0) = ( )\",\"answers\":[\"1\"],\"explain\":\"性质C(n,0)=1:一个都不选,只有1种情况。\"},{\"type\":\"judge\",\"stem\":\"把6人平均分成2组,方法数直接用 C(6,3)=20 就可以了。\",\"answer\":1,\"explain\":\"错。两组本身没有先后、名称之分,每种分法被数了两次,应再除以2!,即20÷2=10种。\"},{\"type\":\"judge\",\"stem\":\"线段AB和线段BA是同一条线段,所以连线段问题是组合问题。\",\"answer\":0,\"explain\":\"对。交换两个端点结果不变,无序,用组合数C(n,2)。\"}]}\n```\n\n## ⑤ 本课小结\n- C(n,m)=C(n,n-m);C(n,0)=1,C(n,1)=n,C(n,n)=1\n- 握手、连线段、单循环比赛都用 C(n,2)=n×(n-1)÷2\n- 每3个一组的问题用 C(n,3)\n- 平均分成无区别的组,要除以组数的阶乘去重"
|
||||
},
|
||||
{
|
||||
"id": "calc-60",
|
||||
"title": "树形图与图示法",
|
||||
"grade": "二~五年级",
|
||||
"difficulty": 1,
|
||||
"summary": "用树形图、列表法、连线法计数全部方案",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n可能性一多,枚举就容易乱。本课学习用树形图、列表法、连线法把所有方案清楚地表示出来再计数。\n\n## ② 知识精讲\n### 概念/方法 1:树形图的画法与计数\n- 从起点出发,把每一步的选择画成分支\n- 每个分支代表一种可能,沿着分支走到底就是一个完整方案\n- 画树形图要有序、不遗漏:按固定顺序排列第一层,记住\"先定后变\"\n\n计数方法:数清所有叶子节点(最末端的点)就是总方案数;也可以用乘法原理验证——每层分支数相乘 = 总方案数。\n\n**例1** 小明有2件上衣(红、蓝)和3条裤子(黑、白、灰),有几种搭配?\n【思路】第一层画上衣,每个分支下再画3条裤子。\n【解答】红上衣下3片叶子,蓝上衣下3片叶子,共6片叶子,6种搭配。\n【验算】2×3=6,乘法原理验证一致。\n\n**例2** 用1、2、3三张卡片组成三位数,能组成几个?\n【思路】百位→十位→个位逐层展开,先固定百位。\n【解答】百位是1:123、132;百位是2:213、231;百位是3:312、321。共6个。\n【提醒】若数字中含0,要排除首位为0的情况(如02不是两位数)。\n\n### 概念/方法 2:列表法与连线法\n列表法:用表格把所有可能情况一一列出,适合两个变量搭配的问题。口诀:横行一类,纵列一类,交叉就是搭配。\n连线法:把两类事物分别列出,用连线表示搭配,线的总数就是搭配方法数。\n\n**例3** 学校食堂有3种菜和2种汤,选一种菜和一种汤有几种搭配?\n【解答】横行写3种菜,纵列写2种汤,交叉格共3×2=6个,即6种搭配。\n\n## ③ 常见错误与提醒\n- 画树形图时分支顺序混乱,导致重复或遗漏(要按固定顺序排列)\n- 忘记数完所有叶子节点,只数了某几层\n- 含0的数字问题中没有排除首位为0的情况\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"fill\",\"stem\":\"2件上衣(红、蓝)和3条裤子(黑、白、灰),一共有( )种搭配。\",\"answers\":[\"6\"],\"explain\":\"树形图:每件上衣各配3条裤子,2个分支各长出3片叶子,共6种。\"},{\"type\":\"fill\",\"stem\":\"用1、2、3三张卡片组成三位数,能组成( )个。\",\"answers\":[\"6\"],\"explain\":\"123、132、213、231、312、321,共6个。\"},{\"type\":\"choice\",\"stem\":\"掷两枚硬币,可能出现的结果有几种?\",\"options\":[\"2种\",\"3种\",\"4种\"],\"answer\":2,\"explain\":\"画树形图:正正、正反、反正、反反,共4种。\"},{\"type\":\"fill\",\"stem\":\"学校食堂有3种菜和2种汤,选一种菜和一种汤,有( )种搭配。\",\"answers\":[\"6\"],\"explain\":\"3×2=6,用列表法可以一一列出全部6种。\"},{\"type\":\"fill\",\"stem\":\"从甲地到乙地有2条路,从乙地到丙地有3条路,从丙地到丁地有2条路,从甲地到丁地有( )种走法。\",\"answers\":[\"12\"],\"explain\":\"分三步:2×3×2=12,可画树形图逐层展开验证。\"},{\"type\":\"choice\",\"stem\":\"用树形图计数时,总方案数等于?\",\"options\":[\"叶子节点(最末端的点)的个数\",\"第一层分支的个数\",\"层数\"],\"answer\":0,\"explain\":\"沿分支走到底就是一个完整方案,数清所有叶子节点就是总方案数。\"},{\"type\":\"judge\",\"stem\":\"画树形图时要先定后变、按固定顺序展开,才能不重复、不遗漏。\",\"answer\":0,\"explain\":\"对。先固定第一个位置列出所有可能,再换第二个,依次类推。\"},{\"type\":\"judge\",\"stem\":\"列表法的口诀是:横行一类,纵列一类,交叉就是搭配。\",\"answer\":0,\"explain\":\"对。列表法适合两个变量搭配的问题。\"},{\"type\":\"choice\",\"stem\":\"树形图第一层有3种选择,每种下面第二层各有2种选择,叶子节点共有几个?\",\"options\":[\"5个\",\"6个\",\"8个\"],\"answer\":1,\"explain\":\"每层分支数相乘:3×2=6个叶子。\"},{\"type\":\"choice\",\"stem\":\"用连线法把4种笔和3种本一一搭配,共要连几条线?\",\"options\":[\"7条\",\"12条\",\"24条\"],\"answer\":1,\"explain\":\"线的总数就是搭配方法数:4×3=12条。\"}]}\n```\n\n## ⑤ 本课小结\n- 树形图:从起点逐层画分支,先定后变,不重不漏\n- 数叶子节点 = 总方案数,可用乘法原理验证\n- 列表法:横行一类、纵列一类,交叉就是搭配\n- 连线法:线的总数就是搭配方法数"
|
||||
},
|
||||
{
|
||||
"id": "calc-61",
|
||||
"title": "包含与排除(一):两集合问题",
|
||||
"grade": "三~四年级",
|
||||
"difficulty": 2,
|
||||
"summary": "掌握两集合容斥公式与韦恩图",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n两类人有重叠时,总数不能简单相加。本课学习两集合的容斥原理(包含与排除)和韦恩图。\n\n## ② 知识精讲\n### 概念/方法 1:两集合容斥\n公式:|A∪B| = |A| + |B| - |A∩B|\n含义:两个集合的总数 = A的个数 + B的个数 - 两者都有的个数。\n原因:A∩B 被重复算了一次,要减去。\n口诀:先加后减,不重不漏。\n\n**例1** 全班40人,喜欢数学的有25人,喜欢语文的有22人,两科都喜欢的有几人?(每人至少喜欢一科)\n【思路】设都喜欢的人数为x,由容斥原理列式。\n【解答】25+22-x=40,47-x=40,x=7。两科都喜欢的有7人。\n【验算】只喜欢数学25-7=18人,只喜欢语文22-7=15人,18+15+7=40,正好全班人数。\n\n**例2** 某校100名学生中,订阅《数学报》的有65人,订阅《语文报》的有58人,两份都没订的有12人。两份都订的有几人?\n【思路】先求至少订一份的人数,再用容斥。\n【解答】至少订一份:100-12=88人;两份都订:65+58-88=35人。\n\n### 概念/方法 2:韦恩图与常用推论\n韦恩图(文氏图):用两个重叠的圆表示集合,重叠部分表示两个集合都有的元素。画图时先填重叠部分,再填各自独有的部分。\n常用推论:\n- 只属于A的 = |A| - |A∩B|\n- 至少属于一个的 = |A∪B|\n- 两个都不属于的 = 总数 - |A∪B|\n\n**例3** 1~100中,能被3整除或能被5整除的数有几个?\n【思路】能被15整除的数被两类重复计算,要排除。\n【解答】能被3整除:100÷3=33(取整),33个;能被5整除:100÷5=20,20个;能被15整除:100÷15=6,6个。共33+20-6=47个。\n【提醒】求1~n中m的倍数的个数,用n÷m取整。\n\n## ③ 常见错误与提醒\n- 忘记减去重叠部分:直接把A+B当成总数\n- \"至少属于一个\"和\"总数\"混淆:有人两个都不属于时,要用总数先减去这部分\n- 求\"只属于A\"时忘记减去重叠部分\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"choice\",\"stem\":\"两集合容斥公式 |A∪B| = ?\",\"options\":[\"|A|+|B|\",\"|A|+|B|-|A∩B|\",\"|A|×|B|\"],\"answer\":1,\"explain\":\"总数=A的个数+B的个数-两者都有的个数,重叠部分被算了两次,要减去一次。\"},{\"type\":\"fill\",\"stem\":\"全班40人,喜欢数学的有25人,喜欢语文的有22人,每人至少喜欢一科,两科都喜欢的有( )人。\",\"answers\":[\"7\"],\"explain\":\"设都喜欢的有x人:25+22-x=40,解得x=7。\"},{\"type\":\"fill\",\"stem\":\"全班45人,会游泳的有28人,会骑车的有32人,每人至少会一样,两样都会的有( )人。\",\"answers\":[\"15\"],\"explain\":\"28+32-45=15人。\"},{\"type\":\"fill\",\"stem\":\"某兴趣小组有30人,参加绘画的有18人,参加书法的有15人,每人至少参加一项,只参加绘画的有( )人。\",\"answers\":[\"15\"],\"explain\":\"两项都参加:18+15-30=3人;只参加绘画:18-3=15人。\"},{\"type\":\"fill\",\"stem\":\"同上题,只参加书法的有( )人。\",\"answers\":[\"12\"],\"explain\":\"只参加书法:15-3=12人。\"},{\"type\":\"fill\",\"stem\":\"1~100中,能被3整除或能被5整除的数有( )个。\",\"answers\":[\"47\"],\"explain\":\"能被3整除33个,能被5整除20个,能被15整除6个:33+20-6=47。\"},{\"type\":\"fill\",\"stem\":\"1~50中,是2的倍数或是3的倍数的数有( )个。\",\"answers\":[\"33\"],\"explain\":\"2的倍数50÷2=25个,3的倍数50÷3取整=16个,6的倍数50÷6取整=8个:25+16-8=33。\"},{\"type\":\"fill\",\"stem\":\"某校100名学生中,订阅数学报的有65人,订阅语文报的有58人,两份都没订的有12人,两份都订的有( )人。\",\"answers\":[\"35\"],\"explain\":\"至少订一份:100-12=88;都订:65+58-88=35人。\"},{\"type\":\"judge\",\"stem\":\"求两组的总数时,直接把两组人数相加就行,不用考虑重叠。\",\"answer\":1,\"explain\":\"错。两组有重叠时,重叠的人被数了两次,必须减去一次。\"},{\"type\":\"choice\",\"stem\":\"韦恩图中两个圆重叠的部分表示什么?\",\"options\":[\"只属于A的元素\",\"既属于A又属于B的元素\",\"两个都不属于的元素\"],\"answer\":1,\"explain\":\"重叠部分表示两个集合都有的元素,画图时先填重叠部分,再填各自独有的部分。\"}]}\n```\n\n## ⑤ 本课小结\n- |A∪B|=|A|+|B|-|A∩B|:重叠部分被数两次,减去一次\n- 韦恩图:先填重叠部分,再填独有部分\n- 只属于A = |A|-|A∩B|;都不属于 = 总数-|A∪B|\n- 口诀:先加后减,不重不漏"
|
||||
},
|
||||
{
|
||||
"id": "calc-62",
|
||||
"title": "包含与排除(二):三集合问题",
|
||||
"grade": "三~四年级",
|
||||
"difficulty": 3,
|
||||
"summary": "掌握三集合容斥的加减加节奏",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n上节课学了两个集合的容斥。本课学习三个集合互相重叠时,怎样计数才能不重不漏。\n\n## ② 知识精讲\n### 概念/方法 1:三集合容斥公式\n|A∪B∪C| = |A|+|B|+|C| - |A∩B| - |A∩C| - |B∩C| + |A∩B∩C|\n含义:先加单个集合 → 再减两两重叠(多算了一次)→ 最后加三者重叠(被减过头了)。\n节奏记忆:加、减、加。\n\n**例1** 某班50人,参加数学竞赛的有28人、语文竞赛23人、英语竞赛18人;同时参加数学和语文的10人、数学和英语的8人、语文和英语的6人;三科都参加的3人。三科都没参加的有几人?\n【思路】先用容斥求至少参加一科的人数,再用总数减。\n【解答】至少参加一科 = 28+23+18-10-8-6+3 = 48人;三科都没参加 = 50-48 = 2人。\n【验算】用韦恩图逐块填:三科都参加3;只数学+语文10-3=7;只数学+英语8-3=5;只语文+英语6-3=3;只数学28-7-5-3=13;只语文23-7-3-3=10;只英语18-5-3-3=7。合计3+7+5+3+13+10+7=48,正确。\n【提醒】求\"只参加数学\"的:从数学总数里减去只数学+语文、只数学+英语和三科都参加的,即28-(10-3)-(8-3)-3=13。\n\n**例2** 全班50人,喜欢篮球的30人、足球的25人、乒乓球的20人;同时喜欢篮球和足球的12人、篮球和乒乓球的8人、足球和乒乓球的7人;三种都喜欢的4人。至少喜欢一种运动的有几人?\n【解答】30+25+20-12-8-7+4 = 75-27+4 = 52人。\n【提醒】本练习重点在公式流程;52超过了总人数50,实际出题时数据应当自洽,练习时以掌握方法为主。\n\n## ③ 常见错误与提醒\n- 三集合容斥中最后一项忘记加回(+A∩B∩C)\n- 忘记减去两两重叠部分\n- \"至少属于一个\"和\"总数\"混淆:题目问\"几人都不属于\"时,要用总数减去并集\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"choice\",\"stem\":\"三集合容斥公式是哪个?\",\"options\":[\"|A|+|B|+|C|\",\"|A|+|B|+|C|-|A∩B|-|A∩C|-|B∩C|\",\"|A|+|B|+|C|-|A∩B|-|A∩C|-|B∩C|+|A∩B∩C|\"],\"answer\":2,\"explain\":\"先加单个集合,再减两两重叠,最后加回三者重叠。\"},{\"type\":\"fill\",\"stem\":\"某班50人,参加数学竞赛28人、语文竞赛23人、英语竞赛18人;同时参加数学和语文10人、数学和英语8人、语文和英语6人;三科都参加3人。至少参加一科的有( )人。\",\"answers\":[\"48\"],\"explain\":\"28+23+18-10-8-6+3=48人。\"},{\"type\":\"fill\",\"stem\":\"接上题,三科都没参加的有( )人。\",\"answers\":[\"2\"],\"explain\":\"50-48=2人。\"},{\"type\":\"fill\",\"stem\":\"全班50人,喜欢篮球30人、足球25人、乒乓球20人;同时喜欢篮球足球12人、篮球乒乓8人、足球乒乓7人;三种都喜欢4人。至少喜欢一种运动的有( )人。\",\"answers\":[\"52\"],\"explain\":\"30+25+20-12-8-7+4=75-27+4=52人。\"},{\"type\":\"choice\",\"stem\":\"三集合容斥最后为什么要加回 |A∩B∩C|?\",\"options\":[\"它一开始没被计算\",\"减两两重叠时把它减过头了\",\"为了让结果变大\"],\"answer\":1,\"explain\":\"三者重叠的部分在减三个两两重叠时被多减了,所以要加回一次。\"},{\"type\":\"judge\",\"stem\":\"三集合容斥的节奏是:加、减、加。\",\"answer\":0,\"explain\":\"对。先加单个集合,再减两两重叠,最后加三者重叠。\"},{\"type\":\"fill\",\"stem\":\"运动会中,班级参加跑步15人、跳远10人、接力8人;同时参加跑步跳远4人、跑步接力3人、跳远接力2人;三项都参加1人。至少参加一项的有( )人。\",\"answers\":[\"25\"],\"explain\":\"15+10+8-4-3-2+1=25人。\"},{\"type\":\"judge\",\"stem\":\"计算三组的总数时,把三组人数直接相加即可。\",\"answer\":1,\"explain\":\"错。要按容斥公式:加单个、减两两重叠、加三者重叠,否则会重复计数。\"},{\"type\":\"fill\",\"stem\":\"按本课题1竞赛班的条件,只参加数学竞赛的有( )人。\",\"answers\":[\"13\"],\"explain\":\"只参加数学=28-(10-3)-(8-3)-3=28-7-5-3=13人。\"},{\"type\":\"choice\",\"stem\":\"求两个都不属于的人数,应该怎么做?\",\"options\":[\"直接加上并集\",\"用总数减去并集\",\"用两两重叠去减\"],\"answer\":1,\"explain\":\"两个都不属于 = 总数 - |A∪B|(两集合时);三集合同理,先求并集再用总数减。\"}]}\n```\n\n## ⑤ 本课小结\n- 三集合容斥:加单个、减两两重叠、加三者重叠\n- 三者重叠被减过头,最后必须加回\n- 求\"都不属于\":总数减去并集\n- 可用韦恩图逐块填写来验证结果"
|
||||
},
|
||||
{
|
||||
"id": "calc-63",
|
||||
"title": "统计图表初步",
|
||||
"grade": "四~六年级",
|
||||
"difficulty": 1,
|
||||
"summary": "认识条形、折线、扇形统计图并按需选图",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n数据会说话,图表让数据更直观。本课认识条形、折线、扇形三种统计图,学会根据需要选对图。\n\n## ② 知识精讲\n### 概念/方法 1:三种统计图\n**条形统计图**\n- 用长条的高低(或长短)表示数据的大小\n- 特点:能直观看出各项数据的多少,便于比较\n- 分类:单式条形统计图、复式条形统计图(比较两组数据)\n- 要素:标题、横轴(类别)、纵轴(数量)、单位、直条\n\n**折线统计图**\n- 用折线的起伏表示数据的变化趋势\n- 特点:不仅能看出数据的多少,还能看出变化趋势(上升/下降/平稳)\n- 适用场景:随时间变化的数据(气温变化、成绩变化等)\n- 要素:标题、横轴(时间)、纵轴(数量)、折线、数据点\n\n**扇形统计图**\n- 用扇形面积占圆面积的比例表示各部分占总数的百分比\n- 特点:能直观看出各部分与总数的关系\n- 适用场景:表示比例、占比(班级男女比例、支出构成等)\n- 所有扇形百分比之和 = 100%\n\n记忆:条形图看\"多少\",折线图看\"变化\",扇形图看\"占比\"。\n\n**例1** 根据信息选择合适的统计图:\n(1)学校各年级人数 → 条形统计图(比较各年级多少)\n(2)小明一周体温变化 → 折线统计图(看变化趋势)\n(3)家庭月支出各项占比 → 扇形统计图(看各部分占比)\n\n### 概念/方法 2:读图与百分比计算\n读图训练:拿到统计图先问三个问题——①横轴表示什么?②纵轴表示什么?③数据说明了什么?\n\n**例2** 某班40人最喜欢的运动项目统计:篮球12人、足球10人、乒乓球8人、游泳6人、其他4人,各项占全班百分之几?\n【思路】各项百分比 = 该项人数 ÷ 总人数。\n【解答】篮球12÷40=30%;足球10÷40=25%;乒乓球8÷40=20%;游泳6÷40=15%;其他4÷40=10%。\n【验算】30%+25%+20%+15%+10%=100%,正好一个整圆。\n\n## ③ 常见错误与提醒\n- 选错统计图类型:要看清数据是\"比较多少\"还是\"看变化\"还是\"看占比\"\n- 读折线统计图时只看数据点,不看趋势\n- 扇形图各项百分比之和不等于100%,说明计算有误\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"choice\",\"stem\":\"条形统计图最大的优点是?\",\"options\":[\"能看出变化趋势\",\"能直观看出各项数据的多少,便于比较\",\"能看出各部分与总数的关系\"],\"answer\":1,\"explain\":\"条形图用长条的高低表示数据大小,一眼看出多少、便于比较。\"},{\"type\":\"choice\",\"stem\":\"记录小明一周的体温变化,选哪种统计图合适?\",\"options\":[\"条形统计图\",\"折线统计图\",\"扇形统计图\"],\"answer\":1,\"explain\":\"体温随时间变化,要看变化趋势,选折线统计图。\"},{\"type\":\"choice\",\"stem\":\"统计学校各年级的人数,选哪种统计图合适?\",\"options\":[\"条形统计图\",\"折线统计图\",\"扇形统计图\"],\"answer\":0,\"explain\":\"比较各年级人数多少,选条形统计图。\"},{\"type\":\"choice\",\"stem\":\"表示家庭月支出各项的占比,选哪种统计图合适?\",\"options\":[\"条形统计图\",\"折线统计图\",\"扇形统计图\"],\"answer\":2,\"explain\":\"看各部分占总数的比例,选扇形统计图。\"},{\"type\":\"judge\",\"stem\":\"扇形统计图中,所有扇形的百分比之和等于100%。\",\"answer\":0,\"explain\":\"对。各部分占比合起来正好是整个圆,即100%。\"},{\"type\":\"fill\",\"stem\":\"某班40人最喜欢的运动:篮球12人。最喜欢篮球的占全班的( )%。\",\"answers\":[\"30\"],\"explain\":\"12÷40=0.3=30%。\"},{\"type\":\"fill\",\"stem\":\"同上,足球10人,占全班的( )%。\",\"answers\":[\"25\"],\"explain\":\"10÷40=25%。\"},{\"type\":\"judge\",\"stem\":\"折线统计图只能看出数据的多少,看不出变化趋势。\",\"answer\":1,\"explain\":\"错。折线图用折线的起伏表示变化趋势,既能看出多少,又能看出上升、下降、平稳。\"},{\"type\":\"choice\",\"stem\":\"下面哪个不是条形统计图的要素?\",\"options\":[\"标题\",\"横轴\",\"页码\",\"纵轴\"],\"answer\":2,\"explain\":\"条形图的要素:标题、横轴(类别)、纵轴(数量)、单位、直条,没有页码。\"},{\"type\":\"fill\",\"stem\":\"某班40人,最喜欢乒乓球的有8人,占全班的( )%。\",\"answers\":[\"20\"],\"explain\":\"8÷40=20%。\"}]}\n```\n\n## ⑤ 本课小结\n- 条形统计图:看多少、便于比较\n- 折线统计图:看随时间的变化趋势\n- 扇形统计图:看各部分占总数的百分比,总和为100%\n- 读图先问:横轴是什么、纵轴是什么、数据说明了什么"
|
||||
},
|
||||
{
|
||||
"id": "calc-64",
|
||||
"title": "平均数、中位数、众数",
|
||||
"grade": "五~六年级",
|
||||
"difficulty": 2,
|
||||
"summary": "会求一组数据的平均数、中位数、众数",
|
||||
"md": "> ⏱ 本课约 40 分钟:热身 5' → 精讲与例题 20' → 即时练习 15'\n\n## ① 回顾与目标\n同一组数据,可以从不同角度描述它。本课学习三个统计量:平均数、中位数、众数。\n\n## ② 知识精讲\n### 概念/方法 1:平均数\n平均数 = 总和 ÷ 个数,表示一组数据的平均水平。\n理解\"移多补少\":把多的移给少的,最后一样多,那个数就是平均数。平均数不一定等于其中任何一个数。\n\n### 概念/方法 2:中位数与众数\n- 中位数:把数据从小到大排列后,最中间的数;偶数个数据时,取中间两个数的平均值\n- 众数:出现次数最多的数\n\n**例1** 某同学5次数学考试成绩:80、85、90、88、92,求平均数、中位数、众数。\n【思路】平均数用总和÷5;中位数先排序再取中间;众数看有没有重复。\n【解答】\n(1)平均成绩=(80+85+90+88+92)÷5=435÷5=87分;\n(2)从小到大排列:80、85、88、90、92,中位数是第3个,88分;\n(3)没有重复的数,所以没有众数。\n【提醒】求中位数必须先排序!\n\n**例2** 数据 3、5、7、5、8、5、9、6,求平均数、中位数、众数。\n【解答】平均数=(3+5+7+5+8+5+9+6)÷8=48÷8=6;排序后3、5、5、5、6、7、8、9,共8个(偶数个),中位数=(5+6)÷2=5.5;5出现3次最多,众数=5。\n\n**例3** 某水果店一周的苹果销售量(千克):周一25、周二30、周三28、周四35、周五40、周六50、周日45。\n【思路】画折线统计图:横轴是星期,纵轴是销售量;再求平均每天销售量。\n【解答】一周共销售25+30+28+35+40+50+45=253千克;平均每天销售253÷7=36又1/7≈36.1千克。变化趋势:总体上升(周三略有回落),周末销量较高。\n\n## ③ 常见错误与提醒\n- 求平均数时个数数错:总和÷个数,分母是数据的个数而非最大数\n- 求中位数时忘记先排序;偶数个数据时忘记取中间两个的平均值\n- 读折线统计图时只看数据点,不看变化趋势\n\n## ④ 即时练习\n```quiz\n{\"questions\":[{\"type\":\"fill\",\"stem\":\"数据 80、85、90、88、92 的平均数是( )。\",\"answers\":[\"87\"],\"explain\":\"(80+85+90+88+92)÷5=435÷5=87。\"},{\"type\":\"fill\",\"stem\":\"数据 80、85、90、88、92 的中位数是( )。\",\"answers\":[\"88\"],\"explain\":\"先排序:80、85、88、90、92,最中间第3个是88。\"},{\"type\":\"choice\",\"stem\":\"数据 80、85、90、88、92 的众数是?\",\"options\":[\"88\",\"90\",\"没有众数\"],\"answer\":2,\"explain\":\"没有重复出现的数,所以没有众数。\"},{\"type\":\"fill\",\"stem\":\"数据 3、5、7、5、8、5、9、6 的平均数是( )。\",\"answers\":[\"6\"],\"explain\":\"总和3+5+7+5+8+5+9+6=48,48÷8=6。\"},{\"type\":\"fill\",\"stem\":\"数据 3、5、7、5、8、5、9、6 的中位数是( )。\",\"answers\":[\"5.5\"],\"explain\":\"排序后3、5、5、5、6、7、8、9,共8个(偶数),取中间两个5和6的平均值:(5+6)÷2=5.5。\"},{\"type\":\"fill\",\"stem\":\"数据 3、5、7、5、8、5、9、6 的众数是( )。\",\"answers\":[\"5\"],\"explain\":\"5出现了3次,次数最多,众数是5。\"},{\"type\":\"fill\",\"stem\":\"水果店一周苹果销售量(千克):25、30、28、35、40、50、45,这一周共销售( )千克。\",\"answers\":[\"253\"],\"explain\":\"总销售=25+30+28+35+40+50+45=253千克;由此平均每天销售253÷7=36又1/7≈36.1千克。\"},{\"type\":\"choice\",\"stem\":\"求中位数的正确步骤是?\",\"options\":[\"直接取最中间的那个数\",\"先从小到大排序,再取中间值\",\"找出现次数最多的数\"],\"answer\":1,\"explain\":\"必须先排序再取中间;偶数个数据时取中间两个的平均值。\"},{\"type\":\"judge\",\"stem\":\"平均数一定等于这组数据中的某一个数。\",\"answer\":1,\"explain\":\"错。平均数不一定等于其中任何一个数,它反映的是平均水平(移多补少的结果)。\"},{\"type\":\"judge\",\"stem\":\"求平均数时,分母是数据的个数,而不是最大的数。\",\"answer\":0,\"explain\":\"对。平均数=总和÷个数,个数数错结果就错。\"}]}\n```\n\n## ⑤ 本课小结\n- 平均数=总和÷个数,反映平均水平,不一定等于某个数据\n- 中位数:先排序,再取中间(偶数个取中间两个的平均)\n- 众数:出现次数最多的数,可能没有\n- 三个统计量从不同角度描述同一组数据"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -248,3 +248,101 @@ export const grammarProgress = mysqlTable(
|
||||
},
|
||||
(t) => [uniqueIndex("uq_grammar_child_point").on(t.childUserId, t.pointId)],
|
||||
);
|
||||
|
||||
// ===== 数学思维知识点(内容存库,结构同英语语法)=====
|
||||
export const mathPoints = mysqlTable(
|
||||
"math_point",
|
||||
{
|
||||
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
||||
pointId: varchar("point_id", { length: 191 }).notNull(), // 如 calc-01
|
||||
module: varchar("module", { length: 20 }).notNull(), // calc/apply/geo/num/travel/eq/combo
|
||||
moduleName: varchar("module_name", { length: 50 }).notNull(), // 计算与计数 等
|
||||
title: varchar("title", { length: 200 }).notNull(),
|
||||
grade: varchar("grade", { length: 30 }), // 适用学段,如 三~四年级
|
||||
difficulty: int("difficulty").notNull().default(1),
|
||||
summary: text("summary"),
|
||||
md: text("md").notNull(), // 课文 markdown(含 quiz 块)
|
||||
seq: int("seq").notNull(), // 展示顺序
|
||||
updatedAt: datetime("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
},
|
||||
(t) => [uniqueIndex("uq_math_point_id").on(t.pointId)],
|
||||
);
|
||||
|
||||
// ===== 数学思维学习进度(按知识点记录,达标即点亮)=====
|
||||
export const mathProgress = mysqlTable(
|
||||
"math_progress",
|
||||
{
|
||||
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
||||
childUserId: bigint("child_user_id", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
pointId: varchar("point_id", { length: 191 }).notNull(),
|
||||
score: int("score"), // 测验得分 0-100
|
||||
passed: boolean("passed").notNull().default(false),
|
||||
completedAt: datetime("completed_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
},
|
||||
(t) => [uniqueIndex("uq_math_child_point").on(t.childUserId, t.pointId)],
|
||||
);
|
||||
|
||||
// ===== 数学每周能力检测记录 =====
|
||||
export const mathWeeklyTest = mysqlTable(
|
||||
"math_weekly_test",
|
||||
{
|
||||
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
||||
childUserId: bigint("child_user_id", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
weekKey: varchar("week_key", { length: 12 }).notNull(), // 2026-W32
|
||||
score: int("score"),
|
||||
correct: int("correct"),
|
||||
total: int("total"),
|
||||
passed: boolean("passed").notNull().default(false),
|
||||
questions: text("questions"), // 组卷 JSON,供回看
|
||||
takenAt: datetime("taken_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
},
|
||||
(t) => [uniqueIndex("uq_math_weekly_child_week").on(t.childUserId, t.weekKey)],
|
||||
);
|
||||
|
||||
// ===== 考试试卷(内容存库,题目为 quiz JSON)=====
|
||||
export const examPapers = mysqlTable(
|
||||
"exam_paper",
|
||||
{
|
||||
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
||||
paperId: varchar("paper_id", { length: 191 }).notNull(), // 如 g7s1-m09-1
|
||||
subject: varchar("subject", { length: 20 }).notNull().default("math"), // 预留多学科
|
||||
grade: varchar("grade", { length: 20 }).notNull(), // g7
|
||||
semester: varchar("semester", { length: 10 }).notNull(), // s1 上 / s2 下
|
||||
month: int("month").notNull(), // 1-12,按月度分类
|
||||
examType: varchar("exam_type", { length: 20 }).notNull(), // monthly/unit/midterm/final
|
||||
title: varchar("title", { length: 300 }).notNull(),
|
||||
region: varchar("region", { length: 60 }), // 地区,如 湖南长沙
|
||||
year: int("year"), // 试卷年份
|
||||
duration: int("duration"), // 考试时长(分钟)
|
||||
fullScore: int("full_score"), // 卷面满分
|
||||
sourceUrl: varchar("source_url", { length: 500 }), // 参考来源链接
|
||||
summary: text("summary"), // 考查范围说明
|
||||
questions: text("questions").notNull(), // quiz JSON 数组
|
||||
seq: int("seq").notNull(), // 展示顺序
|
||||
createdAt: datetime("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
},
|
||||
(t) => [uniqueIndex("uq_exam_paper_id").on(t.paperId), index("idx_exam_paper_scope").on(t.subject, t.grade, t.semester, t.month)],
|
||||
);
|
||||
|
||||
// ===== 考试作答记录(同一孩子同一卷取最近一次)=====
|
||||
export const examRecords = mysqlTable(
|
||||
"exam_record",
|
||||
{
|
||||
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
||||
childUserId: bigint("child_user_id", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
paperId: varchar("paper_id", { length: 191 }).notNull(),
|
||||
score: int("score"),
|
||||
correct: int("correct"),
|
||||
total: int("total"),
|
||||
passed: boolean("passed").notNull().default(false),
|
||||
answers: text("answers"), // 作答 JSON,供回看
|
||||
takenAt: datetime("taken_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
},
|
||||
(t) => [uniqueIndex("uq_exam_child_paper").on(t.childUserId, t.paperId)],
|
||||
);
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
// xuchao: 考试板块——试卷查询与作答记录
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { db } from "@/db";
|
||||
import { examPapers, examRecords, taskInstances } from "@/db/schema";
|
||||
import { todayStr } from "@/lib/dates";
|
||||
import type { QuizQ } from "@/lib/quiz-parse";
|
||||
|
||||
// xuchao: 试卷题型标签(展示用)
|
||||
export const EXAM_TYPE_LABEL: Record<string, string> = {
|
||||
monthly: "月考",
|
||||
unit: "单元卷",
|
||||
midterm: "期中",
|
||||
final: "期末",
|
||||
};
|
||||
|
||||
export type ExamPaperRow = {
|
||||
paperId: string;
|
||||
subject: string;
|
||||
grade: string;
|
||||
semester: string;
|
||||
month: number;
|
||||
examType: string;
|
||||
title: string;
|
||||
region: string | null;
|
||||
year: number | null;
|
||||
duration: number | null;
|
||||
fullScore: number | null;
|
||||
seq: number;
|
||||
};
|
||||
|
||||
// xuchao: 某年级某学期的全部试卷(按月份+顺序)
|
||||
export async function listPapers(grade: string, semester: string, subject = "math"): Promise<ExamPaperRow[]> {
|
||||
return db
|
||||
.select({
|
||||
paperId: examPapers.paperId,
|
||||
subject: examPapers.subject,
|
||||
grade: examPapers.grade,
|
||||
semester: examPapers.semester,
|
||||
month: examPapers.month,
|
||||
examType: examPapers.examType,
|
||||
title: examPapers.title,
|
||||
region: examPapers.region,
|
||||
year: examPapers.year,
|
||||
duration: examPapers.duration,
|
||||
fullScore: examPapers.fullScore,
|
||||
seq: examPapers.seq,
|
||||
})
|
||||
.from(examPapers)
|
||||
.where(and(eq(examPapers.subject, subject), eq(examPapers.grade, grade), eq(examPapers.semester, semester)))
|
||||
.orderBy(examPapers.month, examPapers.seq);
|
||||
}
|
||||
|
||||
// xuchao: 单张试卷全文(含题目 JSON)
|
||||
export async function getPaper(paperId: string) {
|
||||
const [row] = await db.select().from(examPapers).where(eq(examPapers.paperId, paperId)).limit(1);
|
||||
return row ?? null;
|
||||
}
|
||||
|
||||
// xuchao: 解析试卷题目 JSON
|
||||
export function paperQuestions(paper: { questions: string }): QuizQ[] {
|
||||
try {
|
||||
const arr = JSON.parse(paper.questions);
|
||||
return Array.isArray(arr) ? arr : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// xuchao: 某孩子某卷的最近成绩
|
||||
export async function getExamRecord(childId: number, paperId: string) {
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(examRecords)
|
||||
.where(and(eq(examRecords.childUserId, childId), eq(examRecords.paperId, paperId)))
|
||||
.limit(1);
|
||||
return row ?? null;
|
||||
}
|
||||
|
||||
// xuchao: 某孩子全部考试记录(列表页显示已考分数)
|
||||
export async function recordsByPaper(childId: number): Promise<Map<string, { score: number; passed: boolean }>> {
|
||||
const rows = await db
|
||||
.select({ paperId: examRecords.paperId, score: examRecords.score, passed: examRecords.passed })
|
||||
.from(examRecords)
|
||||
.where(eq(examRecords.childUserId, childId));
|
||||
return new Map(rows.map((r) => [r.paperId, { score: r.score ?? 0, passed: r.passed }]));
|
||||
}
|
||||
|
||||
// xuchao: 记录作答;达标联动当天数学打卡(允许重考覆盖成绩)
|
||||
export async function recordExam(
|
||||
childId: number,
|
||||
paperId: string,
|
||||
paperTitle: string,
|
||||
score: number,
|
||||
correct: number,
|
||||
total: number,
|
||||
passed: boolean,
|
||||
answers: unknown[],
|
||||
) {
|
||||
await db
|
||||
.insert(examRecords)
|
||||
.values({
|
||||
childUserId: childId,
|
||||
paperId,
|
||||
score,
|
||||
correct,
|
||||
total,
|
||||
passed,
|
||||
answers: JSON.stringify(answers),
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: { score, correct, total, passed, answers: JSON.stringify(answers), takenAt: sql`CURRENT_TIMESTAMP` },
|
||||
});
|
||||
|
||||
if (!passed) return;
|
||||
const today = todayStr();
|
||||
const ckTitle = `考试 · ${paperTitle.length > 24 ? paperTitle.slice(0, 24) + "…" : paperTitle}`;
|
||||
const [existing] = await db
|
||||
.select({ id: taskInstances.id, status: taskInstances.status })
|
||||
.from(taskInstances)
|
||||
.where(
|
||||
and(eq(taskInstances.childUserId, childId), eq(taskInstances.date, today), eq(taskInstances.title, ckTitle)),
|
||||
)
|
||||
.limit(1);
|
||||
if (existing) {
|
||||
if (existing.status !== "done")
|
||||
await db
|
||||
.update(taskInstances)
|
||||
.set({ status: "done", doneAt: sql`CURRENT_TIMESTAMP` })
|
||||
.where(eq(taskInstances.id, existing.id));
|
||||
} else {
|
||||
await db.insert(taskInstances).values({
|
||||
childUserId: childId,
|
||||
date: today,
|
||||
title: ckTitle,
|
||||
subject: "math",
|
||||
status: "done",
|
||||
doneAt: new Date(),
|
||||
sortOrder: 96,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// xuchao: 数学每周检测——从已点亮数学课的题池 seeded 抽 10 题
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { db } from "@/db";
|
||||
import { mathPoints, mathProgress, mathWeeklyTest } from "@/db/schema";
|
||||
import { extractQuiz, type QuizQ } from "@/lib/quiz-parse";
|
||||
import { todayStr, weekKey } from "@/lib/dates";
|
||||
|
||||
export const currentMathWeekKey = () => weekKey(todayStr());
|
||||
|
||||
// xuchao: 确定性随机(同 seed 每次得到相同组卷,渲染与判分一致)
|
||||
function hashStr(s: string): number {
|
||||
let h = 5381;
|
||||
for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;
|
||||
return h >>> 0;
|
||||
}
|
||||
function mulberry32(a: number) {
|
||||
return () => {
|
||||
a |= 0;
|
||||
a = (a + 0x6d2b79f5) | 0;
|
||||
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
function seededSample<T>(arr: T[], seed: string, n: number): T[] {
|
||||
const rnd = mulberry32(hashStr(seed));
|
||||
const pool = [...arr];
|
||||
for (let i = pool.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(rnd() * (i + 1));
|
||||
[pool[i], pool[j]] = [pool[j], pool[i]];
|
||||
}
|
||||
return pool.slice(0, n);
|
||||
}
|
||||
|
||||
// xuchao: 从已点亮数学课组卷;题池不足 5 题时用计算模块兜底
|
||||
export async function buildMathWeeklyQuestions(childId: number, wk: string): Promise<QuizQ[]> {
|
||||
const lit = await db
|
||||
.select({ pointId: mathProgress.pointId })
|
||||
.from(mathProgress)
|
||||
.where(and(eq(mathProgress.childUserId, childId), eq(mathProgress.passed, true)));
|
||||
const litIds = new Set(lit.map((r) => r.pointId));
|
||||
|
||||
const all = await db.select().from(mathPoints);
|
||||
let pool: QuizQ[] = [];
|
||||
for (const p of all) {
|
||||
if (litIds.has(p.pointId)) pool.push(...extractQuiz(p.md));
|
||||
}
|
||||
if (pool.length < 5) {
|
||||
for (const p of all) if (p.module === "calc") pool.push(...extractQuiz(p.md));
|
||||
}
|
||||
return seededSample(pool, `${childId}:math:${wk}`, 10);
|
||||
}
|
||||
|
||||
export async function getMathWeeklyRecord(childId: number, wk: string) {
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(mathWeeklyTest)
|
||||
.where(and(eq(mathWeeklyTest.childUserId, childId), eq(mathWeeklyTest.weekKey, wk)))
|
||||
.limit(1);
|
||||
return row ?? null;
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
// xuchao: 数学思维自学板块——结构对标英语语法模块(知识点+进度+打卡联动)
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { db } from "@/db";
|
||||
import { mathPoints, mathProgress, taskInstances } from "@/db/schema";
|
||||
import { todayStr } from "@/lib/dates";
|
||||
import { extractQuiz, type QuizQ } from "@/lib/quiz-parse";
|
||||
import { gradeAnswers, type AnswerValue } from "@/lib/grammar";
|
||||
|
||||
export { extractQuiz, type QuizQ };
|
||||
export { gradeAnswers, type AnswerValue };
|
||||
|
||||
// xuchao: 七大模块元信息(展示用),顺序即学习顺序
|
||||
export const MODULES = [
|
||||
{ id: "calc", name: "计算与计数", stars: 1, desc: "整数/分数/小数运算、运算律、巧算与计数原理,一切数学的地基。" },
|
||||
{ id: "apply", name: "应用题", stars: 2, desc: "和差倍、鸡兔同笼、植树盈亏、工程比例等经典应用题模型。" },
|
||||
{ id: "geo", name: "几何", stars: 2, desc: "平面图形周长面积、立体图形表面积体积、割补与等积变换。" },
|
||||
{ id: "num", name: "数论", stars: 3, desc: "整除特征、质数合数、位值原理、余数与同余。" },
|
||||
{ id: "travel", name: "行程", stars: 3, desc: "相遇追及、流水行船、火车过桥、环形跑道。" },
|
||||
{ id: "eq", name: "方程", stars: 3, desc: "用字母表示数、解方程、列方程解应用题。" },
|
||||
{ id: "combo", name: "组合", stars: 3, desc: "抽屉原理、最值问题、逻辑推理与策略游戏。" },
|
||||
] as const;
|
||||
|
||||
export type MathPointRow = {
|
||||
pointId: string;
|
||||
module: string;
|
||||
moduleName: string;
|
||||
title: string;
|
||||
difficulty: number;
|
||||
seq: number;
|
||||
};
|
||||
|
||||
// xuchao: 全部数学知识点(按展示顺序)
|
||||
export async function listMathPoints(): Promise<MathPointRow[]> {
|
||||
return db
|
||||
.select({
|
||||
pointId: mathPoints.pointId,
|
||||
module: mathPoints.module,
|
||||
moduleName: mathPoints.moduleName,
|
||||
title: mathPoints.title,
|
||||
difficulty: mathPoints.difficulty,
|
||||
seq: mathPoints.seq,
|
||||
})
|
||||
.from(mathPoints)
|
||||
.orderBy(mathPoints.seq);
|
||||
}
|
||||
|
||||
export async function totalMathPoints(): Promise<number> {
|
||||
const [r] = await db.select({ n: sql<number>`COUNT(*)` }).from(mathPoints);
|
||||
return r.n;
|
||||
}
|
||||
|
||||
// xuchao: 单个数学知识点全文
|
||||
export async function getMathPoint(pointId: string) {
|
||||
const [row] = await db.select().from(mathPoints).where(eq(mathPoints.pointId, pointId)).limit(1);
|
||||
return row ?? null;
|
||||
}
|
||||
|
||||
// xuchao: 已通过的数学知识点集合
|
||||
export async function mathPassedByPoint(childId: number): Promise<Set<string>> {
|
||||
const rows = await db
|
||||
.select({ pointId: mathProgress.pointId })
|
||||
.from(mathProgress)
|
||||
.where(and(eq(mathProgress.childUserId, childId), eq(mathProgress.passed, true)));
|
||||
return new Set(rows.map((r) => r.pointId));
|
||||
}
|
||||
|
||||
// xuchao: 记录结果;通过则同步完成当天数学打卡
|
||||
export async function completeMathPoint(
|
||||
childId: number,
|
||||
pointId: string,
|
||||
title: string,
|
||||
score: number,
|
||||
passed: boolean,
|
||||
) {
|
||||
await db
|
||||
.insert(mathProgress)
|
||||
.values({ childUserId: childId, pointId, score, passed })
|
||||
.onDuplicateKeyUpdate({ set: { score, passed, completedAt: sql`CURRENT_TIMESTAMP` } });
|
||||
|
||||
if (!passed) return;
|
||||
const today = todayStr();
|
||||
const ckTitle = `数学思维 · ${title}`;
|
||||
const [existing] = await db
|
||||
.select({ id: taskInstances.id, status: taskInstances.status })
|
||||
.from(taskInstances)
|
||||
.where(
|
||||
and(eq(taskInstances.childUserId, childId), eq(taskInstances.date, today), eq(taskInstances.title, ckTitle)),
|
||||
)
|
||||
.limit(1);
|
||||
if (existing) {
|
||||
if (existing.status !== "done")
|
||||
await db
|
||||
.update(taskInstances)
|
||||
.set({ status: "done", doneAt: sql`CURRENT_TIMESTAMP` })
|
||||
.where(eq(taskInstances.id, existing.id));
|
||||
} else {
|
||||
await db.insert(taskInstances).values({
|
||||
childUserId: childId,
|
||||
date: today,
|
||||
title: ckTitle,
|
||||
subject: "math",
|
||||
status: "done",
|
||||
doneAt: new Date(),
|
||||
sortOrder: 97,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,10 @@ export function correctText(q: QuizQ): string {
|
||||
}
|
||||
|
||||
// 错题入库(同一孩子同一来源同一题干去重)
|
||||
// sourceType:lesson/weekly=英语,mlesson/mweekly=数学,exam=考试卷
|
||||
export async function recordWrong(
|
||||
childId: number,
|
||||
sourceType: "lesson" | "weekly",
|
||||
sourceType: "lesson" | "weekly" | "mlesson" | "mweekly" | "exam",
|
||||
sourceId: string,
|
||||
sourceTitle: string,
|
||||
q: QuizQ,
|
||||
|
||||
Reference in New Issue
Block a user