三条规矩
- 先备份。改之前把那个 JSON 复制一份,例如
indicators.json.bak,出问题换回来就行。 - 保持是合法的 JSON。逗号、双引号、括号要配对;中文标点不能当语法符号用。存盘后按第八节校验一次。
- 强制刷新。浏览器里按
Ctrl+F5,否则可能还是旧数据。
课程内容和程序是分开的:任务书、技术指标、十三步文案、质量屋、约束、场景、器件、预置电路、自动导览、附页「全班的路」,全写在 app/data/ 的十个 JSON 里。改一个字、存盘、刷新浏览器,页面就变了,不需要打开程序文件,也不用重新构建。其中教师最常改的八个在第二、三节逐个讲;另外两个——tour.json(自动导览十三段的字幕与停留秒数)与 class-paths.json(附页「全班的路」的匿名计数,由 tools/build_class_paths.py 生成)——一般不用手改,位置见本节右边那张表的末两行。
indicators.json.bak,出问题换回来就行。Ctrl + F5,否则可能还是旧数据。| 任务书、四个难题卡 | taskbook.json |
| 技术指标表 | indicators.json |
| 某一步的标题、引导语、助教开场白 | steps.json |
| 器件卡的参数与要点 | parts.json |
| 质量屋的行、列、关系强度 | qfd.json |
| 六条设计约束 | constraints.json |
| 四张前后对照图 | scenarios.json |
| 仿真器里的预置电路 | circuits.json + app/circuits/*.txt |
| 自动导览的字幕与停留秒数 | tour.json |
| 附页「全班的路」的匿名计数 | class-paths.json(脚本生成,不手改) |
本手册只讲"教师自己能做的事"。装机、换端口、上服务器、容器与反向代理见仓库里的 deploy/部署说明.md,这里不重复。
这四个管的是"页面上写什么字"。改完只影响文字与表格,不会影响任何曲线的算法。
| 文件 | 管什么 | 关键字段 | 改了以后页面哪里变 |
|---|---|---|---|
taskbook.json |
任务书与四个难题 | course 课程名、title 任务名、background 背景一句;problems[] 每个难题含 ordinal 序号、title 名称、device 设备、symptom 现象、consequence 后果、requirements[] 要求、chapters[] 对应章节、display_indicators[] 展示型指标、scene 对应的成效图、accent 卡片颜色 |
第 1 步四张难题卡的全部文字;顶栏课程名与任务名;成果报告封面。id(p1 至 p4)被别的文件引用,不要改 |
indicators.json |
技术指标表(参考版) | columns[] 表头;rows[] 每行含 name 指标名、target 目标值、unit 单位、problem_id 属于哪个难题、chapter 对应章节、pending 是否待审定;prompts[] 助教追问用的提示句;pending_label 角标文字 |
第 2 步的参考指标表;pending 为 true 的行带"教学取值待审定"角标 |
steps.json |
十三步的全部文案 | steps[] 每步含 title 标题、lead 引导语、primary 主按钮字、assistant_title 助教面板标题、assistant_opening 助教开场白、evidence 这一步留下什么证据、more 右上角"了解更多"展开层的文字、hook_in 上一段留下的问题、completion 完成卡 |
对应那一步的标题栏、引导语、按钮、助教面板与展开层。template、viz、viz_alt、n、seg 是程序用的挂点名,不要改 |
parts.json |
第 5 步的关键器件 | 器件[] 每个含 名称、英文名、副标、一句话、对应难题、章节,以及五个 页签:结构、参数、工程要点、常见故障、位置;页签顺序 决定页签排列 |
器件卡的文字与参数表。id 与三维模型一一对应,不要改;参数为工程典型值范围,需要教师审定 |
这四个既有文字也有数值。文字部分改了立刻生效;带 ★ 的数值改动会牵动曲线,改之前请读那一行的提醒。
| 文件 | 管什么 | 关键字段 | 改了以后页面哪里变 |
|---|---|---|---|
qfd.json |
第 7 步质量屋 | rows[] 需求行(problem 属于哪个难题、text 需求、indicator 对应指标、importance 重要度);cols[] 设计特性列(text、unit、target 目标值、chapter);relations[] 每格的 strength(9 强、3 中、1 弱)与 basis 依据;roof[] 屋顶耦合(sign 取 ++ + − −−) |
质量屋的行列标题、格子符号、悬停时显示的依据、屋顶的正负号 |
constraints.json |
第 8 步六条设计约束 | 每条含 name 名称、expression 约束式、teachingValue 教学取值、source 教材出处、why 为什么这么定、violation 违反了会怎样、pushes 把参数往哪边推;context ★ 是中频、邻道频偏、各项门限等数值 |
约束清单的文字与说明立刻变。★ 提醒:改 context 或 axes 里的数值,第 8 步图上的东西会跟着重画——六条边界线、可行域阴影、薄荷青「最稳的点」菱形、图下的逐条读数与结论行、左栏「六条约束满足几条」与完成卡文案,存盘刷新即生效(app/js/core/viz-registry.js:191-196 把本文件的 context / axes 注进组件,app/js/viz/constraint-boundary.js:126-127 拿它盖住 app/js/rf/constraints.js 的 DEFAULT_CONTEXT / AXES,:146-153 重算边界与可行域、:308 按同一组取值出读数);第 11 步「方案越界核对」用的是同一个组件,图上也跟着变。★ 但有三处不跟着变,要一并改 app/js/rf/constraints.js 才对得上:① 第 8 步左栏「跳到最稳的点」按钮(app/js/steps/step08.js:104,直接走 DEFAULT_CONTEXT / AXES);② 第 11 步左栏的越界核对与对照方案(app/js/steps/step11.js:106-109、137、143、232-236、243);③ 第 8 步两个滑块写死的上下界(app/js/steps/step08.js:53-54、80、83),把 axes 改小,图的坐标轴会变、滑块仍能拖到老范围。另外 expression / teachingValue / why 这些是给人看的文字,不参与计算(只有 name 与 teachingValue 印在图例上),所以只改文字线不会动、只改 context 文字也不会自己跟上——两边都要改。数值改动请记在 tasks/ 里交维护者同步 |
scenarios.json |
第 12 步四张前后对照图 | 每个场景含 title、subtitle、basis 依据等级(物理公式 / 简化模型 / 示意)、basisNote 算法依据、before 与 after ★ 两组驱动参数、readouts 图下方读数、unit 与色标说明;meta.basisLevels 是三个等级的定义 |
四张图的标题、右上角依据角标、读数与色标文字。★ before / after 里的数是喂给公式的物理量(例如 Q 值、驻波比、幅相误差),改了图会重算——这正是它的用法,但改完请把 readouts 的文字一起对上 |
circuits.json |
第 9、10 步的预置电路清单 | 仿真器 段写明名称、入口与许可;电路[] 每个含 编号、id、文件(对应 app/circuits/ 下的 txt)、标题、任务提示、难题、章节、关键取值、可调[] 学生能动的量、步骤[] 出现在第几步 |
仿真页左侧的电路清单、任务提示与关键取值。电路本身的元件值在那个 txt 文件里,用仿真器导出即可 |
照着做一遍,后面所有的改动都是同一个套路:找到那一行 → 改值 → 存盘 → 强制刷新 → 到对应的那一步看一眼。
app/data/indicators.json;"id": "i1" 那一行(中频通道邻道抑制);"target": "≥ 60" 改成你要的值,单位在 "unit" 里;"pending": true 改成 false,角标就没了;连带 同一条要求还写在 taskbook.json 难题一的 requirements 里(第 1 步的卡片),两处要一起改,否则前后对不上。
app/data/constraints.json;"id": "adjacent_rejection"(邻道抑制);teachingValue 里的"单级 ≥ 35 dB"改成新值,expression 末尾的数字一起改;why 与 violation 两句是给学生看的理由,一并改成对得上的说法;提醒 这一步改的都是文字,所以边界线不会动——但原因不是"线由 app/js/rf/constraints.js 的默认取值算出":线的门限读的是同一份 JSON 的 context(邻道抑制这条读 context.rejectionReqDb,见 app/js/rf/constraints.js:50 的 c1AdjacentRejection),teachingValue 只是印在图例上的说法。要让线也动,把 context.rejectionReqDb 一并改成新值,存盘刷新第 8 步的边界、可行域与「最稳的点」菱形就会重画(app/js/core/viz-registry.js:191-196 → app/js/viz/constraint-boundary.js:126-127、146-153)。只剩左栏「跳到最稳的点」按钮(app/js/steps/step08.js:104)与第 11 步左栏的越界核对(app/js/steps/step11.js:236、243)仍按 app/js/rf/constraints.js 的 DEFAULT_CONTEXT 算,这两处要跟上得改那个文件——请把改动记下来交维护者同步,别只改一边。
app/circuits/my-lc.txt;app/data/circuits.json,在 "电路" 数组末尾加一段(注意上一段末尾要补逗号);{
"编号": "C7",
"id": "my-lc",
"文件": "my-lc.txt",
"标题": "我的选频回路",
"任务提示": "把 C 从 22 pF 调到 47 pF,看谐振点往哪边走。",
"难题": "难题一 · 北斗终端失锁",
"章节": "LC 谐振回路",
"关键取值": "L=10 μH,C=22 pF,f0≈10.7 MHz",
"可调": ["电容 C_F", "负载电阻 RL_ohm"],
"步骤": [9]
}
第 3 步"现场现象"和第 10 步"实测验证"共用同一张双曲线图:蓝线理论、金线实测。
两条线都不是写死的数组,而是由 app/js/viz/dual-curve.js 里的探头加载模型按一组元件值当场算出来的;
这组元件值与第 9 步预置电路 C1(单调谐 LC 并联回路)、C6(示波器探头加载)完全相同,
所以图上的读数与电路仿真器里量到的数对得上。
金线是复现值,不是实验台的原始记录——这句口径就写在那两步图下方的说明行里。
这些数全部由 app/js/rf/resonance.js 现算,页面上没有写死的结论。
app/data/steps.json 第 3 步与第 10 步的 lead 与 more,以及 app/data/circuits.json 里 C1 与 C6 的关键取值与任务提示。这一档是教师自己能做的。app/js/viz/dual-curve.js 顶部的 PROBE_MODEL(L、C、r、Rs、RL、探头的 R 与 C),以及 app/js/steps/step03.js 与 step10.js 里传给组件的那一行 model。两处的数必须一样。实验记录里如果带学生姓名或学号,请先去掉再写进文件。手上有实验台原始记录又不想动程序文件的,把数据交给维护者:把这组参数挪进 app/data/ 已经登记为待改进项。
组件里还留着一份十一点的样例数组(VERIFIER_DATA),那是 app/dev/ 演示页与旧调用方的回退路径,第 3、10 步不走它,照着它改不会影响页面。
报告是学生自己在第 13 步生成的;后端在运行时,服务器上还会留一份,整个目录拷走就是全班的记录。不想翻文件的话,学生会话浏览见 teacher.html:列出所有会话,点一个就能看它走到第几步、每步填了什么,也能直接打开那一份成果报告。想一眼看全班而不是一个一个点,就在同一页右栏顶上切到「全班汇总」页签:十三步在哪一步掉队、四个难题谁被选得最多、设计点落没落进可行域、最常写的指标与 Q 估值散在哪里,每块右上角的「口径」角标写明这些数是怎么数出来的。同一行右边还有一个「导出表格」按钮,把全班压成一张 csv,见下面「全班导成一张表」。
第 13 步点"生成成果报告",浏览器下载一份 HTML:任务书与技术指标、原因树与证据计划、参数估算与质量屋、设计点与约束核对、仿真与回填记录、四张成效对照图,以及各步与助教的全部对话。
同一页还能下载一份学习记录 JSON(原始输入与事件),交作业时两份都收更完整。
后端在跑的时候,学生每次保存追加一行到 backend/data/sessions/<会话号>.jsonl;每生成一次报告,存一份 backend/data/reports/<会话号>-<时间戳>.html。
换存放位置:设环境变量 CLASS_AGENT_DATA_DIR 指向一个可写目录(服务化与容器部署都这么配)。
在 teacher.html 右栏切到「全班汇总」页签,标题行右边、「刷新」旁边有一个「导出表格」按钮。点一下,浏览器下载一份 csv,文件名形如 会话汇总-20260915.csv(日期由服务器按下面那个时区给;响应头读不到时按本机日期另拼一个同样式的名字)。导出期间按钮显示「正在导出…」并暂时点不动,完事自己复原。
表里有什么 每行一个会话,一共 28 列:会话编号、最后保存时间、保存版本数、完成步数、走到过的步数、完成到第几步、对话条数、入手难题编号与难题名、设计点 Q、设计点 η、设计点是否可行、违反的约束、第 6 步 Q 估值、指标行数,后面是十三步各自的状态(完成 / 走到过 / 空白)。一次最多导 200 行。服务器上还没有任何记录时,给的是一份只有表头的空表——那是「这一轮还没人保存过」的如实回答,不是故障。
表里没有什么 没有学号,也没有姓名,更没有服务器上的文件路径:第一列的会话编号是浏览器自己发的随机串。时间一律换算到报告时区,默认 Asia/Shanghai,并且写在表头第二格里(「最后保存(Asia/Shanghai)」);要换时区,改环境变量 CLASS_AGENT_REPORT_TZ,表头跟着变。文件是 UTF-8 带字节序标记的,Excel 双击打开中文不乱码。
设了教师令牌时 和这一页的别处一样:令牌在页面第一次打开时问一次,之后放在请求头里带上,不写进地址栏(地址会进浏览器历史和服务器访问日志)。令牌不对,后端回 401,页面再问一次;仍然不对,「全班汇总」那块会写出「表格导不出来 · 需要教师令牌」,并提示先点「刷新」再导一次。整个过程只读,学生记录一个字节都没动。
两条命令就够上课用;换端口、局域网访问、容器、服务化、反向代理、离线安装与排错,全在部署说明里,本手册不重复。
本机(Windows) 在仓库根目录打开 PowerShell:
powershell -ExecutionPolicy Bypass -File scripts\run.ps1
服务器(Linux)
bash scripts/run.sh
两个脚本都会自己准备运行环境,然后在 8020 端口启动;浏览器打开 http://127.0.0.1:8020/,页面与接口同源。停止按 Ctrl + C。
要让同一间教室的学生访问,见部署说明里的监听地址参数。全部细节在仓库的 deploy/部署说明.md。
OPENROUTER_API_KEY 放在系统环境变量里,或仓库根目录的 .env 文件里(这个文件不进仓库)。http://127.0.0.1:8020/api/health,看 model_configured 是 true 还是 false。.env 覆盖——换密钥时记得改的是同一处。四步,两分钟。第一步是硬的:JSON 写坏了页面会停在"正在装入任务书"。
在仓库根目录执行(把文件名换成你改的那个):
backend\.venv\Scripts\python.exe -m json.tool app\data\indicators.json
能把整份内容打印出来就是合法的;报 JSONDecodeError 就照它给的行号去补逗号或引号。
强制刷新,直接跳到改动的那一步(地址栏 #/step/2 直达第 2 步),看文字、角标、按钮是不是你要的样子。
装了检查工具的话,在仓库根目录跑:
backend\.venv\Scripts\python.exe tools\page_check.py
它会用无头浏览器把十三步逐一打开,报告写在 tasks/页面检查报告.md。
还没审定的数值请保留"教学取值待审定"的角标(indicators.json 里是 pending 字段)。宁可标着待审定,也不要让学生把没核过的数当成结论。