App18-条款显微镜:合同风险审查的HarmonyOS开发实践
摘要:本文以HarmonyOS AI应用"条款显微镜"(Contract Risk Reviewer)为例,完整记录从需求对齐、架构设计、原子化拆解、审批、自动化执行到评估的全流程(6A工作流)。深度剖析ArkTS声明式UI构建、Mock数据多场景策略、AI API Stub模式、风险等级可视化编码、条件渲染与列表渲染最佳实践等核心技术,并探讨鸿蒙PC端适配展望、鸿蒙Flutter框架混合开发可行性等进阶话题。全文约11000字,适合HarmonyOS中级及以上开发者阅读。
---

一、引言
在商业活动中,合同是保障交易双方权益的核心法律文书。然而,据统计超过67%的中小企业和个人在签署合同时从未进行过专业法律审查,导致大量合同纠纷。一份看似普通的劳动合同可能隐藏着"竞业限制范围过大""加班费已包含在工资中"等违法条款,一份租赁合同可能暗含"维修责任全部转嫁承租方"的不公平约定,一份合作协议可能缺乏"退出机制"和"利润分配"等关键条款。
"条款显微镜"(App18)正是为解决这一痛点而生------它是一款运行在HarmonyOS上的AI合同风险智能审查工具,覆盖劳动合同、租赁合同、采购合同、服务合同、合作协议五大常见合同类型,通过对合同条款的智能分析,自动识别风险条款并给出修改建议,同时指出缺失的必备条款,帮助用户在签署合同前发现潜在风险。
本文将按照6A工作流(Align-Architect-Atomize-Approve-Automate-Assess)完整复盘该应用的开发过程,从技术选型、架构设计、代码实现、性能优化到质量评估,为HarmonyOS开发者提供一套可复用的工程实践参考。
关键词:鸿蒙、鸿蒙PC、鸿蒙Flutter框架、ArkTS、AI应用、合同风险审查、Mock数据、声明式UI、6A工作流
二、Align(对齐阶段):从模糊需求到精确规范
2.1 项目上下文分析
在开发"条款显微镜"之前,我们首先对现有HarmonyOS工程进行了全面分析:
- 技术栈:HarmonyOS NEXT(API 26,modelVersion 26.0.0),ArkTS语言,声明式UI框架
- 架构模式:单页面应用(SPA),Entry Ability作为入口,HAP模块化结构,每个应用独立为一个页面文件
- 现有组件 :已有
router导航机制,@Entry/@Component装饰器使用模式,ForEach列表渲染模式 - 依赖关系:当前工程无第三方依赖,纯ArkTS原生实现,无外部UI库
- 代码模式 :全工程已有40个AI应用,均采用相似的Mock数据驱动模式,
@Component单文件实现
2.2 需求理解与边界确认
原始需求
构建一个合同风险智能审查应用,用户选择合同类型并输入合同文本,系统返回风险条款分析和缺失条款提示。
需求细化
经过多轮对齐,我们确定了以下核心功能边界:
| 维度 | 决策 |
|---|---|
| 合同类型 | 劳动、租赁、采购、服务、合作五种,通过按钮组切换 |
| 输入方式 | 文本粘贴(TextInput),暂不支持文件上传和OCR识别 |
| 审查输出 | 风险条款列表(含条款名称、风险描述、风险等级、修改建议)+ 缺失条款列表 |
| AI能力 | 一期采用Mock数据验证交互流程,预留AI API Stub接口 |
| 风险等级 | 三级:高(红色)、中(橙色)、低(绿色),使用颜色编码增强辨识度 |
| 设备类型 | 优先适配Phone,保留鸿蒙PC适配扩展点 |
| 国际化 | 一期仅支持中文,后续可通过资源文件扩展 |
关键决策点确认
- Q:Mock数据应该覆盖多少场景? A:五种合同类型各一个场景,每个场景包含3-4条风险条款和3-4条缺失条款,确保每种类型的审查结果具有专业性和领域针对性。
- Q:合同文本输入是否必填? A:一期不强制要求输入,即使用户仅选择合同类型,也返回该类型的通用风险分析,降低使用门槛。
- Q:风险等级如何划分? A:采用三级体系:高风险(涉及违法或重大权益损害,如"约定实行996工作制")、中风险(涉及不公平但未违法,如"押金为3个月租金")、低风险(改进建议类)。
- Q:AI API调用策略? A:采用Stub模式,Mock数据与真实API使用相同的数据契约(
ContractResult接口),确保后续切换零成本。callAI方法以注释形式保留,标注了完整的fetch调用逻辑。 - Q:鸿蒙PC端是否需要适配? A:当前UI已采用响应式布局思路,后续可扩展鸿蒙PC窗口适配和多列布局。
2.3 验收标准
- 用户可通过按钮组选择五种合同类型之一
- 用户可粘贴合同条款文本
- 点击"开始审查"后展示对应场景的风险分析结果
- 风险条款以卡片形式展示,包含条款名称、风险等级标签(颜色编码)、风险描述和修改建议
- 缺失条款以列表形式展示,开头有橙色感叹号标识
- 所有交互在1秒内完成响应
- 代码通过ArkTS编译检查,无类型错误
- 返回按钮可正常导航至主页
三、Architect(架构阶段):从规范到系统设计
3.1 整体架构设计
"条款显微镜"采用单页面MVP(Model-View-Presenter)架构,将数据模型、UI渲染和业务逻辑清晰分离:
┌──────────────────────────────────────────────────────────────┐
│ Entry Ability │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ ClauseMicroscope │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │ │
│ │ │ Navigation │ │ Type Select │ │ Text Input │ │ │
│ │ │ Bar │ │ (Button组) │ │ (TextInput) │ │ │
│ │ └──────────────┘ └──────────────┘ └───────────────┘ │ │
│ │ │ │ │
│ │ ┌────────┴────────┐ │ │
│ │ │ Action Button │ │ │
│ │ │ "开始审查" │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────────────────┴──────────────────────────┐ │ │
│ │ │ State Management (@State) │ │ │
│ │ │ contractInput / typeIndex / risks[] / missing[] │ │ │
│ │ │ scenarioLabel / showResults │ │ │
│ │ └──────────────────────────┬──────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────────────────┴──────────────────────────┐ │ │
│ │ │ Data Layer (MockData / AI API Stub) │ │ │
│ │ │ ┌─────────────────────┐ ┌──────────────────────┐ │ │ │
│ │ │ │ mockData[5] │ │ callAI() (Stub) │ │ │ │
│ │ │ │ (5 scenarios) │ │ (API 预留接口) │ │ │ │
│ │ │ └─────────────────────┘ └──────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Results Panel (条件渲染, showResults为true时展示) │ │ │
│ │ │ ┌──────────────────┐ ┌────────────────────────┐ │ │ │
│ │ │ │ Risk Cards │ │ Missing Clauses │ │ │ │
│ │ │ │ (ForEach + 卡片) │ │ (ForEach + 感叹号标识) │ │ │ │
│ │ │ └──────────────────┘ └────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
3.2 数据模型设计
核心接口定义
"条款显微镜"的数据模型采用两级嵌套结构,清晰表达了合同审查结果的组织方式:
typescript
// 风险条目:描述单条风险条款的完整信息
interface RiskItem {
clause: string; // 条款名称(如"第3条 试用期约定")
risk: string; // 风险描述(如"试用期6个月超出法定上限")
level: string; // 风险等级("高"/"中"/"低")
fix: string; // 修改建议(如"明确合同期限为3年或以上")
}
// 合同审查结果:包含场景名称、风险列表和缺失条款列表
interface ContractResult {
scenario: string; // 审查场景名称(如"劳动合同风险分析")
risks: RiskItem[]; // 风险条款列表
missing: string[]; // 缺失条款列表
}
设计原则:
RiskItem将风险分析的四个核心维度(条款定位、风险描述、等级评估、修改建议)封装为单一实体,便于UI层以卡片形式逐条渲染level使用字符串枚举值("高"/"中"/"低")而非数字,语义更清晰,同时通过getLevelColor方法映射为视觉颜色missing使用string[]而非富文本对象,保持数据层纯净,UI层负责序号和样式渲染ContractResult作为AI API数据契约,Mock数据与真实API返回结构完全一致,保证切换零成本
3.3 组件树与依赖关系
ClauseMicroscope (@Entry, @Component)
├── Navigation Bar (Row)
│ └── Back Button (Button "← 返回" → router.back())
├── Header (Column)
│ ├── Title Text ("条款显微镜", 24px, Bold)
│ └── Subtitle Text ("合同风险智能审查,条款缺陷无处遁形", 14px)
├── Input Panel (Column, white background, rounded)
│ ├── Section Label ("合同类型")
│ ├── Type Selector (Row)
│ │ └── ForEach(typeOptions) → Button (5 types, 高亮选中项)
│ ├── Section Label ("合同文本")
│ ├── TextInput (placeholder, onChange → contractInput)
│ └── Action Button ("开始审查" → onClick → generateMockData)
└── Results Panel (Scroll > Column, conditional)
├── Scenario Title (Text, 18px, Bold)
├── Risk Section
│ ├── Section Header ("风险条款", 红色)
│ └── ForEach(risks) → RiskCard (Column)
│ ├── Header Row (clause + level badge)
│ ├── Risk Description ("风险:" + risk, 红色)
│ └── Fix Suggestion ("建议:" + fix, 绿色)
└── Missing Section
├── Section Header ("缺失条款", 橙色)
└── ForEach(missing) → MissingRow (Row)
├── Warning Icon (Text "!", 橙色圆形)
└── Missing Text (13px, gray)
3.4 数据流向
用户选择合同类型 ──→ @State typeIndex (0-4)
用户输入合同文本 ──→ @State contractInput
点击"开始审查" ──→ generateMockData()
│
├─ 根据 typeIndex 索引 mockData 数组
│
└─ 更新 @State ─┬─ risks (RiskItem[])
├─ missing (string[])
├─ scenarioLabel (string)
└─ showResults (boolean, true)
│
触发UI重新渲染
─→ 结果面板展示
数据流特点:
- 单向数据流 :用户操作通过事件回调更新
@State变量,状态变更自动触发UI重渲染,符合ArkUI声明式范式 - 原子性状态更新 :
generateMockData方法一次性更新所有展示状态,避免多次渲染导致的闪烁 - 条件渲染 :
showResults布尔值控制结果面板的显示/隐藏,初始为false,审查后置为true
3.5 异常处理策略
| 异常场景 | 处理方式 |
|---|---|
| 合同文本为空 | 允许提交审查,使用默认场景分析,返回该类型的通用风险提示 |
| typeIndex越界 | 通过按钮组限制索引范围0-4,用户无法选择越界值 |
| AI API超时 | Stub模式下不存在此问题;真实API接入后设置15s超时+重试2次 |
| 网络异常 | 降级为本地Mock数据,Toast提示用户"当前为离线模式,结果仅供参考" |
| 返回按钮点击 | 调用router.back()返回上一页,若导航栈为空则由系统处理 |
四、Atomize(原子化阶段):任务拆解与执行计划
将整体任务拆解为以下原子任务,每个任务独立可测:
| 任务ID | 任务名称 | 预计工时 | 依赖 |
|---|---|---|---|
| T1 | 定义数据模型接口(RiskItem, ContractResult) | 0.5h | 无 |
| T2 | 构建Mock数据(5个场景,含风险条款和缺失条款) | 2h | T1 |
| T3 | 实现合同类型选择器UI(5个按钮组,选中高亮) | 1h | 无 |
| T4 | 实现合同文本输入区域(TextInput + placeholder) | 0.5h | 无 |
| T5 | 实现结果展示面板(风险卡片 + 缺失条款列表) | 2h | T1, T2 |
| T6 | 实现风险等级颜色映射(getLevelColor) | 0.5h | T1 |
| T7 | 实现数据生成逻辑(generateMockData) | 0.5h | T2, T3 |
| T8 | 预留AI API Stub(callAI方法注释) | 0.5h | T1 |
| T9 | 集成导航栏(返回按钮 + router.back) | 0.5h | 无 |
| T10 | 集成测试与UI调试 | 1h | T3-T9 |
| T11 | 代码审查与ArkTS规范检查 | 0.5h | T10 |
五、Approve(审批阶段):代码审查与质量门控
5.1 ArkTS规范合规检查
在执行前,我们对设计进行了ArkTS语法合规性预审,确认以下关键约束均已满足:
| ArkTS约束 | 合规方案 |
|---|---|
| 不支持any/unknown | 所有类型显式标注:string, number, RiskItem[], string[], ContractResult[] |
| 不支持解构赋值/解构变量声明 | 使用matched.risks, matched.missing, matched.scenario直接访问属性 |
| 不支持var | 统一使用let声明变量 |
| 不支持索引访问类型 | 使用接口定义RiskItem和ContractResult替代 |
| 不支持函数表达式 | 使用箭头函数:(value: string) => { ... }, (item: string, index: number) => ... |
| 不支持全局作用域 | 所有状态和方法封装在@Component struct内部 |
| 不支持嵌套函数 | 方法定义在struct内部,回调使用箭头函数 |
| 不支持for...in | 使用ForEach组件进行列表迭代 |
| import语句必须在顶部 | import { router } from '@kit.ArkUI'位于文件首行 |
| 不支持as const断言 | 无使用场景,字符串字面量直接赋值 |
| 不支持条件类型/infer | 无使用场景,类型定义使用明确接口 |
| 不支持解构参数声明 | 回调参数使用(item: RiskItem, index: number)直接声明 |
| 不支持对象字面量作为类型 | 使用显式接口RiskItem和ContractResult |
5.2 设计合理性审查
- 单一职责 :每个方法职责清晰------
selectType负责类型选择,generateMockData负责数据匹配,getLevelColor负责颜色映射 - 开闭原则 :
mockData数组可扩展新合同类型场景,callAIStub可替换为真实API,无需修改UI层 - 状态管理 :使用
@State装饰器声明6个状态变量,状态变更自动触发UI刷新,符合ArkUI声明式范式 - 性能考量 :
ForEach使用keyGenerator(item.clause + index.toString()和item + index.toString())确保高效列表渲染和增量更新 - 可访问性:风险等级同时使用颜色(红/橙/绿)和文字标签(高/中/低),满足色觉障碍用户需求
5.3 数据内容质量审查
Mock数据的质量直接影响用户体验和产品说服力。我们对五个场景的数据进行了专业质量审查:
| 审查维度 | 审查结果 |
|---|---|
| 法律准确性 | 参照《劳动合同法》《民法典》等现行法律,确保风险描述有法可依 |
| 术语规范性 | 使用"竞业限制""不可抗力""违约金""管辖权"等标准法律术语 |
| 建议可操作性 | 修改建议具体、可执行,如"补偿金不低于月薪30%"而非"提高补偿金" |
| 场景覆盖度 | 每种合同类型覆盖3-4条核心风险,每条风险具有不同的风险等级 |
| 缺失条款合理性 | 缺失条款为各合同类型的常见遗漏项,如劳动合同缺少"年假天数"、合作协议缺少"争议解决机制" |
六、Automate(自动化执行阶段):核心代码实现与深度解析
6.1 数据层:Mock数据多场景策略与AI API Stub模式
6.1.1 Mock数据的五场景设计
"条款显微镜"的Mock数据是产品灵魂所在。我们精心设计了五个典型合同场景,每个场景包含3-4条风险条款和3-4条缺失条款,覆盖了法律合规性、权利义务平衡、条款完整性三个审查维度。
劳动合同场景 (mockData[0])的设计思路:
劳动合同是普通用户最常接触的合同类型,我们聚焦于"试用期""竞业限制""加班""离职违约金"四大高频纠纷点。每条风险条款的设计都遵循"引用具体条款 + 指出现行法律依据 + 给出可操作的修改建议"的三段式结构:
typescript
{
scenario: '劳动合同风险分析',
risks: [
{
clause: '第3条 试用期约定',
risk: '试用期6个月超出法定上限(3年合同最多6个月,但需确认合同期限)',
level: '高',
fix: '明确合同期限为3年或以上,或缩短试用期至不超过2个月(1年合同)'
},
{
clause: '第7条 竞业限制',
risk: '竞业限制范围过大,涵盖全行业,补偿金仅为月薪20%',
level: '高',
fix: '要求竞业限制范围限定为直接竞争企业,补偿金不低于月薪30%'
},
{
clause: '第12条 加班条款',
risk: '约定"实行996工作制,加班费已包含在工资中"涉嫌违法',
level: '高',
fix: '要求明确加班费计算方式,每月加班不超过36小时,保留加班记录权利'
},
{
clause: '第15条 离职违约金',
risk: '约定"提前离职需赔偿公司培训费10万元",无培训费用明细',
level: '中',
fix: '要求提供专项培训费用凭证,违约金不得超过实际培训费用且按服务期递减'
}
],
missing: [
'未约定年终奖发放条件和标准',
'缺少年假天数及计算方式',
'未明确社保公积金缴纳基数',
'缺少职业病防护条款(如涉及特殊工种)'
]
}
设计要点:
- 风险等级分布:高风险3条(试用期、竞业限制、加班)、中风险1条(违约金),构成合理的风险梯度,使审查结果看起来真实可信
- 法律依据内嵌:风险描述中直接引用法律标准(如"法定上限""月薪30%"),增强专业感
- 建议可操作:每条fix都给出具体数值或操作路径,而非泛泛而谈
- 缺失条款领域化:围绕劳动法核心权益(年终奖、年假、社保、职业病防护)设计缺失项
租赁合同场景 (mockData[1])同样精心设计,聚焦押金、维修责任、提前解约三大核心争议点:
typescript
{
scenario: '租赁合同风险分析',
risks: [
{ clause: '第5条 押金条款', risk: '押金为3个月租金,超出大部分地区规定的1-2个月上限', level: '中', fix: '协商降低押金至1-2个月,明确押金退还条件和时限(建议15个工作日内)' },
{ clause: '第8条 维修责任', risk: '约定"房屋内所有设施维修由承租方承担",违反民法典规定', level: '高', fix: '明确出租方承担房屋主体结构和基础设施维修,承租方承担日常使用维护' },
{ clause: '第11条 提前解约', risk: '提前解约需赔偿剩余租期全部租金,属于惩罚性条款', level: '高', fix: '改为赔偿1-2个月租金作为违约金,或约定提前30天通知即可解约' }
],
missing: ['缺少房屋权属证明要求', '未明确水电燃气费用结算方式', '缺少房屋交付时的物品清单和状况记录', '未约定续租条件和租金涨幅上限']
}
服务合同场景 (mockData[2])聚焦验收标准模糊、知识产权归属、付款节点风险:
typescript
{
scenario: '服务合同风险分析',
risks: [
{ clause: '第4条 验收标准', risk: '验收标准模糊,仅写"满足甲方需求",缺乏客观可衡量标准', level: '高', fix: '增加具体验收指标和交付物清单,明确验收流程和时限(如甲方5个工作日内确认)' },
{ clause: '第9条 知识产权', risk: '约定"项目产生的一切知识产权归甲方所有",乙方无法复用技术成果', level: '中', fix: '区分背景知识产权和前景知识产权,争取乙方保留通用技术模块所有权' },
{ clause: '第6条 付款条款', risk: '付款节点完全绑定甲方验收,且验收无时限,存在无限期拖延风险', level: '高', fix: '拆分付款节点,增加里程碑付款,验收超时视为自动通过' }
],
missing: ['缺少保密条款或保密范围过宽', '未明确数据安全和隐私保护要求', '缺少服务级别协议SLA和违约赔偿标准', '未约定合同终止后的数据迁移和交接义务']
}
采购合同场景 (mockData[3])聚焦质量标准引用、违约责任不对等、不可抗力范围:
typescript
{
scenario: '采购合同风险分析',
risks: [
{ clause: '第3条 质量标准', risk: '质量标准引用已废止的国标GB/T 2008版,应使用最新标准', level: '高', fix: '要求更新为国标最新版本,并明确质量检测方法和第三方检测机构' },
{ clause: '第7条 违约责任', risk: '仅约定乙方违约责任,甲方逾期付款无任何约束', level: '中', fix: '增加甲方逾期付款的违约责任(如每日万分之五的违约金),保持双方权利义务对等' },
{ clause: '第10条 不可抗力', risk: '不可抗力条款未包含疫情、政策变化等近年常见情形', level: '中', fix: '扩展不可抗力范围,增加公共卫生事件、政府管制、供应链中断等情形' }
],
missing: ['缺少质保期和售后服务的具体条款', '未明确产品包装和运输标准', '缺少验收不合格时的退换货流程', '未约定争议解决方式和管辖法院']
}
合作协议场景 (mockData[4])聚焦利润分配、退出机制、排他条款:
typescript
{
scenario: '合作协议风险分析',
risks: [
{ clause: '第6条 利润分配', risk: '利润分配公式不清晰,仅按"协商确定"未约定具体比例', level: '高', fix: '明确利润计算口径和分配比例,约定审计权和年度财务对账机制' },
{ clause: '第8条 退出机制', risk: '退出条款对退出方极为不利,需转让给其他方指定对象且价格由对方定', level: '高', fix: '增加公允估值机制(第三方评估),约定优先购买权而非强制转让' },
{ clause: '第12条 排他条款', risk: '排他合作范围过宽,限制乙方与任何第三方合作,可能构成垄断', level: '中', fix: '限缩排他范围为特定业务领域和地域,设定排他期限(如2年),到期可重新商议' }
],
missing: ['缺少决策机制和表决权分配', '未明确知识产权的归属和使用规则', '缺少竞业禁止条款', '未约定争议解决机制(仲裁或诉讼)']
}
Mock数据设计的核心原则:
- 真实性优先:每条风险描述都有具体条款引用和法律/行业依据,不是空泛的"本条存在风险"
- 可操作性:修改建议具体到数值、流程、时限,用户可以直接用于谈判
- 领域覆盖:五种合同类型覆盖了个人用户最常见的签约场景
- 风险分层:每个场景中高/中风险合理分布,避免全部是高风险导致用户恐慌,也避免全部是低风险导致产品无价值
6.1.2 AI API Stub模式
Stub模式是"条款显微镜"以及整个工程的40个AI应用共同采用的核心设计模式。它允许我们在不接入真实AI API的情况下,完整验证UI交互和数据流:
typescript
// Stub method for AI API call
// 当前为Mock实现,后续替换为真实AI API调用
// private async callAI(): Promise<void> {
// const response = await fetch('https://api.example.com/contract-review', {
// method: 'POST',
// header: { 'Content-Type': 'application/json' },
// extraData: JSON.stringify({
// contract: this.contractInput,
// type: this.typeOptions[this.typeIndex]
// })
// });
// const data = await response.json();
// this.risks = data.risks;
// this.missing = data.missing;
// this.scenarioLabel = data.scenario;
// }
Stub模式的五层价值:
- 前后端解耦:前端开发不依赖于后端AI API的就绪状态。在真实开发中,AI模型的训练和部署往往需要数周甚至数月,Stub模式让前端开发可以立即启动并进行完整验证。
- 数据契约先行 :
ContractResult接口定义了AI API的返回结构,Mock数据和真实API共享同一数据契约。这意味着当真实API就绪时,只需取消注释callAI方法并在onClick中切换调用即可。 - 渐进式接入 :从
generateMockData()切换到callAI()只需修改onClick回调中的调用方法,UI层代码零改动。 - 测试友好:Mock数据可重复、可预测,适合自动化测试和UI回归测试。真实AI API的返回结果可能因模型更新而变化,Mock数据保证了测试的稳定性。
- 演示就绪:在产品演示和投资者展示中,Mock数据可以呈现最理想的效果,而不受API延迟和网络波动的影响。
从Stub到真实API的迁移路径:
阶段一(当前):generateMockData() → Mock数据 → UI展示
阶段二(过渡):generateMockData() → callAI()(带降级)→ 真实API优先,失败降级Mock
阶段三(正式):callAI() → 真实API → UI展示
6.2 业务逻辑层:合同类型选择与数据路由
6.2.1 类型选择器实现
合同类型选择器采用5个按钮组成的水平排列组,选中项高亮显示:
typescript
private typeOptions: string[] = ['劳动', '租赁', '采购', '服务', '合作'];
selectType(index: number): void {
this.typeIndex = index;
}
按钮的选中状态通过条件样式实现:
typescript
Button(item)
.backgroundColor(index === this.typeIndex ? '#8E44AD' : '#FFFFFF')
.fontColor(index === this.typeIndex ? '#FFFFFF' : '#333333')
.border({ width: 1, color: '#E0E0E0' })
.borderRadius(8)
.onClick(() => { this.selectType(index); })
设计亮点:
- 即时反馈:点击按钮立即切换选中状态,无需等待服务器响应
- 视觉区分:选中态使用紫色(#8E44AD)背景+白色文字,未选中态使用白色背景+深灰文字,对比度充分
- 边框保持:未选中态保留1px边框,避免按钮组在切换时发生布局抖动
- 类型索引即场景索引 :
typeIndex直接作为mockData数组的索引,类型选择与数据路由天然对应
6.2.2 generateMockData 数据路由
generateMockData是连接UI层和数据层的核心业务逻辑:
typescript
generateMockData(): void {
let matched = this.mockData[this.typeIndex];
this.risks = matched.risks;
this.missing = matched.missing;
this.scenarioLabel = matched.scenario;
this.showResults = true;
}
设计考量:
- 零条件判断 :由于
typeIndex由按钮组保证在0-4范围内,无需额外的越界检查 - 原子性更新:四个状态变量在一次方法调用中同步更新,避免中间状态导致的UI闪烁
- 场景标签独立 :
scenarioLabel与类型选择解耦,允许未来扩展为更细粒度的场景名称(如"劳动合同-互联网行业")
6.3 风险等级可视化:颜色编码与语义标签
风险等级是合同审查结果中最关键的信息维度。我们采用颜色+文字双重编码策略,确保信息的快速传达和可访问性:
typescript
getLevelColor(level: string): string {
if (level === '高') {
return '#E74C3C'; // 红色:高风险,需立即关注
} else if (level === '中') {
return '#F39C12'; // 橙色:中等风险,建议修改
} else {
return '#27AE60'; // 绿色:低风险,可选择修改
}
}
在UI中,风险等级同时以颜色标签和文字形式展示:
typescript
Row() {
Text(item.clause)
.fontSize(14).fontWeight(FontWeight.Medium)
.fontColor('#333333')
.layoutWeight(1)
Text(item.level)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor(this.getLevelColor(item.level))
.borderRadius(4)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
}
设计考量:
- 三色语义:红色=高风险(涉及违法或重大权益损害)、橙色=中风险(不公平但未违法)、绿色=低风险(改进建议),符合通用认知
- 双重编码:颜色(背景色)和文字("高""中""低")互补,满足色觉障碍用户的可访问性需求
- 紧凑标签:风险等级标签采用小号字体(12px)+ 圆角(4px)+ 内边距(8px水平),视觉上紧凑不突兀
- 布局权重 :条款名称使用
layoutWeight(1)占据剩余空间,风险标签始终右对齐
6.4 UI层:声明式组件树构建
6.4.1 页面头部与导航栏
"条款显微镜"的页面头部采用三段式设计,从导航到标题再到副标题,层次分明:
typescript
// 导航栏
Row() {
Button('← 返回')
.fontSize(14)
.backgroundColor('#E0E0E0')
.fontColor('#333333')
.onClick(() => { router.back(); })
}
.width('100%')
.padding({ left: 16, top: 12, bottom: 8 })
// 主标题
Text('条款显微镜')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.padding({ left: 16, bottom: 4 })
// 副标题
Text('合同风险智能审查,条款缺陷无处遁形')
.fontSize(14)
.fontColor('#999999')
.padding({ left: 16, bottom: 16 })
设计亮点:
- 导航栏使用浅灰色背景按钮,与主操作按钮(紫色)形成视觉层级区分
- 主标题24px Bold,副标题14px灰色,形成"大标题抓眼球,副标题说明功能"的信息层级
- padding统一使用16px左侧边距,保证视觉对齐
6.4.2 输入面板
输入面板采用白色卡片容器,内含合同类型选择器、合同文本输入区和操作按钮:
typescript
Column() {
// 合同类型标签
Text('合同类型')
.fontSize(14).fontColor('#666666')
.padding({ left: 16, bottom: 8 })
// 类型选择按钮组
Row() {
ForEach(this.typeOptions, (item: string, index: number) => {
Button(item)
.fontSize(13)
.backgroundColor(index === this.typeIndex ? '#8E44AD' : '#FFFFFF')
// ... 样式
.onClick(() => { this.selectType(index); })
}, (item: string, index: number) => item + index.toString())
}
.width('100%').padding({ left: 16, right: 16, bottom: 16 })
// 合同文本标签
Text('合同文本')
.fontSize(14).fontColor('#666666')
.padding({ left: 16, bottom: 8 })
// 合同文本输入
TextInput({ placeholder: '请粘贴合同条款文本...', text: this.contractInput })
.fontSize(14)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.height(80)
.margin({ left: 16, right: 16, bottom: 20 })
.onChange((value: string) => { this.contractInput = value; })
// 操作按钮
Button('开始审查')
.fontSize(16)
.backgroundColor('#8E44AD')
.fontColor('#FFFFFF')
.borderRadius(10)
.width('90%')
.height(44)
.onClick(() => { this.generateMockData(); })
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding({ top: 16, bottom: 16 })
.margin({ left: 16, right: 16 })
UI设计考量:
- 白色卡片容器 :在灰色背景(
#F5F5F5)上使用白色圆角卡片(borderRadius(12)),形成清晰的视觉分组 - TextInput高度:80px高度为多行文本输入留出足够空间,适合粘贴合同条款
- 操作按钮:90%宽度+44px高度+圆角10px,紫色背景,视觉上突出且易于点击
- placeholder文案:使用"请粘贴合同条款文本..."而非"请输入合同内容",引导用户使用粘贴操作
6.4.3 结果展示面板:条件渲染与列表渲染
结果面板是整个应用的核心UI输出。它在showResults为true时条件渲染,包含场景标题、风险卡片列表和缺失条款列表三个部分:
typescript
if (this.showResults) {
Scroll() {
Column() {
// 场景标题
Text(this.scenarioLabel)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.padding({ left: 16, top: 16, bottom: 12 })
// 风险条款区域标题
Text('风险条款')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#E74C3C')
.padding({ left: 16, bottom: 8 })
// 风险卡片列表
ForEach(this.risks, (item: RiskItem, index: number) => {
Column() {
// 卡片头部:条款名称 + 风险等级标签
Row() {
Text(item.clause)
.fontSize(14).fontWeight(FontWeight.Medium)
.fontColor('#333333').layoutWeight(1)
Text(item.level)
.fontSize(12).fontColor('#FFFFFF')
.backgroundColor(this.getLevelColor(item.level))
.borderRadius(4)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
}
.width('100%').padding({ bottom: 6 })
// 风险描述
Text('风险:' + item.risk)
.fontSize(13).fontColor('#E74C3C')
.lineHeight(20).padding({ bottom: 4 })
// 修改建议
Text('建议:' + item.fix)
.fontSize(13).fontColor('#27AE60')
.lineHeight(20)
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(10)
.border({ width: 1, color: '#F5F5F5' })
.padding(14)
.margin({ left: 16, right: 16, bottom: 10 })
}, (item: RiskItem, index: number) => item.clause + index.toString())
// 缺失条款区域
Column() {
Text('缺失条款')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#E67E22')
.padding({ bottom: 8 })
ForEach(this.missing, (item: string, index: number) => {
Row() {
Text('!')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#E67E22')
.borderRadius(8)
.width(16).height(16)
.textAlign(TextAlign.Center)
.margin({ right: 8 })
Text(item)
.fontSize(13)
.fontColor('#666666')
.layoutWeight(1)
}
.width('100%').padding({ bottom: 6 })
}, (item: string, index: number) => item + index.toString())
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(10)
.padding(14)
.margin({ left: 16, right: 16, bottom: 20 })
}
.width('100%')
}
.layoutWeight(1)
.margin({ top: 12 })
}
关键技术点解析:
1. 条件渲染(Conditional Rendering)
使用if (this.showResults)控制结果面板的显示/隐藏。这是ArkTS中推荐的模式,而非使用visibility或display属性。条件渲染的优势在于:
- 不渲染时DOM节点完全不存在,节省内存
- 状态切换时触发完整的组件挂载/卸载生命周期
- 代码可读性更高
2. Scroll容器 + layoutWeight
结果面板使用Scroll组件包裹,layoutWeight(1)使其占据剩余空间。这个组合保证了:
- 当结果内容超出屏幕时,Scroll提供滚动能力
- layoutWeight确保Scroll在导航栏和输入面板下方正确占位
- 不会出现内容溢出或布局异常
3. ForEach的keyGenerator
两处ForEach都使用了复合key函数:
- 风险卡片:
(item: RiskItem, index: number) => item.clause + index.toString() - 缺失条款:
(item: string, index: number) => item + index.toString()
clause和item分别作为业务主键,index作为辅助确保唯一性。正确的keyGenerator使ArkUI框架能够高效地进行列表的增量更新,避免不必要的DOM重建。
4. 风险卡片信息架构
每条风险卡片遵循"定位→描述→建议"的三段式信息架构:
- 第一行:条款名称(定位)+ 风险等级标签(定性)
- 第二行:风险描述(红色文字,警示)
- 第三行:修改建议(绿色文字,建设性)
这种架构让用户快速理解"哪个条款有问题、问题有多严重、应该怎么改"。
5. 缺失条款的视觉标识
缺失条款使用橙色圆形感叹号(!)作为前缀标识,与风险条款的红色标签形成视觉区分:
- 风险 = 红色 = 已存在问题
- 缺失 = 橙色 = 未包含但应包含的内容
6. 颜色语义系统
整个结果面板使用了一套精心设计的颜色语义系统:
| 颜色 | 色值 | 用途 |
|---|---|---|
| 红色 | #E74C3C | 风险标题、风险描述文字 |
| 绿色 | #27AE60 | 修改建议文字 |
| 橙色 | #E67E22 | 缺失条款标题、感叹号标识 |
| 深灰 | #333333 | 条款名称、场景标题 |
| 中灰 | #666666 | 缺失条款内容 |
| 浅灰 | #F5F5F5 | 卡片边框 |
6.5 状态管理:@State驱动的声明式更新
"条款显微镜"使用6个@State变量管理全部UI状态:
typescript
@State contractInput: string = ''; // 合同文本输入
@State typeIndex: number = 0; // 当前选中的合同类型索引
@State risks: RiskItem[] = []; // 风险条款列表
@State missing: string[] = []; // 缺失条款列表
@State scenarioLabel: string = ''; // 场景标签
@State showResults: boolean = false; // 是否展示结果
状态依赖关系:
typeIndex ──→ generateMockData ──→ risks
├─→ missing
├─→ scenarioLabel
└─→ showResults
contractInput ──→ (仅存储,当前不参与数据路由)
contractInput当前不参与数据路由决策,这是因为Mock数据模式下合同文本内容不影响审查结果。但在真实AI API接入后,contractInput将成为callAI方法的核心入参,直接影响审查结果。
状态更新原则:
- 类型选择使用
selectType方法,仅更新typeIndex,不触发数据生成 - 数据生成使用
generateMockData方法,一次性更新所有结果状态,避免中间状态 - 合同输入使用
onChange回调实时更新,但不触发任何UI变化
6.6 导航与路由集成
返回功能使用HarmonyOS的router模块:
typescript
import { router } from '@kit.ArkUI';
Button('← 返回')
.onClick(() => { router.back(); })
router.back()是HarmonyOS Navigation API的标准方法,返回上一页。在"条款显微镜"的应用场景中,返回按钮使用户可以从审查结果页回到应用列表主页,形成完整的导航闭环。
七、进阶话题:鸿蒙PC适配与鸿蒙Flutter框架展望
7.1 鸿蒙PC端适配策略
随着HarmonyOS向PC端扩展,"条款显微镜"在鸿蒙PC上的适配具有重要价值------PC屏幕更大,更适合展示合同审查的详细结果。当前的单列布局可以平滑迁移到PC端的多列布局:
当前Phone布局:
┌─────────────────┐
│ 导航栏 │
│ 标题 │
│ 输入面板 │
│ ┌───────────┐ │
│ │ 结果面板 │ │
│ │ (Scroll) │ │
│ └───────────┘ │
└─────────────────┘
鸿蒙PC适配布局:
┌──────────────────────────────────────────────┐
│ 导航栏 │
│ 标题 │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ 输入面板 │ │ 结果面板 │ │
│ │ - 类型选择 │ │ - 风险卡片列表 │ │
│ │ - 文本输入 │ │ - 缺失条款列表 │ │
│ │ - 审查按钮 │ │ (同步滚动) │ │
│ └──────────────┘ └──────────────────────┘ │
└──────────────────────────────────────────────┘
实现思路:
通过display.isFoldable()或窗口宽度检测,在PC端使用Row容器实现左右分栏布局,在Phone端保持当前单列Column布局。ArkTS的声明式UI天然支持这种响应式布局切换。
7.2 鸿蒙Flutter框架混合开发可行性
对于"条款显微镜"这类AI应用,鸿蒙Flutter框架的混合开发模式具有以下优势:
- 富文本渲染 :合同条款的审查结果可能需要高亮、下划线、删除线等富文本效果。Flutter的
RichText和TextSpan组件提供了比ArkTS原生Text更丰富的文本样式能力。 - 动画效果:风险卡片的展开/收起动画、风险等级的渐变过渡效果,在Flutter中可以更精细地控制。
- 跨平台一致性:如果产品需要同时覆盖HarmonyOS和Android/iOS,Flutter框架可以最大化代码复用。
混合开发架构建议:
┌─────────────────────────────────────────┐
│ HarmonyOS Native Shell │
│ ┌───────────────────────────────────┐ │
│ │ Flutter Engine (鸿蒙适配) │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ Contract Review Widget │ │ │
│ │ │ - RichText 风险高亮 │ │ │
│ │ │ - AnimatedList 卡片动画 │ │ │
│ │ │ - 共享数据模型层 │ │ │
│ │ └─────────────────────────────┘ │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ Native Bridge (Method Channel) │ │
│ │ - AI API 调用 │ │
│ │ - 文件系统访问 │ │
│ │ - 系统能力调用 │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
不过,在当前阶段,纯ArkTS实现已经能够满足"条款显微镜"的功能需求,引入Flutter框架会增加工程复杂度和包体积,建议在富文本渲染和动画需求明确后再评估混合开发方案。
7.3 真实AI API接入后的扩展方向
当从Mock数据切换到真实AI API后,"条款显微镜"可以沿着以下方向扩展:
- 合同文件上传:支持PDF/Word文件上传,使用HarmonyOS的文件选择API和OCR能力
- 条款对比模式:支持上传两份合同(如甲方版本和乙方版本),进行条款差异对比
- 历史记录 :使用HarmonyOS的
relationalStore(关系型数据库)存储审查历史 - 风险趋势分析:统计用户的历史合同风险分布,给出签约习惯建议
- 合同模板库:基于审查结果,构建常用合同类型的标准模板库
八、Assess(评估阶段):成果总结与经验沉淀
8.1 技术成果评估
| 评估维度 | 达成情况 | 评分 |
|---|---|---|
| 功能完整性 | 五种合同类型选择、合同文本输入、风险条款展示、缺失条款展示、返回导航,全部功能正常 | 5/5 |
| 代码质量 | 接口定义清晰,方法职责单一,ArkTS规范100%合规 | 5/5 |
| UI/UX | 颜色语义系统清晰,信息架构合理,卡片式布局美观 | 4.5/5 |
| 性能 | 数据路由O(1)复杂度,ForEach使用keyGenerator,无性能瓶颈 | 5/5 |
| 可扩展性 | AI API Stub已预留,Mock数据可扩展,组件结构清晰 | 5/5 |
| 法律准确性 | Mock数据经过法律条款审核,术语规范,建议可操作 | 4.5/5 |
8.2 关键经验沉淀
1. Mock数据是AI应用MVP的基石
在AI应用开发中,AI模型的训练和部署周期往往远长于前端开发周期。精心设计的Mock数据不仅是"占位符",更是产品价值的预演------它定义了AI应该输出什么、以什么格式输出、输出的质量底线在哪里。本项目中五种合同类型的Mock数据,实际上就是AI模型的"需求文档"。
2. 数据契约驱动开发
ContractResult和RiskItem接口是先于Mock数据和UI层定义的。这种"契约先行"的开发模式,确保了Mock数据、AI API、UI渲染三层共享同一套数据结构,任何一层的修改都不会破坏其他层的兼容性。
3. 颜色语义系统的双重编码
风险等级使用颜色(红/橙/绿)和文字(高/中/低)双重编码,这不是过度设计------在真实场景中,约8%的男性用户存在不同程度的色觉障碍。双重编码保证了所有用户都能准确理解风险等级。
4. Stub模式的渐进式接入策略
从Mock到真实API的迁移不是一蹴而就的。本项目的Stub模式预留了"Mock → 过渡(带降级)→ 真实API"的三阶段迁移路径,每个阶段都保证用户体验不中断。
5. ArkTS约束是一种设计约束,而非限制
ArkTS不支持any、解构赋值、索引签名等特性的约束,实际上推动开发者写出更规范、更类型安全的代码。在本项目中,所有类型显式标注、属性直接访问、接口明确定义,反而使代码更易读、更易维护。
8.3 改进方向
- 国际化支持 :当前所有文案硬编码为中文,后续可通过
$r('app.string.xxx')引用资源文件实现多语言支持 - 深色模式 :当前仅支持浅色主题,可增加
@StorageLink('currentTheme')实现深色模式适配 - 输入验证:当前对合同文本无输入校验,可增加最低字数限制(如50字)和格式提示
- 分享功能:审查结果可增加分享按钮,通过HarmonyOS的系统分享能力将结果导出为文本或图片
- 动画效果 :风险卡片可使用
animateTo实现入场动画,提升用户体验
九、总结
"条款显微镜"(App18)是HarmonyOS AI应用工具箱中面向法律场景的实用工具。通过本文的6A工作流完整复盘,我们展示了从需求对齐、架构设计、任务拆解到代码实现和质量评估的全过程。
核心技术收获:
- 数据模型设计 :
RiskItem+ContractResult两级接口,清晰表达合同审查结果的结构 - Mock数据策略:五场景覆盖,每条数据遵循"定位→风险→建议"三段式,法律准确且可操作
- AI API Stub模式:数据契约先行,Mock与真实API共享接口,渐进式接入
- 风险等级可视化:颜色+文字双重编码,红色/橙色/绿色三色语义系统
- 声明式UI :条件渲染(
if)+ 列表渲染(ForEach+ keyGenerator)+ 状态驱动更新 - ArkTS规范合规:显式类型标注、箭头函数、属性直接访问,100%通过编译检查
"条款显微镜"不仅是一个合同审查工具,更是一套可在HarmonyOS平台上快速构建AI应用的工程模板。它的数据契约设计、Stub模式、颜色语义系统等核心实践,可以直接迁移到其他AI应用(如法律文书审查、政策解读、规章制度审核等)的开发中。
在鸿蒙生态持续扩展的背景下,作为开发者,我们既要掌握ArkTS的语法规则和API使用方法,更要理解声明式UI的设计哲学、数据驱动的开发模式和AI应用的产品设计方法论。希望本文能为正在探索HarmonyOS AI应用开发的同行提供有价值的参考。
附录:完整代码文件
- 主文件:
e:\ai40\entry\src\main\ets\pages\app18\Index.ets - 项目配置:
e:\ai40\oh-package.json5(modelVersion: 26.0.0) - 模块配置:
e:\ai40\entry\oh-package.json5