【句匠|01】HarmonyOS ArkTS 英语纠错页实战:把原句、修改建议和解释层级展示清楚

英语纠错页看起来只是一个输入框加一个结果列表,但真正写到 HarmonyOS 5.0+ ArkTS 页面里,容易出现几个实际问题:用户输入长句后页面挤压,示例句点击后旧结果还留在页面上,空输入时没有明确反馈,规则匹配到了"已经正确"的表达也被当成错误展示,结果卡片把错误片段、修改建议和解释混在一起,最后用户看不出到底该改哪里、为什么改。对学习工具来说,纠错页不是"显示一段答案",而是要把原句、建议和解释层级展示清楚。

本文基于「句匠」真实源码复核,包名标记为 com.jiaweikang.one18。核心文件是 entry/src/main/ets/pages/AICorrectPage.ets。需要先说明边界:当前页面标题里写了"AI 纠错练习",但源码实现是本地规则版,使用 RegExp 规则匹配中式英语、拼写、介词、时态、冠词和第三人称单数等高频错误;本文不会把它写成云端大模型、联网推理或服务端纠错能力。module.json5 中虽然声明了 ohos.permission.INTERNET,但本文所讨论的 AICorrectPage 纠错结果来自本地 RULES 数组。

1. 页面要解决的问题:纠错不是一个字符串,而是一组结构化建议

如果纠错结果只返回一段文本,例如"这里应该改成 like very much",用户需要自己找原句中的位置,也看不出规则类型。当前源码没有这样做,而是定义了 CorrectionItem:

ts 复制代码
interface CorrectionItem {
  rule: string
  before: string
  after: string
  reason: string
}

这个结构把纠错建议拆成四层。rule 是错误类型,例如副词搭配、固定表达、介词搭配、时态错误;before 是原句中匹配到的错误片段;after 是建议替换片段;reason 是面向学习者的解释。页面渲染时就能分别展示标签、删除线、绿色建议和解释文本。

这种结构比直接拼接字符串更稳。后续如果要把纠错结果保存到学习记录、加入错题本、做分类型统计,或者给每条建议加"我已掌握"状态,只需要扩展 CorrectionItem,不需要重新解析一段自然语言结果。

2. 规则层:RulePattern 把匹配和构造结果绑定在一起

源码里第二个关键接口是 RulePattern:

ts 复制代码
interface RulePattern {
  pattern: RegExp
  build: (m: RegExpMatchArray) => CorrectionItem
}

它的设计目标很明确:pattern 只负责发现问题,build 负责把匹配结果转换成结构化建议。例如 very like 这类中式英语问题,规则写成:

ts 复制代码
{
  pattern: /\bvery\s+like\b/i,
  build: (m: RegExpMatchArray) => {
    return {
      rule: '副词搭配',
      before: m[0],
      after: 'like ... very much',
      reason: 'very 不能直接修饰一般动词,常用 very much 后置。'
    } as CorrectionItem
  }
}

这里的工程价值不是正则本身,而是"发现问题"和"解释问题"放在同一条规则里。这样页面不用知道 very like 为什么错,也不用根据规则名再查一次说明。每条规则都自带展示所需的信息,ResultCard 只负责渲染。

当前规则覆盖的类型包括:

规则方向 示例
中式英语 I very like the book.
固定表达 I no idea.
主谓一致 She go to school every day.
介词搭配 interested on、good in、depend of
高频拼写 recieve、tomorow、seperate
时态错误 I yesterday go、since 2020 I live
冠词使用 a honest、an university

这些都是本地规则能复核的内容,不能被扩大描述为"覆盖所有语法错误"。

3. 状态层:input、results、analyzed 三个状态各司其职

AICorrectPage 的页面状态很少,但职责清晰:

ts 复制代码
@State input: string = ''
@State results: CorrectionItem[] = []
@State analyzed: boolean = false
@StorageLink('navigationIndicatorHeightPx') navigationIndicatorHeightPx: number = 0

input 是用户正在编辑的原句;results 是分析后的结构化结果;analyzed 决定是否展示结果卡片。这里最关键的是 analyzed。如果只靠 results.length 判断是否展示结果,空输入、无错误和未分析三种状态会混在一起:它们的结果数组都可能是空的,但用户含义完全不同。

