整个课程和数据UI重新设计版本

This commit is contained in:
shenlei
2026-09-18 17:55:44 +09:00
parent febfd30f49
commit 0a4f97d105
154 changed files with 12366 additions and 4784 deletions
+59 -4
View File
@@ -1,6 +1,6 @@
# 课程内容包 JSON 格式
> 对应《学习引擎规格》第 5 节。本文说明 A1–B1 单元内容包的文件位置、字段和程序校验规则。A0 仍按《A0 初始核心课程包》,不在此格式内
> 对应《学习引擎规格》第 5 节。本文说明 A0–B1 单元内容包的文件位置、字段和程序校验规则。所有等级共用这一套结构,由 `CourseRepository` 用同一套规则转换成学习和复习数据;A0 的内容标准仍以《A0 初始核心课程包》为准(见 4.10
## 1. 文件与工具
@@ -8,6 +8,7 @@
|---|---|
| `kouyu_english/assets/courses/course-map.json` | 主题地图:等级、单元顺序、每级的数量范围、名称白名单 |
| `kouyu_english/assets/courses/<等级>/<单元ID>.json` | 单元内容包,一个单元一个文件 |
| `kouyu_english/assets/courses/A0/assessment.json` | A0 阶段测评题(见 4.11 |
| `kouyu_english/tool/courses/lexicon/` | 锁定的 CEFR-J 数据与 `SOURCES.md` |
| `kouyu_english/tool/courses/pools/` | 等级词池(`build_word_pools.py` 生成) |
| `kouyu_english/tool/courses/validate.dart` | 校验全部内容包,报告词池覆盖率 |
@@ -20,17 +21,18 @@ dart run tool/courses/validate.dart --uncovered B1 # 按主题列出未覆盖
dart run tool/courses/validate.dart --levels B1-U14 # 打印条目的计算等级
```
内容包目前只是资源文件,尚未加入 `pubspec.yaml`,运行时也还不读取
运行时由 `lib/core/courses/course_repository.dart` 读取:`main` 启动前先加载 A0`AppState.load` 再加载其余等级。学习(教学段、对话、写作、独立表达)和复习(产出核心的 `match``review`)都只读这些 JSON,代码里不再写死课程数据。`test/course_self_consistency_test.dart` 要求每个示范答案都能通过它自己的判定规则
## 2. 当前内容(2026-09-17,全部为 `draft`
## 2. 当前内容(2026-09-18A1B1 为 `draft`
| 等级 | 单元 | 产出核心 | 理解词 | 听读材料 | 词池覆盖 |
|---|---|---|---|---|---|
| A0 | 10 | 见 A0 标准 | — | — | —(`approved`,不参与词池校验) |
| A1 | 21 | 265 | 598 | 42 | 840/93390.0% |
| A2 | 15 | 217 | 862 | 44 | 1040/107896.5% |
| B1 | 14 | 206 | 1121 | 42 | 1345/155286.7% |
全部由 AI 起草、程序校验通过,尚未经过独立审核和用户确认,不能冻结。
A1B1 全部由 AI 起草、程序校验通过,尚未经过独立审核和用户确认,不能冻结。
## 3. course-map.json
@@ -48,6 +50,7 @@ dart run tool/courses/validate.dart --levels B1-U14 # 打印条目的计算等
"coreRange": [10, 15],
"receptiveRange": [70, 110],
"materials": {"count": [2, 3], "listeningWords": [250, 550], "readingWords": [300, 500]},
"assessment": "A0/assessment.json", // 可选:本级阶段测评题,目前只有 A0 有
// A1 的材料较短,且可选 "unknownTolerance"(默认 0.02)放宽超纲词占比
"units": [{"id": "B1-U01", "file": "B1/B1-U01.json", "title": "…", "categories": ["旅行"], "canDo": "…"}]
}]
@@ -202,6 +205,58 @@ A1 材料短、面向零基础,`unknownTolerance` 设为 0.08(听读材料
至少 2 个维度,每维至少 3 档;`pass` 是及格档在 `bands` 中的下标(从 0 起),须大于 0。
### 4.9 可选字段:显式规则优先
以下字段都可省略;省略时由 `itemIds` 引用的产出核心推导(`match` 合并成一组“或”,示范取 `en`),写了就以写的为准。规则写法见 `lib/core/courses/answer_rules.dart`:外层“且”、内层“或”,内层首项可写 `#min:N` 表示至少用上 N 项;词条可写 `a + b``/正则/`,以及 `#spelling``#digit``#word``#question``#numbers:N``#words:N` 这些结构。
| 位置 | 字段 | 作用 | 省略时 |
|---|---|---|---|
| 单元 | `status` | `approved` / `draft` 等 | `draft` |
| 单元 | `vocabulary` | 整课词表(离线查词) | 产出核心 + 理解词 |
| 教学段 | `grammarNote` | 本段语法提示 | 不显示 |
| 教学段 | `vocabulary` | 预习词表 `[{id, word, meaning, example, exampleMeaning}]` | 本段产出核心 |
| 教学段 | `sceneId` | 本段对话用的场景 | 第 N 段用第 N 个场景,没有则用 main 场景 |
| 教学段 | `writing.accept` / `writing.hint` | 写作判定规则 / 未通过时的提示 | 由 `writing.itemIds` 推导 |
| 教学段 | `independent.accept` / `independent.help` | 独立表达的完整规则 / 帮助 | 由 `independent.itemIds` 推导;为空则任何真实尝试都算 |
| 场景 | `goal` | 对话目标 | `setting`,再没有则 `title` |
| 场景 | `practice` | 同时作为自由练习场景:`{title, summary, recapPrompt, alwaysOpen}`,都可省略;写 `{}` 即可 | 不进入自由练习 |
| 轮次 | `aiZh` | `ai` 这句的中文(`zh` 是示范回答的中文) | 不提供翻译 |
| 轮次 | `accept` | 本轮判定用的一组“或” | 由本轮 `itemIds` 推导 |
自由练习场景在其所属单元完成后解锁,`alwaysOpen: true` 则一开始就开放。
### 4.10 A0 单元
A0 用同一结构,文件为 `A0/a0-01.json``a0-10.json`,在 `course-map.json` 中排在最前。为兼容已保存的学习进度,A0 保留原有 ID:单元 `a0-01`,教学段 `a0-01-a`(拆段课为 `a0-04-a/b/c``a0-08-a/b/c`),产出核心 `A0-W01` / `A0-P01`。A0 的规则、提示和对话都用上面的显式字段写明;`validate.dart` 只把 A0 的产出核心和听写句当作“已教的词”,不按 A1–B1 的数量范围和词池校验 A0。
### 4.11 阶段测评(assessment.json
等级在 `course-map.json` 里写了 `assessment`,就由 `CourseRepository` 加载对应文件,运行时代码在 `lib/core/courses/assessment.dart`。目前只有 A0 的 `A0/assessment.json`
```jsonc
{
"schemaVersion": 1,
"packs": [
{"id": "A0-E1", "level": "A0", "tasks": [
{"id": "A0-A-L1", "skill": "listening", "prompt": "选择正确答案",
"audio": "Hello. Im Mia.", "choices": ["自我介绍", "买东西", "问时间"], "answerIndex": 0},
{"id": "A0-A-W1", "skill": "writing", "prompt": "用英语介绍自己。",
"accept": [["hello", "hi"], ["i am", "my name is"]],
"guide": "试着同时写一句问候和姓名,例如:Hello. I’m …"}
]},
{"id": "A0-E1R", "level": "A0", "replaces": "A0-E1", "tasks": []}
]
}
```
- `skill``listening` / `reading` / `writing` / `speaking`。听力、阅读是选择题,写 `choices``answerIndex`(从 0 开始);听力另写 `audio`
- 写作、口语是开放题,`accept` 用 4.9 的规则写法,`guide` 是答错后展示的要求。
- 没有 `replaces` 的套题按文件顺序解锁:第一套直接开放,之后每套要在上一套通过 24 小时后才开放。
-`replaces` 的是替换题,用不同的人名、地点、数字考同样的能力,成绩记在原题 ID 上。
- 题目和套题 ID 写进学习记录,发布后不要改。
`test/course_self_consistency_test.dart` 检查:每道开放题都有 `accept``guide`,每道选择题的 `answerIndex` 有效,`replaces` 指向存在的套题。
## 5. 文本用词检查
“已知词”包括:`alwaysAllowedWords`、功能词、A0 已教的词、推荐顺序在前的单元的产出核心和理解词、本单元的产出核心和理解词(均按词形还原),以及全局与本单元的 `names`。数字不计。
+150 -31
View File
@@ -24,6 +24,7 @@
#learning-app-prototype .screen-list { display:flex; flex-direction:column; gap:3px; margin-top:15px; }
#learning-app-prototype .screen-link { text-align:left; border:0; border-radius:10px; color:var(--ka-ink); background:transparent; padding:9px 10px; font-size:13px; }
#learning-app-prototype .screen-link[aria-pressed="true"] { background:var(--ka-soft-green); color:var(--ka-green); font-weight:500; }
#learning-app-prototype .screen-link.sub { padding-left:22px; font-size:12px; }
#learning-app-prototype .legend-title { font-size:13px; font-weight:500; margin:0 0 10px; }
#learning-app-prototype .flow { display:flex; flex-direction:column; gap:8px; }
#learning-app-prototype .flow-row { border-left:2px solid var(--ka-line); padding:3px 0 3px 9px; font-size:12px; line-height:1.35; }
@@ -115,6 +116,62 @@
#learning-app-prototype .mac-main h1 { font-size:23px; }
#learning-app-prototype .mac-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:16px; }
#learning-app-prototype .mac-grid .slot { margin:0; min-height:104px; }
#learning-app-prototype .me-card { background:var(--ka-surface); border:1px solid var(--ka-line); border-radius:18px; padding:14px; }
#learning-app-prototype .me-head { display:flex; align-items:center; gap:12px; }
#learning-app-prototype .avatar { width:48px; height:48px; border-radius:50%; display:grid; place-items:center; background:var(--ka-soft-green); color:var(--ka-green); font-size:19px; font-weight:500; }
#learning-app-prototype .me-name { font-size:17px; font-weight:500; }
#learning-app-prototype .level-badge { display:inline-block; margin-left:6px; padding:2px 7px; border-radius:999px; background:var(--ka-soft-green); color:var(--ka-green); font-size:11px; vertical-align:2px; }
#learning-app-prototype .stats { display:grid; grid-template-columns:repeat(4,1fr); margin-top:13px; padding-top:12px; border-top:1px solid var(--ka-line); }
#learning-app-prototype .stat { border:0; background:transparent; color:var(--ka-ink); text-align:center; padding:0; }
#learning-app-prototype .stat b { display:block; font-size:19px; font-weight:500; }
#learning-app-prototype .stat span { color:var(--ka-muted); font-size:11px; }
#learning-app-prototype .stat.due b { color:var(--ka-warm-ink); }
#learning-app-prototype .goal-card { width:100%; display:block; text-align:left; border:0; border-radius:16px; padding:13px 14px; margin-top:11px; background:var(--ka-soft-green); color:var(--ka-ink); }
#learning-app-prototype .goal-card .meter { margin:9px 0 8px; background:light-dark(#c9e4d3, #2a4a37); }
#learning-app-prototype .group-title { color:var(--ka-muted); font-size:12px; margin:17px 4px 6px; }
#learning-app-prototype .menu { background:var(--ka-surface); border:1px solid var(--ka-line); border-radius:15px; overflow:hidden; }
#learning-app-prototype .menu-row { width:100%; display:flex; align-items:center; gap:11px; border:0; border-bottom:1px solid var(--ka-line); background:transparent; color:var(--ka-ink); padding:11px 12px; text-align:left; font-size:14px; }
#learning-app-prototype .menu-row:last-child { border-bottom:0; }
#learning-app-prototype .menu-row .ico { width:28px; height:28px; flex:none; display:grid; place-items:center; border-radius:8px; background:var(--ka-surface-2); font-size:14px; }
#learning-app-prototype .menu-row .label { flex:1; }
#learning-app-prototype .menu-row .label small { display:block; color:var(--ka-muted); font-size:11px; margin-top:1px; }
#learning-app-prototype .menu-row .val { color:var(--ka-muted); font-size:12px; }
#learning-app-prototype .menu-row .chev { color:var(--ka-muted); font-size:16px; }
#learning-app-prototype .menu-row.danger { color:light-dark(#b3261e, #ffb4ab); justify-content:center; }
#learning-app-prototype .version { text-align:center; color:var(--ka-muted); font-size:11px; margin:12px 0 0; }
#learning-app-prototype .scroll { height:593px; overflow-y:auto; }
#learning-app-prototype .ability { padding:11px 0; border-bottom:1px solid var(--ka-line); }
#learning-app-prototype .ability:last-child { border-bottom:0; }
#learning-app-prototype .ability .meter { margin:7px 0 0; }
#learning-app-prototype details { margin-top:11px; padding:11px 12px; background:var(--ka-surface); border:1px solid var(--ka-line); border-radius:15px; font-size:13px; }
#learning-app-prototype details p { color:var(--ka-muted); font-size:12px; line-height:1.55; margin:8px 0 0; }
#learning-app-prototype .tag { display:inline-block; padding:2px 7px; border-radius:999px; font-size:11px; font-weight:400; background:var(--ka-surface-2); color:var(--ka-muted); }
#learning-app-prototype .tag.ok { background:var(--ka-soft-green); color:var(--ka-green); }
#learning-app-prototype .tag.help { background:var(--ka-warm); color:var(--ka-warm-ink); }
#learning-app-prototype .day { color:var(--ka-muted); font-size:12px; margin:14px 4px 6px; }
#learning-app-prototype .field { padding:10px 12px; border-bottom:1px solid var(--ka-line); font-size:13px; }
#learning-app-prototype .field span { display:block; color:var(--ka-muted); font-size:11px; margin-bottom:3px; }
#learning-app-prototype .segmented { display:flex; padding:3px; border-radius:12px; background:var(--ka-surface-2); margin-top:9px; }
#learning-app-prototype .segmented button { flex:1; border:0; border-radius:9px; padding:8px 0; background:transparent; color:var(--ka-muted); font-size:13px; }
#learning-app-prototype .segmented button.on { background:var(--ka-surface); color:var(--ka-ink); font-weight:500; box-shadow:0 1px 3px rgba(0,0,0,.12); }
#learning-app-prototype .row2 { display:flex; gap:8px; } #learning-app-prototype .row2 > * { flex:1; }
#learning-app-prototype .state-switch { display:flex; flex-wrap:wrap; align-items:center; gap:5px; margin:10px 0 2px; padding:7px 8px; border:1px dashed var(--ka-line); border-radius:11px; font-size:11px; color:var(--ka-muted); }
#learning-app-prototype .state-switch button { border:1px solid var(--ka-line); border-radius:999px; padding:3px 8px; background:var(--ka-surface); color:var(--ka-muted); font-size:11px; }
#learning-app-prototype .state-switch button.on { border-color:var(--ka-green); color:var(--ka-green); }
#learning-app-prototype .slot[hidden] { display:none; }
#learning-app-prototype .path-head { display:flex; justify-content:space-between; align-items:baseline; margin-top:20px; font-size:15px; }
#learning-app-prototype .path-head b { font-weight:500; }
#learning-app-prototype .path { margin-top:8px; background:var(--ka-surface); border:1px solid var(--ka-line); border-radius:15px; overflow:hidden; }
#learning-app-prototype .lesson-row { display:flex; align-items:center; gap:11px; padding:11px 12px; border-bottom:1px solid var(--ka-line); }
#learning-app-prototype .lesson-row:last-child { border-bottom:0; }
#learning-app-prototype .lesson-row .label { flex:1; }
#learning-app-prototype .lesson-row b { display:block; font-size:14px; font-weight:500; }
#learning-app-prototype .lesson-row small { color:var(--ka-muted); font-size:11px; }
#learning-app-prototype .lesson-ico { width:26px; height:26px; flex:none; display:grid; place-items:center; border-radius:50%; font-style:normal; font-size:12px; background:var(--ka-surface-2); color:var(--ka-muted); }
#learning-app-prototype .lesson-row.done .lesson-ico { background:var(--ka-green); color:var(--ka-green-ink); }
#learning-app-prototype .lesson-row.current { background:var(--ka-soft-green); }
#learning-app-prototype .lesson-row.current .lesson-ico { background:var(--ka-green); color:var(--ka-green-ink); }
#learning-app-prototype .lesson-row.locked b { color:var(--ka-muted); }
@media (max-width: 880px) { #learning-app-prototype .studio { grid-template-columns:180px minmax(300px, 1fr); } #learning-app-prototype .flow-panel { display:none; } }
@media (max-width: 590px) { #learning-app-prototype .studio { display:block; } #learning-app-prototype .panel { margin-bottom:15px; } #learning-app-prototype .screen-list { display:grid; grid-template-columns:1fr 1fr; } #learning-app-prototype .phone { width:100%; max-width:352px; } #learning-app-prototype .phone.mac { width:100%; max-width:430px; } }
</style>
@@ -125,8 +182,7 @@
<div class="screen-list" role="group" aria-label="选择页面">
<button class="screen-link" data-screen-target="welcome" aria-pressed="false" type="button">01 欢迎与每日时长</button>
<button class="screen-link" data-screen-target="placement" aria-pressed="false" type="button">02 起点定位</button>
<button class="screen-link" data-screen-target="home" aria-pressed="true" type="button">03 首页</button>
<button class="screen-link" data-screen-target="map" aria-pressed="false" type="button">04 学习地图</button>
<button class="screen-link" data-screen-target="home" aria-pressed="true" type="button">03 学习(首页 + 地图合并)</button>
<button class="screen-link" data-screen-target="listen" aria-pressed="false" type="button">05 听力练习</button>
<button class="screen-link" data-screen-target="speak" aria-pressed="false" type="button">06 跟读练习</button>
<button class="screen-link" data-screen-target="write" aria-pressed="false" type="button">07 造句练习</button>
@@ -135,8 +191,15 @@
<button class="screen-link" data-screen-target="confirm" aria-pressed="false" type="button">10 转写确认</button>
<button class="screen-link" data-screen-target="summary" aria-pressed="false" type="button">11 练习总结</button>
<button class="screen-link" data-screen-target="review" aria-pressed="false" type="button">12 今日复习</button>
<button class="screen-link" data-screen-target="progress" aria-pressed="false" type="button">13 我的进步</button>
<button class="screen-link" data-screen-target="settings" aria-pressed="false" type="button">14 设置</button>
<button class="screen-link" data-screen-target="progress" aria-pressed="false" type="button">13 我的</button>
<button class="screen-link sub" data-screen-target="me-level" aria-pressed="false" type="button">13a 升级进度</button>
<button class="screen-link sub" data-screen-target="me-assess" aria-pressed="false" type="button">13b 四技能评估</button>
<button class="screen-link sub" data-screen-target="me-records" aria-pressed="false" type="button">13c 学习记录</button>
<button class="screen-link sub" data-screen-target="me-audio" aria-pressed="false" type="button">13d 我的录音</button>
<button class="screen-link" data-screen-target="settings" aria-pressed="false" type="button">14 学习偏好</button>
<button class="screen-link sub" data-screen-target="set-theme" aria-pressed="false" type="button">14a 外观</button>
<button class="screen-link sub" data-screen-target="set-sync" aria-pressed="false" type="button">14b 云同步</button>
<button class="screen-link sub" data-screen-target="set-ai" aria-pressed="false" type="button">14c AI 服务</button>
<button class="screen-link" data-screen-target="mac" aria-pressed="false" type="button">15 Mac 适配</button>
<button class="screen-link" data-screen-target="dictionary" aria-pressed="false" type="button">16 查词卡</button>
<button class="screen-link" data-screen-target="preview" aria-pressed="false" type="button">17 课前词汇预热</button>
@@ -167,23 +230,25 @@
</section>
<section class="screen" data-screen="home">
<div class="status"><span>9:41</span><span>● ● ●</span></div>
<div class="appbody">
<div class="eyebrow">早上好,Shen</div><h1>今天,说 3 句英语。</h1><p class="bodycopy">第 1 课已完成。今天把姓名介绍说得更自然。</p>
<div class="meter" aria-label="当前掌握程度"><span style="width:30%"></span></div><p class="small">A0 起步 · 已完成 3 个核心任务</p>
<div class="slot selected"><div class="slot-title"><span>第 2 课 · 拼读姓名</span><span>12 分钟</span></div><div class="slot-note">先认识 4 个词 · 听 · 说 · 读 · 写</div><button class="primary" data-go="preview" type="button">开始今天的学</button></div>
<div class="slot"><div class="slot-title">快速练习 · 初次见面</div><div class="slot-note">3 分钟 AI 对话 · 介绍姓名与来自哪里</div><button class="secondary" data-go="dialogue" type="button">开始对话</button></div>
<div class="appbody scroll">
<div class="eyebrow">早上好,Shen</div><h1>今天,说几句能用的英语。</h1>
<div class="state-switch" role="group" aria-label="原型:切换今日任务状态"><span>原型状态:</span><button class="on" data-today="review" type="button">有到期复习</button><button data-today="lesson" type="button">上新课</button><button data-today="stage" type="button">A0 学完</button></div>
<div class="slot selected today" data-today-card="review"><div class="slot-title"><span>今天先复习 3 项</span><span class="small">6 分钟</span></div><div class="slot-note">昨天练过的关键句,今天换个情境再用一次。复习完再上新课。</div><button class="primary" data-go="review" type="button">开始</button></div>
<div class="slot selected today" data-today-card="lesson" hidden><div class="slot-title"><span>第 3 课 · 来自哪里 · 第 2/3 段</span><span class="small">12 分钟</span></div><div class="slot-note">预热词汇 → 听说读写 → 对话 → 独立尝试</div><button class="primary" data-go="preview" type="button">开始今天的学习</button></div>
<div class="slot selected today" data-today-card="stage" hidden><div class="slot-title"><span>A0 阶段评估 · A0-E1</span><span class="small">15 分钟</span></div><div class="slot-note">可使用 48/48 · 已掌握 30/30。两套评估间隔至少 24 小时。</div><button class="primary" type="button">开始阶段评估</button></div>
<div class="path-head"><b>A0 课程</b><span class="small">2 / 8 完成</span></div>
<div class="meter" style="margin:6px 0 4px"><span style="width:25%"></span></div>
<div class="path">
<div class="lesson-row done"><i class="lesson-ico"></i><div class="label"><b>第 1 课 · 你好,我是……</b><small>介绍姓名并回应问候</small></div></div>
<div class="lesson-row done"><i class="lesson-ico"></i><div class="label"><b>第 2 课 · 拼读姓名</b><small>听懂拼写,说出名字字母</small></div></div>
<div class="lesson-row current"><i class="lesson-ico"></i><div class="label"><b>第 3 课 · 来自哪里</b><small>说出来自哪里 · 小段 1/3</small></div></div>
<div class="lesson-row locked"><i class="lesson-ico">🔒</i><div class="label"><b>第 4 课 · 数字与电话号码</b><small>说 010 与简单号码</small></div></div>
<div class="lesson-row locked"><i class="lesson-ico">🔒</i><div class="label"><b>第 5 课 · 你好吗?</b><small>问候并表达简单状态</small></div></div>
<div class="lesson-row locked"><i class="lesson-ico">🔒</i><div class="label"><b>…… 第 8 课</b><small>A0 课程结束后进入巩固与阶段评估</small></div></div>
</div>
<p class="small" style="margin-top:10px">已完成的课可以随时点开重练;有复习积压时,新课会暂时锁住。</p>
</div>
<nav class="bottom" aria-label="主导航"><button class="navitem active" data-go="home" type="button"><i class="nav-dot"></i>首页</button><button class="navitem" data-go="map" type="button"><i class="nav-dot"></i>学习</button><button class="navitem" data-go="scenes" type="button"><i class="nav-dot"></i>对话</button><button class="navitem" data-go="review" type="button"><i class="nav-dot"></i>复习</button><button class="navitem" data-go="progress" type="button"><i class="nav-dot"></i>我的</button></nav>
</section>
<section class="screen" data-screen="map" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="home" type="button"></button><h2>A0 起步</h2></div>
<div class="appbody"><div class="eyebrow">学习地图 · 已完成 3 个任务</div><h1>从认识到开口</h1><p class="bodycopy">核心课建立能力;达到出口任务才进入下一阶段。</p>
<div class="slot"><div class="slot-title">✓ 第 1 课 · 你好,我是……</div><div class="slot-note">介绍姓名并回应问候</div></div>
<div class="slot selected"><div class="slot-title">第 2 课 · 拼读姓名 <span>进行中</span></div><div class="slot-note">听懂拼写,并说出自己的名字字母</div></div>
<div class="slot"><div class="slot-title">第 3 课 · 你好吗?</div><div class="slot-note">问候并表达简单状态</div></div>
<div class="slot"><div class="slot-title">第 4 课 · 数字与电话号码</div><div class="slot-note">说 010 与简单号码</div></div>
</div>
<nav class="bottom" aria-label="主导航"><button class="navitem" data-go="home" type="button"><i class="nav-dot"></i>首页</button><button class="navitem active" type="button"><i class="nav-dot"></i>学习</button><button class="navitem" data-go="scenes" type="button"><i class="nav-dot"></i>对话</button><button class="navitem" data-go="review" type="button"><i class="nav-dot"></i>复习</button><button class="navitem" data-go="progress" type="button"><i class="nav-dot"></i>我的</button></nav>
<nav class="bottom" aria-label="主导航"><button class="navitem active" data-go="home" type="button"><i class="nav-dot"></i>学习</button><button class="navitem" data-go="scenes" type="button"><i class="nav-dot"></i>对话</button><button class="navitem" data-go="review" type="button"><i class="nav-dot"></i>复习</button><button class="navitem" data-go="progress" type="button"><i class="nav-dot"></i>我的</button></nav>
</section>
<section class="screen" data-screen="preview" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="home" type="button"></button><h2>第 2 课 · 拼读姓名</h2></div>
@@ -221,7 +286,7 @@
<div class="slot selected"><div class="slot-title">初次见面 <span>A0</span></div><div class="slot-note">介绍姓名和来自哪里 · 4 轮</div><button class="primary" data-go="dialogue" type="button">开始对话</button></div>
<div class="slot"><div class="slot-title">认识新同学 <span>A0</span></div><div class="slot-note">问候并问对方姓名</div></div><div class="slot"><div class="slot-title">咖啡店 <span>A1</span></div><div class="slot-note">点一杯饮料 · 尚未解锁</div></div>
</div>
<nav class="bottom" aria-label="主导航"><button class="navitem" data-go="home" type="button"><i class="nav-dot"></i>首页</button><button class="navitem" data-go="map" type="button"><i class="nav-dot"></i>学习</button><button class="navitem active" type="button"><i class="nav-dot"></i>对话</button><button class="navitem" data-go="review" type="button"><i class="nav-dot"></i>复习</button><button class="navitem" data-go="progress" type="button"><i class="nav-dot"></i>我的</button></nav>
<nav class="bottom" aria-label="主导航"><button class="navitem" data-go="home" type="button"><i class="nav-dot"></i>学习</button><button class="navitem active" data-go="scenes" type="button"><i class="nav-dot"></i>对话</button><button class="navitem" data-go="review" type="button"><i class="nav-dot"></i>复习</button><button class="navitem" data-go="progress" type="button"><i class="nav-dot"></i>我的</button></nav>
</section>
<section class="screen" data-screen="dialogue" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="scenes" type="button"></button><h2>初次见面 · 2 / 4</h2></div>
@@ -244,19 +309,69 @@
<section class="screen" data-screen="review" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><h2>今天复习 · 3 项</h2></div>
<div class="appbody"><div class="eyebrow">昨天的对话</div><h1>再回答一次。</h1><p class="bodycopy">这次换成新的情境,也能用上同一句话。</p><div class="review-prompt">AIHi! Where are you from?</div><div class="assist"><button type="button">提示</button><button type="button">慢一点</button><button type="button">翻译</button></div><div class="record">按住说话 ● 或输入文字</div><p class="small" style="margin-top:13px">完成后还有 2 项 · 每项约 1 分钟</p></div>
<nav class="bottom" aria-label="主导航"><button class="navitem" data-go="home" type="button"><i class="nav-dot"></i>首页</button><button class="navitem" data-go="map" type="button"><i class="nav-dot"></i>学习</button><button class="navitem" data-go="scenes" type="button"><i class="nav-dot"></i>对话</button><button class="navitem active" type="button"><i class="nav-dot"></i>复习</button><button class="navitem" data-go="progress" type="button"><i class="nav-dot"></i>我的</button></nav>
<nav class="bottom" aria-label="主导航"><button class="navitem" data-go="home" type="button"><i class="nav-dot"></i>学习</button><button class="navitem" data-go="scenes" type="button"><i class="nav-dot"></i>对话</button><button class="navitem active" data-go="review" type="button"><i class="nav-dot"></i>复习</button><button class="navitem" data-go="progress" type="button"><i class="nav-dot"></i>我的</button></nav>
</section>
<section class="screen" data-screen="progress" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><h2>我的进步</h2></div>
<div class="appbody"><div class="eyebrow">当前:A0 起步</div><h1>你已经能做到</h1><p class="bodycopy">进步以“能完成什么”呈现,而非单纯的分数。</p>
<div class="levelmap"><div class="level"><i class="on"></i><div><b>介绍姓名</b><span>完成 2 次对话</span></div><span></span></div><div class="level"><i class="on"></i><div><b>说来自哪里</b><span>已加入复习</span></div><span></span></div><div class="level"><i></i><div><b>拼读电话号码</b><span>下一项核心练习</span></div><span></span></div></div>
<div class="slot"><div class="slot-title">A0 当前掌握 <span>准备出口挑战</span></div><div class="meter"><span style="width:30%"></span></div></div><button class="secondary" data-go="settings" type="button">调整学习设置</button>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar" style="justify-content:space-between"><h2>我的</h2><button class="back" data-go="settings" aria-label="设置" type="button"></button></div>
<div class="appbody scroll">
<div class="me-card"><div class="me-head"><div class="avatar">S</div><div><div class="me-name">Shen<span class="level-badge">A0 起步</span></div><p class="small">每天 20 分钟 · 已坚持 6 天</p></div></div>
<div class="stats"><button class="stat" data-go="me-records" type="button"><b>12</b><span>已学课</span></button><button class="stat" data-go="me-level" type="button"><b>31</b><span>可使用</span></button><button class="stat" data-go="me-level" type="button"><b>9</b><span>已掌握</span></button><button class="stat due" data-go="review" type="button"><b>3</b><span>待复习</span></button></div></div>
<button class="goal-card" data-go="me-level" type="button"><div class="slot-title">距离 A1 <span class="small">52% </span></div><div class="meter"><span style="width:52%"></span></div><p class="small">还需:17 项可使用 · 21 项掌握 · 2 套评估</p></button>
<div class="group-title">学习</div>
<div class="menu"><button class="menu-row" data-go="me-level" type="button"><i class="ico">📈</i><span class="label">升级进度</span><span class="val">52%</span><span class="chev"></span></button><button class="menu-row" data-go="me-assess" type="button"><i class="ico">📝</i><span class="label">四技能评估</span><span class="val">0/2 通过</span><span class="chev"></span></button><button class="menu-row" data-go="me-records" type="button"><i class="ico">🕘</i><span class="label">学习记录</span><span class="val"></span><span class="chev"></span></button><button class="menu-row" data-go="me-audio" type="button"><i class="ico">🎙</i><span class="label">我的录音</span><span class="val">8 条</span><span class="chev"></span></button></div>
<div class="group-title">设置</div>
<div class="menu"><button class="menu-row" data-go="settings" type="button"><i class="ico">📚</i><span class="label">学习偏好</span><span class="val">20 分钟</span><span class="chev"></span></button><button class="menu-row" data-go="set-theme" type="button"><i class="ico">🎨</i><span class="label">外观</span><span class="val">跟随系统</span><span class="chev"></span></button><button class="menu-row" data-go="set-sync" type="button"><i class="ico">☁️</i><span class="label">云同步</span><span class="val">已登录</span><span class="chev"></span></button><button class="menu-row" data-go="set-ai" type="button"><i class="ico">🤖</i><span class="label">AI 服务</span><span class="val">DeepSeek</span><span class="chev"></span></button></div>
<div class="menu" style="margin-top:17px"><button class="menu-row danger" type="button">清除本机学习数据</button></div>
<p class="version">开口英语 1.0.0</p>
</div>
<nav class="bottom" aria-label="主导航"><button class="navitem" data-go="home" type="button"><i class="nav-dot"></i>首页</button><button class="navitem" data-go="map" type="button"><i class="nav-dot"></i>学习</button><button class="navitem" data-go="scenes" type="button"><i class="nav-dot"></i>对话</button><button class="navitem" data-go="review" type="button"><i class="nav-dot"></i>复习</button><button class="navitem active" type="button"><i class="nav-dot"></i>我的</button></nav>
<nav class="bottom" aria-label="主导航"><button class="navitem" data-go="home" type="button"><i class="nav-dot"></i>学习</button><button class="navitem" data-go="scenes" type="button"><i class="nav-dot"></i>对话</button><button class="navitem" data-go="review" type="button"><i class="nav-dot"></i>复习</button><button class="navitem active" data-go="progress" type="button"><i class="nav-dot"></i>我的</button></nav>
</section>
<section class="screen" data-screen="me-level" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="progress" type="button"></button><h2>升级进度</h2></div>
<div class="appbody scroll"><div class="eyebrow">A0 → A1</div><h1>已完成 52%</h1><p class="bodycopy">进度来自掌握证据,不是上完固定课数就升级。</p>
<div class="me-card" style="margin-top:13px"><div class="ability"><div class="slot-title">可使用 <span class="small">31 / 48</span></div><p class="small">核心内容已获得独立使用证据</p><div class="meter"><span style="width:65%"></span></div></div><div class="ability"><div class="slot-title">已掌握(间隔复习) <span class="small">9 / 30</span></div><p class="small">达到四次间隔复习要求</p><div class="meter"><span style="width:30%"></span></div></div><div class="ability"><div class="slot-title">两套四技能评估 <span class="small">0 / 2</span></div><p class="small">两套题组需间隔至少 24 小时</p><div class="meter"><span style="width:0%"></span></div></div></div>
<details><summary>进入下一阶段的条件</summary><p>60 项固定核心内容中至少 48 项可使用、30 项已掌握,且两套不同题组的听说读写评估都通过并间隔至少 24 小时。</p></details>
<button class="primary" data-go="review" type="button">去复习 3 项,推进掌握</button></div>
</section>
<section class="screen" data-screen="me-assess" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="progress" type="button"></button><h2>四技能评估</h2></div>
<div class="appbody scroll"><p class="bodycopy">评估不提供翻译或句框。两套都通过,且间隔 24 小时以上,才计入升级。</p>
<div class="slot"><div class="slot-title">A0-E1 <span class="tag">未开始</span></div><p class="slot-note">听 · 说 · 读 · 写,各 2 题 · 约 12 分钟</p><button class="primary" type="button">开始评估</button></div>
<div class="slot"><div class="slot-title">A0-E2 <span class="tag">未解锁</span></div><p class="slot-note">A0-E1 通过 24 小时后可开始</p></div>
<p class="small" style="margin-top:12px">完成后可在这里查看得分和错题回顾。</p></div>
</section>
<section class="screen" data-screen="me-records" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="progress" type="button"></button><h2>学习记录</h2></div>
<div class="appbody scroll"><div class="segmented"><button class="on" type="button">全部</button><button type="button">独立完成</button><button type="button">带帮助</button></div>
<div class="day">今天</div><div class="menu"><div class="menu-row"><span class="label">I'm from Hong Kong.<small>复习 · 口语</small></span><span class="tag ok">独立完成</span></div><div class="menu-row"><span class="label">My number is 1-2-3.<small>听写</small></span><span class="tag help">带提示</span></div></div>
<div class="day">昨天</div><div class="menu"><div class="menu-row"><span class="label">第 5 课 · 来自哪里<small>课程 · 6 步完成</small></span><span class="tag ok">完成</span></div><div class="menu-row"><span class="label">初次见面<small>AI 对话 · 4 轮</small></span><span class="tag help">带帮助</span></div><div class="menu-row"><span class="label">Nice to meet you.<small>复习 · 写作</small></span><span class="tag ok">独立完成</span></div></div>
<p class="small" style="margin-top:12px">记录只保存在本机,用于解释进度。</p></div>
</section>
<section class="screen" data-screen="me-audio" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="progress" type="button"></button><h2>我的录音</h2></div>
<div class="appbody scroll"><div class="menu"><div class="menu-row"><i class="ico"></i><span class="label">I'm from Hong Kong.<small>今天 9:12 · 0:03</small></span><span class="val">删除</span></div><div class="menu-row"><i class="ico"></i><span class="label">My name is Shen.<small>昨天 20:40 · 0:02</small></span><span class="val">删除</span></div><div class="menu-row"><i class="ico"></i><span class="label">Nice to meet you.<small>昨天 20:35 · 0:02</small></span><span class="val">删除</span></div></div>
<div class="menu" style="margin-top:12px"><div class="menu-row"><span class="label">保存原始录音<small>仅保存在本机</small></span><div class="toggle"><i></i></div></div></div>
<button class="secondary" type="button">清除全部录音</button></div>
</section>
<section class="screen" data-screen="settings" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="progress" type="button"></button><h2>学习设置</h2></div>
<div class="appbody"><div class="setting"><div>AI 对话服务<br><span>Gemini API · 可切换兼容反代</span></div><b></b></div><div class="setting"><div>每日学习时间<br><span>20 分钟</span></div><b></b></div><div class="setting"><div>英文朗读速度<br><span>慢速</span></div><b></b></div><div class="setting"><div>默认显示中文提示<br><span>A0 阶段开启</span></div><div class="toggle"><i></i></div></div><div class="setting"><div>保存原始录音<br><span>仅保存在本机</span></div><div class="toggle"><i></i></div></div><button class="secondary" data-go="home" type="button">清空本机练习记录</button></div>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="progress" type="button"></button><h2>学习偏好</h2></div>
<div class="appbody scroll"><div class="menu"><button class="menu-row" type="button"><i class="ico"></i><span class="label">每日学习时间</span><span class="val">20 分钟</span><span class="chev"></span></button><div class="menu-row"><i class="ico"></i><span class="label">默认显示中文提示<small>A0 阶段建议开启</small></span><div class="toggle"><i></i></div></div><button class="menu-row" type="button"><i class="ico">🔊</i><span class="label">英文朗读速度</span><span class="val">慢速</span><span class="chev"></span></button><button class="menu-row" type="button"><i class="ico">🗣</i><span class="label">离线语音识别</span><span class="val">SenseVoice</span><span class="chev"></span></button></div></div>
</section>
<section class="screen" data-screen="set-theme" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="progress" type="button"></button><h2>外观</h2></div>
<div class="appbody scroll"><div class="group-title">主题</div><div class="segmented"><button class="on" type="button">跟随系统</button><button type="button">浅色</button><button type="button">深色</button></div><p class="small" style="margin:10px 4px 0">选择“跟随系统”时,随手机的深色模式自动切换。</p></div>
</section>
<section class="screen" data-screen="set-sync" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="progress" type="button"></button><h2>云同步</h2></div>
<div class="appbody scroll"><div class="me-card"><div class="me-head"><div class="avatar"></div><div><div class="me-name">shen</div><p class="small">上次同步:今天 9:15</p></div></div></div>
<div class="menu" style="margin-top:12px"><div class="menu-row"><span class="label">自动同步<small>学习后在后台同步</small></span><div class="toggle"><i></i></div></div><button class="menu-row" type="button"><i class="ico">🌐</i><span class="label">服务器地址</span><span class="val">sync.example.com</span><span class="chev"></span></button></div>
<button class="primary" type="button">立即同步</button><button class="secondary" type="button">退出登录</button></div>
</section>
<section class="screen" data-screen="set-ai" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="progress" type="button"></button><h2>AI 服务</h2></div>
<div class="appbody scroll"><div class="menu"><button class="menu-row" type="button"><i class="ico">🤖</i><span class="label">服务类型</span><span class="val">OpenAI 兼容</span><span class="chev"></span></button><div class="field"><span>接口地址</span>https://api.deepseek.com</div><div class="field"><span>模型</span>deepseek-flash</div><div class="field" style="border-bottom:0"><span>API Key</span>••••••••••••</div></div>
<div class="row2"><button class="secondary" type="button">测试连接</button><button class="primary" style="margin-top:8px" type="button">保存</button></div>
<p class="small" style="margin:12px 4px 0">AI 只提供建议,不会直接决定你的掌握程度。</p></div>
</section>
<section class="screen" data-screen="dictionary" hidden>
<div class="status"><span>9:41</span><span>● ● ●</span></div><div class="appbar"><button class="back" data-go="dialogue" type="button"></button><h2>初次见面 · 2 / 4</h2></div>
@@ -264,12 +379,12 @@
</section>
<section class="screen" data-screen="mac" hidden>
<div class="status"><span>开口英语</span><span>◉ Shen</span></div>
<div class="mac-body"><aside class="mac-side"><b>开口英语</b><span class="on">首页</span><span>学习</span><span>对话</span><span>复习</span><span>我的</span></aside><main class="mac-main"><div class="eyebrow">早上好,Shen</div><h1>今天,说 3 句英语。</h1><p class="bodycopy">Mac 复用手机端的课程与进度,本机也能独立练习。</p><div class="mac-grid"><div class="slot selected"><div class="slot-title">第 2 课 · 拼读姓名</div><div class="slot-note">12 分钟 · 先认识 4 个词 · 听说读写</div><button class="primary" data-go="preview" type="button">继续学习</button></div><div class="slot"><div class="slot-title">AI 对话 · 初次见面</div><div class="slot-note">3 分钟 · 介绍姓名与地点</div><button class="secondary" data-go="dialogue" type="button">开始对话</button></div></div><div class="slot" style="margin-top:12px"><div class="slot-title">今天复习 · 3 项</div><div class="slot-note">昨天的关键句将在新情境中再次练习。</div></div></main></div>
<div class="mac-body"><aside class="mac-side"><b>开口英语</b><span class="on">学习</span><span>对话</span><span>复习</span><span>我的</span></aside><main class="mac-main"><div class="eyebrow">早上好,Shen</div><h1>今天,说 3 句英语。</h1><p class="bodycopy">Mac 复用手机端的课程与进度,本机也能独立练习。</p><div class="mac-grid"><div class="slot selected"><div class="slot-title">第 2 课 · 拼读姓名</div><div class="slot-note">12 分钟 · 先认识 4 个词 · 听说读写</div><button class="primary" data-go="preview" type="button">继续学习</button></div><div class="slot"><div class="slot-title">AI 对话 · 初次见面</div><div class="slot-note">3 分钟 · 介绍姓名与地点</div><button class="secondary" data-go="dialogue" type="button">开始对话</button></div></div><div class="slot" style="margin-top:12px"><div class="slot-title">今天复习 · 3 项</div><div class="slot-note">昨天的关键句将在新情境中再次练习。</div></div></main></div>
</section>
</div>
</main>
<aside class="panel flow-panel" aria-label="原型流程说明">
<p class="legend-title">M1 流程</p><div class="flow"><div class="flow-row"><strong>首次使用</strong>每日时长 → 起点定位 → 首页</div><div class="flow-row active"><strong>日常学习</strong>首页 → 词汇预热 → 课程 → AI 对话 → 总结</div><div class="flow-row"><strong>查词帮助</strong>点词/短语 → 查词卡 → 回到原步骤</div><div class="flow-row"><strong>纠错保护</strong>低置信转写 → 确认 / 修改 / 重说</div><div class="flow-row"><strong>巩固</strong>总结 → 次日微对话复习</div><div class="flow-row"><strong>设备</strong>手机优先;Mac 使用更宽的同一学习结构</div></div><p class="legend-title" style="margin-top:20px">设计原则</p><span class="chip">一个主任务</span><span class="chip">预热不算掌握</span><span class="chip">短语优先查词</span><span class="chip">口音可理解即接受</span><span class="chip">一次只纠正一个点</span><span class="chip">语音失败可改文字</span>
<p class="legend-title">M1 流程</p><div class="flow"><div class="flow-row"><strong>首次使用</strong>每日时长 → 起点定位 → 学习</div><div class="flow-row active"><strong>日常学习</strong>学习页今日任务 → 词汇预热 → 课程 → AI 对话 → 总结</div><div class="flow-row"><strong>查词帮助</strong>点词/短语 → 查词卡 → 回到原步骤</div><div class="flow-row"><strong>纠错保护</strong>低置信转写 → 确认 / 修改 / 重说</div><div class="flow-row"><strong>巩固</strong>总结 → 次日微对话复习</div><div class="flow-row"><strong>设备</strong>手机优先;Mac 使用更宽的同一学习结构</div></div><p class="legend-title" style="margin-top:20px">设计原则</p><span class="chip">一个主任务</span><span class="chip">预热不算掌握</span><span class="chip">短语优先查词</span><span class="chip">口音可理解即接受</span><span class="chip">一次只纠正一个点</span><span class="chip">语音失败可改文字</span>
</aside>
</div>
<script>
@@ -285,6 +400,10 @@
};
links.forEach(link => link.addEventListener('click', () => show(link.dataset.screenTarget)));
root.querySelectorAll('[data-go]').forEach(button => button.addEventListener('click', () => show(button.dataset.go)));
root.querySelectorAll('[data-today]').forEach(button => button.addEventListener('click', () => {
root.querySelectorAll('[data-today]').forEach(b => b.classList.toggle('on', b === button));
root.querySelectorAll('[data-today-card]').forEach(card => { card.hidden = card.dataset.todayCard !== button.dataset.today; });
}));
show('home');
})();
</script>