Files
lexgo/docs/02-architecture-and-code-map.md
T
ila 527d8af0c4 docs: record issue 7 acceptance and merge (#7)
- Home、Project-Profile、Architecture-and-Code-Map、Product-Requirements-Overview
  记录 #7 于 2026-09-11 通过用户验收,PR #26 已 fast-forward-only 合入 main
- 功能提交 8c0946a 与用户验收版本一致;本次只更新验收状态文档与镜像
2026-09-11 20:29:33 +08:00

27 KiB
Raw Blame History

generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Architecture-and-Code-Map wiki_url: https://git.ilapage.cn/OPC/lexgo/wiki/Architecture-and-Code-Map.- wiki_revision: 02b7fc12b222c0050652443ca1368730f35f5652 synchronized_at: 2026-09-11T12:28:55Z

架构与代码地图

项目定位

Go 承担业务与后台任务,浏览器提供阅读学习界面,NLP 保留独立边界是现有分析的建议。用户区与管理区共享身份服务,但功能权限与个人数据所有权分别校验。

代码地图

现有位置 内容 能证明什么
docs/01-LinguaCafe需求提取.md、docs/linguacafe-requirements.md 两份需求研究 参考需求与上游证据索引
docs/02-Go复刻与框架选型分析.md、docs/linguacafe-go-analysis.md 两份设计建议 候选方案,含未解决差异
docs/ 核心页面 Gitea Wiki 的单向镜像 仅文档与治理准备,不是产品实现
dev_scripts/harness.py、dev_scripts/wiki_docs.py 原样复制的 DevHarness 工具 治理工具入口,无业务 API
tests/ 上游治理工具与文档结构测试 不验证阅读、NLP 或 SRS

工程入口与已实现模块见下方 #2/#18;学习领域拟定职责:identity(身份)、library(书库)、ingestion(导入)、lexicon(全局词典)、vocabulary(个人词语)、review(复习)、progress(统计)、administration(管理)。底座固定后再决定具体目录。

两条主要执行路径

目标路径一:上传文本 → 创建有所有者的导入任务 → 提取/分词/索引 → 章节就绪 → 阅读器按 token 展示原文和用户词语状态。

目标路径二:阅读保存词语或短语 → 写入个人学习状态与例句 → 查询到期词条 → 服务端计算作答后的状态 → 写入幂等事件 → 更新统计。

不可破坏的边界

  • 词典资源与个人释义分离,共享缓存不能混入个人记录。
  • Go/Python/JavaScript 的偏移契约必须统一;原文与内容版本必须保留。
  • API 与 Worker 复用业务规则;NLP 通过明确契约调用,不泄露模型内部对象到前端。
  • 任务至少可重试而不重复产出;学习事件不重复计数。
  • 共享 API/NLP/状态契约在批准后的主题 Wiki 中建立唯一来源,字段和版本尚未定案;不能以本页概要直接生成冻结接口。

管理端选型落地边界

管理端采用用户指定的 D:/github_project/goadmin,前后端固定 commit 见项目档案。后端现有入口为源仓库 main.go,业务分布于 app/cmd/common;管理前端入口为 go-admin-ui/src/main.js,页面位于 src/views,路由/API 封装位于 src/router、src/api。这些是外部参考仓库路径,尚未复制或运行于 LexGo。

当前 #2 已固定首批 LexGo 目录和身份衔接契约;通用管理能力可复用,用户与语言的数据所有权检查由业务层实现。既有分析中 gin-vue-admin 的推荐已被本次用户决策替代。

学习端与管理端的目标架构

以下是目标结构;账号、管理端与学习空空间已由 #2 实现。英语 NLP 已完成 #3 独立验证,尚未接入业务 API。

flowchart TD
    L[学习端 Vue 3 / TypeScript / Vite] --> API[共享 Go API:基于 go-admin 扩展]
    A[管理端 go-admin-ui] --> API
    API --> B[独立学习业务模块]
    B --> DB[(MySQL 8)]
    B --> W[后台任务:具体选型待验证]
    W --> N[NLP:Python 小样已验证,生产集成待实施]
交付部分 建设方式 复用与自建边界
学习端 create-vue 新建独立 SPA 工程 复用 Vue Router、Pinia、Element Plus;定制书库、阅读器、划词与复习组件
管理端 指定本地 go-admin-ui 二次开发 沿用现有 Vue CLI/Vuex 工程,按 LexGo 需要适配管理页面
共享后端 指定 go-admin 后端扩展为模块化单体 复用适配后的身份、权限与管理能力;新增 library、vocabulary、review、progress 等业务模块

两个前端分别构建,共用账号体系、API 与数据库。角色权限和数据所有权分别校验,隐藏路由不能替代服务端授权。共享业务规则由后端维护;两端不各实现一套词汇或复习状态规则。

部署方向为同域名 /app 学习端、/admin 管理端、共享 API,首期不拆业务微服务。这些前缀是部署建议,具体网关、静态资源 base、路由回退和登录方式在接口/部署设计中验证后固定。学习端独立工程不要求单独部署后端。

先按浏览器响应式界面设计电脑与手机使用;PWA 保留为后续候选,完整离线同步不在本次技术确认中。最终页面和 MVP 范围将在后续需求讨论中确定。

go-admin 模块参考与原型边界(2026-09-10)

以下来自已记录固定版本的本地源码检查,尚未执行配套构建。

LexGo 内容 参考来源 复用边界与设计证据
账号、角色、菜单和 API 权限 后端 app/admin 的 sys_user/sys_role 路由、API、service/DTO/model;前端 src/views/admin/sys-user、sys-role 两端用户名+密码登录;账号页面沿用表格/弹窗,字段说明即可;学习接口另加强制用户归属
语言资源列表、配置 SysConfig、SysDict 与管理前端对应列表/表单 复用表单模式;SysDict 是系统枚举,不能作为自然语言词典;简单配置不另画全套原型
文本处理状态 app/jobs 的 SysJob 状态操作可参考 原版是 cron 任务管理,不能作为可靠文本队列的证据;新任务需明确持久状态、恢复和幂等
学习领域 API 与表 router/apis/service/dto/models、版本迁移机制 沿用结构,独立设计词条身份、归属、原文位置与复习事务;后端无需 UI 原型
阅读、查词、短语、复习 已验收 Quant-UX v1 与运行划词小样 使用定制学习组件,主要交互改变时补关键原型;真实触摸/连续选择必须运行验证

源码检查发现 common/actions/permission.go 允许关闭 EnableDP,且 DataScope 默认分支不添加过滤;该机制用于管理数据范围,不能单独保证私人学习数据隔离。学习接口必须强制按认证用户归属查询,不允许客户端指定用户替代认证身份。

仅当管理流程引入多步安装、处理中/失败重试等明显交互不确定性时,补关键状态原型。通常的列表、编辑、删除确认沿用 go-admin 表格、表单和弹窗,记录字段、权限、异常和验收即可。

已实现工程入口(#2)

路径 职责
server/cmd/lexgo/main.go 配置连接、migrate/bootstrap/serve 命令;默认只监听 127.0.0.1:8000
server/app/admin/models、server/common/models 按 upstream.json 原样选用的 go-admin 模型和约定
server/app/lexgo/database.go MySQL 8 显式版本迁移、所有权标记、启动检查
server/app/lexgo/service.go 用户名校验、账号创建、bcrypt、随机会话摘要和撤销
server/app/lexgo/router.go JSON 请求、角色和归属授权、API 路由和安全错误响应
server/app/lexgo/*_test.go 真实 MySQL 隔离、撤销、并发创建、迁移拒绝/恢复、bootstrap 验证
admin/src/views、admin/src/session.mjs 基于 go-admin-ui 的账号页面与管理端会话
learner/src/views、learner/src/stores/session.ts 独立学习端登录与私人空空间、会话及迟到响应隔离
scripts/server.py 安全读取本机 .env.local,传入子进程;不输出秘密

账号 API v1

请求使用 JSON;受保护接口使用 Authorization: Bearer。成功结构 {code:200,data:...},创建账号 HTTP 201;失败使用实际 HTTP 400/401/403/404/409/429/500 和 {code:状态码,msg:必要提示}。响应 Cache-Control:no-store。

方法与路径 行为和权限
POST /api/v1/login username/password,返回 token、expiresAt、user;不要求邮箱
GET /api/v1/me 当前登录者 id/username/role/disabled
POST /api/v1/logout 撤销当前会话
GET /api/v1/space 当前用户英语空间;拒绝查询参数指定用户
GET /api/v1/spaces/:id 仅本人的空间可读,他人编号返回 404
GET /api/v1/accounts 管理员查看账号列表;只输出必要字段
POST /api/v1/accounts 管理员创建 learner;拒绝客户端 role 等未知字段
PATCH /api/v1/accounts/:id 管理员启停或重置 learner 密码;不能修改自己或其他管理员
GET /healthz 检查数据库连接,仅返回健康状态

schema v1:sys_user 保留选用 go-admin 模型字段,唯一小写用户名;lexgo_spaces 以 owner_id 为主键;lexgo_sessions 保存 token_hash/owner_id/expires_at;lexgo_schema 记录版本与产品所有权。服务启动不自动迁移。迁移仅接受空库或合法已有 LexGo marker,拒绝空 marker、其他产品、负版本与未来版本;版本 0 可重试部分迁移,版本 1 幂等。

凭据字段只存在本地环境和必要数据库哈希中。后台账号密码更新使用表/字段更新,避免上游 BeforeUpdate hook 对已有哈希再次加密。账号行再会话行的锁顺序用于串行化撤销与请求;API 在事务提交后才返回成功。

登录与操作审计(#18)

server/app/lexgo/audit.go 定义两类白名单字段日志、筛选分页、失败记录和过期清理;router.go 在登录/账号操作边界接入。登录成功时会话与日志同一事务;账号操作成功时业务写入与日志同一事务;失败时先回滚业务,再以独立、有 3 秒超时的事务写失败记录。日志写入失败返回通用 500,不输出数据库原始错误或凭据。

database.go 显式迁移至 v2,两张新增表均以 created_at/id 建立排序清理索引,账号字段建查询索引,无业务表级联删除。cmd/lexgo/main.go 的服务进程在启动和每小时执行审计清理,每次最多运行一分钟、每批删除 1000 条,仅影响过期审计记录。

admin/src/views/AuditLogs.vue 通过 kind 复用登录/操作列表;audit-logs.mjs 负责筛选编码和请求序号,session.mjs 继续进行管理员及会话 generation 校验。切换页面/账号清空日志,普通翻页保留总数,防止分页组件跳回第一页。菜单与标题按当前路由显示。

英语分词与本地词典验证(#3,已验收)

spikes/english/ 是独立可运行验证小样,不是学习端生产功能。推荐后续采用 Python 3.12.12、spaCy 3.8.7、en_core_web_sm 3.8.0(保留 tok2vec/tagger/attribute_ruler/lemmatizer,停用 parser/ner)和 NLTK 3.9.2 读取 WordNet 3.0。Go 继续管理用户、权限、任务和持久数据,后续通过显式契约调用 NLP;本单未新增 Go API、MySQL 表或常驻部署实例。

文件 作用
engine.py 原文分词、lemma、三个位置单位、直接/lemma 查词;只读本地资源
app.py、index.html、app.mjs、view.mjs、style.css loopback 临时 HTTP 小样、输入/阅读/查词结果;单进程串行,输入不落盘
resources.json、setup_resources.py、requirements.lock 固定版本、来源和 SHA256;显式联网准备,运行期无自动下载
test_engine.py、test_app.py、view.test.mjs 真实模型离线验证、HTTP 边界与浏览器偏移/迟到响应测试
benchmark.py、benchmark-result.json 虚构语料的候选对照、长文/查询实测及环境样本

WordNet 使用 ZIP 内原始 index/data/exception 文件,不使用 SysDict 或新增业务库。NLTK 默认 synsets 会隐式词形还原,本小样直接读取其固定版本索引以区分 exact 和显式 lemma;禁用依赖全局 corpus 的 OMW 跨版本映射,只接受 WordNet 3.0。升级 NLTK 或词典时必须重跑契约测试。

候选比较:正则分词+WordNet 默认名词 morphology 依赖少、速度快,但不具备上下文判断,缩写和词性歧义处理弱;纯 Go 规则同样需要自行维护这些语言规则。本次 spaCy 在 12 个明确样例中答对 11 个,基线 6 个,因此推荐保留独立 Python NLP 边界。样例量不足以证明总体准确率;不宣称部署或正式阅读功能已完成。

#4 阅读选择小样与 LinguaCafe 对照

小样位于 spikes/selection:serve.py 只提供白名单静态文件,app.mjs 负责 DOM 原生选择/键盘/面板状态,range.mjs 负责原文范围和匹配,fixtures.mjs 提供两章虚构文本。保存仅在内存 Map,刷新清空,不接入账号、MySQL、正式词典、复习或 #21 附件。使用 Intl.Segmenter 的词与字形边界验证 UI,不能替代 #3 的 spaCy 结果;正式阅读器必须以章节原文、内容版本与 NLP tokens 为共同基准。

参考版本固定 LinguaCafe c1ea298ce40c65b9dd33e9b26fd2e52fae66f2c8。本次实际读取以下源码;未启动 LinguaCafe、未亲测其浏览器或手机行为。下表“参考行为”是源码证据;LexGo 方案是独立实现与取舍,不应标为上游已经验证的体验。

项目 参考行为与来源 LexGo 采用及保留差异
阅读器 TextBlockGroup.vue L56–100 用词/短语 stage、selected、hover 等状态;InteractiveTextStyling.scss L20–126 由主题决定颜色。TextReaderChapterList.vue L15–43 展示章节统计及已处理章节阅读入口;TextReader.vue L195–214 提供阅读完成后的书库/下一章 保留正文上下文、词语状态与选中区分、章节切换;小样以两章和学习中/已认识/忽略验证,章节统计及已读持久化仍属后续功能。侧栏/底部面板为 LexGo 布局,不宣称复制上游排版
查词面板 VocabularyBox.vue L124–169、374–404 区分 Translation 与字典搜索,新短语需 Save phrase;TextBlockGroup.vue L1351–1380 失选时自动保存单词/已有短语。VocabularySearchBox.vue L149–179 及保存 catch 未证明完整错误反馈 沿用 v1 的词典释义/我的释义、明确保存、关闭继续阅读。LexGo 关闭不自动保存;保存后可见反馈,无词典/无结果仍可手填。小样用固定虚构释义与故障状态,不把它当成真实词典/网络重试验证
短语与键盘 TextBlockGroup.vue L399–599 自定义鼠标范围;L374–457 为手机 500ms 长按及后续触摸移动,选区开始后阻止默认滚动;L1071–1202 的 Shift+方向键跳高亮词,而非扩展范围,Esc 失选 LexGo 采用原生鼠标拖选、手机长按/系统手柄,不拦截 touchmove;起止端点按钮可调整。←/→ 相邻词,Shift+←/→ 扩缩范围,Esc 取消。原型的预设短语按钮被真实正文选区替代,属于 #4 明确要求的验证;手机手柄是否与底部面板冲突仍待真机
复习 Review.vue L270–345、559–692 为 Reveal→I was correct/Again,正确移除卡、Again 保留并随机抽剩余卡,最后一张正确完成;ReviewHotkeyInformationDialog.vue L14–24 提供快捷键。练习模式不写状态 已验收 v1 保留中文显答、答对/答错、再学与完成,单词/短语分别有状态。上游随机下一卡、阶段降级和快捷键不是本单已实现行为;#8 再明确调度、重学和幂等,#4 不新增复习引擎

源码链接:

上游 LICENSE 为 GPL v3,本单只核对并描述行为,没有移植源码。#1 评论 7497 的四项对照登记由本节补充;原型 v1 的保存/关闭主要流程保持,不更改其历史验收记录,不声称已经在 Quant-UX 新建修订版。范围调整与原生手柄作为可运行小样验证,若真机结果导致主要流程变化,应先更新关键原型状态并由用户确认。

粘贴导入、章节与阅读(#5,schema v3)

#5 实现了目标路径一的第一段可运行链路:粘贴英语文本 → 持久导入任务 → 固定分章 → 处理中/就绪 → 本人阅读原文。Go 单进程同时承担 API 与后台任务。本单不接入 Python NLP,token、lemma 与词典索引仍待 #6,路线未决边界见业务规则页。

路径 职责
server/app/lexgo/database.go schema v3 显式迁移:lexgo_books、lexgo_chapters、lexgo_ingest_jobs;按版本累加语句,版本行只在全部语句成功后推进
server/app/lexgo/library.go 粘贴校验与固定分章、书籍/章节/任务写入、本人归属查询、重试与请求幂等
server/app/lexgo/ingest.go 任务声明 claim、处理完成、固定失败原因、启动恢复
server/app/lexgo/router.go 新增书籍/章节/任务路由;粘贴请求使用独立的 4 MiB 体积上限
server/cmd/lexgo/main.go serve 启动时恢复遗留任务,并按秒轮询处理待处理任务
learner/src/stores/library.ts、views/ImportView.vue、BookView.vue、ReaderView.vue 粘贴导入、书库与章节状态、失败重试、原文阅读

任务状态为 pending → processing → ready/failed,章节与任务共用同一套状态词。声明与完成分属两个事务:声明一经提交,即使进程随即退出,也只会留下可被启动恢复重新入队的 processing 记录。

粘贴导入 API v1(#5)

方法与路径 行为和权限
POST /api/v1/books {requestId,title,text,language?};创建书籍+首个章节+导入任务;重复 requestId 返回首次结果(HTTP 200,duplicate=true)
POST /api/v1/books/:id/chapters 向本人书籍追加一个章节
GET /api/v1/books 本人书库与章节状态计数;拒绝查询参数,避免用参数替换认证身份
GET /api/v1/books/:id 本人书籍与章节列表,含 jobId 与可读失败原因
GET /api/v1/chapters/:id 本人章节详情;仅 ready 时返回 originalText,并附带前后章节编号
GET /api/v1/jobs/:id 本人任务状态、尝试次数与失败原因
POST /api/v1/jobs/:id/retry 仅失败任务可重试;复用同一章节,不新建章节

所有接口按认证身份过滤 owner_id;他人书籍、章节或任务编号统一返回 404,管理员角色也不能解除学习数据的本人归属过滤。后台任务只使用任务行内的 owner_id,不接受客户端用户编号;请求体含未知字段(例如 ownerId)直接返回 400。

schema v3

lexgo_books(owner_id, title, language)、lexgo_chapters(book_id, owner_id, ordinal, title, original_text MEDIUMTEXT, char_count, content_sha256, status, error_reason) 与 lexgo_ingest_jobs(owner_id, book_id, chapter_id, request_key, content_sha256, status, attempts, error_reason, finished_at)。owner_id 在章节与任务上冗余存放,使任何查询都能直接按认证身份过滤而不依赖连接;UNIQUE(book_id, ordinal) 与 UNIQUE(owner_id, request_key) 分别阻止重复章节与重复提交。启动检查要求版本 3,服务不自动迁移。

并发重复提交:请求命中 request_key 唯一键冲突后,用加锁读读取已提交结果,因为该请求事务的快照早于并发提交;因此两个并发相同提交只会产生一个章节,另一个得到 duplicate=true 的首次结果。

#5 审核整改(R1~R4,2026-09-11)

提交见工单 #5 的整改评论;本条记录实现与验证方式。

  • 追加契约(R1):学习端把新建与追加拆成两个请求类型,追加不发送 language;后端保持严格解码,并新增回归测试断言“追加带 language 返回 400、不带则 201”,学习端单测断言追加请求体只有 requestId/title/text。
  • 运行期任务恢复(R2):server/app/lexgo/ingest.go 的恢复逻辑合并为一处——启动恢复使用阈值 0,运行期每轮清扫使用 15 秒阈值并把超过 5 次尝试的任务置为 failed(原因码 attempts_exhausted);cmd/lexgo/main.go 的 worker 每秒先清扫再处理,日志分别说明“已重新入队”与“本批未完成、等待下一次清扫”,不再声称已完成实际跳过的重试。
  • 离页作废在途请求(R3):closeBook/closeChapter 推进请求序号并清理 loading;ImportView 记录是否已卸载,卸载后的成功响应不再触发跳转。
  • 重试自愈(R4):retryChapter 先把重试返回的章节状态应用到列表与阅读器并重新安排轮询,再做静默刷新。

验证:Go 全量用例 20 项通过(新增运行期恢复与尝试上限两项);学习端单测 38 项通过,其中 7 项在整改前的代码上复现失败;真实联调确认追加路径可用、被中断的任务在运行中被自动恢复(约 0.5 秒,无需重启)、重试在首次刷新失败后仍自动显示最终结果。

全 Go 正式架构决定(2026-09-11,#6)

用户已明确选择全 Go:正式英语分词、原文位置映射、本地词典解析和词形候选查询由 Go 后端完成,不运行 Python NLP 服务。此前“Python 建议/全 Go 未决”仅为历史决策记录,由本决定覆盖;spikes/english 保留历史验证,不接入产品。#6 按该方向实施,当前方案见工单最新启动评论;WordNet 3.0 仍为首个资源(英语释义),词形规则候选不等同于 spaCy 上下文消歧,原文及个人学习状态不按候选合并。

#6 全 Go 词典与阅读器(2026-09-11,已验收并合入 main)

server/app/lexgo/wordnet.go 负责固定 WordNet ZIP 校验/内存解析、Unicode 分词及词形候选;dictionary.go 负责资源与章节查词 API;database.go schema v4 新增单槽共享资源表 lexgo_dictionaries(元数据、enabled、SHA、ZIP LONGBLOB),已有学习数据不改写。每个 Router 按 SHA 缓存一个不可变词典,查询先读资源元数据,缓存未命中才读取 ZIP;进程重启从数据库恢复,不需要 Python NLP 或额外资源目录。

接口 权限与输入/输出
GET /api/v1/dictionaries 管理员;items + supported,状态 ready/disabled/unavailable,不返回 archive 或本机路径
POST /api/v1/dictionaries/import 管理员;multipart name/language/version/source/format/file;返回 resource + duplicate;格式/来源/版本固定
PATCH /api/v1/dictionaries/:id 管理员;{enabled:boolean};返回 resource
GET /api/v1/chapters/:id/tokens 本人 ready 章节;{textSha256,tokens:[{text,start,end,startUtf16,endUtf16,kind}]}
POST /api/v1/lookup {chapterId,start,end},cp半开范围;本人ready完整单词;返回 status/query/matchedForm/candidates/entries/resource

管理端 Dictionaries.vue + dictionaries.mjs + session multipart 方法,复用 go-admin 导航/表单与身份失效保护。学习端 useReaderLookup.ts 校验原文片段/SHA/所有位置,ReaderTokens.vue 渲染可聚焦单词,LookupPanel.vue 展示释义及临时个人草稿。桌面侧栏,手机固定底部45dvh面板;关闭恢复焦点,仅无后续手动滚动时恢复自动调整前位置。旧响应在换词/换章/退出/离页后失效。

参考:WordNet 数据格式、词形规则。#3 仅历史实验,#6 不调用其实验服务。

#7 个人词条与阅读器状态(2026-09-11,已验收并合入 main)

schema v5 新增 lexgo_terms:一个学习者对一个词形一条记录。身份键为 (owner_id, language, term),term 是 Go 侧 normalizeWord 的结果(NFC、小写、弯撇号转直撇号),列使用 utf8mb4_bin,避免折叠 resume/résumé;original_form 保存最近一次保存的原词形供显示。definition/examples 是学习者自己的文本,例句按行存储;共享词典仍只在 lexgo_dictionaries,两者不混存。status 与 level 由数据库检查约束守住:只有 learning 允许 1~7,其他状态必须为 0。

接口 权限与输入/输出
POST /api/v1/terms 本人;{chapterId,start,end,definition,examples[],status,level?};服务端按 #6 同一套 token 范围反推词形,owner/language/term 一律不接受客户端输入;唯一键 upsert,重复保存更新同一行;首次 201、更新 200,返回 {term,created}
GET /api/v1/terms/:id 本人;他人编号与不存在编号统一 404,不泄露存在性
GET /api/v1/chapters/:id/tokens 在原响应上为 word 片段增加可选 term:{id,status,level};服务端按本章词形分批(每批 500)查本人词条,其他章节保存的同形词同样命中

server/app/lexgo/terms.go 负责身份、状态/等级边界、文本上限、upsert 与章节点词状态;database.go 提供 v5;dictionary.go 的 tokens 与 lookup 共用 wordAtRange,保存与查询必须落在同一个完整单词范围上,所以客户端无法命名自己没有读到的词。个人词条不写审计日志。

学习端 useReaderLookup.ts 在原有查询状态上增加个人释义、例句、状态、已保存编号、预填与保存;打开已保存词先读 GET /terms/:id,读取失败时禁用保存,避免用空表单覆盖原内容。LookupPanel.vue 提供状态单选、释义与例句输入、保存与简短反馈;ReaderTokens.vue 按状态高亮 is-new/is-learning/is-known/is-ignored。换词、换章、离页、退出或切换账号都会清空表单、状态与高亮。管理端无改动。