当前源码用 analyzed 把这三种状态拆开:

状态 input results analyzed 页面含义
初始 空 空 false 不显示结果
空输入后点击分析 空 空 true 显示分析后的空结果
输入但规则未命中 非空 空 true 显示未发现明显错误
输入且命中规则 非空 非空 true 显示建议列表
输入变化后 变化中 旧值清空或隐藏 false 等待重新分析

这个设计能避免一个常见 UI 问题:用户改了输入句子,页面仍显示上一句的纠错建议。源码在 TextArea.onChange 中把 analyzed 重置为 false,就是为了让结果和当前输入保持一致。

4. 输入卡片:TextArea 承载原句,按钮只做两件事

输入区域使用 TextArea,而不是 TextInput。这符合英语句子纠错的场景,因为用户可能粘贴长句、复合句或多个短句。源码中设置了占位文本、字体颜色、背景色、圆角、padding、高度和宽度:

ts 复制代码
TextArea({
  placeholder: 'e.g. I very like the book and I want to recieve it tomorow.',
  text: this.input
})
  .placeholderColor(Colors.INPUT_PLACEHOLDER)
  .fontColor(Colors.INPUT_TEXT)
  .fontSize(Sizes.BODY_FONT)
  .backgroundColor(Colors.BACKGROUND_ALT)
  .borderRadius(12)
  .padding(12)
  .height(120)
  .width('100%')
  .onChange((v: string) => { this.input = v; this.analyzed = false })

这段代码有两个发布级细节。第一,输入框颜色使用 Colors.INPUT_*,不是系统默认颜色,能降低深色模式下输入文字不可读的风险。第二,输入变化只更新 input 和 analyzed,不立刻执行分析,避免每次输入都遍历规则并造成结果跳动。

按钮区只有"清空"和"开始纠错"两个动作。清空时同步重置输入、结果和分析状态:

ts 复制代码
.onClick(() => {
  this.input = ''
  this.results = []
  this.analyzed = false
})

开始纠错只调用 analyze()。这种按钮职责很清楚,测试时也容易覆盖:清空后不应显示旧结果;输入变化后旧结果隐藏;点击分析后才展示当前句子的结果。

5. analyze:空输入、规则遍历和结果过滤

页面核心方法是 analyze():

ts 复制代码
private analyze(): void {
  const text = this.input
  const items: CorrectionItem[] = []
  if (text.trim().length === 0) {
    this.results = []
    this.analyzed = true
    return
  }
  for (const r of RULES) {
    const m = text.match(r.pattern)
    if (m) {
      const item = r.build(m)
      if (item.before !== item.after) items.push(item)
    }
  }
  this.results = items
  this.analyzed = true
}

这里有三个值得保留的工程判断。

第一,空输入不是异常。用户点击"开始纠错"但没有输入内容时,页面把 results 置空,并把 analyzed 设为 true。这样 UI 可以明确进入"已分析但没有建议"的状态,而不是保持无反馈。

第二,规则遍历是本地同步逻辑。当前 RULES 数量有限,适合在页面方法里直接遍历。后续如果规则数量变大,或者要加入网络纠错,就应该把规则引擎抽到 service 层,但当前源码还没有这个复杂度。

第三,只保留 before !== after 的建议。冠词规则里有些情况会返回"已经正确"的表达,如果不做过滤,页面会显示没有实际改动的建议。这个过滤让结果列表只包含真正需要用户关注的变化。

6. 示例句:fillExample 重置状态,避免示例和旧结果混在一起

示例卡片提供五条句子:

ts 复制代码
this.ExampleRow('I very like the book.')
this.ExampleRow('She go to school every day.')
this.ExampleRow('I am interested on music.')
this.ExampleRow('I will recieve it tomorow.')
this.ExampleRow('I yesterday go to park.')

点击示例时调用 fillExample:

ts 复制代码
private fillExample(s: string): void {
  this.input = s
  this.analyzed = false
  this.results = []
}

这一步很关键。示例句本质上是快捷输入,不是自动分析。如果用户刚分析过上一句,再点一个示例,旧结果必须清空,否则用户会看到"输入已经变了,建议还是上一句"的错位状态。

示例卡片也用了单行省略:

