chatontcf App v1 · 首页与单词 · 交互说明
2026-11-13(太平洋时间)。方向 D(卡片活泼)+ 冷白底 #F5F6FA,无光效(光效只留给以后的音乐页)。
这份文档给 Cursor 用:照着在 frontend-app/(Expo 58 / RN 0.88 / Reanimated 4 / Gesture Handler 3)里实现。
0. 文件和用法
| 路径 | 用途 |
|---|---|
index.html + proto/ |
可点击原型。顶栏切换 iPhone / iPad 竖屏 / iPad 横屏,下拉直接跳到任意一屏。 |
functions/api/[[path]].js |
Cloudflare Pages Function,把 /api/* 转发到查词服务,key 放在 Pages 加密变量 WORD_API_KEY 里,不进前端文件。 |
html/tokens.css · tokens.json |
设计 token(颜色、字号、间距、圆角、阴影、动效、触感)。RN 落到 lib/theme/index.ts。 |
html/components.css · proto/proto.css |
组件样式。类名对应下面「组件表」里的 RN 组件。 |
html/NN-*.html |
每一屏的静态 DOM 快照(脚本已去掉),给 Cursor 读结构和 class。 |
png/NN-*.png |
每一屏 2x 截图;png/00-overview-board.png 总览板。 |
contrast-report.md |
WCAG AA 对比度检查(40 组,全部通过)。 |
sample-data/ |
实时接口抓下来的真实响应(frapper、épanouissement、néanmoins、联想 frapp / fra / 开花、发音),已去掉 usage 字段。 |
src/capture.py · src/contrast.py |
重新截图、重新检查对比度的脚本。 |
原型里实时接口的数据带绿色「实时接口」标;没有接口或要登录的数据带黄色「示意」标。原型 URL 可以带参数直达,例如 #r=word&w=frapper&device=ipad-l。
1. 关键设计决定
- 首页 = 任务仪表盘。 一张深色「今日计划」大卡:新词 / 待复习两个大数字、本周学习柱、今日新词进度(
new_introduced_today / new_goal)、一个「开始学习 · 约 N 分钟」按钮。分钟数按 复习 12 秒/个 + 新词 30 秒/个 估算。下面是 D 风格的便当格(继续收听、课本、连续打卡)和 TCF 四项色块。全部做完后按钮变成「今天学完了 · 再来一组」。 - 查询 = 一个输入框做完所有事。 单词 / 句子分段切换;空闲时显示最近查询、TCF 高频词建议、查询统计;输入 220ms 防抖后出联想(前缀加粗、变形来源「来自变形 fraie」、中文反查显示中文释义);回车查第一个。麦克风只在单词模式。
- 单词详情 = 网页
WordResult的全部分区,换成卡片 + 吸顶分区条。 顺序不变:单词 → 主释义 → 详细释义 → 英语桥梁 → 例句 → 变位 → 学习辅助 → 权威英语 → 我的备注(已收藏时)。分区条随滚动高亮当前分区,点击平滑滚动。详细释义默认每个词性只展开前几条,「展开全部 26 条」。变位改成「时态胶囊 + 两列表格」,不再是网页的长折叠。 - 法语特性做进界面。 名词阴阳性用颜色标签(阳 蓝 / 阴 玫红,文字同时写「阳性 n.m.」,不只靠颜色);查到变形词时提示「你查的 frappe 是 frapper 的变形」并可跳原形;词频 10 格条,≥7 标「TCF 高频」;IPA 用 Inter(Outfit 没有 ʁ ɑ̃ 字形)。
- 点任意词都能查。 例句、详细释义里的例句、复习卡背面例句、句子精讲原句,每个词都可点,弹出同一个「点词浮层」:原形、音标、词性、阴阳性、主释义三行、发音、一键加生词本、「详细释义 ›」。这是对标「每日法语听力」最该借的一点:单词模块不是孤岛。
- 背单词卡片跟着手指走。 点卡片翻面(3D rotateY 420ms);右滑 = 认识,左滑 = 忘记,拖动时出现「认识 / 忘记」印章;翻面后四档评分,按钮下写下次间隔(10 分钟 / 4 天 / 8 天 / 10 天),让人知道每一档的后果;右上角撤回上一张。键盘:空格翻面、1–4 评分、⌘Z 撤回(iPad 外接键盘)。
- 小结要能接着学。 完成率环、四档计数、「再练 N 个忘记 / 模糊的词」、本组单词和下次复习时间,点词进详情。
- 本子列表照网页:搜索 + 全部 / 新词 / 待复习 筛选,加上到期提示卡(「5 个今天到期 · 去复习」)、状态标签(新词 / 学习中 / 今天 / 过期 N 天 / N 天后)、左滑移除(带撤销)。
- iPad 不是放大的手机。 宽度 ≥ 720 换左侧导航栏(横屏 220,竖屏 84 只放图标+短字)。查询和本子是「列表 + 详情」两栏,左栏不动、右栏换内容;首页横屏两列、竖屏一列;复习和小结居中限宽 560。
- 所有写操作先改界面再等服务器,都带撤销:收藏、移除、评分(撤回)。失败时改回去并提示。
2. 现有交互 → 新设计(对照表)
先盘点了 tcf_canada/frontend-web(网页)、frontend-app(App 骨架)和后端 /api/app/v1/,新设计保留全部现有行为和数据,只改界面和手感。
| 现有(文件) | 现有行为 / 数据 | 新设计里的样子 | 变化 |
|---|---|---|---|
components/words/WordSearch.tsx |
输入防抖调 dict/search?q=&limit=8;↑↓ 选、回车选中或第一个、Esc 关;中文走反查(mode=zh 显示 gloss);变形词显示 via.surface |
查询页联想列表(05、06、12) | 保留全部键盘行为;前缀加粗;via.tags 前三个写成中文提示;英文词性 noun/verb 映射成中文;无结果时给「直接查」 |
components/words/WordLookup.tsx + wordSession.ts |
查词结果存 sessionStorage;记录查询事件到 stats/lookups/,显示「全站 x 次 · 你 y 次」 |
最近查询胶囊(本地存储,可清空);详情头部的查询次数 | 新增「最近查询」列表(只在本机,见缺口 G3) |
components/words/WordResult.tsx |
分区:单词(词、音标、词形切换、summary、原形/词性/阴阳性、词频条)、主释义、详细释义、英语桥梁、例句(中/法/英)、完整变位(折叠)、学习辅助(用法、记忆、词源、搭配、近/反/派生词)、权威英语 | 单词详情 07–11 | 分区和字段一个不少;变位改时态胶囊;近义/反义/派生词可点跳转;例句的法语放第一行(网页是中文在前),每句带朗读 |
components/words/WordSectionStrip.tsx / WordSectionNav.tsx / useActiveWordSection.ts |
分区导航,滚动高亮,hash 定位 | 吸顶分区条(sticky chip strip) | 同一逻辑,改成横向胶囊,吸顶后出现下边线 |
components/words/WordLemmaSwitch.tsx |
lemmas[] 多个候选时切换原形(explain?lemma=) |
头部候选胶囊 + 「是 xx 的变形 ›」提示条 | 保留 |
components/words/WordAudioPlayers.tsx |
word-audio 返回男女声各一条,按性别标签播放,同时只播一个 |
「音标 + 喇叭 + 女声」胶囊 + 「男声」胶囊;播放中胶囊变实心紫 | 合成一个胶囊(沿用 v1 规范「音标和喇叭合一」) |
components/words/WordVoiceInput.tsx |
浏览器语音识别查词 | 麦克风按钮 → 语音底部弹层(声波 + 识别中文字) | App 用 expo-speech-recognition(fr-CA),不可用时不显示按钮 |
components/vocabulary/SaveWordControls.tsx |
加入 / 移除生词本;下拉选本子,记住 last_notebook_id;add_lemma_with_inflected 时同时加原形并提示 |
详情底部操作栏「加入默认生词本」+ 文件夹按钮选本子;长按主按钮也能选本子;Toast「已加入默认生词本,明天开始复习 · 撤销」 | 增加撤销;选本子从下拉改底部弹层(iPad 居中弹窗) |
components/vocabulary/StudyPageClient.tsx |
取 study/summary、study/queue;正面词+音标+发音,点击翻面看释义和例句;三档 again/hard/good;完成页显示复习数 / 新词数、再来一组;空队列提示 |
背单词 14–18、空状态 28 | 加滑动、四档(「简单」需后端,见 G1)、间隔预告、撤回、分段进度、小结页再练弱项;翻面背面加记忆提示和我的备注 |
components/vocabulary/WordsPageClient.tsx |
本子筛选、搜索、全部/新词/待复习、到期日、备注编辑、移除、更新释义、详情弹窗 | 本子 19–22、单词详情里的「我的备注」卡 | 备注失焦自动保存;移除改左滑;详情手机上是新页、iPad 是右栏 |
components/vocabulary/NotebooksPageClient.tsx |
新建、改名、删除本子 | 本子页右上「+」新建;长按本子胶囊出操作菜单(改名 / 删除) | 用系统弹窗和操作菜单 |
components/vocabulary/EntryDetailDialog.tsx |
词条详情弹窗 | 合并进单词详情(同一组件,多一张备注卡) | 不再单独做弹窗 |
components/sentences/SentenceLookup.tsx / SentenceResult.tsx |
句子精讲:整句翻译、分块、语法点、结构、词汇、连读、总结、焦点词 | 句子页 23–24(原句卡 + 翻译 + 分块 + 语法点 + 连读) | 原句每个词可点;结构/词汇/总结分区照网页加(原型只画了前几块) |
components/sentences/SentenceTokenSelect.tsx |
点选句中词作为精讲 targets,有上限(d.focusLimit) |
长按任一词进入多选,底部黑色条「已选 2 个词 · 精讲」 | 上限保持 3 |
components/sentences/SentenceAudioControls.tsx |
text-audio 先 check_cache,没有再生成;男女声;未登录要求登录 |
句子卡「女声朗读 / 男声」胶囊、例句左侧喇叭 | 生成中按钮呼吸闪烁 |
components/sentences/SaveSentenceControls.tsx |
收藏到句子本 | 句子卡「收藏到句子本」胶囊;例句长按收藏整句 | 新增长按 |
components/lookup/PracticeTextAssist.tsx |
真题原文里划词 → 可拖动的小词典面板:原形、词性、阴阳性、男女声、英语桥梁、学习辅助、加入生词本、详细释义 | 点词浮层(23) | 从「划选」改成「点一下」,手机上更顺手;浮层固定宽 300,在词的下方(上半屏)或上方(下半屏)出现 |
frontend-app/lib/theme/index.ts |
蓝色主题 #2F7BF6、底 #F3F5F9、圆角 16 |
tokens.json |
换成 D:紫 #5E5DD6、底 #F5F6FA、卡片圆角 20;评分色加深到 AA;结构不变(colors/spacing/radius/typography) |
frontend-app/(tabs)/_layout.tsx |
4 个标签 学习/查询/本子/我的 | 同 | 选中项用淡紫胶囊底;宽屏换侧栏,项目和顺序不变 |
frontend-app/lib/layout/ |
宽度 ≥ 720 = wide | 同 | wide 再按方向分横/竖(侧栏宽度不同) |
3. 全局规则
3.1 布局
- 手机:页面左右边距 18;卡片间距 10–12;分区标题上 22 下 10;底部标签栏 83(含 home indicator)。
- 宽屏(≥ 720):左侧栏横屏 220 / 竖屏 84;两栏的左栏横屏 360 / 竖屏 320;右栏自适应。分屏把窗口拉窄到 < 720 时回到手机布局,当前选中的词保持不变。
- 内页(单词详情、句子)在手机上是全屏推入页,不显示标签栏;底部固定操作栏。
- 复习、小结在手机上全屏无标签栏,宽屏也隐藏侧栏,内容居中限宽 560。
3.2 组件表(HTML class → RN 组件)
| class | RN 组件 | 说明 |
|---|---|---|
.tab-bar .tab |
(tabs)/_layout.tsx 自定义 tabBar |
选中:图标描边 2 + 淡紫胶囊 54×30 |
.rail .ri |
components/shell/SideRail |
本子项带待复习红色数字徽标 |
.card |
ui/Card |
白底、圆角 20、1px 描边 line,不加阴影 |
.card-dark.plan |
study/TodayPlanCard |
圆角 24 |
.pastel.{lilac,sky,mint,pink} |
home/BentoCard variant |
卡上一律深字 |
.btn-primary/-secondary/-ghost |
ui/Button variant |
高 48(sm 36),胶囊 |
.chip .chip.on |
ui/Chip |
高 32;选中为深色实心 |
.segmented |
ui/SegmentedControl |
选中块用白底 + 轻阴影,滑动 200ms |
.search-field |
ui/SearchField |
聚焦 2px 紫色描边 |
.list .list-row |
ui/ListRow |
行内分隔线左缩 14,不把每行做成卡片 |
.tag.* |
ui/Tag |
pos / m / f / exam / new / learning / review / due |
.audio-pill .audio-btn |
audio/AudioPill、audio/AudioButton |
播放中实心紫 |
.sec-strip |
words/WordSectionStrip |
sticky |
.wd-sec |
words/WordSection |
标题前 26×26 彩色小图标底 |
.tok |
words/TappableText 的单个词 |
见 4.6 |
.define-pop |
words/DefinePopover |
|
.fc |
study/Flashcard |
|
.grade.* |
study/GradeButton |
|
.sheet |
ui/BottomSheet(宽屏为居中 Modal) |
|
.toast |
ui/Toast |
带可选「撤销」 |
.sk |
ui/Skeleton |
1.2s 线性 shimmer |
.empty |
ui/EmptyState |
图标块 84×84 圆角 28 |
3.3 动效与触感(Reanimated / Haptics)
| 场景 | 参数 | 实现提示 |
|---|---|---|
| 按下反馈 | scale 0.97,120ms,standard | Pressable + useAnimatedStyle,或 Animated.View 包 withTiming |
| 页面推入 | 系统原生栈动画 | expo-router Stack,不自己做 |
| 分段控件 | 选中块平移 200ms,out | withTiming(x, {duration:200, easing: Easing.bezier(.22,.9,.25,1)}) |
| 分区条滚动高亮 | 跟随 scrollY | useAnimatedScrollHandler,分区 onLayout 记 y |
| 点词浮层出现 | 透明度 0→1 + 上移 6 + scale .98→1,200ms | entering={FadeInDown.duration(200)} |
| 卡片翻面 | rotateY 0→180°,420ms,out;正反面 backfaceVisibility:'hidden' |
两个 Animated.View 共享 progress shared value |
| 卡片拖动 | translateX 跟手,rotate = dx/22 度;印章透明度 = | dx |
| 卡片判定 | dx | |
| 评分 / 收藏 | 轻触感 | Haptics.impactAsync(Light);「忘记」用 notificationAsync(Warning) |
| 长按 | 480ms,中等触感 | Gesture.LongPress().minDuration(480) |
| 行左滑 | 露出 88 宽「移除」;超过 60 吸附 | Swipeable(Gesture Handler) |
| 底部弹层 | spring(damping 22, stiffness 240);遮罩 200ms | @gorhom/bottom-sheet 或自写 |
| Toast | 进 200ms;普通 2.6s、带撤销 4s 自动消失 | |
| 骨架 | 1.2s 线性 shimmer | withRepeat(withTiming(1,{duration:1200, easing: Easing.linear}), -1) |
| 进度条 | 宽度变化 320ms,out |
系统「减弱动态效果」打开时:翻面改成淡入淡出 150ms,卡片飞出改成淡出,shimmer 停止。
4. 每一屏
4.1 首页(学习 tab)· png 01–03
组件:HomeHeader(头像、问候、日期 + 今天查词次数、通知)→ TodayPlanCard → 便当格(ContinueListeningCard 跨两行、TextbookCard、StreakCard)→ TcfTile × 4。宽屏横屏两列:左「今日计划 + TCF 四项 + 我的本子」,右「便当格 + 今天要复习(前 5 个到期词)」。
| 元素 | 数据 | 来源 |
|---|---|---|
| 新词 / 待复习大数字 | new_remaining / due_count |
GET vocabulary/study/summary/?notebook_id=(不带 = 全部生词本) |
| 今日新词进度 | new_introduced_today / new_goal |
同上 |
| 本子选择胶囊 | 本子列表 | GET vocabulary/notebooks/;选择存本机(v1 规范) |
| 本周学习柱 | 每天学习分钟 | 缺口 G2 |
| 连续打卡 | 天数、本周 7 格 | 缺口 G2 |
| 今天查词 N 次 | your_today |
GET stats/lookups/summary/ |
| 继续收听 / 课本 / TCF 四项计数 | — | 缺口 G5(Podcast 进度只在网页 localStorage;课本无模型;真题正确率只有网页 /api/v1/) |
| 今天要复习(iPad) | 到期词 | GET vocabulary/entries/?status=due&limit=5 |
交互:点「开始学习」→ 复习页;点本子胶囊 → 底部弹层选本子(列出各本待复习数);点便当卡 / TCF → 对应模块(本批未设计,原型给提示)。计划全部完成:按钮文字「今天学完了 · 再来一组」,点了提前复习已学的词。
4.2 查询 tab · png 04–06、12–13
组件:大标题、SegmentedControl(单词 / 句子)、SearchField(清除、麦克风)、RecentLookups、LookupStats、SuggestionList。
状态:空闲(最近查询 + 建议 + 统计)→ 输入中(骨架 4 行)→ 有联想 → 无联想(「直接查 xx」)。句子模式:多行输入 + 「精讲这句话」+ 句子本里的句子。
| 元素 | 字段 | 来源 |
|---|---|---|
| 联想行 | word、ipa、pos、via.surface/tags、gender;中文模式 gloss / zh.term |
worker 外的 api.dict.chatontcf.com/api/dict/search?q=&limit=8(网页同款) |
| 最近查询 | 本机 AsyncStorage,最多 12 个,去重,最新在前 | 缺口 G3(后端没有查询记录列表) |
| 统计三格 | your_today、your_total、global_today |
GET stats/lookups/summary/ |
键盘:回车查当前高亮或第一个;↑↓(iPad 外接键盘)移动高亮;Esc 清空。查询成功后调 POST stats/lookups/(client_event_id 去重)记录一次。iPad:左栏保持输入框和联想不动,右栏显示结果;连续查词不用返回。
4.3 单词详情 · png 07–13
组件:NavBar(返回、数据来源标、更多)、WordHeader、WordSectionStrip、WordSection × N、WordActionBar。
| 元素 | word-explain 字段(schema v3) | 备注 |
|---|---|---|
| 词头 | explanation.word |
>11 字母 32px,>15 字母 26px,不断词 |
| 音标胶囊 | explanation.ipa(无斜杠时补 /…/) |
点 = POST word-audio {text} → items[gender=f].url;男声胶囊取 gender=m |
| 词性 / 阴阳性 | display.pos / display.gender(m/f) |
|
| 变形提示 | display.lemma ≠ query |
点原形 = 重新查 |
| 原形候选 | lemmas[](label、lemma) |
点 = word-explain/{word}?lemma= |
| 概要 | display.summary |
|
| 词频 | frequency(0–10) |
≥7 加「TCF 高频」 |
| 查询次数 | global_count、your_count |
GET stats/lookups/words/?word= |
| 主释义 | meaning.zh |
19/28 Semibold |
| 详细释义 | meaning.senses[].pos、definitions[].zh/fr/example{fr,zh} |
第一个词性展开 3 条、其他 1 条,「展开全部 N 条」 |
| 英语桥梁 | english_bridge.thinking、english_equivalents[] |
|
| 例句 | examples[].fr/zh/en |
默认 4 句,「再看 N 句」;喇叭 = text-audio;每个词可点(4.6);长按整句收藏到句子本 |
| 变位 | conjugations.infinitif/participe_present/participe_passe/tenses[].name/forms[].p,v |
时态胶囊切换,默认第一个时态 |
| 学习辅助 | ai_help.usage/mnemonic/collocations[]/synonyms/antonyms/derivatives/etymology |
近/反/派生词胶囊可点查词;搭配前 6 条 |
| 权威英语 | authoritative_en[] |
|
| 我的备注 | EntryDetailOutput.note、status、due_at、notebooks[] |
只在已收藏时出现;失焦 PATCH entries/<id>/ {note};「更新释义」= POST entries/<id>/refresh/ |
| 底部「加入默认生词本」 | vocabulary/settings/ 的 last_notebook_id;状态 GET entries/status/?word=¬ebook_id= |
加入 POST notebooks/<id>/entries/(带 word, lemma, ipa, pos, meaning_zh, payload= 整个 explanation,后端存为缓存,返回 added_words);移除 DELETE notebooks/<id>/entries/?word= |
状态:加载骨架(首次 AI 生成 3–8 秒,骨架下写「首次查询约需 3–8 秒」)→ 内容;出错(断网 / 超时 12 秒)→ 重试页;外部服务失败但词已收藏 → 用 payload 缓存显示并标「离线数据」。所有字段都可能为空,空就不出该分区,分区条同步隐藏。
4.4 背单词 · png 14–18、28
组件:StudyHeader(关闭、分段进度、n / N、撤回)、Flashcard(正 / 背)、GradeBar、SessionSummary、StudyEmpty。
流程:GET study/queue/?notebook_id= → 第 1 张自动读一次女声 → 点卡片或「显示答案」翻面 → 评分 → POST study/reviews/ {entry_id, rating} → 卡片飞出、下一张自动发音 → 全部完成进小结。关闭时如果已评过至少一张,先进小结。
| 元素 | 字段 |
|---|---|
| 正面 | QueueItem.word、ipa、queue_kind(新词 / 复习标签)、阴阳性(从 payload.display.gender) |
| 背面释义 | meaning_zh;例句 payload.examples[0];记忆提示 payload.ai_help.mnemonic;我的备注 note(缺口 G4:QueueItem 没有 note) |
| 间隔预告 | 客户端按 SM-2 算(需要 ease、interval_days、repetitions,缺口 G1:QueueItem 没有这三个字段) |
| 四档 | 忘记 = again(10 分钟后)、模糊 = hard、认识 = good、简单 = easy(缺口 G1:后端只有三档) |
| 小结 | 本地累计;连续打卡 缺口 G2 |
手势:右滑 = 认识(good),左滑 = 忘记(again);只在正面和背面都可滑。撤回:把上一张放回、界面恢复评分前的状态(缺口 G1:后端没有撤回接口;可先把最后一次评分延迟 4 秒提交,或加 study/reviews/<id>/undo/)。提交失败:停在当前卡片,按钮可再点,进度不丢(v1 规范)。
4.5 本子 tab · png 19–22、27
组件:大标题 + 新建、SegmentedControl(生词本 / 句子本)、NotebookChips、到期提示卡、SearchField、StatusFilter、EntryList(可左滑)。
| 元素 | 来源 |
|---|---|
| 本子胶囊 + 数量 | GET vocabulary/notebooks/(entry_count) |
| 列表 | GET vocabulary/entries/?notebook_id=&q=&status=all|new|due&limit=50&offset= → word, ipa, pos, meaning_zh, status, due_at, notebooks |
| 筛选计数 | 缺口 G6:列表接口只回当前筛选的 total,三个计数要三次请求,建议加 counts |
| 状态标签 | status + due_at 算:新词 / 学习中 / 今天 / 过期 N 天 / N 天后 |
| 到期卡「去复习」 | study/summary/?notebook_id= → 复习页(只复习这个本子) |
| 左滑移除 | DELETE vocabulary/notebooks/<id>/entries/?word=;「全部生词本」视图下移除 = 从所有本子移除,要二次确认 |
| 句子本 | GET sentences/notebooks/、GET sentences/entries/?notebook_id=;句子没有复习(后端无接口) |
排序:按字母(后端 order_by("word"));按加入时间排序是缺口 G7。空本子、搜索无结果、待复习为 0 各有一种空状态文案。iPad:左栏列表、右栏单词详情(带备注卡),选中行淡紫高亮。
4.6 点词浮层 · 句子 · png 23–25
- 任何「法语正文」都用
TappableText渲染:按词切分(保留撇号缩合:l' d' c' qu' j' n' s' m' t'映射成le de ce que je ne se me te去查),标点不可点。 - 点一下:该词淡紫底高亮,浮层出现在词下方(词在上半屏)或上方(下半屏),宽 300,左右不出屏。内容先骨架,
word-explain回来后显示:原形、喇叭、音标、词性、阴阳性、「句中 frappe 是 frapper 的变形」、主释义 3 行截断、「+ 生词本」「详细释义 ›」。点外面只关浮层,不触发别的按钮;Esc 也关。 - 长按(480ms,中等触感):在句子页进入多选,最多 3 个词,底部黑色条「已选 N 个词 · 取消 · 精讲」→
sentence-explain的targets,返回focus_words精讲;在例句里长按 = 收藏整句到句子本。 - 句子页:原句卡(可点词)+ 中文翻译 + 朗读(女 / 男)+ 收藏;下面是
translation / chunks / grammar_points / sound_links等分区,字段照lib/sentence-api/types.ts。
4.7 状态 · png 26–29
| 状态 | 画法 |
|---|---|
| 加载 | 骨架:词头块、两个胶囊、两行概要、分区条、三张卡;不整页转圈 |
| 空本子 | 淡紫图标块 + 「这个本子还是空的」+ 说明怎么加词 + 「去查词」 |
| 没有要复习的 | 薄荷绿勾 + 「今天没有要复习的词」+ 「去查词」「再来一组(提前复习)」 |
| 搜索无结果 | 天蓝放大镜 + 文案 + 「去查 xx」 |
| 断网 / 超时 | 浅红断网图标 + 原因 + 「重试」;已收藏的词仍可看缓存 |
| 写操作失败 | 界面改回去 + Toast「没保存上,请重试」 |
文案短、具体、不用感叹号和表情(v1 规范)。
5. 接口总表
| 用途 | 接口 | 归属 | 原型里 |
|---|---|---|---|
| 联想 | GET https://api.dict.chatontcf.com/api/dict/search?q=&limit=8 |
外部服务(不在 tcf_worker 仓库) | 实时,经 /api/dict/search 代理 |
| 单词释义 | GET /api/word-explain/{word}?locale=cn[&lemma=] |
tcf_worker apps/word-explain(网页走 api.explain.chatontcf.com) |
实时 |
| 单词发音 | POST /api/word-audio {text} → items[{voice, gender, url, cached}] |
tcf_worker apps/word-audio |
实时 |
| 例句 / 句子朗读 | POST https://api.text.chatontcf.com/api/text-audio {text, lang:"fr-CA", gender, check_cache?} → items[{url, words[]}] |
外部服务;tcf_worker 的 sentence-audio 只能按 id 读管理员预生成的音频,不适合任意例句 |
实时(先查缓存,没有再生成) |
| 句子精讲 | POST https://api.sentence-explain.chatontcf.com/api/sentence-explain |
外部服务 | 示意(只有一句) |
| 生词本 / 复习 / 设置 | /api/app/v1/vocabulary/... |
Django,Bearer | 示意 |
| 句子本 | /api/app/v1/sentences/... |
Django | 示意 |
| 查询统计 | /api/app/v1/stats/lookups/... |
Django | 示意 |
| 用量上报 | POST /api/app/v1/usage/report/(worker 返回的 usage.data) |
Django | 代理把 usage 去掉了,不上报 |
App 端仍按 v1 规范直接请求查词服务(key 在 App 配置里)。原型改用 Pages Function 代理只是为了不把 key 放进公开网页。
6. 接口缺口(需要后端补,按优先级)
| # | 缺口 | 影响 | 建议 |
|---|---|---|---|
| G1 | 复习只有 again/hard/good 三档;QueueItem 没有 ease / interval_days / repetitions;没有撤回 |
「简单」档、按钮下的间隔预告、撤回 | 加 easy(ease +0.15,间隔 = good × 1.3,首次 4 天);QueueItem 加三字段或直接回 next_intervals{again,hard,good,easy};加撤回接口或客户端延迟提交 |
| G2 | 没有学习记录 / 打卡 / 每日学习时长 | 首页本周柱、连续打卡、小结里的打卡 | 新增 study/history/?days=7(每天复习数、新词数、秒数)和 study/streak/ |
| G3 | 没有查询记录列表 | 最近查询只能存本机,换设备就没了 | stats/lookups/ 加 GET 列表(按时间倒序、去重) |
| G4 | QueueItem 没有 note、gender |
卡片背面的「我的备注」、正面阴阳性要额外请求 | QueueItem 加 note;gender 可从 payload.display.gender 取,或单列 |
| G5 | Podcast 进度、课本、真题正确率没有 App 接口 | 首页继续收听、课本卡、TCF 四项计数 | 后续批次一起设计;先补 listening/accuracy/ 等的 app_v1 版本 |
| G6 | 词条列表没有分状态计数 | 筛选胶囊上的数字要 3 次请求 | entries/ 返回加 counts{all,new,due} |
| G7 | 词条只能按字母排序 | 看不到「最近加入」 | entries/?ordering=-added_at |
| G8 | 考试日期没有字段 | 首页「距 TCF 考试 N 天」 | Profile 加 exam_date |
| G9 | 例句朗读依赖外部 text-audio,tcf_worker 的 sentence-audio 是管理员按 id 生成 |
例句和词典例句的音频不在同一个可控服务里 | 把 text-audio 收进 tcf_worker,或给 examples[] 预生成音频 id |
| G10 | 首页「今天要复习」预览没有专门接口 | iPad 首页右栏 | 复用 entries/?status=due&limit=5 即可,非阻塞 |
7. 对比度
见 contrast-report.md:40 组文字 / 底色组合全部 ≥ 4.5(图标 ≥ 3)。改动:分段控件未选文字从 muted 改成 ink-2;占位 / 箭头图标色从 #9A9AA6(2.8:1)压深到 #868692;评分色全部用「浅底 + 深字」,不用白字。阴阳性和状态不只靠颜色,都带文字。
8. 原型的实现说明(不进 App)
- 纯 HTML/CSS/JS,无构建。
proto/core.js(状态、路由、接口、音频、SM-2)、proto/screens.js(各屏)、proto/app.js(事件、手势、设备框)。 /api/*只放行 5 条路径(dict/search、word-explain、word-audio、text-audio、sentence-audio/{id}),POST 体限 1200 字节,转发时加 key,返回时删掉usage。- 接口失败时退回:
sample-data/里抓好的真实响应 → 本地生词本里的基本信息(标「示意数据」);发音失败退回浏览器朗读并提示。 - 原型里的生词本、复习结果都只在内存,刷新就恢复;最近查询存在浏览器 localStorage。