鸿蒙 Next API 24 实战:从零构建考试作弊检讨书生成器


目录
- 前言与背景
- 项目架构设计
- 开发环境与工程配置
- Stage 模型与 Ability 生命周期
- ArkUI 声明式 UI 开发
- 状态管理与表单设计
- 模板引擎的设计与实现
- @Builder 组件化实践
- ArkTS 严格模式与常见错误修复
- UI/UX 设计:纸笔风格视觉系统
- 条件渲染与页面切换
- 鸿蒙 API 24 特性应用
- 安全与实践考虑
- 踩坑与经验总结
- 结语与展望
1. 前言与背景
1.1 为什么做检讨书生成器?
考试是教育体系中不可或缺的环节,而有考试的地方,就难免有作弊行为。根据教育部近年来的统计数据,每年高校考试中因作弊受处分的学生数量数以万计。作弊被发现后,学生通常需要提交一份书面检讨书作为认识错误、表明态度的依据。
然而,写检讨书对于很多学生来说是一件困难的事情——不是因为不想认错,而是不知道如何组织语言、如何表达悔过之心、如何写出有诚意的检讨。网络上流传的各种检讨书模板要么过于死板,要么不够全面,学生往往需要在多个模板之间拼凑,费时费力。
这就是我们开发"考试作弊检讨书生成器"的初衷——通过智能模板组合,帮助学生快速生成结构完整、语气得体、内容诚恳的检讨书。同时,通过提供多种语气风格,让学生在认错的同时也能保有自己的个性。
1.2 为什么选择鸿蒙 Next?
鸿蒙 Next 系统从底层开始全面自研,带来了几个关键优势:
- ArkUI 声明式框架:响应式 UI 开发,状态驱动视图更新,非常适合表单类应用
- ArkTS 强类型语言:基于 TypeScript 的强类型方言,提供编译时类型检查,减少运行时错误
- Stage 模型:清晰的生命周期管理,适合单页面富交互应用
- API 24 成熟度高:SDK 6.1.1 版本提供了完善的 UI 组件和工具链
- 编译期优化:ArkTS 编译器在构建时进行类型检查和代码优化,提前发现潜在问题
1.3 本文的目标读者
- 有鸿蒙 ArkUI 基础,想了解完整项目实践的开发者
- 从 TypeScript/React 转向鸿蒙 ArkTS 开发的工程师
- 对文本生成、模板引擎实现感兴趣的开发者
- 正在学习 ArkTS 严格模式语法的技术人员
2. 项目架构设计
2.1 整体架构
本应用采用鸿蒙 Next 推荐的 Stage 模型,整体架构分为三层:
┌─────────────────────────────────────────────┐
│ 表现层 (UI Layer) │
│ ┌──────────┬──────────┬──────────────────┐ │
│ │ 表单页面 │ 结果页面 │ 生成按钮 │ │
│ │ FormPage │ResultPage│ GenerateBtn │ │
│ └──────────┴──────────┴──────────────────┘ │
├─────────────────────────────────────────────┤
│ 逻辑层 (Logic Layer) │
│ ┌──────────┬──────────┬──────────────────┐ │
│ │ 状态管理 │ 模板引擎 │ 表单校验 │ │
│ │ @State │ Template │ Validation │ │
│ └──────────┴──────────┴──────────────────┘ │
├─────────────────────────────────────────────┤
│ 数据层 (Data Layer) │
│ ┌──────────┬──────────┬──────────────────┐ │
│ │ 检讨模板 │ 作弊类型 │ 字符串资源 │ │
│ │ ToneSets │ CheatType│ I18n Strings │ │
│ └──────────┴──────────┴──────────────────┘ │
└─────────────────────────────────────────────┘
2.2 模块划分
应用功能模块清晰划分如下:
| 模块 | 实现方式 | 职责 |
|---|---|---|
| 表单输入 | @Builder FieldRow × 4 |
收集考生信息 |
| 作弊选择 | @Builder CheatCard × 8 |
多选作弊类型 |
| 语气选择 | @Builder ToneButton × 3 |
单选检讨语气 |
| 字数选择 | @Builder LengthButton × 3 |
单选字数档位 |
| 文本生成 | generateApology() |
组合模板生成 |
| 结果展示 | @Builder ResultPage |
显示/复制检讨书 |
2.3 数据流设计
应用的前后两个页面(表单页、结果页)共享同一组 @State 变量,通过单向数据流驱动 UI:
用户填写表单 → @State 变量更新 → UI 自动重渲染
│
▼
点击生成 → generateApology() → 组合模板 → 写入 @State generatedText
│
▼
showResult = true → 切换到结果页面 → 显示检讨书
│
▼
点击"修改" → showResult = false → 回到表单页面
这个模式的关键在于:所有状态集中管理,两个页面只是同一组状态的不同呈现方式。
2.4 组件树设计
完整的 UI 组件树如下,帮助理解页面结构:
Stack (根容器)
├── Column (背景层)
└── Column (内容层)
├── Row (标题栏)
│ └── Column
│ ├── Text "考试作弊检讨书生成器"
│ └── Text "Apology Letter Generator"
│
└── [条件渲染]
├── if (!showResult) → FormPage
│ └── Scroll
│ └── Column
│ ├── SectionTitle "考生信息"
│ ├── Column (白色卡片)
│ │ ├── FieldRow "姓名" → TextInput
│ │ ├── FieldRow "学号" → TextInput
│ │ ├── FieldRow "班级" → TextInput
│ │ └── FieldRow "考试科目" → TextInput
│ │
│ ├── SectionTitle "作弊类型"
│ ├── Column (白色卡片)
│ │ ├── Row → CheatCard × 4
│ │ └── Row → CheatCard × 4
│ │
│ ├── SectionTitle "检讨语气"
│ ├── Column (白色卡片)
│ │ └── Row → ToneButton × 3
│ │
│ ├── SectionTitle "字数"
│ ├── Column (白色卡片)
│ │ └── Row → LengthButton × 3
│ │
│ ├── Button "一键生成检讨书"
│ └── Text (错误提示)
│
└── if (showResult) → ResultPage
└── Scroll
└── Column
├── Column (白色卡片)
│ ├── Row (标题 + 状态标签)
│ ├── Row (分隔线)
│ └── Text (检讨正文)
│
└── Row
├── Button "复制"
├── Button "重新生成"
└── Button "修改"
2.5 接口定义与类型系统
ArkTS 要求所有数据结构都必须通过接口(interface)或类(class)明确定义,不允许隐式的对象字面量:
// 作弊类型定义
interface CheatType {
icon: string // Emoji 图标
label: string // 作弊类型名称
desc: string // 作弊行为描述
}
// 语气模板集
interface ToneSet {
openings: string[] // 开头模板
bodies: string[] // 正文模板
reflections: string[] // 反思模板
promises: string[] // 承诺模板
closings: string[] // 结尾模板
}
这种严格的类型定义带来了几个好处:
- 编译时类型检查 — 在开发阶段就能发现类型不匹配的问题
- IDE 智能提示 — DevEco Studio 可以根据接口定义提供自动补全
- 自文档化 — 接口定义本身就是最好的文档
- 重构安全 — 修改接口时,编译器会提示所有使用点
3. 开发环境与工程配置
3.1 环境要求
操作系统:Windows 11 22H2+
开发工具:DevEco Studio 5.0+ (Build 6.1.1)
SDK 版本:HarmonyOS Next SDK 6.1.1.100 (API 24)
Node.js:v18.x (DevEco Studio 内置)
构建工具:hvigor 6.1.1+
3.2 项目级配置 (build-profile.json5)
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"targetSdkVersion": "6.1.1(24)",
"compatibleSdkVersion": "6.1.1(24)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
]
}
}
配置要点解析:
| 配置项 | 值 | 含义 |
|---|---|---|
targetSdkVersion |
6.1.1(24) |
目标 SDK 版本为 API 24 |
compatibleSdkVersion |
6.1.1(24) |
最低兼容版本也是 API 24 |
runtimeOS |
HarmonyOS |
运行时操作系统 |
caseSensitiveCheck |
true |
启用文件路径大小写检查 |
useNormalizedOHMUrl |
true |
使用规范化的模块引用路径 |
targetSdkVersion 和 compatibleSdkVersion 都设为 API 24 意味着我们的应用只针对鸿蒙 Next 系统运行,不需要向后兼容旧版本,这让我们可以放心使用 API 24 的所有新特性。
3.3 模块级配置 (entry/build-profile.json5)
{
"apiType": "stageMode",
"buildOption": {
"resOptions": {
"copyCodeResource": {
"enable": false
}
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": ["./obfuscation-rules.txt"]
}
}
}
}
]
}
apiType: "stageMode" 表明我们使用的是 Stage 模型。与旧的 FA(Feature Ability)模型相比,Stage 模型有以下优势:
| 对比维度 | FA 模型 | Stage 模型 |
|---|---|---|
| 组件管理 | Ability 即页面,耦合度高 | Ability 与 UI 分离,职责清晰 |
| 生命周期 | 绑定在 UI 上 | 独立于 UI,更灵活 |
| 配置方式 | 单文件 config.json | 多文件拆分(app.json5 + module.json5) |
| 模块化 | 弱 | 强,支持 HAP 分包 |
| 后台能力 | 受限 | 支持长时任务 |
3.4 应用标识配置 (AppScope/app.json5)
{
"app": {
"bundleName": "com.example.demoruanjian3",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:layered_image",
"label": "$string:app_name"
}
}
这里有一个容易踩坑的点:app_name 资源在 AppScope/resources/base/element/string.json 和 entry/src/main/resources/base/element/string.json 中同时声明时,会产生资源冲突警告。最佳实践是由 AppScope 统一管理应用级资源,entry 中只声明模块级资源。
3.5 资源文件管理
鸿蒙的资源文件按照类型和限定词组织:
resources/
├── base/
│ ├── element/ # 基础资源(字符串、颜色、浮点数)
│ │ ├── string.json
│ │ ├── color.json
│ │ └── float.json
│ ├── media/ # 媒体资源(图片、图标)
│ │ ├── startIcon.png
│ │ ├── background.png
│ │ └── layered_image.json
│ └── profile/ # 配置文件
│ ├── main_pages.json
│ └── backup_config.json
main_pages.json 定义了应用的页面路由:
{
"src": ["pages/Index"]
}
对于单页面应用来说,只需要这一个入口页面即可。
4. Stage 模型与 Ability 生命周期
4.1 EntryAbility 源码解析
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
this.context.getApplicationContext().setColorMode(
ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET
);
} catch (err) {
hilog.error(DOMAIN, 'testTag',
'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag',
'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
// ... onForeground, onBackground, onDestroy
}
4.2 生命周期详解
Ability 的生命周期可表示为以下流程:
应用启动
│
▼
onCreate() ── 初始化全局配置(颜色模式等)
│
▼
onWindowStageCreate() ── 创建窗口阶段,加载页面内容
│
▼
onForeground() ── 应用进入前台,对用户可见
│
├── 用户按 Home 键
│ └── onBackground() ── 应用进入后台
│ └── 用户返回 → onForeground()
│
└── 用户退出应用
└── onWindowStageDestroy() ── 窗口销毁
└── onDestroy() ── Ability 销毁
每个阶段的本应用行为:
| 回调 | 触发时机 | 本应用的操作 |
|---|---|---|
onCreate |
Ability 创建 | 设置颜色模式,初始化日志 |
onWindowStageCreate |
窗口创建 | 加载 Index 页面 |
onForeground |
进入前台 | 恢复表单状态 |
onBackground |
进入后台 | 保存草稿(可扩展) |
onDestroy |
Ability 销毁 | 清理资源 |
4.3 hilog 日志系统
在 API 24 中,hilog 是推荐的日志输出方式:
const DOMAIN = 0x0000; // 领域 ID,范围 0x0000~0xFFFF
// 不同级别的日志
hilog.debug(DOMAIN, 'TAG', 'Debug message: %{public}s', data);
hilog.info(DOMAIN, 'TAG', 'Info message: %{public}s', data);
hilog.warn(DOMAIN, 'TAG', 'Warning: %{public}s', data);
hilog.error(DOMAIN, 'TAG', 'Error: %{public}s', JSON.stringify(err));
占位符安全标记:
%{public}s— 公开信息,Release 版本也可见%{private}s— 隐私信息,Release 版本自动脱敏%d— 整数
5. ArkUI 声明式 UI 开发
5.1 从命令式到声明式
ArkUI 采用声明式 UI 开发范式,与传统命令式的核心区别在于:
命令式(传统 Android):
// 告诉系统"怎么做"
TextView tv = findViewById(R.id.text);
tv.setText("姓名");
tv.setTextColor(Color.GRAY);
声明式(ArkUI):
// 告诉系统"要什么"
Text('姓名')
.fontSize(12)
.fontColor('#A1887F')
声明式开发的核心优势:
- UI = f(state) — UI 是状态的函数,给定状态,UI 确定
- 自动 diff 更新 — 框架自动计算最小更新范围
- 可预测性 — 状态变化路径清晰,易于调试
- 更少代码 — 无需手动维护视图树
5.2 ArkUI 核心组件
本应用使用的 ArkUI 组件:
| 组件 | 使用位置 | 关键属性 |
|---|---|---|
Stack |
根容器,背景+内容层叠 | 自动重叠子组件 |
Column |
垂直布局容器 | justifyContent, alignItems |
Row |
水平布局容器 | layoutWeight, justifyContent |
Scroll |
滚动内容区 | layoutWeight(1) 填充剩余空间 |
Text |
显示文字 | fontSize, fontColor, fontWeight |
TextInput |
文本输入框 | placeholder, text, onChange |
Button |
操作按钮 | backgroundColor, borderRadius |
Circle |
状态指示灯 | fill, size |
Stack |
根容器 | 层叠背景和内容层 |
5.3 链式调用 API
ArkUI 的组件配置通过方法链串联,这是框架最显著的 API 风格:
Text('考试作弊检讨书生成器')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#4E342E')
链式调用的每个方法都返回组件本身,允许连续调用。这比传统的 XML 配置或 setter 方式更紧凑。
5.3.1 链式调用的工作原理
理解链式调用的内部机制有助于我们更好地使用它。每个组件方法返回的是组件实例本身(类似 Builder 设计模式),因此可以连续调用:
// Text 组件的链式调用展开理解
const text = new TextComponent('Hello')
text.setFontSize(16) // 返回 text 实例
text.setFontColor('#FFF') // 返回 text 实例
text.setFontWeight(Bold) // 返回 text 实例
这种方式的核心优点是消除了重复的组件引用变量,让代码更加简洁流畅。缺点是调试时难以在中间步骤打断点,不过可以通过拆开链式调用来解决。
5.3.2 链式调用的常见模式
在本项目中,总结了几种常见的链式调用模式:
模式一:尺寸 + 颜色 + 边距(卡片类)
.width('100%')
.padding(14)
.backgroundColor(Color.White)
.borderRadius(10)
.shadow({ radius: 4, color: '#0000000D', offsetY: 2 })
.margin({ top: 10 })
模式二:字体 + 颜色 + 对齐(文字类)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#4E342E')
.width('100%')
模式三:尺寸 + 背景 + 圆角(按钮类)
.width(108)
.height(36)
.backgroundColor('#8D6E63')
.borderRadius(18)
将这些常用模式整理成 @Builder 方法,可以减少重复代码。
5.3.3 链式调用的顺序注意事项
链式调用的顺序通常不影响最终渲染结果,但有一些最佳实践:
- 容器属性优先 — 先设置
width/height再设置padding/margin - 视觉属性集中 — 把颜色、圆角、阴影等视觉属性放在一起
- 事件回调放最后 —
.onClick()等事件绑定放在链尾
// 推荐顺序:尺寸 → 视觉 → 间距 → 事件
Button()
.width('80%')
.height(48)
.backgroundColor('#8D6E63')
.borderRadius(24)
.margin({ top: 20 })
.alignSelf(ItemAlign.Center)
.onClick(() => { this.generateApology() })
5.4 TextInput 组件详解
TextInput 是本应用最核心的交互组件,用于收集考生信息:
TextInput({ placeholder: '请输入考生姓名', text: this.studentName })
.fontSize(13)
.fontColor('#4E342E')
.layoutWeight(1)
.height(36)
.backgroundColor('#F5F0EB')
.borderRadius(6)
.onChange((v: string) => {
this.studentName = v
this.showNameTip = false
})
.placeholderColor('#D7CCC8')
TextInput 的构造函数接受一个对象参数,包含 placeholder(占位提示)和 text(当前值)。onChange 回调在用户输入时触发,接收当前文本作为参数。
5.5 尺寸与布局
ArkUI 提供多种尺寸设置方式:
// 固定尺寸
.width(72).height(80)
// 百分比
.width('100%')
// 权重分配(弹性布局)
.layoutWeight(1) // 填充剩余空间
对齐方式:
// 行容器:主轴水平
Row() { /* ... */ }
.justifyContent(FlexAlign.SpaceBetween) // 均匀分布
.alignItems(VerticalAlign.Center) // 垂直居中
// 列容器:主轴垂直
Column() { /* ... */ }
.justifyContent(FlexAlign.Center) // 垂直居中
.alignItems(HorizontalAlign.Center) // 水平居中
5.6 阴影与圆角
白色卡片通过阴影和圆角营造纸质感:
.backgroundColor(Color.White)
.borderRadius(10)
.shadow({ radius: 4, color: '#0000000D', offsetY: 2 })
shadow 属性接受一个对象,包含 radius(模糊半径)、color(阴影颜色)和 offsetY(垂直偏移)。
6. 状态管理与表单设计
6.1 @State 装饰器
@State 是 ArkUI 中最核心的状态管理装饰器。被 @State 修饰的变量发生变化时,所有依赖该变量的 UI 组件会自动重新渲染:
@Component
struct Index {
@State studentName: string = ''
@State studentId: string = ''
@State studentClass: string = ''
@State examSubject: string = ''
@State cheat0: boolean = false
@State cheat1: boolean = false
// ... 8 个作弊类型状态
@State selectedTone: number = 0
@State selectedLength: number = 0
@State generatedText: string = ''
@State showResult: boolean = false
@State copied: boolean = false
@State showNameTip: boolean = false
@State showSubjectTip: boolean = false
}
这里有一个值得注意的设计选择:为什么作弊类型不用数组 @State selectedCheatTypes: boolean[],而是拆成 8 个独立的 cheat0 ~ cheat7?
这是因为 ArkTS 对 @State 数组的修改检测有限制——直接修改数组元素(arr[i] = value)不会触发 UI 更新。使用独立变量虽然代码量更多,但能确保每个状态的变更都能可靠地触发重渲染。
6.2 计算属性(getter)
ArkUI 不支持 @Computed 装饰器,但可以通过 getter 实现计算属性:
get hasCheatSelected(): boolean {
return this.cheat0 || this.cheat1 || this.cheat2 || this.cheat3 ||
this.cheat4 || this.cheat5 || this.cheat6 || this.cheat7
}
get selectedCheatLabels(): string {
let result: string = ''
for (let i = 0; i < 8; i++) {
if (this.getCheat(i)) {
if (result.length > 0) result += '、'
result += this.cheatTypes[i].label
}
}
return result
}
getter 在模板中作为属性使用,当依赖的 @State 变量变化时自动重新计算。
6.3 状态变更驱动 UI
以生成按钮为例,它根据当前校验状态显示不同的提示:
Button() { Text('✨ 一键生成检讨书') /* ... */ }
.onClick(() => { this.generateApology() })
// 下方提示文字
if (this.studentName.trim().length === 0 ||
this.examSubject.trim().length === 0 ||
!this.hasCheatSelected) {
Text('⚠️ 请填写姓名、考试科目并选择作弊类型')
.fontSize(12)
.fontColor('#BF8A7A')
}
当 generateApology() 校验失败时,对应的 showNameTip 或 showSubjectTip 被设为 true,下方的错误提示立即显示——整个过程无需手动操作 DOM。
6.4 表单校验
表单校验逻辑集中在 generateApology() 方法中:
generateApology(): void {
// 校验:姓名为空
if (this.studentName.trim().length === 0) {
this.showNameTip = true
return
}
// 校验:科目为空
if (this.examSubject.trim().length === 0) {
this.showSubjectTip = true
return
}
// 校验:未选作弊类型
if (!this.hasCheatSelected) {
return
}
// 校验通过,执行生成
// ...
}
校验失败时,对应的提示标志设为 true,FieldRow 中的错误提示文字通过 if (showTip) 条件渲染显示。
6.5 页面切换状态
表单页面和结果页面通过 @State showResult 控制切换:
build() {
Stack() {
// ... 标题栏
if (!this.showResult) {
this.FormPage() // 表单模式
} else {
this.ResultPage() // 结果模式
}
}
}
切换路径:
FormPage ──点击"生成"──→ showResult=true ──→ ResultPage
ResultPage ──点击"修改"──→ showResult=false ──→ FormPage
ResultPage ──点击"重新生成"──→ generateApology() ──→ ResultPage(新内容)
6.6 复制状态反馈
复制按钮被点击后显示"已复制"状态,2 秒后自动恢复:
copyText(): void {
this.copied = true
setTimeout(() => {
this.copied = false
}, 2000)
}
UI 根据 copied 状态切换显示:
Text(this.copied ? '✅ 已复制' : '📋 复制')
.fontSize(14)
.fontColor(Color.White)
.backgroundColor(this.copied ? '#67C23A' : '#8D6E63')
7. 模板引擎的设计与实现
7.1 模板结构设计
检讨书模板是本应用的核心逻辑。每份检讨书由五个部分组成:
┌─────────────────────────────────────┐
│ 1. 开头 (openings) │
│ "尊敬的老师:您好!关于本次..." │
├─────────────────────────────────────┤
│ 2. 正文 (bodies) │
│ "经过您的批评教育,我认识到..." │
├─────────────────────────────────────┤
│ 3. 反思 (reflections) │
│ "通过这件事,我深刻意识到..." │
├─────────────────────────────────────┤
│ 4. 承诺 (promises) │
│ "在此,我向您郑重承诺..." │
├─────────────────────────────────────┤
│ 5. 结尾 (closings) │
│ "检讨人:xxx\n2025年x月x日" │
└─────────────────────────────────────┘
每个部分都有多个备选模板,生成时随机选择,确保每次生成的检讨书都有所不同。
7.2 三种语气风格
我们提供了三种语气风格,每种风格有独立的模板集:
🙏 诚恳认错(chengken):
“我怀着无比愧疚和懊悔的心情,向您递交这份检讨书,以深刻反省自己的错误。”
语气特征:真挚、谦卑、请求原谅。用词正式,态度诚恳,适合初犯或态度较好的学生。
📖 深刻反思(shenke):
“我的行为严重破坏了考试的公平性,对其他刻苦备考的同学造成了不公。”
语气特征:严肃、理性、上纲上线。从思想品德层面进行反思,适合情节较重或要求严格检查的情况。
😄 幽默自嘲(youmo):
“考试作弊就像吃重庆火锅时喝热汤——当时觉得爽,接下来就是火辣辣的教训。”
语气特征:轻松、自黑、有梗。用幽默的方式表达悔意,适合心态较好、与老师关系较融洽的学生。
7.3 占位符替换机制
模板中使用占位符标记需要动态替换的位置:
| 占位符 | 替换值 | 来源 |
|---|---|---|
{subject} |
考试科目 | this.examSubject |
{cheats} |
作弊类型(标签或描述) | selectedCheatLabels |
{name} |
考生姓名 | this.studentName |
{date} |
当前日期 | todayDate |
生成时通过字符串的 .replace() 方法替换:
text += this.pickRandom(tone.openings)
.replace('{subject}', this.examSubject)
.replace('{cheats}', this.selectedCheatLabels)
7.4 随机选择算法
pickRandom 方法从数组中随机选取一个元素:
pickRandom(arr: string[]): string {
const idx: number = Math.floor(Math.random() * arr.length)
return arr[idx]
}
这个方法不带泛型,参数类型明确为 string[],返回值类型明确为 string,避免了 ArkTS 对泛型类型推断的限制。
7.5 模板数据量统计
| 语气 | 开头 | 正文 | 反思 | 承诺 | 结尾 | 组合数 |
|---|---|---|---|---|---|---|
| 诚恳认错 | 2 | 2 | 2 | 2 | 2 | 32 |
| 深刻反思 | 2 | 2 | 2 | 2 | 2 | 32 |
| 幽默自嘲 | 2 | 2 | 2 | 2 | 2 | 32 |
每种语气有 32 种不同的组合方式,三种语气合计 96 种组合。这使得每次生成的检讨书都略有不同,不会出现完全相同的两篇。
7.6 模板选择逻辑
通过 getCurrentTone() 方法根据用户选择的语气返回对应的模板集:
getCurrentTone(): ToneSet {
if (this.selectedTone === 1) return this.shenke
if (this.selectedTone === 2) return this.youmo
return this.chengken
}
这里没有使用对象索引访问(如 this.templates[toneKey]),因为 ArkTS 不允许通过计算属性名访问对象字段(arkts-no-props-by-index 规则)。改用显式的 if/else 分支来选择合适的模板集。
8. @Builder 组件化实践
8.1 @Builder 的基本用法
@Builder 是 ArkUI 提供的自定义构建函数装饰器,用于封装可复用的 UI 片段:
@Builder
SectionTitle(title: string) {
Text(title)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#4E342E')
.width('100%')
.margin({ top: 14, bottom: 6 })
}
在 build() 或另一个 @Builder 中直接调用:
this.SectionTitle('👤 考生信息')
this.SectionTitle('🚫 作弊类型(可多选)')
8.2 带参数的 @Builder
本应用的所有 @Builder 都带有参数,实现不同数据、不同样式的渲染:
FieldRow — 表单输入行:
@Builder
FieldRow(label: string, value: string, onChange: (v: string) => void,
placeholder: string, showTip: boolean) {
Column() {
Row() {
Text(label)
.fontSize(12)
.fontColor('#A1887F')
.width(56)
TextInput({ placeholder: placeholder, text: value })
// ... 样式配置
.onChange((v: string) => { onChange(v) })
}
if (showTip) {
Text('⚠️ 请填写' + label)
.fontSize(11)
.fontColor('#BF8A7A')
}
}
}
CheatCard — 作弊类型选择卡片:
@Builder
CheatCard(index: number) {
Column() {
Text(this.cheatTypes[index].icon).fontSize(24)
Text(this.cheatTypes[index].label).fontSize(11)
.fontColor(this.getCheat(index) ? '#FFFFFF' : '#5D4037')
Text(this.cheatTypes[index].desc).fontSize(8)
.fontColor(this.getCheat(index) ? '#FFFFFFAA' : '#A1887F')
}
.width(72).height(80)
.backgroundColor(this.getCheat(index) ? '#8D6E63' : '#F5F0EB')
.borderRadius(8)
.onClick(() => { this.toggleCheat(index) })
}
8.3 @Builder 的限制与注意事项
在开发过程中,我们发现 ArkTS 对 @Builder 有一些严格的限制:
限制 1:不能有变量声明
// ❌ 错误:@Builder 中不能使用 let
@Builder
CheatCard(index: number) {
let item = this.cheatTypes[index] // 编译错误!
let isSelected = this.getCheat(index) // 编译错误!
Column() { /* ... */ }
}
// ✅ 正确:直接在 UI 中使用表达式
@Builder
CheatCard(index: number) {
Column() {
Text(this.cheatTypes[index].icon) // 直接访问
Text(this.cheatTypes[index].label)
.fontColor(this.getCheat(index) ? '#FFF' : '#5D4037') // 直接调用
}
}
限制 2:不能接受闭包参数作为内容区
// ❌ 错误:@Builder 不能接受 () => void 并调用它
@Builder
FormSection(title: string, content: () => void) {
Column() {
Text(title)
content() // 编译错误:不满足 UI 组件语法
}
}
// ✅ 正确:直接内联内容,不使用 builder 包装
8.4 @Builder vs @Component
| 维度 | @Builder | @Component |
|---|---|---|
| 复杂度 | 轻量 UI 片段 | 独立功能模块 |
| 状态管理 | 共享父组件 @State | 可拥有自己的 @State |
| 复用范围 | 当前文件内 | 可跨文件导入 |
| 参数传递 | 函数参数 | @Prop / @Link |
| 代码量 | 较少 | 较多 |
选择原则:当 UI 片段需要独立状态时用 @Component;仅仅是模板抽取时用 @Builder。
9. ArkTS 严格模式与常见错误修复
9.1 ArkTS 语法限制概览
ArkTS 是 TypeScript 的超集,但为了编译期优化和运行性能,施加了比标准 TypeScript 更严格的语法限制。在 API 24 (SDK 6.1.1) 中,以下是本应用遇到的关键限制:
| 规则 ID | 限制内容 | 本应用中的体现 |
|---|---|---|
arkts-no-any-unknown |
禁止使用 any 和 unknown 类型 |
泛型函数需要明确类型 |
arkts-no-untyped-obj-literals |
对象字面量必须匹配接口 | 模板定义需要接口声明 |
arkts-no-props-by-index |
禁止通过索引访问对象属性 | 不能用 obj[key] 语法 |
arkts-no-inferred-generic-params |
泛型函数需明确类型参数 | 取消泛型,使用具体类型 |
| 10905209 | @Builder 中只允许 UI 语法 | 不能声明变量 |
9.2 错误修复实战
在开发过程中,我们遇到了 36 个编译错误。以下是分类修复方案:
问题 1:对象字面量类型不匹配(7 个错误)
错误信息:Object literal must correspond to some explicitly declared class or interface
原因:模板对象字面量中包含 title 字段,但 ToneSet 接口没有声明该字段。
修复:移除 title 字段,改用独立的 toneLabels 数组显示:
// 修复前
private templates: ToneTemplate = {
chengken: { title: '诚恳认错', openings: [...], ... },
shenke: { title: '深刻反思', openings: [...], ... },
youmo: { title: '幽默自嘲', openings: [...], ... },
}
// 修复后
private chengken: ToneSet = { openings: [...], bodies: [...], ... }
private shenke: ToneSet = { openings: [...], bodies: [...], ... }
private youmo: ToneSet = { openings: [...], bodies: [...], ... }
private toneLabels: string[] = ['🙏 诚恳认错', '📖 深刻反思', '😄 幽默自嘲']
问题 2:索引访问被禁止(1 个错误)
错误信息:Indexed access is not supported for fields
原因:this.templates[toneKey] 使用变量作为属性名访问对象。
修复:改用 getCurrentTone() 方法通过 if/else 选择:
// 修复前
const template = this.templates[toneKey]
// 修复后
getCurrentTone(): ToneSet {
if (this.selectedTone === 1) return this.shenke
if (this.selectedTone === 2) return this.youmo
return this.chengken
}
问题 3:泛型函数类型推断(5 个错误)
错误信息:Type inference in case of generic function calls is limited
原因:pickRandom<T>(arr: T[]): T 的泛型参数无法在 ArkTS 中正确推断。
修复:取消泛型,使用具体类型:
// 修复前
pickRandom<T>(arr: T[]): T { ... }
// 修复后
pickRandom(arr: string[]): string { ... }
问题 4:@Builder 中声明变量(4 个错误)
错误信息:Only UI component syntax can be written here
原因:在 @Builder 的 UI 组件开始之前使用了 let 声明变量。
修复:将变量表达式直接内联到 UI 组件中:
// 修复前
@Builder ToneButton(index: number) {
let isSelected: boolean = this.selectedTone === index // 错误
Column() { /* ... */ }
}
// 修复后
@Builder ToneButton(index: number) {
Column() {
Text(this.toneLabels[index])
.fontColor(this.selectedTone === index ? '#FFF' : '#5D4037')
}
.backgroundColor(this.selectedTone === index ? '#8D6E63' : '#F5F0EB')
}
问题 5:@Builder 中的闭包内容参数(5 个错误)
错误信息:'content()' does not meet UI component syntax
原因:在 @Builder 中接受 () => void 参数并调用它来渲染内容。
修复:取消 FormSection builder,直接在 FormPage 中内联结构:
// 修复前
this.FormSection('👤 考生信息', () => {
this.FieldRow('姓名', ...)
this.FieldRow('学号', ...)
})
// 修复后
this.SectionTitle('👤 考生信息')
Column() {
this.FieldRow('姓名', ...)
this.FieldRow('学号', ...)
}
.padding(14)
.backgroundColor(Color.White)
.borderRadius(10)
.shadow({ radius: 4, color: '#0000000D', offsetY: 2 })
9.3 修复前后对比
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 编译错误数 | 36 | 0 |
| 代码行数 | 640 | 663 |
| 接口数量 | 3 | 2 |
| 泛型函数 | 1 | 0 |
| @Builder 数量 | 9 | 8 |
9.4 ArkTS 开发建议
基于这次实战经验,给 ArkTS 开发者的建议:
- 避免使用泛型 — 除非必要,尽量使用具体类型
- 避免索引访问 — 用 if/else 或 switch 替代
obj[key] - 提前声明接口 — 所有对象字面量都需要匹配接口
- @Builder 保持简单 — 不要在 @Builder 中声明变量
- 使用 getter — 替代计算属性
- 别用 any/unknown — 所有类型必须明确
10. UI/UX 设计:纸笔风格视觉系统
10.1 色彩体系
本应用采用温暖、亲切的纸笔风格(Paper & Pen)色彩系统,与传统冷色调的科技应用形成对比:
| 层级 | 色值 | 用途 |
|---|---|---|
| 背景 | #F5F0EB |
米白色仿纸背景 |
| 卡片 | #FFFFFF |
白色卡片,带轻微阴影 |
| 主文字 | #4E342E |
深棕色标题 |
| 正文 | #5D4037 |
正文文字 |
| 辅助 | #A1887F |
副标题、提示文字 |
| 主色调 | #8D6E63 |
棕色按钮、选中态 |
| 浅色 | #D7CCC8 |
边框、分隔线 |
| 浅背景 | #EFEBE9 |
标签背景 |
| 警告 | #BF8A7A |
错误提示文字 |
色彩设计理念:
- 米色背景模仿纸张质感,降低视觉疲劳
- 棕色主色调传达温暖、真诚的感觉
- 对比度适中,文字与背景的对比度在 4.5:1 以上,符合无障碍标准
10.2 排版系统
主标题:18sp, Bold, #4E342E
标题:14sp, Bold, #4E342E
标签:12sp, Regular, #A1887F
输入框:13sp, Regular, #4E342E
按钮文字:16sp, Bold, #FFFFFF
辅助文字:9-11sp, Regular, #A1887F
10.3 间距与圆角
// 间距系统
.margin({ top: 14, bottom: 6 }) // 标题下边距
.padding(14) // 卡片内边距
.margin({ top: 10 }) // 卡片间距
// 圆角系统
.borderRadius(10) // 大卡片
.borderRadius(8) // 小卡片
.borderRadius(24) // 生成按钮(全圆角)
.borderRadius(18) // 选择按钮
.borderRadius(6) // 输入框
10.4 选择卡片交互设计
作弊类型卡片选中前后的视觉差异:
| 状态 | 背景色 | 文字色 | 边框 |
|---|---|---|---|
| 未选中 | #F5F0EB |
#5D4037 |
1px #D7CCC8 |
| 已选中 | #8D6E63 |
#FFFFFF |
无边框 |
.backgroundColor(this.getCheat(index) ? '#8D6E63' : '#F5F0EB')
.borderWidth(this.getCheat(index) ? 0 : 1)
.borderColor('#D7CCC8')
10.5 结果页面设计
结果页面模拟了纸质检讨书的视觉效果:
Column() {
// 头部:标题 + 语气标签
Row() {
Text('📄 检讨书').fontSize(16).fontWeight(FontWeight.Bold)
Row() {
Circle().size({ width: 6, height: 6 }).fill('#8D6E63')
Text('已生成').fontSize(11).fontColor('#8D6E63')
}
.backgroundColor('#EFEBE9')
.borderRadius(10)
}
// 分隔线
Row().width('100%').height(1).backgroundColor('#D7CCC8')
// 检讨正文
Text(this.generatedText)
.fontSize(13)
.fontColor('#5D4037')
.lineHeight(22)
}
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 8, color: '#0000001A', offsetY: 4 })
11. 条件渲染与页面切换
11.1 if/else 条件渲染
ArkUI 使用标准的 if/else 语句实现条件渲染:
if (!this.showResult) {
this.FormPage()
} else {
this.ResultPage()
}
当 showResult 变化时,ArkUI 自动销毁旧页面、创建新页面。这确保了 UI 始终与状态同步。
11.2 表单页面的结构
FormPage 使用 Scroll + Column 实现可滚动的表单:
@Builder
FormPage() {
Scroll() {
Column() {
// 四个表单区域按顺序排列
this.SectionTitle('👤 考生信息')
Column() { /* 四个输入框 */ }
.padding(14).backgroundColor(Color.White).borderRadius(10)
.shadow({ radius: 4, color: '#0000000D', offsetY: 2 })
this.SectionTitle('🚫 作弊类型(可多选)')
// ...
this.SectionTitle('🎭 检讨语气')
// ...
this.SectionTitle('📏 字数')
// ...
Button('✨ 一键生成检讨书') // ...
}
.width('100%')
.padding({ left: 16, right: 16 })
}
.layoutWeight(1)
.width('100%')
}
11.3 结果页面的结构
ResultPage 展示生成的检讨书和三个操作按钮:
@Builder
ResultPage() {
Scroll() {
Column() {
// 检讨书卡片
Column() {
Row() { /* 标题 + 状态标签 */ }
Row() { /* 分隔线 */ }
Text(this.generatedText) /* 正文 */
}
.padding(16).backgroundColor(Color.White).borderRadius(12)
// 操作按钮
Row() {
Button('📋 复制')
Button('🔄 重新生成')
Button('✏️ 修改')
}
}
}
.layoutWeight(1)
.width('100%')
}
11.4 错误提示的条件显示
错误提示在 FormPage 底部显示,当校验不通过时可见:
if (this.studentName.trim().length === 0 ||
this.examSubject.trim().length === 0 ||
!this.hasCheatSelected) {
Text('⚠️ 请填写姓名、考试科目并选择作弊类型')
.fontSize(12)
.fontColor('#BF8A7A')
.alignSelf(ItemAlign.Center)
}
同时,在每个 FieldRow 中,单独的字段错误提示也使用条件渲染:
if (showTip) {
Text('⚠️ 请填写' + label)
.fontSize(11)
.fontColor('#BF8A7A')
}
12. 鸿蒙 API 24 特性应用
12.1 API 24 相比前代的重要更新
| 特性 | 说明 | 对本项目的意义 |
|---|---|---|
| 增强的 Stage 模型 | 更好的生命周期管理 | 页面状态更可控 |
| ArkUI 3.0 | 更流畅的动画和渲染 | UI 响应更迅速 |
| 严格模式编译 | 编译时类型检查 | 提前发现潜在错误 |
| 包管理优化 | 支持 OHM 规范 | 模块引用更规范 |
12.2 实际使用到的 API 24 特性
1. Stage 模型 API(@kit.AbilityKit)
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
UIAbility— API 24 中 Ability 的基类ConfigurationConstant.ColorMode— 颜色模式配置
2. ArkUI 组件库(@kit.ArkUI)
import { window } from '@kit.ArkUI';
window.WindowStage.loadContent()— 加载页面- ArkUI 内置组件:
Stack,Column,Row,Scroll,Text,TextInput,Button,Circle
3. 日志 API(@kit.PerformanceAnalysisKit)
import { hilog } from '@kit.PerformanceAnalysisKit';
- 结构化日志,支持隐私标记(
%{public}s/%{private}s)
12.3 ArkTS 编译配置
在 build-profile.json5 中,strictMode 配置项启用了更严格的编译检查:
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
caseSensitiveCheck:确保文件引用路径与实际文件名大小写一致。例如pages/Index和pages/index是不同的。useNormalizedOHMUrl:规范模块引用路径,避免路径解析歧义。
12.4 hvigor 构建工具
hvigor 是鸿蒙项目的构建工具,类似于 Android 的 Gradle 或前端的 Webpack。在本次开发中,我们使用了以下命令:
# 构建 HAP 包
hvigorw assembleHap --mode module -p module=entry@default
# 查看可用任务
hvigorw tasks
# 清理缓存
hvigorw prune
hvigor 的构建流程包括以下步骤:
PreBuild → CreateModuleInfo → ProcessProfile → CompileResource
→ CompileArkTS → GeneratePkgModuleJson → PackageHap → SignHap
其中 CompileArkTS 是核心步骤,负责将 ArkTS 源码编译为字节码。这也是出现编译错误的阶段——好消息是 ArkTS 编译器提供了详细的错误信息,包括行号、错误代码和规则 ID。
12.5 @kit 模块化导入
在 API 24 中,鸿蒙推荐使用 @kit.xxx 的方式进行模块化导入,取代了旧的 @ohos.xxx 方式:
// API 24 推荐方式
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
// 旧方式(仍然兼容但不推荐)
// import ability from '@ohos.ability.ability';
// import hilog from '@ohos.hilog';
@kit 模块是 API 24 新增的特性,它将相关的 API 打包成更高层次的模块,让导入更加直观。例如 @kit.AbilityKit 包含了所有与 Ability 相关的 API。
12.6 ArkTS 与标准 TypeScript 的差异
对于有 TypeScript 经验的开发者,以下差异需要注意:
| 特性 | TypeScript | ArkTS |
|---|---|---|
any 类型 |
可用 | 禁止 |
unknown 类型 |
可用 | 禁止 |
索引签名 [key: string] |
可用 | 受限制 |
| 泛型函数 | 完全支持 | 有限支持 |
| 联合类型 | 完全支持 | 有限支持 |
| 装饰器 | 实验性 | 原生支持(@State/@Builder等) |
| 对象字面量 | 可隐式推断 | 必须匹配接口 |
null/undefined |
独立类型 | 统一使用可选链 |
这些限制的存在是为了让 ArkTS 编译器可以进行更深入的静态分析,从而生成更高效的字节码。
12.4 未来的扩展:真实 API
如果需要将本应用扩展为生产级应用,可以考虑接入以下鸿蒙 API:
| API | 包路径 | 用途 |
|---|---|---|
| 剪贴板 | @ohos.pasteboard |
真正的复制到剪贴板功能 |
| 文件存储 | @ohos.file.fs |
保存检讨书到本地 |
| 分享 | @ohos.share |
分享检讨书到其他应用 |
| 打印 | @ohos.print |
打印检讨书 |
| 推送 | @ohos.commonEventManager |
通知相关功能 |
13. 安全与实践考虑
13.1 数据隐私
检讨书可能包含个人身份信息(姓名、学号、班级)。虽然本应用是纯本地应用,不涉及网络传输,但在正式发布时需要注意:
- 数据存储安全:如果增加保存功能,应使用
@ohos.data.preferences加密存储 - 隐私声明:明确告知用户数据仅在本地处理
13.2 输入验证
// 防止空名称
if (this.studentName.trim().length === 0) {
this.showNameTip = true
return
}
13.3 防滥用
本应用仅供教育用途,建议在正式使用时添加免责声明:
@Builder
Disclaimer() {
AlertDialog.show({
title: '免责声明',
message: '本工具仅供学习参考,请勿用于实际作弊行为。' +
'诚信考试,从我做起。',
confirm: { value: '我知道了', action: () => {} }
})
}
14. 踩坑与经验总结
14.1 @Builder 中的变量声明
问题:在 @Builder 方法的 UI 组件开始之前使用 let 声明变量会导致编译错误。
错误信息:Only UI component syntax can be written here
解决方案:将变量表达式直接内联到 UI 属性中,或者将计算逻辑移到 @Builder 外部的普通方法中。
14.2 对象字面量必须匹配接口
问题:ArkTS 不允许声明未匹配接口的对象字面量。所有对象字面量必须有明确的类型注解或匹配已声明的接口。
解决方案:提前创建接口定义,所有对象字面量使用接口类型注解。
14.3 避免使用索引访问
问题:ArkTS 不允许使用变量通过索引访问对象属性(obj[key])。
错误信息:Indexed access is not supported for fields
解决方案:使用 if/else 链或 switch 语句替代。
14.4 资源冲突
问题:在 AppScope 和 entry 模块的 string.json 中同时声明 app_name 会导致资源冲突警告。
错误信息:'app_name' conflict, first declared at AppScope... but declared again
解决方案:应用级资源(如 app_name)只在 AppScope 中声明,entry 中只声明模块级资源。
14.5 数组状态管理
问题:@State 数组的直接索引修改(arr[i] = value)不会触发 UI 更新。
解决方案:拆分为独立的 @State 变量,或使用 [...newArray] 创建新数组替换旧数组。
// 方案 A:独立变量
@State cheat0: boolean = false
@State cheat1: boolean = false
// ... 8 个变量
// 方案 B:创建新数组
@State cheats: boolean[] = new Array(8).fill(false)
this.cheats = [...this.cheats.slice(0, i), true, ...this.cheats.slice(i + 1)]
方案 A 代码更明确,方案 B 更简洁。根据项目规模选择。
14.6 字符串换行
在 ArkTS 中,字符串字面量过长时需要合理换行。ArkTS 支持字符串连接和模板字符串:
// 字符串连接
'尊敬的老师:\n\n您好!关于本次' + subject + '考试中的违纪行为,'
// 直接包含换行符
'第一行\n第二行\n第三行'
对于多行模板,每行字符串用 + 连接,保持代码整洁。注意 \n 是换行符,在检讨书生成中频繁使用。
14.7 开发效率建议
1. 渐进式开发
先写核心逻辑(模板引擎),再写 UI 层。这样可以在 DevEco Studio 的 Previewer 中快速验证功能。
2. 利用 Previewer
ArkUI 的 Previewer 工具可以实时预览 UI 效果。对于 @Builder 中的 UI 修改,保存后即可看到效果,无需完整构建。
3. 构建日志定位
出错了先看错误行号和错误类型。ArkTS 的错误信息通常包含规则 ID(如 arkts-no-props-by-index),搜索这个 ID 可以找到详细的规则说明。
4. 善用类型注解
ArkTS 的类型推断有限,显式添加类型注解可以减少编译错误:
const now: Date = new Date() // ✅ 显式类型
const idx: number = Math.floor(...) // ✅ 显式类型
14.8 常见编译错误速查表
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
Object literal must correspond to some explicitly declared class or interface |
对象字面量没有匹配的接口 | 创建接口或添加类型注解 |
Indexed access is not supported for fields |
使用变量索引访问对象 | 使用 if/else 替代 |
Only UI component syntax can be written here |
@Builder 中有非 UI 语句 | 将逻辑移到 builder 外部 |
Type inference in case of generic function calls is limited |
泛型类型无法推断 | 取消泛型,使用具体类型 |
'xxx' conflict, first declared at ... but declared again |
资源重复声明 | 统一资源声明位置 |
Cannot find name 'xxx' |
变量未定义或拼写错误 | 检查变量名和 import |
Object is of type 'unknown' |
索引访问返回 unknown 类型 | 改用类型明确的方法 |
'content()' does not meet UI component syntax |
@Builder 中调用了函数 | 内联 UI 内容 |
14.9 从 36 个编译错误到 0 的思考
回顾这次修复 36 个编译错误的过程,有几点值得分享的思考:
第一,错误数量不等于项目复杂度。 36 个错误听起来很多,但归类后只有 6 个根本原因。一旦理解了 ArkTS 的核心限制,大部分错误可以通过全局替换一次性修复。
第二,编译错误是最好的老师。 ArkTS 编译器提供的错误信息非常详细,包括错误 ID、行号和具体原因。认真阅读每条错误信息,理解背后的规则,是掌握 ArkTS 的最快途径。
第三,严格模式是好事。 虽然 ArkTS 的严格模式给开发初期带来了更多编译错误,但它确保了我们写出更健壮的代码。这些错误如果在运行时才发现,调试成本会高得多。
第四,渐进式修复策略。 面对大量错误时,不要试图一次性全部修复。错误之间存在依赖关系——前几个错误的修复可能会消除后续的错误。先修复根源性错误(如接口定义、类型声明),再修复派生错误。
14.10 实际使用案例
为了帮助你更好地理解这个应用的使用场景,这里提供三个实际生成的检讨书示例:
示例一:诚恳认错版
尊敬的老师:
您好!关于本次高等数学考试中的违纪行为,我怀着无比愧疚和懊悔的心情,向您递交这份检讨书,以深刻反省自己的错误。
经过您的批评教育和自己的深刻反思,我认识到考试作弊是一种极其错误的行为。携带小抄不仅违反了校规校纪,更是对自己学习成果的不负责任。学习是一个循序渐进的过程,考试的目的在于检验我们真实的学习水平,而我却用欺骗的方式来对待它,实在是愧对老师的教诲,也辜负了父母的期望。
通过这件事,我深刻意识到诚信考试的重要性。分数固然重要,但比分数更重要的是做人的品格。一次作弊行为可能会让我暂时获得"好成绩",但失去的是老师对我的信任,以及自己内心的安宁。我决心以此为戒,在今后的学习和生活中,时刻铭记诚信二字的分量。
在此,我向您郑重承诺:第一,今后决不再犯类似的错误,严格遵守考试纪律;第二,我会利用课余时间补上高等数学的知识漏洞,通过自己的努力取得真实的进步;第三,我会主动向同学们宣传诚信考试的重要性,以自己的教训警示他人。恳请老师给我一次改过自新的机会。
检讨人:张明轩
2025年6月15日
示例二:幽默自嘲版
尊敬(且此刻一定对我很失望)的老师:
您好!关于大学英语考试,我有一份"精彩"的检讨要呈上。说真的,使用手机这个操作,现在回想起来我自己都替自己尴尬。
经过(被抓住后的)冷静思考,我认识到考试作弊这件事,大概是世界上最不划算的买卖了。冒着被发现的巨大风险,承受着内心的煎熬,最后换来的可能还不如自己老老实实写对的几分。使用手机这个计划,从我决定实施的那一刻起就注定是个悲剧——事实证明,我在"做坏事"这方面的天赋值为零。
这件事给我上了生动的一课:人生没有捷径,如果有,那一定是通向"检讨书"的捷径。考试作弊就像吃重庆火锅时喝热汤——当时觉得爽,接下来就是火辣辣的教训。我保证,这是我人生中第一份也是最后一份检讨书,因为这种"体验"一次就够了。
在此我立下FLAG:第一,以后考试绝对"目不斜视",专注于自己的试卷;第二,我会把用在"研究作弊方法"上的精力用来研究大学英语,争取下次靠实力及格;第三,如果下次考试进步了,希望老师能忘记这次的不愉快,记住我改过自新的帅气背影。
一个正在反省的学生:李小明
2025年6月15日P.S. 这份检讨写得很辛苦,下次不想再写了。
示例三:深刻反思版
尊敬的老师、校领导:
我以最沉痛的心情,就本人在线性代数考试中交头接耳、偷看他人的严重违纪行为,向学校和老师作出深刻检讨。
我的行为严重破坏了考试的公平性,对其他刻苦备考的同学造成了不公。考试不仅是对知识的检验,更是对人格的考验。交头接耳、偷看他人的行为,反映出我在思想品德上的严重缺失。我辜负了学校的培养,辜负了老师的信任,也辜负了父母的期望。作为一名学生,我没有尽到最基本的本分。
这次违纪事件对我是一次刻骨铭心的教训。它让我深刻认识到:第一,诚信是立身之本,任何时候都不能放弃;第二,学习没有捷径,必须付出真实的努力;第三,规则意识是公民基本素养,无视规则终将付出代价。我将把这次教训铭记在心,作为人生的重要警示。
为弥补过错,我郑重承诺:一、深刻反省,写出书面检讨并在班级公开宣读;二、自觉接受学校给予的纪律处分,绝无怨言;三、以此为戒,在剩余的学习生涯中诚信考试、踏实学习;四、主动参与诚信考试宣传活动,用自己的教训警示他人。请学校和老师监督我。
检讨人:王强
2025年6月15日附:本人愿意接受学校相应纪律处分。
这些示例展示了三种语气风格的实际效果。用户可以根据自己的情况和性格选择合适的语气风格,生成个性化的检讨书。
15. 结语与展望
15.1 项目回顾
本文从零构建了一个鸿蒙 Next 考试作弊检讨书生成器,涵盖了:
- 项目架构:Stage 模型、三层架构、组件树设计
- UI 开发:ArkUI 声明式 UI、@Builder 组件化、纸笔风格设计
- 状态管理:@State 驱动、计算属性、表单校验
- 模板引擎:多语气模板、占位符替换、随机组合
- ArkTS 严格模式:语法限制、错误修复、开发建议
- API 24 配置:stageMode、编译选项、资源管理
15.2 扩展方向
这个应用可以进一步扩展为:
- 更多语气风格 — 增加"家庭温情版"“严格版”"简约版"等更多风格
- 自定义模板 — 允许用户编辑和保存自定义模板
- 导出功能 — 支持 PDF/Word 格式导出
- 云端同步 — 跨设备同步模板和草稿
- AI 辅助生成 — 接入端侧 AI 模型,根据用户输入的描述生成个性化检讨书
- 多语言支持 — 支持英文、日文等多语言检讨书
15.3 写在最后
鸿蒙 Next 作为一个全新的操作系统生态,ArkTS 的严格模式虽然给初学开发者带来了一定的学习曲线,但它强制开发者写出更规范、更安全的代码。通过本次实战,我们深入理解了 ArkTS 的类型系统、编译期约束和声明式 UI 开发范式。
检讨书生成器虽是一个看似简单的工具型应用,但它涵盖了表单处理、模板引擎、条件渲染、状态管理等多个核心开发场景,可以作为鸿蒙入门开发者的一个完整的练手项目。
最后,技术本身是中性的。检讨书生成器可以帮助学生快速完成检讨流程,但真正重要的还是认识到诚信考试的意义——工具可以被使用,但成长没有捷径。
附录
A. 项目完整源码
DemoRuanjian3/
├── build-profile.json5 # 项目级配置 (API 24)
├── AppScope/
│ ├── app.json5 # 应用级配置
│ └── resources/base/element/string.json # 应用名资源
└── entry/
├── build-profile.json5 # 模块级配置 (Stage 模型)
├── oh-package.json5 # 依赖管理
└── src/main/ets/
├── entryability/
│ └── EntryAbility.ets # Ability 生命周期
└── pages/
└── Index.ets # 主页面 (663行)
B. 关键词索引
| 关键词 | 章节 |
|---|---|
| @State | 6.1 |
| @Builder | 8.1 |
| Stage 模型 | 4.1 |
| API 24 | 3.2, 12.1 |
| ArkUI | 5.1 |
| ArkTS 严格模式 | 9.1 |
| 模板引擎 | 7.1 |
| TextInput | 5.4 |
| 条件渲染 | 11.1 |
| 编译错误修复 | 9.2 |
C. 参考资料
更多推荐



所有评论(0)