ts 复制代码
Text(s)
  .layoutWeight(1)
  .fontSize(Sizes.BODY_FONT)
  .fontColor(Colors.TEXT_SECONDARY)
  .maxLines(1)
  .textOverflow({ overflow: TextOverflow.Ellipsis })

这保证较长示例不会挤掉右侧"试用"按钮。对手机、小窗和 2in1 窄窗口来说,这类文本约束是页面稳定性的基础。

7. ResultCard:先区分无建议,再渲染建议列表

结果卡片只有在 analyzed 为 true 时显示。进入卡片后,再根据 results.length 分支。没有建议时,页面展示"没有发现明显错误"的提示,并说明当前本地规则库覆盖范围有限:

ts 复制代码
if (this.results.length === 0) {
  Column({ space: 6 }) {
    Text('恭喜,没有发现明显错误')
    Text('当前 AI 助教使用本地规则库,覆盖高频中式英语、拼写、介词、时态、冠词、第三人称单数等常见错误。如果句子较短或类型未覆盖,也可能不会给出建议。')
  }
}

这段文案很重要,因为它没有夸大能力。规则没有命中,不等于句子绝对正确。页面明确告诉用户"类型未覆盖也可能不会给出建议",这比直接写"句子完全正确"更符合本地规则版的边界。

有建议时,页面用 ForEach 渲染每条 CorrectionItem:

ts 复制代码
ForEach(this.results, (r: CorrectionItem, i: number) => {
  this.CorrectionItemView(r, i + 1)
}, (r: CorrectionItem, i: number) => `c_${i}_${r.before}`)

key 使用序号加 before,能区分同一结果列表中的不同建议。虽然当前 analyze() 对每条规则只取一次 text.match,不会列出同一规则的多个命中,但这个 key 至少避免纯 index 在内容变化时完全失去业务含义。

8. CorrectionItemView:错误、建议、解释三层分开

每条纠错建议由 CorrectionItemView 渲染。它先显示规则标签,再显示错误片段、建议片段和解释原因。核心结构可以简化为:

ts 复制代码
Text(`#${idx}  ${r.rule}`)
Text(r.before).decoration({ type: TextDecorationType.LineThrough })
Text(r.after).fontWeight(FontWeight.Bold)
Text(r.reason).lineHeight(18)

这几个视觉选择服务于同一个目标:让用户按顺序读懂"错在哪里、改成什么、为什么"。before 使用删除线和错误色;after 使用成功色和加粗;reason 使用较小字体和固定行高,承担学习解释而不是抢占视觉焦点。

如果把这三层放在一段普通文本里,用户很难快速定位。分层展示后,每条建议都像一个小型学习卡片,尤其适合手机屏幕上的连续阅读。

这里还要注意 maxLines(2)。错误片段和建议片段最多两行,能避免某条长建议撑爆整个卡片。解释文本则允许用 lineHeight(18) 保持阅读节奏。

9. 本地规则版的边界:不要把页面写成云端纠错

当前 AICorrectPage 有"AI 助教"的产品表达,但源码层是本地规则库。这个边界要在技术文章和发布材料中讲清楚。

能力 当前源码是否支撑 说明
本地高频语法纠错 支撑 RULES 中有 RegExp 和解释
拼写纠错 支撑部分高频词 如 receive、tomorrow、environment
介词搭配 支撑部分固定搭配 如 interested in、good at
任意长文润色 不支撑 没有通用模型或长文本重写
云端 AI 纠错 未在此页体现 页面逻辑没有请求服务端
多语言翻译 不支撑 当前只围绕英文错误建议

module.json5 里声明了 ohos.permission.INTERNET,这说明应用级别存在联网权限配置。但就本页源码而言,AICorrectPage 没有发起 HTTP、WebView 或外部模型调用。写文章时必须把"应用权限声明"和"当前页面实现"分开,不能因为页面标题含 AI 就虚构云端推理链路,也不能因为应用声明 INTERNET 就把本地规则页写成在线服务。

10. 验证清单:输入、示例、空态和多结果都要测

围绕当前源码,纠错页至少要覆盖下面这些测试:

