This commit is contained in:
ray zhou
2026-06-29 14:51:55 +08:00
parent 225fb2bd28
commit 2dd9f17da9
319 changed files with 29461 additions and 9412 deletions

162
plans/Plan-95f2e196.plan.md Normal file
View File

@@ -0,0 +1,162 @@
<!-- 95f2e196-ce33-4253-ace0-0a2ba260a0c2 -->
---
todos:
- id: "finalize-article-schema"
content: "course_article 增加 content_json含 nameList/skipWords正文不再依赖 course_article_sentence"
status: pending
- id: "nce-import-command"
content: "NCE_*.json → course + course_article导入时写入 skipWords/nameList人名/地名免敲)"
status: pending
- id: "article-api"
content: "GET 课文 APIcontent_json 直出 TypeWords Article DTOnameList 供默写/听写跳过)"
status: pending
- id: "article-vocab-api"
content: "课文生词 APIcourse_article_vocab词头+ vocab_sense + course_word_sense与正文分离"
status: pending
- id: "content-patch-admin"
content: "低频勘误PATCH content_json 的 text/translate/skipWords或单课重导"
status: pending
isProject: false
---
# 课文内容存储方案建议(修订 v2
## 结论
- **课文正文**`course_article.content_json`(几乎不改,偶发勘误)
- **课文生词**:关系表(`course_article_vocab` + `course_word_sense`
- **人名/地名免敲**:写在 `content_json`**`skipWords`**API 层映射为 TypeWords 的 `nameList`
---
## 人名 / 地名:练习时不用敲
### 需求
默写或听写课文时,**人名、地名**应预先标注,练习流程 **自动跳过**,不要求学生输入(如 `Silbury``Pinhurst``James``Scott`)。
### TypeWords 已有能力(可直接复用)
[`TypingArticle.vue`](TypeWords/packages/core/src/components/article/TypingArticle.vue) 已支持 `article.nameList`
- 命中 `nameList` 的 token 在 `next()`**自动跳过**
- 内置跳过:`Mr` / `Mrs` / `Ms` / `Dr` / `Miss`
- 多词条目 `"James Scott"` 会拆成 `james``scott` 分别匹配
**不需要**在正文里加 `{Silbury}` 这类 inline 标记;**维护一份免敲词表**即可。
### 在 `content_json` 里怎么存
推荐 **两层字段**(存库清晰 + 对接 TypeWords 零改动):
```json
{
"title": "No wrong numbers",
"text": "Mr.James Scott has a garage in Silbury ...",
"textTranslate": "...",
"audioSrc": "/sound/article/nce2-1/No wrong numbers.mp3",
"lrcPosition": [[15.45, 24.87]],
"skipWords": [
{ "token": "James", "type": "person" },
{ "token": "Scott", "type": "person" },
{ "token": "Silbury", "type": "place" },
{ "token": "Pinhurst", "type": "place" }
]
}
```
| 字段 | 用途 |
|------|------|
| `skipWords[]` | collin **权威存储**`type`: `person` / `place`(可扩展 `org` |
| `nameList` | **API 输出** 时由 `skipWords[].token` 去重生成为 TypeWords 字段(可不落库重复存) |
Lesson5 示例映射:
```
skipWords → nameList: ["James", "Scott", "Silbury", "Pinhurst"]
Mr 已由 TypeWords 内置跳过)
```
### 练习端行为
```mermaid
flowchart LR
API["GET 课文 API"]
Map["skipWords → nameList"]
TW["TypeWords TypingArticle"]
Skip["isNameWord 自动 next"]
API --> Map --> TW --> Skip
```
- **默写 / 听写**:同一套 `nameList` 逻辑
- **UI 可选增强**(后续):按 `skipWords.type` 在课文预览里给人名/地名不同样式(不影响是否跳过)
### 导入时谁维护 skipWords
1. **人工标注**(推荐 MVP导入 NCE 时在源 JSON 或 Excel 补一列「免敲词」
2. **半自动**:导入脚本从 `text` 匹配已知的本课 `course_article_vocab` 之外的专名词表(准确度有限)
3. **勘误**:只改 `content_json.skipWords``content_version + 1`
**不要**单独建 `course_article_skip_token` 表 — 与「几乎不改」的 JSON 方案一致,免敲词随课文打包即可。
---
## 课文正文content_json不变
| 层级 | 存储 |
|------|------|
| 正文 | `course_article.content_json` |
| 元数据 | `course_article` 列:`lesson_no`, `title`, `audio_url`, `status` |
| 生词 | 关系表(一词一行 + 义项) |
| 词典 | `word` / `dict` |
`content_json` **不含** `newWords`**含** `skipWords`(人名/地名)。
完整示例:
```json
{
"title": "A private conversation",
"titleTranslate": "私人谈话",
"text": "...",
"textTranslate": "...",
"audioSrc": "...",
"lrcPosition": [],
"skipWords": [
{ "token": "theatre", "type": "place" }
],
"quote": null,
"question": null
}
```
---
## 与 TypeWords 字段对照
| TypeWords `Article` | collin 来源 |
|---------------------|-------------|
| `title`, `text`, `textTranslate`, `lrcPosition`, `audioSrc` | `content_json` |
| `nameList` | API 由 `skipWords[].token` 生成 |
| `sections` | 前端 `genArticleSectionData(text)` |
| 生词 | 独立 API不进 JSON |
---
## 对迁移 / API 的调整
1. `course_article.content_json` 文档化 **`skipWords`** 结构
2. `ArticleApiService`:读 JSON → 填充 `nameList` → 返回 TypeWords DTO
3. NCE 导入 Command支持从源文件 `nameList` 迁移,或从 sidecar 配置读 `skipWords`
4. 废弃 `course_article_sentence`(正文不再用句子表)
---
## 实施优先级
1. 定稿 `content_json` schema`skipWords`
2. NCE 导入 + 首批课文手工标注免敲词
3. GET 课文 API`skipWords``nameList`
4. 生词 API与正文分离
5. 勘误PATCH `text` / `translate` / `skipWords`