feat(parent-portal): 完成参考项目(CICD)家长端功能全量覆盖 - 9 新页面 + 3 页面增强 + 763 测试通过
## 变更内容 ### P0 高优先级新页面 - /parent/leave: 请假表单(react-hook-form + Zod) + 历史列表(状态筛选) - /parent/grades/report-card: 报告卡(学年/学期筛选 + 打印 + 教师评语) - /parent/children/[studentId]: 子女详情聚合页(5 Tab: overview/homework/grades/exams/schedule) ### P1 中优先级新页面 - /parent/error-book: 错题本(5 项统计 + Top 错题 + 薄弱知识点) - /parent/diagnostic: 诊断报告(掌握度摘要 + 已发布诊断报告) - /parent/practice: 练习统计(4 项统计 + 练习历史) ### P2 低优先级新页面 - /parent/course-plans + [id]: 课程计划列表 + 详情(章节列表) - /parent/lesson-plans + [planId]/view: 备课列表(学科筛选) + 只读详情 - /parent/elective: 选修课(分类色点 + 状态徽标) ### P3 现有页面增强 - /parent/dashboard: ParentAttentionBanner + 多子女卡片网格 + 趋势图标 + 逾期高亮 - /parent/attendance: AttendanceRateCard + AttendanceWarningBanner + 月份导航 - /parent/grades: GrowthArchiveChart + ExportGradesButton + 班级均对比线 ### 基础设施 - types/index.ts: +20 新类型(LeaveRequest/ReportCard/ErrorBookStats/DiagnosticReport/PracticeStats/CoursePlan/LessonPlan/ElectiveSelection/ChildDetail/ScheduleItem 等) - operations.ts: +19 GraphQL operations(17 query + 2 mutation) - fixtures.ts: +18 mock 数据集 - handlers.ts: +19 MSW GraphQL handler ### 质量校验 - typecheck: 0 错误 - lint: 0 错误 - test: 71 文件 / 763 测试全部通过(从 413 增至 763, +350 测试) ### 文档 - workline.md: 新增 §7 参考项目(CICD)差距分析与实现安排 + §7.3-7.5 实现进度与覆盖完成度 ### 设计决策 - 保留当前"单子女切换"范式(MultiChildTabBar + useChildSwitcher),不迁移到"多子女同屏对比" - 仪表盘除外:已增强为多子女卡片网格并列展示 - 参考项目所有 11 个家长端页面功能已 100% 覆盖 Refs: ARB-020 §22, ARB-022 §24.4
This commit is contained in:
125
apps/parent-portal/src/components/CoursePlanDetail.tsx
Normal file
125
apps/parent-portal/src/components/CoursePlanDetail.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
// CoursePlanDetail:课程计划详情(基本信息 + 章节列表)
|
||||
// 依据:02-architecture-design.md §15 组件设计
|
||||
// 章节:标题/描述/排序/课时数/预计学时
|
||||
|
||||
"use client";
|
||||
|
||||
import type { CoursePlanDetail } from "@/types";
|
||||
import { cn, formatDate } from "@/lib/utils";
|
||||
|
||||
interface CoursePlanDetailProps {
|
||||
coursePlan: CoursePlanDetail;
|
||||
}
|
||||
|
||||
type CoursePlanStatus = CoursePlanDetail["status"];
|
||||
|
||||
const STATUS_LABELS: Record<CoursePlanStatus, string> = {
|
||||
active: "进行中",
|
||||
completed: "已完成",
|
||||
archived: "已归档",
|
||||
};
|
||||
|
||||
const STATUS_STYLES: Record<CoursePlanStatus, string> = {
|
||||
active: "text-success",
|
||||
completed: "text-ink-muted",
|
||||
archived: "text-ink-muted",
|
||||
};
|
||||
|
||||
export function CoursePlanDetail({
|
||||
coursePlan,
|
||||
}: CoursePlanDetailProps): JSX.Element {
|
||||
const chapters = [...coursePlan.chapters].sort(
|
||||
(a, b) => a.sortOrder - b.sortOrder,
|
||||
);
|
||||
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
{/* 基本信息 */}
|
||||
<section
|
||||
aria-label="课程计划基本信息"
|
||||
className="rounded border border-rule bg-paper-elevated p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="rounded bg-accent-soft px-2 py-0.5 text-xs text-ink">
|
||||
{coursePlan.subject}
|
||||
</span>
|
||||
<h2 className="mt-2 font-serif text-xl">{coursePlan.title}</h2>
|
||||
<p className="mt-2 text-sm text-ink-muted">
|
||||
{coursePlan.description}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
"flex-shrink-0 text-xs font-medium",
|
||||
STATUS_STYLES[coursePlan.status],
|
||||
)}
|
||||
>
|
||||
{STATUS_LABELS[coursePlan.status]}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 border-t border-rule pt-3">
|
||||
<dl className="grid grid-cols-2 gap-y-3 text-sm sm:grid-cols-4">
|
||||
<div>
|
||||
<dt className="text-xs text-ink-muted">班级</dt>
|
||||
<dd className="mt-1">{coursePlan.className}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-ink-muted">教师</dt>
|
||||
<dd className="mt-1">{coursePlan.teacherName}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-ink-muted">起止日期</dt>
|
||||
<dd className="mt-1">
|
||||
{formatDate(coursePlan.startDate)} ~{" "}
|
||||
{formatDate(coursePlan.endDate)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-ink-muted">章节数</dt>
|
||||
<dd className="mt-1 font-mono">{coursePlan.chapterCount}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 章节列表 */}
|
||||
<section aria-label="章节列表" className="space-y-3">
|
||||
<h3 className="font-serif text-base">章节安排</h3>
|
||||
<ol className="space-y-3">
|
||||
{chapters.map((chapter) => (
|
||||
<li
|
||||
key={chapter.id}
|
||||
className="rounded border border-rule bg-paper-elevated p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-mono text-xs text-ink-muted">
|
||||
第 {chapter.sortOrder} 章
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="mt-1 font-serif text-base">{chapter.title}</h4>
|
||||
<p className="mt-1 text-sm text-ink-muted">
|
||||
{chapter.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 text-right">
|
||||
<div className="font-mono text-sm">
|
||||
{chapter.lessonCount} 课时
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-ink-muted">
|
||||
预计 {chapter.estimatedHours} 学时
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default CoursePlanDetail;
|
||||
Reference in New Issue
Block a user