测试项 操作 预期
空输入 不输入直接点开始纠错 显示已分析状态,不崩溃
示例填充 点击 I very like the book. 输入框更新,旧结果清空
单条命中 点击分析 显示副词搭配建议
多条命中 输入 I very like it and recieve it tomorow. 显示多条建议
无命中 输入规则未覆盖句子 显示本地规则覆盖范围提示
清空 点击清空 输入、结果和 analyzed 全部重置
长句 粘贴较长英文句 TextArea 可滚动或保持布局,不遮挡按钮
底部安全区 手势导航设备 页面底部 Blank 使用 bottomSafePadding() 留出空间

这些测试都能从源码推导,不需要伪造线上接口结果。尤其是"无命中"场景,应该确认文案没有误导用户认为句子绝对正确。

11. 常见问题和修正方式

问题一:输入变化后旧建议仍显示。 检查 TextArea.onChange 是否把 analyzed 设为 false。如果只改 input,结果卡片会继续展示旧建议。

问题二:示例句填入后立即显示上一句结果。 检查 fillExample 是否同步清空 results。示例填充不是分析动作,应该等待用户点击"开始纠错"。

问题三:规则返回了没有变化的建议。 检查 analyze() 中的 item.before !== item.after 过滤。冠词规则这类可能返回"已正确"的场景,不应进入建议列表。

问题四:错误、建议和解释混在一起不好读。 把 CorrectionItemView 拆成规则标签、错误片段、建议片段和解释文本四个视觉层级,错误用删除线,建议用成功色,解释使用较小字号。

问题五:产品文案夸大为在线 AI。 以源码为准。当前页面是本地规则版,可以说"本地规则纠错""AI 助教式体验",但不能说"云端大模型实时改写"或"覆盖所有语法错误"。

12. 小结:清晰的纠错页来自结构化结果,而不是更长的提示词

「句匠」当前 AICorrectPage 的关键点不是算法复杂,而是页面边界清楚:TextArea 负责原句输入,RULES 负责本地规则匹配,analyze() 负责空输入、遍历和过滤,ResultCard 负责区分无建议和建议列表,CorrectionItemView 负责把错误、建议和解释分层展示。

这种写法适合 HarmonyOS 学习类应用的第一版纠错页。它不会伪装成万能纠错系统,但能把高频错误讲清楚,让用户知道"哪里错、改成什么、为什么改"。后续如果要接入真正的云端纠错或模型能力,也应该保留 CorrectionItem 这种结构化输出,再把网络请求、错误处理、隐私说明和权限声明补齐。

本篇文章的封面使用当前文章独立的 media/cover.png,同一软件合集继续复用应用级 collection-cover.png;合集封面保持统一,合集内每篇文章封面保持不同,便于跨平台发布和回读核验。

部分内容由AI辅助生成。

相关推荐
Token_w1 小时前
2026鸿蒙平板远控电脑怎么选?大屏适配、自动校准等适配完整度逐项实测
华为·harmonyos
梦想不只是梦与想3 小时前
鸿蒙应用市场:版本升级
harmonyos·应用升级·签名不一致
resh_people3 小时前
开源鸿蒙平台 KMP_CMP 三方库「WhatIf」适配全流程
华为·开源·harmonyos
小雨青年4 小时前
【HarmonyOS 7 闪控窗深度实战】04 闪控窗调整窗口形态与显示区域
华为·harmonyos
李游Leo5 小时前
HarmonyOS 7 + ArkUI + RelationalStore 学习笔记:本地数据分层存储与状态持久化架构实践【鸿蒙心迹】
笔记·学习·架构·harmonyos
李游Leo6 小时前
《HarmonyOS 7 精准碰一碰跨设备协作开发实战》07:异常恢复、状态机与CrossDrop工程收尾【鸿蒙心迹】
android·harmonyos
kiros_wang6 小时前
渐变/阴影/滤镜⾼阶⽤法:统⼀项⽬UI、精简冗余组件
ui·harmonyos
贾伟康7 小时前
【HarmonyOS 7新能力|065】QUIC长连接实战:弱网推送、会话恢复与连接治理
harmonyos·arkts·quic·长连接·弱网优化
m0_738185828 小时前
Flutter 鸿蒙化实战:flutter_app_minimizer_plus 适配 OpenHarmony,一键最小化应用
flutter·华为·harmonyos·鸿蒙
TMT星球8 小时前
智界RX及鸿蒙智行新品发布会,多款重磅新品正式上市、开启预订!
华为·harmonyos