ea6f12e7ac
- tools/teaching_ppt/gen.py:python-pptx 生成引擎(页面内容与讲稿数据分离, 幂等可重生成);tools/teaching_ppt/day1..5.py:每天的页面+讲稿数据 - docs/teaching/ppt/day1..5.pptx:5 份 PPT 产物,页面内容对应 slides/*.html, 详细讲稿(建议时长/讲法/演示/互动/过渡)只写入演讲者备注,不进主页面 - 讲稿去 AI 味:破折号过度堆砌 152 处砍到 6 处(7.8‰ → 0.3‰)、 升华腔(分水岭/点燃探索欲)清零、重复词(现场 26→11)替换; 保留教学口语生动感(你们/当堂/点破/埋点/回指)与【过渡】悬念钩子 - 生成方式:python-pptx 在隔离 venv 装,不影响系统 Python; 改 day*.py 的 notes 字段后重跑 gen.py 即可幂等重生成
172 lines
15 KiB
Python
172 lines
15 KiB
Python
# -*- 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 答辩见。"散场。"""
|
||
},
|
||
]
|