# -*- coding: utf-8 -*-
"""Day5 · 综合项目案例讲解：学员信息管理器（全天）—— 页面对应 slides/day5.html。"""

SLIDES = [
{
 'kind': 'title',
 'badge': 'C++/Qt 方向实训 · 综合案例日 · 明天起进入项目开发',
 'title': 'Day 5 · 综合项目案例讲解：学员信息管理器',
 'subtitle': '需求拆解 → 界面骨架 → 数据层 → 提升：QThread 防卡界面 → 流程规范',
 'bullets': [
   '上午：需求拆解 → 界面骨架（QMainWindow + QTableWidget + 对话框）→ 数据层（Student ↔ JSON）',
   '下午 1：提升——QThread 防卡界面（worker-object 模式）',
   '下午 2：开发流程与规范（Git/考核）+ 分组定项目方向',
   'Day6–9 学员项目开发；Day10 下午项目答辩',
 ],
 'source': '参考实现：examples/student_manager/qt_student_manager.cpp（README）',
 'minutes': 5, 'segment': '开场',
 'notes': """【定位】今天不是新知识日，是"组装日"，把四天学的全部东西捏成一个完整小软件，再补最后一块拼图（多线程）。开场明确这个定位，让学员从"听课模式"切换到"项目模式"。
【讲法建议】全天以"先看成品→拆解→回指知识点"的节奏推进，每个环节都让学员意识到"这部分我已经学过"，建立项目信心。教师参考实现 examples/student_manager/ 全程作为对照。
【时长骨架】上午 1（需求加骨架）约 90 分钟；上午 2（数据层）约 90 分钟；下午 1（QThread 新授）约 70 分钟；下午 2（流程规范加分组）约 50 分钟。每段留 15 分钟缓冲。
【红线提醒】下午 QThread 是全天最高优先级保底项。上午超时宁可砍需求拆解的深度也不能砍 QThread，否则项目期"界面冻结/跨线程崩溃"答疑成本数倍返还。"""
},
{
 'badge': '上午 1 · 需求拆解 → 界面骨架',
 'title': '需求拆解：先想清楚再动手',
 'boxes': [
   ('concept', '核心概念——开发流程的第一步不是写代码', [
     '需求：管理学员记录（姓名/成绩/标签），支持添加、保存、读取、批量导入',
     '拆功能 → 映射到已学组件：列表=QTableWidget(Day4)、添加=模态对话框(Day3)、保存读取=QFile+JSON(Day4)、批量导入=后台线程(今天新授)、外壳=QMainWindow(Day3)',
     '拆完就得到任务清单——这正是 Day6 起你们在 Jira 上要做的事',
   ]),
 ],
 'minutes': 25, 'segment': '上午 · 需求拆解',
 'notes': """【地位】这一页是"从学生到工程师"思维转换的关键页：写代码之前先拆任务。中初级学员最常见的项目障碍不是"不会写"而是"不知道先写什么"，本页就是治这个。
【讲法】白板当堂拆：把"学员信息管理器"这个一句话需求，逐条拆成 5 个功能点，再把每个功能点映射到已学组件。每映射一条就问"这是哪天学的？"让全班齐声回答 Day2/3/4。这个回指动作能极大强化"我已经具备做项目的全部知识"的信心。
【交付物】拆解的产物就是任务清单。明确告诉学员："Day6 起你们在 Jira 上建的每一个 task，就应该长这样：一个动词开头、可验收、映射到具体技术点。"今天先有这个意识，明天分组就用得上。
【过渡】"任务清单有了，开始拼第一块：界面骨架。" """
},
{
 'badge': '上午 1 · 需求拆解 → 界面骨架',
 'title': '界面骨架走查',
 'boxes': [
   ('concept', '回指 Day3–4', [
     'QMainWindow：菜单（文件/记录/帮助）+ 工具栏 + 状态栏 + setCentralWidget(table)',
     'QAction 一个动作三处复用（菜单/工具栏/快捷键），每个 action 都要 connect',
     '添加记录：模态 exec() + QFormLayout + QDialogButtonBox',
     '表格 item 所有权：new 出来交给表格——父子所有权模型第 N 次出现',
   ]),
 ],
 'source': '现场跑：tools/run_qt.sh qt_student_manager（离屏自测：QT_QPA_PLATFORM=offscreen）',
 'minutes': 40, 'segment': '上午 · 骨架走查',
 'notes': """【讲法】自顶向下：先跑参考实现 examples/student_manager 给全班看成品长什么样，然后关掉投影，带学员从空 main() 开始一步步搭——QApplication → MainWindow(继承 QMainWindow) → setCentralWidget(table) → 菜单/工具栏/action → connect。每一步都问"这步对应哪天的哪个示例"。
【回指密度】本页 4 条 bullet 每条都是一次回指：QMainWindow→Day3、QAction→Day3（坑卡"忘 connect"）、模态对话框→Day3、表格 item 所有权→Day4 加贯穿全课程的所有权主线。回指的密度本身就是"项目=已学内容组装"的最佳论证。
【代码组织建议】当场把 MainWindow 拆成 .h/.cpp，演示"声明在头、实现在 cpp"的工程结构，这是 Day6 起项目实作的基本功，今天第一次正式做。
【互动】故意演示一次"建了 action 忘 connect"，菜单项灰色点了没反应，让学员用 Day3 坑卡的语言定位问题。坑卡回炉一次。
【过渡】"骨架立住了，往里灌数据。" """
},
{
 'badge': '上午 2 · 数据层',
 'title': '记录 ↔ JSON ↔ 磁盘',
 'code': 'struct Student { QString name; double score; QString tag; };\nQJsonObject toJson(const Student&);      // 互逆的一对\nStudent fromJson(const QJsonObject&);    // round-trip = 正确性标准',
 'boxes': [
   ('concept', '回指 Day4（昨晚作业的完整版）', [
     '数据结构与界面分离：Student 结构体不知道 QTableWidget 的存在——Model/View 思想的朴素版',
     '健壮性三处检查全部在场：open() 返回值、QJsonParseError、路径用 applicationDirPath() 拼',
   ]),
   ('pitfall', '复盘昨晚作业的常见问题', [
     '保存和读取的字段名拼写不一致（"score" vs "Score"）——round-trip 一测就露馅',
     '这就是为什么验收标准是「写进去读出来一致」而不是「能生成文件」',
   ]),
 ],
 'minutes': 45, 'segment': '上午 · 数据层',
 'notes': """【讲法】把昨晚作业的数据层"升级"成案例版本：结构体 Student 加两个自由函数 toJson/fromJson。重点讲"数据结构与界面分离"：Student 不知道 QTableWidget 的存在，这是 Model/View 思想的朴素实践（回指昨天表格页的钩子）。
【当堂对照】让 1–2 位学员展示昨晚作业的数据层代码，对照参考实现讲差异，尤其是字段名拼写不一致的坑（昨晚作业的高发问题），round-trip 测试一跑就露馅，这正是"验收标准为什么是 round-trip"的活教材。
【三处健壮性检查】打开参考实现源码，逐处指认 open() 判返回值、ParseError 检查、applicationDirPath() 拼路径，四天坑卡的成果在此集中体现。让学员看到"这些检查不是教条，是真实代码里必须有的"。
【代码组织】toJson/fromJson 放在独立的 student.h（纯数据加序列化），不依赖 Qt Widgets，演示"按依赖方向分层"的工程习惯。
【过渡】"功能齐了：能加、能存、能读。但有一个场景会让它冻住，下午第一项，新授。" """
},
{
 'badge': '下午 1 · 提升：多线程防卡界面（新授）',
 'title': '先看病症：把耗时活儿放主线程',
 'boxes': [
   ('pitfall', '现场演示', [
     '把 msleep 循环直接放按钮槽里 → 整个窗口冻结：点不动、拖不动、系统提示「未响应」',
     'Day4 讲过：主线程阻塞 = 事件循环停摆——学员项目最高频的「卡死」来源',
   ]),
 ],
 'minutes': 15, 'segment': '下午 · QThread 新授',
 'notes': """【演示流程】在参考实现的"后台导入"按钮槽里，把 moveToThread 那套注释掉，直接在工作函数里 sleep 循环，跑给全班看：点导入后窗口完全冻结 5 秒，鼠标点哪都没反应，系统标题栏提示"未响应"。
【讲法】让学员先用 Day4 的语言描述现象："事件循环停摆了"。然后定性："这是你们项目里最容易出现的卡死，根源不是代码错，而是把耗时活儿放错了线程。"
【不急着给解法】演示完病症后停一下，问"你们觉得该怎么解决？"收集学员想法（多数会猜"开个线程"），自然引出下一页的 worker 模式。先有痛点再给方案，教学效果远好于直接讲语法。
【过渡】"对，要开线程。但 Qt 开线程有个最容易被带歪的姿势，下一页讲正确做法。" """
},
{
 'badge': '下午 1 · 提升：多线程防卡界面（新授）',
 'title': '正解：worker-object 模式',
 'code': 'auto* thread = new QThread(this);\nauto* worker = new ImportWorker;          // 不给 parent：有 parent 禁止 moveToThread\nworker->moveToThread(thread);             // 必须在 start() 之前\nconnect(thread, &QThread::started, worker, [worker]{ worker->doImport(10); });\nconnect(worker, &ImportWorker::rowReady, this, /* 更新表格 */);   // 自动 Queued\nthread->start();',
 'boxes': [
   ('concept', '核心概念（三件套）', [
     '线程亲和性：每个 QObject 归属一个线程，GUI 控件必须归主线程',
     'worker 对象：官方推荐——耗时逻辑放 worker，moveToThread 搬进工作线程，而非继承 QThread 重写 run',
     '跨线程信号槽自动 Queued（Day2 连接方式闭环）：worker 发信号、主线程槽更新表格，不需手动加锁',
   ]),
   ('pitfall', None, [
     '在工作线程直接操作 GUI 控件——未定义行为，跨线程崩溃头号来源',
     'moveToThread 写在 start() 之后 / worker 带了 parent——都不生效',
   ]),
 ],
 'source': '最小版：examples/qthread_worker/ · 案例集成版：student_manager 的 startImport()',
 'minutes': 40, 'segment': '下午 · QThread 新授',
 'notes': """【地位】这是全天最高优先级的新授点，也是四天来埋的"主线程阻塞"暗线的收口，必须讲透，时间是 40 分钟，不能压缩。
【讲法顺序】先讲"不要怎么做"：继承 QThread 重写 run 是网上最常见的写法但官方不推荐（容易把 worker 和线程生命周期搞混），明确否定它；再讲正解 worker-object 模式三件套：①worker 是普通 QObject 不给 parent；②moveToThread 搬家；③用信号槽通信。
【三件套逐条讲】线程亲和性用"每个 QObject 有户籍"类比；worker 模式用"雇外援干活，干完打电话汇报"类比，打电话就是信号槽；跨线程信号槽自动 Queued 是 Qt 的魔法所在（回指 Day2 的 Direct/Queued/Auto，闭环）。强调"不需要手动加锁"，这是 worker 模式相比 std::thread+mutex 的核心优势。
【坑卡演示】在工作线程里直接 table->setItem，可能崩也可能"看起来正常"（又是 UB 双面性）。规则给死："工作线程绝不碰 GUI，只发信号让主线程碰。"
【闭环验证】参考实现的离屏自测会自动校验"导入 3 条后总数 5 条、界面未阻塞"，跑一次让全班看绿色通过。
【过渡】"最后一块拼图补上了，你们现在具备做一个完整 Qt 桌面软件的全部技能。下午最后一节，讲怎么把这些技能变成答辩成绩。" """
},
{
 'badge': '下午 2 · 开发流程与规范 + 分组定方向',
 'title': 'Git 提交习惯',
 'boxes': [
   ('concept', None, [
     '每完成一个任务就提交，提交粒度对应任务粒度',
     'commit 信息说清「做了什么、为什么」——项目的可追溯记录，评审和回滚都靠它',
   ]),
   ('pitfall', None, [
     '攒几天代码一次性提交、信息写"update"——历史失去可追溯性，出问题无法定位是哪次改动引入',
     '从 Day6 第一次 commit 开始就按规范来',
   ]),
 ],
 'minutes': 15, 'segment': '下午 · 流程规范',
 'notes': """【讲法】用"代码是资产、commit 历史是资产的账本"类比，账本记得乱，资产就管不住。展示一个写得好的 commit 历史（每个消息动词开头、说清意图）和一个写得烂的（全是 update）的对比。
【规范给具体可执行标准】①粒度：一个任务一次提交；②消息格式：动词开头（"添加"/"修复"/"重构"），一行说做了什么，必要时正文说为什么；③不要提交无法编译的代码。
【当堂练习（可选 10 分钟）】让每组在刚刚的案例代码上练习一次规范提交，这是明天起每天都要做的事，今天建立肌肉记忆。
【过渡】"代码写得好只是考核的一部分。下一页看清考核的全貌。" """
},
{
 'badge': '下午 2 · 开发流程与规范 + 分组定方向',
 'title': '考核标准与分组定方向',
 'boxes': [
   ('concept', None, [
     '考核不只看代码：项目管理（任务拆解/进度可见）+ 项目技术 + 职业素养（协作/答辩表达）',
     '只埋头写代码会丢掉其他维度的分',
     '今天散场前每组确认：项目选题 + 初步分工 + 任务拆解草稿（上午「需求拆解」那页就是模板）',
   ]),
   ('info', '兴趣钩子（各 5 分钟演示，选学）', [
     '动效：QPropertyAnimation（任何 Q_PROPERTY 都能被缓动曲线驱动）',
     '多状态 UI：QStateMachine（状态成为一等对象，替代一堆 bool 标志）',
     '线程的更高抽象：QtConcurrent / 线程池',
   ]),
 ],
 'minutes': 25, 'segment': '下午 · 考核 + 分组',
 'notes': """【考核讲法】投出考核标准三维度图，重点强调"技术只占三成"，这是中初级学员最容易踩的认知坑（以为代码好就满分）。项目管理维度对应今天上午的"任务拆解"，职业素养对应每天的协作和最后的答辩表达，把考核标准和今天讲的方法论一一对应。
【分组实操】留 15 分钟让每组完成三件事：①确认项目选题（参考方案书备选项目，或自拟）；②初步分工（谁主界面、谁主数据、谁主协调）；③用今天上午"需求拆解"的格式写一版任务拆解草稿。教师巡场逐组确认，散场前每组交一份。
【钩子讲法】三个钩子各 5 分钟演示即可，目的有二：①给学有余力的组指明加分方向（动画/状态机/QtConcurrent 都是答辩亮点）；②告诉所有人"Qt 还有很多没讲的能力，项目期可以按需自学"，留个引子。
【收尾承诺】明确告诉学员：明天起进入项目实作，工程师全程在场答疑；今天案例的全部代码（examples/student_manager/）对他们开放作参考。"""
},
{
 'badge': '当日收尾 · 进入项目期',
 'title': '当日产出验证 · 进入项目期',
 'code': 'QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_student_manager\n# 自测 1/2：JSON 写入 → 清空 → 读回，2 条记录完整\n# 自测 2/2：后台线程导入 3 条，总计 5 条，界面未阻塞',
 'bullets': [
   '每组提交：项目选题 + 分工说明 + 任务拆解草稿',
   '明天 Day6 起：学员编码 + 工程师指导',
   'Day10 下午：项目答辩',
 ],
 'minutes': 5, 'segment': '收尾',
 'notes': """【验收】跑参考实现的离屏自测，全班看两行绿色通过；每组当场提交三件套（选题/分工/拆解草稿）。
【结训语】"五天前你们刚进门，今天你们已经能独立做一个会存数据、不卡界面、有规范流程的桌面软件。后面五天，把它变成你们自己的项目。Day10 答辩见。"散场。"""
},
]
