【句匠|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 ongood independ of
高频拼写 recievetomorowseperate
时态错误 I yesterday gosince 2020 I live
冠词使用 a honestan 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_*,不是系统默认颜色,能降低深色模式下输入文字不可读的风险。第二,输入变化只更新 inputanalyzed,不立刻执行分析,避免每次输入都遍历规则并造成结果跳动。

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

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 和解释
拼写纠错 支撑部分高频词 receivetomorrowenvironment
介词搭配 支撑部分固定搭配 interested ingood 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辅助生成。

相关推荐
淡写成灰3 小时前
「Flutter 文件保存太难了?」一个插件打通 7 大平台,我把方案开源了 🎉
flutter·harmonyos
大雷神9 小时前
HarmonyOS ArkGraphics 2D 自定义字体实操:注册字体并验证中文回退
pytorch·华为·harmonyos
Nayana10 小时前
《Web 到 HarmonyOS》-- 第一课:前端经验哪些能带走
vue.js·harmonyos
大雷神11 小时前
HarmonyOS ArkGraphics 2D 复杂文本排版实操:用 ParagraphBuilder 做资讯阅读卡片
华为·harmonyos
大雷神11 小时前
HarmonyOS ArkGraphics 2D NativeImage 实操:获取 NativeWindow 与 SurfaceId
华为·harmonyos
小玮看世界11 小时前
当“教用户配置系统“成为产品的遮羞布:从鸿蒙拦截栈与小红书联系机制看科技公司的“驯化式创新“
科技·华为·harmonyos
梦想不只是梦与想11 小时前
鸿蒙 应用类型:企业应用
华为·harmonyos·企业应用
大雷神12 小时前
HarmonyOS ArkGraphics 2D 文本绘制实操:用 TextBlob 做动态文字排版
华为·harmonyos
搬砖的kk12 小时前
从 0 到 1:用 KMP + Compose Multiplatform + Ktor 实现鸿蒙「历史上的今天」应用
开源·harmonyos