diff --git a/.trae/documents/在 TypingArticle.vue 中忽略人名(基于 editArticle.nameList).md b/.trae/documents/在 TypingArticle.vue 中忽略人名(基于 editArticle.nameList).md new file mode 100644 index 00000000..7454be32 --- /dev/null +++ b/.trae/documents/在 TypingArticle.vue 中忽略人名(基于 editArticle.nameList).md @@ -0,0 +1,71 @@ +## 目标 +- 在 `onTyping` 方法(291-382行)中判断当前/下一个单词是否为人名,并在练习时自动忽略(不要求输入、不提示错误、不停顿等待空格)。 + +## 数据来源 +- 使用 `props.article.nameList: string[]`(来自编辑页保存),作为要忽略的人名列表。 + +## 匹配策略 +- 构建一个人名集合 `nameSet`: + - `trim()` 后的字符串; + - 若开启 `ignoreCase` 则统一转小写匹配。 +- 判定函数 `isNameWord(word: ArticleWord)`: + - 仅当 `word.type === PracticeArticleWordType.Word` 时参与匹配; + - 对 `word.word` 进行同样的规范化后 `nameSet.has(...)`。 + +## 处理时机与行为 +- 在 `onTyping` 开始处、拿到 `currentWord` 后: + - 若是“人名”,则直接跳过本词;若该词 `nextSpace` 为真,连带空格也跳过(避免进入 `isSpace` 状态)。 + - 跳过后继续处理当前按键:复用已有模式(如 `isSpace` 分支里)调用 `next()` 和递归 `onTyping(e)`。 +- 在 `next()` 内也追加“人名跳过”逻辑(与已有忽略符号/数字类似),保证连续多个需要忽略的词可以被连续跳过: + - 当 `currentWord` 是人名: + - 若 `currentWord.nextSpace` 为真:`isSpace = false`; + - 递归调用 `next()` 继续到下一个词; + - 否则正常 `emit('nextWord', currentWord)`。 + +## 代码改动点 +- 在组件顶部或方法内构建 `nameSet`(建议用 `$computed`): +```ts +const nameSet = $computed(() => { + const list = props.article?.nameList ?? [] + return new Set(list.map(s => (settingStore.ignoreCase ? s.toLowerCase() : s).trim()).filter(Boolean)) +}) +function isNameWord(w: ArticleWord) { + if (w.type !== PracticeArticleWordType.Word) return false + const token = (settingStore.ignoreCase ? w.word.toLowerCase() : w.word).trim() + return nameSet.has(token) +} +``` +- 在 `onTyping` 里,`let currentWord = currentSentence.words[wordIndex]` 之后: +```ts +if (isNameWord(currentWord)) { + // 跳过当前人名,连带空格 + isSpace = false + const savedTypingFlag = isTyping + next() + isTyping = false + return onTyping(e) +} +``` +- 在 `next()` 内,设置 `currentWord` 后、`emit('nextWord', currentWord)` 之前: +```ts +if (isNameWord(currentWord)) { + // 人名与后续空格都跳过 + isSpace = false + return next() +} +``` + +## 注意事项 +- 保持与现有忽略规则一致(符号/数字已通过 `ignoreSymbol` 处理),人名跳过逻辑与其同层级。 +- 忽略人名时不触发 `wrong` 或提示音,不进入 `isSpace` 等待。 +- 若连续出现多个需要忽略的词(如人名+标点+人名),递归 `next()` 将逐个跳过。 + +## 验证 +- 在包含人名的文本中练习: + - 人名处不需要输入,光标自动跳到下一非忽略词; + - 不会卡在空格等待; + - 大小写忽略效果符合 `settingStore.ignoreCase` 设置。 + +## 交付 +- 按上述方案在 `TypingArticle.vue` 中实现辅助函数与两处调用点的改动; +- 仅修改该文件,不影响其他页面。 \ No newline at end of file diff --git a/.trae/documents/完善人物名称管理弹框(读取与保存 editArticle.nameList).md b/.trae/documents/完善人物名称管理弹框(读取与保存 editArticle.nameList).md new file mode 100644 index 00000000..a0d1de22 --- /dev/null +++ b/.trae/documents/完善人物名称管理弹框(读取与保存 editArticle.nameList).md @@ -0,0 +1,69 @@ +## 目标 +- 在“人物名称管理”弹框中使用临时变量编辑名称列表,只在点击“确定”时写回 `editArticle.nameList: string[]` + +## 数据结构 +- `editArticle.nameList: string[]` +- 临时变量:`let nameListRef = $ref([])` + +## 生命周期 +- 弹框打开时初始化:`nameListRef = cloneDeep(editArticle.nameList || [])` +- 弹框关闭时不写回:丢弃修改 + +## 交互设计 +- 弹框 `v-model="showNameDialog"`、`:footer="true"`、`@close="showNameDialog = false"`、`@ok="saveNameList"` +- 按钮: + - “添加名称” → `nameListRef.push('')` + - 每行名称使用 `BaseInput v-model="nameListRef[i]"` + - “删除”名称 → `nameListRef.splice(i,1)` + +## 保存逻辑 +- `saveNameList()`: + - 清理:`trim()` + `filter(Boolean)` + - 写回:`editArticle.nameList = cleaned` + - 关闭弹框:`showNameDialog = false` + +## 实现细节(EditArticle.vue 增加) +- 脚本: +```ts +let showNameDialog = $ref(false) +let nameListRef = $ref([]) + +watch(() => showNameDialog, (v) => { + if (v) nameListRef = cloneDeep(Array.isArray(editArticle.nameList) ? editArticle.nameList : []) +}) + +function addName() { nameListRef.push('') } +function removeName(i: number) { nameListRef.splice(i,1) } +function saveNameList() { + const cleaned = nameListRef.map(s => (s ?? '').trim()).filter(Boolean) + editArticle.nameList = cleaned +} +``` +- 模板(620-628 区域): +```vue + +
+
+
配置需要忽略的人名,练习时自动忽略这些名称
+ 添加名称 +
+ +
+
+ + 删除 +
+
+
+
+``` + +## 验证 +- 打开弹框 → 编辑临时列表 → 点击“确定”后检查 `editArticle.nameList` 是否更新;点击关闭则不更新 + +## 注意 +- 若类型仍为旧版 `string[][]`,请同步调整为 `string[]` 以与当前实现一致 \ No newline at end of file diff --git a/.trae/documents/静态首页移动端适配.md b/.trae/documents/静态首页移动端适配.md new file mode 100644 index 00000000..684a8fca --- /dev/null +++ b/.trae/documents/静态首页移动端适配.md @@ -0,0 +1,109 @@ +## 目标与范围 + +* 适配 `public/static-home.html` 在 768px 以下与 480px 以下的移动端展示与触控体验 + +* 不改变页面文案与结构,只进行样式与布局响应式改造 + +## 结构与布局 + +* 将固定宽度 `.w { width: 60vw; }` 改为容器 `.container { width: min(1200px, 92%); }` 并在模板中替换为 `.container` + +* `.card-wrap` 改为自适应栅格:`grid-template-columns: repeat(auto-fit, minmax(240px, 1fr))` + +* 移动端(<=768px)卡片单列展示;间距适当增大,避免拥挤 + +## 样式改造 + +* 标题缩放: + + * `@media (max-width: 768px) h1 { font-size: 3rem; }` + + * `@media (max-width: 480px) h1 { font-size: 2.4rem; }` + +* 主按钮组: + + * 移动端按钮全宽:`.base-button { width: 100%; margin: .5rem 0; height: 2.8rem; font-size: 1rem; }` + + * 悬浮降低透明度改为轻微位移:`transform: translateY(-1px);` + +* 内容区与间距: + + * `@media (max-width: 768px) .content { margin-top: 4rem; gap: 1.4rem; }` + + * `@media (max-width: 480px) .content { margin-top: 3.2rem; gap: 1.2rem; }` + +* 赞助区 `.sky`: + + * 图片强制全宽:`.sky a { width: 100% !important; } .sky-img { width: 100%; }` + + * 移动端上下内边距缩小,减少跳动 + +* 卡片 `.card`: + + * 移动端去掉固定 `width: 25%`,改为自适应栅格控制 + + * 增加触控阴影:`box-shadow: 0 6px 20px rgba(0,0,0,.08);` + +* 底部链接 `.bottom`: + + * 在 <=768px 改为纵向堆叠:`flex-direction: column; align-items: flex-start; gap: .6rem;` + +## 示例变更片段(将添加到 +] \ No newline at end of file diff --git a/src/pages/word/DictDetail.vue b/src/pages/word/DictDetail.vue index a97f863f..5b233ac3 100644 --- a/src/pages/word/DictDetail.vue +++ b/src/pages/word/DictDetail.vue @@ -1,11 +1,11 @@