种草笔杆子-小红书文案生成的HarmonyOS开发实践
App39「种草笔杆子」—— 小红书文案生成的 HarmonyOS 开发实践
本文基于 HarmonyOS Next API 26,使用 ArkTS + ArkUI 声明式框架,完整记录「种草笔杆子」AI 应用从需求对齐到上线评估的全流程开发实践。文章涵盖 6A 工作流(Align、Architect、Atomize、Approve、Automate、Assess),包含完整的 ArkTS 代码解析、AI API Stub 设计模式、鸿蒙PC适配策略及鸿蒙Flutter框架协同方案。关键词:鸿蒙、鸿蒙PC、鸿蒙Flutter框架。
—

目录
- Align —— 需求对齐与规范制定
- Architect —— 架构设计与技术选型
- Atomize —— 原子化任务分解
- Approve —— 审批与质量门控
- Automate —— 自动化执行与编码实现
- Assess —— 评估与总结
1. Align —— 需求对齐与规范制定
1.1 项目背景
在「AI 智能应用工具箱」项目中,我们规划了 40 个 AI 驱动的轻量级应用,覆盖品质生活、职场效率、AI 创作等六大品类。该项目采用了 HarmonyOS Next 的 Stage Mode 架构,每个子应用都是一个独立的页面组件,通过统一的路由系统进行管理。应用首页展示了 40 个应用的卡片网格,用户可按分类筛选和关键词搜索,点击卡片即可进入对应的 AI 工具。
其中第 39 号应用「种草笔杆子」定位为小红书风格的内容生成工具,旨在帮助用户快速生成符合平台调性的种草文案。在 40 个应用中,它属于「AI 创作」品类,与「文字抛光机」(文本润色)、「标题炼金术」(电商标题优化)、「镜头故事家」(短视频脚本)等应用并列,共同构成了内容创作工具矩阵。
这个应用的典型用户场景是:一位小红书博主在撰写笔记时,面对空白编辑器不知道如何下笔。她打开「种草笔杆子」,输入产品主题(如"通勤必备好物推荐"),选择自己的人设定位和想要的情绪风格,点击生成按钮,立刻获得结构完整、风格鲜明的文案初稿。封面文案可以直接用于制作封面图,标签可以直接复制到发布页面,正文稍作调整即可发布。
1.2 原始需求
经过与产品团队的沟通,我们明确了以下需求:
-
输入参数:用户需要提供三个维度的创作参数。笔记主题是文案的核心内容,可以是产品名称、场景描述或话题关键词;人设定位决定了文案的语调和视角,不同身份的人说同一件事会有完全不同的表达方式;内容情绪覆盖了小红书四种主流风格——种草(推荐好物)、吐槽(分享踩坑经验)、干货(知识科普)、治愈(生活感悟)。
-
输出结构:生成的文案需要包含四个部分。封面文案是用于小红书封面图上的文字,通常需要分行、简洁、有冲击力;标题是笔记的标题,需要吸引点击,长度通常在 20 字以内;正文是笔记的主体内容,需要符合选定的人设和情绪,段落分明,可读性强;推荐标签是发布时使用的话题标签,通常以 # 开头,数量在 5 个左右。
-
交互体验:简洁直观的移动端表单,用户输入主题和人设,通过按钮组选择情绪,点击生成后展示结构化的文案结果。整个过程应该流畅无阻塞,结果展示清晰,便于用户复制使用。
-
技术约束:纯前端实现,当前阶段使用模拟数据展示效果,但必须预留 AI API 接口扩展能力,确保未来接入大语言模型时不需要重构 UI 层。
1.3 需求边界确认
在正式开始编码之前,我们对需求进行了详细的边界确认,确保所有技术决策都有明确的依据:
| 维度 | 确认结果 | 决策依据 |
|---|---|---|
| 目标平台 | 鸿蒙 HarmonyOS Next(API 26) | 项目统一使用的 API Level,确保与其它 39 个应用兼容 |
| 设备类型 | Phone(手机),预留鸿蒙PC适配能力 | 移动端优先,但布局设计需考虑未来的大屏适配 |
| 开发语言 | ArkTS(TypeScript 严格子集) | HarmonyOS 原生开发语言,编译时类型检查更严格 |
| UI 框架 | ArkUI 声明式框架 | 与项目整体架构一致,声明式 UI 开发效率高 |
| 架构模式 | Stage Mode + 单页面组件 | 遵循 HarmonyOS 推荐的应用架构模式 |
| 数据策略 | 当前阶段 Mock 数据模拟,预留 AI API Stub 接口 | 先验证 UI 交互,再接入 AI 能力,降低开发风险 |
| 与鸿蒙Flutter框架的关系 | 本项目采用原生 ArkUI 方案,不依赖 Flutter | 40 个轻量级应用场景不需要跨平台能力,但保留数据层的可移植性 |
| 页面路由 | 统一在 main_pages.json 中注册 | 所有 40 个应用遵循统一的路由管理规范 |
1.4 关键决策点
在需求对齐过程中,我们识别出了三个需要深入讨论的关键决策点,每个决策都经过了充分的分析和权衡。
Q1:AI 生成能力如何实现?
这是最核心的决策。当前阶段采用 Mock 数据模拟 策略,而非直接接入 AI API。做出这个决策的原因有三:第一,AI 接口的选型和对接需要时间,而 UI 开发可以先行启动;第二,Mock 数据可以精确控制输出质量,确保演示效果稳定;第三,通过 Stub 模式隔离数据层,未来切换 AI 引擎时不会影响 UI 层。
具体实现上,我们在 App39 组件中内置了 3 组预设场景数据,分别覆盖种草(通勤好物推荐)、吐槽(装修踩坑)和治愈(独居生活)三种风格。核心函数 getMockData() 通过场景键(scenario key)进行精确匹配,场景键由三个输入参数按下划线拼接而成。当用户输入的组合没有命中任何预设场景时,默认回退到第一个场景,确保始终有结果展示。
同时,我们在代码中预留了 generateMockData() 作为 AI API 调用的 Stub 入口。这个函数当前只有一行代码 this.getMockData(),但它的存在意味着整个 UI 层只依赖这个函数,而不直接依赖 Mock 数据。未来接入真实 AI 服务时,只需替换这个函数的实现——构建 HTTP 请求、调用 AI 端点、解析响应并赋值给 currentResult——UI 层完全不需要改动。这种设计模式是我们在整个 40 个应用的项目中统一采用的策略。
Q2:为什么选择原生 ArkUI 而非鸿蒙Flutter框架?
在 HarmonyOS 生态中,开发者有两种主流的 UI 方案:原生 ArkUI(使用 ArkTS 语言)和鸿蒙Flutter框架(使用 Dart 语言)。对于「种草笔杆子」以及整个 AI 智能应用工具箱项目,我们选择了原生 ArkUI 方案。
对于 40 个轻量级 AI 应用组成的工具箱场景,每个应用都是独立的单页面组件,页面复杂度低,不需要跨平台能力。原生 ArkUI 方案具有以下优势:
-
零额外依赖:无需引入 Flutter Engine,应用的包体积更小,这对包含 40 个子应用的聚合型项目尤为重要。如果使用鸿蒙Flutter框架,每个页面都需要加载 Flutter 运行时,40 个应用累积的体积开销会非常可观。
-
启动速度更快:原生组件渲染没有 Flutter Engine 的桥接开销。在用户快速切换不同 AI 工具的体验中,毫秒级的启动速度差异会被放大。
-
API 兼容性直接:直接使用 HarmonyOS 官方 API(如
@kit.ArkUI的 router),无需等待 Flutter 插件适配。HarmonyOS 的 API 正在快速迭代,原生方案可以第一时间使用新特性。 -
与鸿蒙PC适配路线一致:ArkUI 原生支持跨设备自适应布局,提供了
BreakpointSystem等响应式工具。后续向鸿蒙PC扩展时,核心组件可以直接复用,只需调整布局参数。
当然,如果未来需要将这套 AI 工具同时发布到 iOS 和 Android 平台,鸿蒙Flutter框架将是更优选择——它提供了统一的 Dart 代码库和 Skia 渲染引擎,一份代码可以覆盖多个平台。当前的架构设计为这种可能性保留了接口:业务逻辑(数据模型、Mock 策略、AI API Stub)与 UI 层(ArkUI 组件)清晰分离,迁移到 Flutter 时只需重写 UI 层,数据层可以完整复用。从这个角度看,选择 ArkUI 还是鸿蒙Flutter框架,取决于项目的目标平台范围,而非技术优劣。
Q3:数据模型如何设计?
我们定义了清晰的数据接口,这些接口是 UI 层和数据层之间的契约:
interface CopyResult {
title: string;
body: string;
tags: string[];
cover_text: string;
}
interface CopyScenario {
scenario: string;
result: CopyResult;
}
CopyResult 定义了 AI 生成结果的标准结构。四个字段分别对应小红书笔记的四个核心元素:cover_text(封面文案)是直接印在封面图上的文字,需要简短有力、分行排布;title(标题)是笔记的第一行文字,决定了用户是否会点击进去看全文;body(正文)是内容的详细展开,包含具体的产品描述、使用体验、个人感受等;tags(标签)是发布时添加的话题标签,影响笔记的搜索曝光。
CopyScenario 封装了输入场景与输出结果的映射关系。scenario 字段是三个输入参数(主题、人设、情绪)用下划线拼接的字符串,作为匹配键。这种设计使得未来切换到真实 AI API 时,只需确保返回值符合 CopyResult 接口,而 CopyScenario 结构可以完全废弃。
1.5 验收标准
基于以上需求对齐,我们制定了明确的验收标准:
- 用户可输入主题、人设定位,选择情绪标签(种草/吐槽/干货/治愈四选一)
- 点击生成按钮后,展示结构化文案(封面文案、标题、正文、推荐标签四个板块)
- 界面适配手机屏幕,内容区域可滚动,无布局溢出
- 代码结构清晰,数据模型通过 interface 定义,与 UI 组件分离
- 预留 AI API 接入点(
generateMockData()函数),未来可无缝替换 Mock 数据 - 页面可在主页应用中通过路由正常跳转和返回
2. Architect —— 架构设计与技术选型
2.1 整体架构
「种草笔杆子」采用单页面组件的架构设计,所有业务逻辑和 UI 展示都封装在一个 @Entry 组件中。这种设计对于轻量级 AI 工具来说是最优选择——页面复杂度低,不需要引入状态管理库,也不需要跨页面共享数据。
┌─────────────────────────────────────────────────────┐
│ App39 组件 (@Entry + @Component) │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 视图层 (build) │ │
│ │ ┌─────────────┐ ┌──────────────┐ ┌─────────┐ │ │
│ │ │ Input Panel │ │ Generate Btn │ │ Result │ │ │
│ │ │ - 主题输入 │ │ - 生成文案 │ │ Panel │ │ │
│ │ │ - 人设定位 │ │ │ │-封面文案 │ │ │
│ │ │ - 情绪选择 │ │ │ │-标题 │ │ │
│ │ └─────────────┘ └──────────────┘ │-正文 │ │ │
│ │ │-标签 │ │ │
│ │ └─────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 状态层 (@State) │ │
│ │ topic, persona, emotion, showResult, │ │
│ │ currentResult │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 数据层 (MockData / AI API Stub) │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ mockScenarios: CopyScenario[] │ │ │
│ │ │ getMockData() → 场景匹配 │ │ │
│ │ │ generateMockData() → [AI API Stub 入口] │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
2.2 分层设计
| 层级 | 职责 | 实现方式 | 关键约束 |
|---|---|---|---|
| 视图层 (View) | 用户交互与结果展示 | ArkUI 声明式组件(Column、Row、Scroll、Flex 等) | 不直接操作数据,通过 @State 驱动渲染 |
| 状态层 (State) | 响应式数据管理 | @State 装饰器,单向数据流 | 状态变更通过事件触发,不通过外部注入 |
| 数据层 (Data) | 数据模型与业务逻辑 | interface 定义 + Mock 数据 + Stub 函数 | 与视图层完全解耦,可独立替换 |
| 路由层 (Router) | 页面导航与生命周期 | @kit.ArkUI 的 router 模块 | 统一使用 pushUrl 和 back 模式 |
2.3 数据流设计
应用采用严格的单向数据流:
用户输入 → @State 状态更新 → generateMockData() →
→ 场景匹配 → CopyResult 赋值 → @State 触发 UI 更新 → 结果渲染
单向数据流的设计确保了状态变更的可预测性和可追溯性。在 ArkUI 中,当 @State 变量发生变化时,框架会自动触发 build() 方法的重新执行,更新受影响的 UI 部分。这种机制类似于 React 的虚拟 DOM diff,但 ArkUI 的更新粒度更细,性能更好。
当 showResult 从 false 变为 true 时,build() 中的条件渲染 if (this.showResult) 会自动创建结果面板的 UI 树;当用户修改输入参数时,对应的 @State 变量更新,但不会触发结果面板的重新渲染(因为 currentResult 未变化)。这种精确的更新机制避免了不必要的渲染开销。
2.4 组件树
从 ArkUI 的视角看,整个页面的组件树结构如下:
App39 (Column, width: 100%, height: 100%, bg: #F5F5F5)
├── Navigation Row (Row, bg: #FFFFFF)
│ └── Button('← 返回') → router.back()
├── Header Area (Column, bg: #FFFFFF)
│ ├── Text('种草笔杆子') fontSize: 24 Bold
│ └── Text('AI生成小红书种草文案') fontSize: 14 color: #999
├── Scroll (layoutWeight: 1)
│ └── Column
│ ├── Input: 笔记主题
│ │ ├── Text('笔记主题') label
│ │ └── TextInput({ text: $topic }) → onChange 更新 topic
│ ├── Input: 人设定位
│ │ ├── Text('人设定位') label
│ │ └── TextInput({ text: $persona }) → onChange 更新 persona
│ ├── Input: 内容情绪
│ │ ├── Text('内容情绪') label
│ │ └── Row (Button x4: 种草/吐槽/干货/治愈)
│ │ └── 每个Button: onClick → 更新 emotion, 条件样式切换
│ ├── Button('生成文案') → onClick → generateMockData()
│ └── [if showResult] Result Panel (Column, bg: #FFF, borderRadius: 12)
│ ├── Section: 封面文案
│ │ ├── Text('封面文案') label color: #FF6B81
│ │ └── Text(currentResult.cover_text) bg: #FFF0F3
│ ├── Section: 标题
│ │ ├── Text('标题') label
│ │ └── Text(currentResult.title) fontSize: 16 Bold
│ ├── Section: 正文
│ │ ├── Text('正文') label
│ │ └── Text(currentResult.body) fontSize: 14 lineHeight: 22
│ ├── Divider
│ └── Section: 推荐标签
│ ├── Text('推荐标签') label
│ └── Flex({ wrap: Wrap })
│ └── ForEach(tags) → Text(tag) 胶囊样式
2.5 异常处理策略
虽然当前应用逻辑简单,但我们仍然设计了完整的异常处理策略:
-
Mock 数据未命中:当用户输入的组合在
mockScenarios中不存在时,getMockData()默认回退到第一个场景数据。这是一种优雅降级策略,确保用户始终能看到生成结果,而不是面对空白页面。在真实 AI 场景下,这个回退逻辑可以替换为错误提示或重试机制。 -
路由跳转失败:主页的
router.pushUrl()和子页面的router.back()都使用了.catch()捕获可能的异常。这遵循了项目统一的异常处理规范,所有 40 个应用都采用相同的路由错误处理模式。 -
空状态处理:所有输入参数都有默认值(
topic默认为"通勤必备好物推荐",persona默认为"职场打工人",emotion默认为"种草"),currentResult也有初始值(所有字段为空字符串或空数组)。这避免了 ArkTS 中不允许 undefined 的问题。
2.6 与鸿蒙PC的适配考量
虽然当前 module.json5 中 deviceTypes 仅配置了 "phone",但组件的布局设计已经为鸿蒙PC适配做好了准备。ArkUI 的响应式布局能力使得这一点成为可能:
-
弹性宽度:所有容器使用
width('100%')和calc(100% - 32px)而不是固定像素值,确保在不同屏幕宽度下都能正确填充。输入框和结果面板的左右边距使用margin({ left: 16, right: 16 })控制,在大屏上可以调整为更大的值。 -
无绝对定位:整个页面使用 Flex 布局(Column/Row),没有任何绝对定位的元素。这意味着窗口大小变化时,布局会自动适应,不会出现元素重叠或错位。
-
自适应换行:标签列表使用
Flex({ wrap: FlexWrap.Wrap }),标签会根据容器宽度自动换行,而不是使用固定列数。在小屏上可能每行显示 3-4 个标签,在大屏上可以显示更多。
未来向鸿蒙PC扩展时,只需在 deviceTypes 中添加 "tablet" 和 "2in1",然后利用 ArkUI 的断点系统(BreakpointSystem)对大屏布局做以下微调:将表单输入区和结果展示区从上下排列改为左右分栏,让用户可以看到输入和输出的对应关系;增加 fontSize 以适配大屏阅读距离;为按钮添加键盘快捷键支持。核心组件逻辑无需任何改动。
2.7 与鸿蒙Flutter框架的协同设计
在设计架构时,我们特意考虑了未来可能迁移到鸿蒙Flutter框架的场景。关键是保持数据模型和业务逻辑的独立性:
-
数据模型可移植:
CopyResult和CopyScenario接口定义的是纯数据结构,不依赖任何 ArkUI 或 ArkTS 特有类型。这些接口可以直接翻译为 Dart 的 class 或 map 结构。 -
Stub 函数可替换:
generateMockData()和getMockData()的逻辑是纯 TypeScript 代码,不涉及 UI 操作。在 Flutter 中,这些逻辑可以直接放入 Controller 或 ViewModel 中。 -
UI 层隔离:
build()方法和所有.fontSize()、.backgroundColor()等样式链式调用是唯一需要重写的部分。在 Flutter 中,这些对应的是 Widget 树和 Theme 系统。
3. Atomize —— 原子化任务分解
将开发任务拆解为以下原子任务,按依赖关系排序。每个原子任务都足够小,可以在一个开发周期内完成并验证:
| 编号 | 原子任务 | 预估工时 | 依赖 | 验证标准 |
|---|---|---|---|---|
| A1 | 定义数据模型接口 CopyResult、CopyScenario |
0.5h | — | 编译通过,类型检查无错误 |
| A2 | 编写 Mock 数据集(3 组场景,覆盖种草/吐槽/治愈) | 1.0h | A1 | 每组数据包含完整的四字段,文案符合小红书风格 |
| A3 | 实现 getMockData() 场景匹配逻辑(含回退策略) |
0.5h | A1, A2 | 精确匹配和回退匹配均正确 |
| A4 | 实现 generateMockData() AI API Stub |
0.25h | A3 | 调用后 showResult 变为 true,currentResult 被赋值 |
| A5 | 搭建页面骨架(导航栏、标题、Scroll 容器) | 0.5h | — | 页面可渲染,导航可返回 |
| A6 | 实现输入面板(主题 TextInput、人设 TextInput) | 0.5h | A5 | 输入框可编辑,onChange 事件正常触发 |
| A7 | 实现情绪选择按钮组(4 个互斥按钮) | 0.5h | A5 | 按钮可切换,选中样式正确 |
| A8 | 实现生成按钮及状态联动 | 0.25h | A4, A6, A7 | 点击后结果面板出现 |
| A9 | 实现结果展示面板(封面、标题、正文、标签四板块) | 1.0h | A1, A8 | 四个板块均正确渲染,标签可换行 |
| A10 | 整体样式微调与视觉对齐 | 0.5h | A9 | 颜色、间距、字号符合设计规范 |
| A11 | 在主页路由中注册 app39 页面 | 0.25h | A5 | 从主页可正常跳转和返回 |
| A12 | 端到端测试与回归验证 | 0.5h | A10 | 完整流程无异常,不影响其他 39 个应用 |
总计预估工时约 6.5 小时。其中 Mock 数据编写(A2)和结果面板实现(A9)是工作量最大的两个任务,因为它们分别涉及文案创作和复杂的 UI 布局。
4. Approve —— 审批与质量门控
4.1 代码审查要点
在代码提交之前,我们进行了全面的代码审查,重点关注 ArkTS 语法合规性、组件规范性和性能优化:
| 审查项 | 状态 | 详细说明 |
|---|---|---|
| ArkTS 语法合规 | 通过 | 代码中无 any/unknown 类型,无解构赋值,无 var 声明,无索引签名,无函数表达式,所有回调使用箭头函数 |
| 接口定义完整 | 通过 | 所有数据模型通过 interface 显式定义,字段类型明确,无隐式类型推断 |
| 组件装饰器正确 | 通过 | @Entry + @Component 组合使用,符合页面组件规范 |
| 状态管理合理 | 通过 | @State 用于 UI 响应式数据,private 用于内部常量(mockScenarios),职责清晰 |
| 生命周期使用 | 通过 | 无冗余生命周期方法,初始化在声明时完成,不需要 aboutToAppear |
| 路由配置 | 通过 | 已在 main_pages.json 中注册,路由路径与主页跳转逻辑一致 |
| 资源引用 | 不适用 | 当前使用硬编码文案,未来可迁移至 $r 资源引用以支持国际化和主题切换 |
| 类型安全检查 | 通过 | CopyScenario | null 联合类型正确处理了 null 检查,无类型断言滥用 |
4.2 性能考量
虽然应用逻辑简单,但我们仍然关注了性能细节:
-
无
@Watch过度监听:所有的状态变更都是用户主动触发的,不存在循环依赖或监听链。每个状态变量的变更路径清晰可追溯。 -
条件渲染而非隐藏:使用
if (this.showResult)而不是通过visibility或opacity隐藏结果面板。条件渲染意味着结果面板不展示时完全不创建 UI 节点,节省内存和渲染开销。 -
Scroll 容器 + layoutWeight:使用
layoutWeight(1)让 Scroll 容器填充剩余空间,而不是使用固定高度。这避免了不同屏幕尺寸下的布局适配问题,同时 Scroll 容器提供了原生的滚动性能和回弹效果。 -
Flex 换行:标签列表使用
FlexWrap.Wrap而不是固定列数的 Grid,避免了在小屏上标签被截断的问题,同时在大屏上能充分利用空间。 -
无动画开销:当前版本没有添加动画效果,这避免了不必要的渲染帧开销。未来如果接入真实 AI API 并需要添加 loading 动画,建议使用
animateTo而非持续改变width/height等布局属性,以避免布局重排的性能问题。
4.3 鸿蒙PC兼容性检查
当前组件可以无缝运行在鸿蒙PC的窗口模式下,但经过审查,我们注意到了以下适配点:
-
在 PC 大屏上,
fontSize(24)的标题可能显得偏小,后续可适配displayMode或使用vp单位动态调整。PC 端适合的标题字号通常在 32-40 之间。 -
情绪选择按钮在小屏上可能需要横向滚动(已通过 Row 容器的自然溢出处理),PC 上按钮间距可以适当增大,提升点击区域。
-
结果面板在手机上是全宽显示,在 PC 上可以限制最大宽度(如 680px 居中),避免长文本行宽过大影响阅读体验。
-
与鸿蒙Flutter框架的交互:当前组件为纯 ArkUI 实现,如需在 Flutter 中嵌入,可以通过 PlatformView 机制将 ArkUI 组件作为原生视图嵌入 Flutter Widget 树,或通过 MethodChannel 进行数据通信。
4.4 ArkTS 特有约束的合规检查
HarmonyOS 的 ArkTS 语言是 TypeScript 的严格子集,有诸多特有的语法约束。我们对代码进行了逐项检查:
- 不支持 any/unknown:代码中所有变量都有显式类型标注,如
let found: CopyScenario | null = null。 - 不支持解构:所有数据访问都通过属性访问(如
this.currentResult.title)而非解构。 - 不支持 var:所有变量声明使用
let或const(实际上 ArkTS 中 const 也有特定限制,当前代码中未使用)。 - 不支持函数表达式:所有回调使用箭头函数,如
.onClick(() => { this.emotion = '种草'; })。 - 不支持索引签名:没有使用
[key: string]: any这样的索引签名,所有对象类型都通过 interface 定义。 - 不支持 in 运算符:没有使用
for...in循环,数组遍历使用标准的for循环。 - 不支持展开运算符(非数组场景):没有在对象上使用展开运算符,仅在数组字面量中使用
ForEach渲染列表。 - 不支持命名空间:没有使用 namespace,所有代码都在模块作用域内。
5. Automate —— 自动化执行与编码实现
5.1 数据模型定义
首先定义组件所需的数据接口。在 ArkTS 中,我们使用 interface 关键字声明类型契约。接口是纯编译时概念,不会生成运行时代码,但提供了完整的类型检查能力:
interface CopyResult {
title: string;
body: string;
tags: string[];
cover_text: string;
}
interface CopyScenario {
scenario: string;
result: CopyResult;
}
CopyResult 定义了 AI 生成的文案结构,包含四个字段。每个字段的设计都有其业务考量:cover_text(封面文案)是单独分离出来的,因为它在视觉上需要特殊处理(通常放在封面图片上,需要分行和精简);title(标题)和 body(正文)区分开,是因为标题决定了笔记的点击率,正文决定了完读率,两者在优化目标上不同;tags(标签)使用 string[] 而非单个字符串,因为小红书允许添加多个话题标签,且前端需要逐个渲染。
CopyScenario 将输入场景与输出结果绑定。scenario 字段作为匹配键,由三个输入参数(topic、persona、emotion)拼接而成。这种设计简单直观,但有一个局限:当用户输入的自定义主题无法精确匹配时,只能回退到默认场景。在真实 AI 接入后,CopyScenario 结构将被废弃,因为 AI 会根据任意输入动态生成内容。
5.2 组件声明与状态管理
@Entry
@Component
struct App39 {
@State topic: string = '通勤必备好物推荐';
@State persona: string = '职场打工人';
@State emotion: string = '种草';
@State showResult: boolean = false;
@State currentResult: CopyResult = {
title: '',
body: '',
tags: [],
cover_text: ''
};
// ...
}
@Entry 装饰器标记该组件为页面入口,这意味着它会被路由系统直接加载和渲染。@Component 声明其为可复用的 UI 组件,虽然当前它只被路由系统使用,但这个装饰器是必需的,因为它包含了 build() 方法。
五个 @State 变量驱动整个页面的状态变化:
-
topic:用户输入的笔记主题,默认值"通勤必备好物推荐"起到了引导和示例的作用。在真实产品中,这个默认值可以替换为热门话题或个性化推荐。 -
persona:人设定位,影响文案的语气和视角。例如"职场打工人"的人设会使用"姐妹们"、"宝子们"等亲切称呼,语气轻松活泼;而"专业测评师"的人设则会更客观、更数据化。 -
emotion:内容情绪,通过按钮组切换,直接影响文案的风格走向。这是一个互斥选择,任何时候只能有一个情绪被选中。初始值为"种草",这是小红书最主流的内容类型。 -
showResult:控制结果面板的显示与隐藏。初始值为false,用户点击生成按钮后变为true。这个布尔值起到了"是否已生成"的语义标记作用。 -
currentResult:存储当前展示的 AI 生成结果。初始值中所有字符串字段为空,标签数组为空数组,确保在showResult为false时不会渲染任何内容。
5.3 Mock 数据与 AI API Stub 模式
这是本应用最核心的设计模式,也是整个 40 个应用项目中统一采用的数据策略。当前阶段使用本地 Mock 数据模拟 AI 生成效果,同时预留了清晰的 API 接入点。
Mock 数据集包含 3 组场景,每组都是完整的小红书风格文案:
private mockScenarios: CopyScenario[] = [
{
scenario: '通勤必备好物推荐_职场打工人_种草',
result: {
title: '打工人通勤包大公开!这5件好物让我每天多睡半小时',
body: '哈喽宝子们!作为一个每天通勤两小时的打工人,今天终于把我的通勤必备好物整理出来啦!\n\n第一个必推的就是这个降噪耳机,地铁上戴上它,整个世界都安静了,终于可以在嘈杂的车厢里安心听播客了。\n\n第二个是这个折叠咖啡杯,早上冲好咖啡带出门,到公司还是热的,一年省下几千块咖啡钱!\n\n第三个是便携充电宝,自带线的那种,再也不用在包里翻找充电线了,强迫症患者必入。\n\n第四个是通勤背包,分区超多,电脑、水杯、雨伞各就各位,背起来还特别轻。\n\n第五个是折叠平底鞋,上班路上穿运动鞋,到公司换上平底鞋,脚再也不疼了!\n\n这些好物真的拯救了我的通勤生活,宝子们有什么好用的也可以在评论区分享哦!',
tags: ['#通勤好物', '#打工人必备', '#职场好物推荐', '#通勤包', '#上班族'],
cover_text: '通勤包大公开\n5件好物推荐\n打工人必看'
}
},
{
scenario: '装修踩坑实录_装修小白_吐槽',
result: {
title: '装修半年,我踩了10个坑!看完至少省5万',
body: '姐妹们,装修真的是场修行,半年下来我踩的坑比走过的路还多!今天含泪分享,希望能帮大家避雷。\n\n第一大坑:水电改造没留够插座!现在客厅沙发旁边没有插座,手机充电要拉插线板,丑哭了。\n\n第二大坑:找了熟人装修!以为能省钱,结果工期一拖再拖,质量还不行,最后连朋友都做不成了。\n\n第三大坑:定制柜子没做到顶!上面留了10cm缝隙,积灰不说,看着就难受,强迫症真的受不了。\n\n第四大坑:卫生间没做干湿分离!洗个澡整个卫生间都是水,每次都要拖地,后悔死了。\n\n第五大坑:地板颜色选深了!深色地板太显灰,一天不拖就脏得不行,懒人慎选!\n\n还有很多坑就不一一说了,总之装修一定要多做功课,不要像我一样交智商税!\n\n你们装修踩过什么坑?评论区一起吐槽!',
tags: ['#装修踩坑', '#装修避雷', '#装修经验', '#装修小白', '#装修日记'],
cover_text: '装修半年踩了10个坑\n看完至少省5万\n装修小白必看'
}
},
{
scenario: '独居生活指南_独居博主_治愈',
result: {
title: '一个人住第三年,我把30平小屋住成了治愈小窝',
body: '傍晚六点,夕阳透过百叶窗洒进房间,我打开音响,放一首喜欢的歌。这一刻,觉得独居真好。\n\n三年前刚搬进这个30平的小公寓时,空荡荡的什么都没有。慢慢添置,慢慢布置,现在每个角落都是我喜欢的样子。\n\n窗台上养了绿萝和薄荷,每天早上给它们浇水,看着新叶子长出来,生活就有了期待。\n\n厨房虽小,但足够做出一个人的三餐。周末做一锅番茄牛腩,能吃两天,边吃边看剧,幸福感爆棚。\n\n床头放了一排书,睡前读半小时,比刷手机入睡快多了。\n\n独居不是孤独,是跟自己好好相处。学会了照顾自己,也学会了享受安静。\n\n如果你也在独居,或者准备开始独居,希望我的分享能给你一些温暖。',
tags: ['#独居生活', '#治愈系', '#独居女孩', '#小户型', '#生活美学'],
cover_text: '一个人住第三年\n30平治愈小窝\n独居生活指南'
}
}
];
这三组数据分别展示了三种典型的小红书内容风格:种草文案以"必推"、“必入"等推荐语气为主,使用数字列举增强说服力;吐槽文案以"踩坑”、"避雷"为主题,用感叹号和夸张描述增加情绪感染力;治愈文案以场景描写开头,用细腻的观察和感受营造温暖氛围。
场景匹配逻辑是数据层的核心:
getMockData(): void {
let key: string = this.topic + '_' + this.persona + '_' + this.emotion;
let found: CopyScenario | null = null;
for (let i = 0; i < this.mockScenarios.length; i++) {
if (this.mockScenarios[i].scenario === key) {
found = this.mockScenarios[i];
break;
}
}
if (found === null) {
found = this.mockScenarios[0]; // 默认回退到第一个场景
}
if (found !== null) {
this.currentResult = found.result;
}
this.showResult = true;
}
这里有几个值得注意的 ArkTS 编码细节:
- 使用
let key: string显式标注类型,ArkTS 不支持省略类型推断(在函数返回类型被省略时尤其严格)。 - 使用
CopyScenario | null联合类型 +null初始值,模拟可选值的语义。ArkTS 不支持undefined,null是唯一合法的空值标记。 - 使用标准的
for循环而非for...of或forEach。ArkTS 对迭代器的支持有限,for循环是最通用的遍历方式。 - 在
break之后执行 null 检查,确保类型窄化(type narrowing)正确。TypeScript 的 control flow analysis 在 ArkTS 中同样有效。
AI API Stub 模式解析:
generateMockData() 是关键的抽象层,它是 UI 层和数据层之间的唯一接口:
generateMockData(): void {
this.getMockData();
}
当前的实现只有一行代码,直接调用 getMockData()。但它的存在意义重大——整个 UI 层只依赖这个函数,而不依赖 Mock 数据的具体实现。未来接入真实 AI API 时,只需替换此函数体:
// 未来接入真实 AI API 时的替换方案
generateMockData(): void {
// 1. 构建请求参数,将三个输入字段序列化为 JSON
let requestBody: string = JSON.stringify({
topic: this.topic,
persona: this.persona,
emotion: this.emotion
});
// 2. 调用 AI API(示例,需替换为真实的华为盘古大模型或其他 LLM 端点)
// httpRequest.request('https://api.example.com/ai/generate', {
// method: 'POST',
// header: { 'Content-Type': 'application/json' },
// body: requestBody
// }).then((response) => {
// this.currentResult = JSON.parse(response.result) as CopyResult;
// this.showResult = true;
// }).catch((error) => {
// // 网络错误时回退到 Mock 数据
// this.getMockData();
// });
// 3. 当前仍使用 Mock 数据
this.getMockData();
}
这种 Stub 模式 的价值在于:
- UI 层与数据层完全解耦,替换数据源不影响任何界面代码。UI 开发者只需关心
CopyResult接口,不需要了解数据是如何获取的。 - 开发阶段可以独立验证 UI 交互,无需等待 AI 接口就绪。前端和后端(或 AI 团队)可以并行工作。
- 为后续的 A/B 测试(不同 AI 模型对比)提供了灵活的切换点。可以在
generateMockData()中根据配置选择不同的 AI 端点。 - 离线降级:当 AI 接口不可用时,可以回退到 Mock 数据或本地缓存,保证应用的基本可用性。
在整个 40 个应用的 AI 智能工具箱项目中,所有子应用都遵循这个 Stub 模式,这保证了项目的一致性和可维护性。
5.4 情绪选择按钮组
情绪选择是用户交互的核心,使用四个互斥按钮实现。按钮的选中/未选中状态通过 @State emotion 的值动态切换:
Row() {
Button('种草')
.fontSize(13)
.backgroundColor(this.emotion === '种草' ? '#FF6B81' : '#E8E8E8')
.fontColor(this.emotion === '种草' ? '#FFFFFF' : '#333333')
.borderRadius(20)
.height(36)
.onClick(() => { this.emotion = '种草'; })
Button('吐槽')
.fontSize(13)
.backgroundColor(this.emotion === '吐槽' ? '#FF6B81' : '#E8E8E8')
.fontColor(this.emotion === '吐槽' ? '#FFFFFF' : '#333333')
.borderRadius(20)
.height(36)
.margin({ left: 8 })
.onClick(() => { this.emotion = '吐槽'; })
Button('干货')
.fontSize(13)
.backgroundColor(this.emotion === '干货' ? '#FF6B81' : '#E8E8E8')
.fontColor(this.emotion === '干货' ? '#FFFFFF' : '#333333')
.borderRadius(20)
.height(36)
.margin({ left: 8 })
.onClick(() => { this.emotion = '干货'; })
Button('治愈')
.fontSize(13)
.backgroundColor(this.emotion === '治愈' ? '#FF6B81' : '#E8E8E8')
.fontColor(this.emotion === '治愈' ? '#FFFFFF' : '#333333')
.borderRadius(20)
.height(36)
.margin({ left: 8 })
.onClick(() => { this.emotion = '治愈'; })
}
.padding({ left: 16, top: 4 })
.width('100%')
设计要点分析:
-
选中状态可视化:使用三元表达式
this.emotion === '种草' ? '#FF6B81' : '#E8E8E8'实现条件样式切换。品牌色#FF6B81(小红书标志性粉色)用于选中状态,灰色#E8E8E8用于未选中状态。这种高对比度的颜色方案让用户一眼就能看出当前选择的情绪。 -
胶囊按钮造型:
borderRadius(20)配合height(36)创建了圆角矩形按钮,这是移动端 UI 中常见的胶囊按钮样式,视觉上比直角矩形更柔和友好。 -
箭头函数回调:ArkTS 不支持函数表达式,所有事件回调必须使用箭头函数。
.onClick(() => { this.emotion = '种草'; })是标准写法,不能写成onClick(function() { ... })。 -
按钮间距:使用
margin({ left: 8 })保持按钮之间的视觉呼吸感。8px 的间距在移动端是一个合适的值,既不会让按钮看起来过于拥挤,也不会浪费屏幕空间。 -
互斥逻辑:四个按钮的 onClick 各自设置不同的
emotion值,天然实现了互斥选择。由于emotion是@State变量,点击任何一个按钮都会触发 UI 更新,所有四个按钮的样式都会重新计算。
5.5 生成按钮与状态联动
Button('生成文案')
.width('calc(100% - 32px)')
.height(48)
.backgroundColor('#FF6B81')
.fontColor('#FFFFFF')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.borderRadius(24)
.margin({ top: 24, left: 16, right: 16 })
.onClick(() => {
this.generateMockData();
})
生成按钮的设计遵循了移动端主操作按钮的规范:高度 48px(符合手指点击区域的最小推荐尺寸),圆角 24px(全圆角,视觉上更突出),品牌色背景白色文字(高对比度,吸引用户注意)。margin({ top: 24 }) 在输入区和按钮之间提供了足够的视觉分隔,让用户清楚地知道这是操作区域。
点击生成按钮后,generateMockData() 完成数据匹配,currentResult 和 showResult 状态更新。由于 showResult 从 false 变为 true,build() 方法中的条件渲染 if (this.showResult) 会自动创建结果面板的 UI 树,用户无需手动刷新或等待。
当前流程是同步的(Mock 数据在内存中直接匹配),但在真实 AI 场景下,需要在这个位置添加异步处理:先设置一个 isLoading 状态显示加载动画,等待 AI API 返回后再更新 currentResult 和 showResult,同时处理可能的网络错误和超时。
5.6 结果展示面板
结果面板使用条件渲染,仅在 showResult 为 true 时创建。整个面板分为四个功能区块,每个区块都有明确的视觉层级:
if (this.showResult) {
Column() {
// 区块一:封面文案
Text('封面文案')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FF6B81')
.padding({ top: 8, bottom: 4 })
.width('100%')
Text(this.currentResult.cover_text)
.fontSize(14)
.fontColor('#333333')
.width('100%')
.padding({ bottom: 12 })
.backgroundColor('#FFF0F3')
.borderRadius(8)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
// 区块二:标题
Text('标题')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.padding({ top: 12, bottom: 4 })
.width('100%')
Text(this.currentResult.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.width('100%')
.padding({ bottom: 12 })
// 区块三:正文
Text('正文')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.padding({ bottom: 8 })
.width('100%')
Text(this.currentResult.body)
.fontSize(14)
.fontColor('#555555')
.width('100%')
.lineHeight(22)
.padding({ bottom: 12 })
// 分隔线
Divider().color('#E8E8E8').margin({ bottom: 12 })
// 区块四:推荐标签
Text('推荐标签')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.padding({ bottom: 8 })
.width('100%')
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.currentResult.tags, (tag: string) => {
Text(tag)
.fontSize(12)
.fontColor('#FF6B81')
.backgroundColor('#FFF0F3')
.borderRadius(12)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.margin({ right: 6, bottom: 6 })
})
}
.width('100%')
}
.width('calc(100% - 32px)')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding(16)
.margin({ top: 16, left: 16, right: 16, bottom: 24 })
}
设计要点详解:
-
封面文案区块:使用浅粉色背景
#FFF0F3(品牌色#FF6B81的极淡版本)区分视觉层级。这个区块模拟了小红书封面图的视觉效果——封面文案通常印在图片上,背景色让它在白色卡片中脱颖而出。borderRadius(8)的圆角比整体卡片的 12px 略小,形成了内外的视觉层次。 -
标题区块:标题使用
fontSize(16)和FontWeight.Bold,与正文的fontSize(14)和普通字重形成对比。标题是用户第一眼看到的内容,需要足够醒目。 -
正文区块:正文使用
fontSize(14)、fontColor('#555555')(深灰色而非纯黑,减轻视觉疲劳)、lineHeight(22)(行间距约 1.57 倍,提升长文本可读性)。正文是信息密度最高的部分,合理的排版参数直接影响用户的阅读体验。 -
标签区块:使用
Flex({ wrap: FlexWrap.Wrap })实现标签的自动换行。ForEach是 ArkUI 中渲染数组列表的标准方式,相当于 React 的map。每个标签使用胶囊样式(borderRadius(12)、padding左右 10px 上下 4px),颜色与品牌色保持一致(#FF6B81文字 +#FFF0F3背景),强化品牌识别。标签间距使用margin({ right: 6, bottom: 6 })确保换行后也有合适的间距。
5.7 页面骨架与导航
整个页面采用 Column 根容器 + Scroll 内容区的经典布局。这种布局在移动端 HarmonyOS 应用中非常常见,适用于内容可能超出屏幕高度的场景:
build() {
Column() {
// 导航栏:返回按钮,白底,固定在顶部
Row() {
Button('← 返回')
.fontSize(14)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.onClick(() => {
router.back();
})
}
.width('100%')
.padding({ left: 16, top: 12, bottom: 8 })
.backgroundColor('#FFFFFF')
// 标题区域:应用名 + 副标题
Text('种草笔杆子')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.padding({ left: 16, top: 8, bottom: 4 })
.width('100%')
.backgroundColor('#FFFFFF')
Text('AI生成小红书种草文案')
.fontSize(14)
.fontColor('#999999')
.padding({ left: 16, bottom: 12 })
.width('100%')
.backgroundColor('#FFFFFF')
// 可滚动内容区:输入面板 + 生成按钮 + 结果面板
Scroll() {
Column() {
// ... 输入面板、生成按钮、结果面板
}
.width('100%')
}
.layoutWeight(1)
.width('100%')
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
layoutWeight(1) 是此布局的关键。它让 Scroll 容器占据导航栏和标题之外的剩余空间,确保内容区域在任何屏幕高度下都能正常工作。如果不用 layoutWeight 而使用固定高度,就可能在矮屏手机上出现内容被截断、在高屏手机上出现大量空白的问题。
导航栏和标题区域使用白色背景 #FFFFFF,与内容区的灰色背景 #F5F5F5 形成视觉分区,让用户清楚地知道哪些是固定区域,哪些是可滚动区域。这种设计遵循了 HarmonyOS 原生应用的视觉规范。
5.8 路由注册与主页集成
在 main_pages.json 中注册页面路径,这是 HarmonyOS Stage Mode 的标准路由配置方式:
{
"src": [
"pages/Index",
"pages/app39/Index",
// ... 其他 39 个页面
]
}
主页通过 router.pushUrl() 导航到子页面,路由路径由 app id 动态拼接:
onAppClick(app: AppInfo): void {
let url: string = 'pages/app' + app.id + '/Index';
router.pushUrl({ url: url }).catch((err: Error): void => {
console.error('router push failed: ' + err.message);
});
}
这种动态路由拼接的方式使得 40 个应用的管理变得极其简单——主页不需要维护一个庞大的路由映射表,只需要确保每个子应用的页面路径遵循 pages/app{id}/Index 的命名规范即可。添加新应用时,只需在 main_pages.json 中追加一行,在主页数据中添加一个 AppInfo 条目,无需修改路由逻辑。
5.9 代码质量说明:ArkTS 编码实践
以下是一些在「种草笔杆子」开发中值得注意的 ArkTS 编码实践,这些实践对于确保代码通过 HarmonyOS 编译器的严格检查至关重要:
① 显式类型标注:ArkTS 不支持 any 和 unknown 类型,所有变量必须显式标注类型。这是与标准 TypeScript 最大的区别之一:
// 正确:显式类型,联合类型包含 null
let found: CopyScenario | null = null;
// 错误:不允许 any 类型
// let found: any = null;
② 使用 null 而非 undefined 标记缺失值:ArkTS 的对象布局在编译时确定且运行时不可更改,因此不支持删除属性。使用可空类型 + null 赋值是标准的模拟方式:
let found: CopyScenario | null = null;
if (found === null) {
found = this.mockScenarios[0]; // 默认回退
}
③ 箭头函数用于回调:ArkTS 不支持函数表达式(function() {}),所有回调必须使用箭头函数。这是 ArkTS 限制 this 语义的结果——this 只能在实例方法中使用,箭头函数不绑定自己的 this,完美适配 ArkTS 的 OOP 语义:
// 正确:箭头函数
.onClick(() => { this.emotion = '种草'; })
// 错误:函数表达式
// .onClick(function() { this.emotion = '种草'; })
④ 避免解构赋值:ArkTS 不支持解构(包括数组解构和对象解构),所有数据访问必须通过属性访问或索引访问:
// 正确:直接属性访问
let title: string = this.currentResult.title;
// 错误:不支持解构
// let { title } = this.currentResult;
⑤ 接口定义紧凑:ArkTS 不支持声明合并(declaration merging),所有接口属性必须在一个声明中定义完毕。不能像 TypeScript 那样分多次声明同一个接口来扩展属性。
⑥ 使用 instanceof 而非 is 运算符:ArkTS 中类型判断必须使用 instanceof,is 运算符不被支持。在当前代码中虽然未涉及,但这是 ArkTS 开发中需要牢记的规则。
6. Assess —— 评估与总结
6.1 成果回顾
「种草笔杆子」作为 AI 智能应用工具箱中的第 39 号应用,成功实现了以下核心功能:
-
三参数输入系统:主题、人设、情绪三位一体的输入模型,精准覆盖小红书文案的核心创作要素。三个参数的组合可以产生数十种不同的文案风格,满足不同用户的需求。
-
四情绪风格切换:种草、吐槽、干货、治愈四种情绪,覆盖了小红书平台主流的内容类型。每种情绪对应不同的文案语气、用词习惯和结构模式。
-
结构化四件套输出:封面文案 + 标题 + 正文 + 标签的完整输出,用户可以直接复制使用。封面文案适配图片制作场景,标签适配搜索优化需求,正文适配内容发布需求。
-
AI API Stub 模式:清晰的数据层抽象,Mock 数据与真实 AI API 可以无缝切换。这个模式已经被推广到整个 40 个应用的项目中,成为统一的数据策略。
6.2 技术亮点
AI API Stub 模式是本项目最值得推广的设计模式。在 HarmonyOS AI 应用开发中,AI 接口往往是最不稳定的部分——大语言模型可能在迭代(从 GPT-3.5 到 GPT-4,从盘古 2.0 到 3.0),API 端点可能变更,响应格式可能调整,甚至供应商可能更换。通过 Stub 模式将 AI 调用封装在 generateMockData() 中,可以:
- 在 AI 接口未就绪时独立开发 UI 和交互逻辑,实现前后端并行开发
- 在 AI 接口变更时仅修改一个函数,不影响上层组件,变更风险可控
- 在测试时快速切换 Mock 和真实数据,提升调试效率,降低测试成本
- 为离线模式提供降级方案,确保应用在无网络环境下的基本可用性
- 支持 A/B 测试:可以同时接入多个 AI 模型,根据用户分组路由到不同的端点
除了 Stub 模式,本应用还展现了以下技术亮点:
-
ArkTS 严格模式的实践:代码完全遵守 ArkTS 的语法约束,无
any、无解构、无函数表达式,所有类型显式标注。这为项目的长期维护和团队协作奠定了基础。 -
声明式 UI 的最佳实践:通过
@State驱动 UI 更新,使用条件渲染而非显示/隐藏控制可见性,使用FlexWrap.Wrap实现自适应布局。这些实践确保了 UI 的性能和可维护性。 -
响应式布局设计:使用百分比宽度、
layoutWeight、margin等相对单位,避免了固定像素值带来的适配问题。为后续向鸿蒙PC的迁移打下了基础。
6.3 与鸿蒙生态的契合度
| 维度 | 评价 | 详细说明 |
|---|---|---|
| API 兼容性 | 优秀 | 仅使用 HarmonyOS 官方 API(@kit.ArkUI 的 router 和基础组件),无任何第三方依赖,确保在 HarmonyOS 版本升级时的兼容性 |
| 架构模式 | 优秀 | 严格遵循 Stage Mode 规范,使用 @Entry/@Component 装饰器,页面在 main_pages.json 中注册,与鸿蒙应用架构完全一致 |
| 设备适配 | 良好 | 响应式布局设计,可平滑迁移至鸿蒙PC。当前仅适配 Phone,但布局结构已考虑大屏场景 |
| 与鸿蒙Flutter框架协同 | 良好 | 纯 ArkUI 实现,但数据层设计(CopyResult 接口、Stub 模式)与 Flutter 可共享。如需迁移到鸿蒙Flutter框架,只需重写 UI 层 |
| 性能 | 优秀 | 声明式 UI + 条件渲染 + 无动画开销,渲染性能优越。Scroll 使用原生滚动,流畅度有保障 |
| 代码可维护性 | 优秀 | 283 行代码,结构清晰,命名规范,注释合理。新成员可以快速理解和修改 |
6.4 已知局限与改进方向
在完成当前版本后,我们识别出以下局限和改进方向:
-
Mock 数据覆盖有限:当前仅 3 组预设场景,用户自定义输入(如"母婴用品推荐"、"数码产品测评"等)的匹配率很低,基本都会回退到默认场景。这是 Mock 策略的固有局限。后续应接入大语言模型 API(如华为盘古大模型、文心一言等),实现真正的 AI 动态生成。接入方式已在
generateMockData()中预留。 -
无 loading 状态:当前 Mock 数据是同步返回的,用户点击生成后立即看到结果。接入真实 AI API 后,网络请求通常需要 2-5 秒,这期间需要添加 loading 动画或骨架屏(skeleton screen),避免用户以为应用卡住了。
-
无历史记录:每次生成结果会覆盖前一次。如果用户想回顾之前生成的文案,需要重新输入参数并生成。后续可添加历史记录列表,用
@State数组存储多次生成的结果,让用户可以浏览和对比。 -
无一键复制功能:生成的文案需要用户手动长按选择复制。可添加各板块的复制按钮,使用 HarmonyOS 的
pasteboardAPI 实现一键复制到剪贴板。 -
鸿蒙PC适配未完成:虽然布局是响应式的,但未针对大屏做专门优化。PC 端适合的交互模式是左右分栏(左侧输入,右侧实时预览),而不是手机端的上下滚动模式。此外,PC 端需要支持键盘快捷键(如 Ctrl+Enter 触发生成)。
-
国际化待完善:当前文案全部硬编码为中文,未使用
$r资源引用。如果未来要支持英文、日文等语言的小红书文案生成,需要将 UI 文案和 Mock 数据都迁移到资源文件中。 -
参数校验缺失:当前用户可以输入空字符串作为主题或人设,虽然不会导致崩溃,但生成结果可能不符合预期。后续应添加输入校验,至少要求主题不为空。
-
没有使用
@Builder提取可复用组件:情绪选择按钮和输入面板的代码存在重复。可以使用@Builder装饰器提取可复用的子组件,提升代码的 DRY 程度。
6.5 向鸿蒙PC迁移的路线图
如果要将「种草笔杆子」扩展到鸿蒙PC平台,建议按以下路径分阶段推进:
-
第一阶段:基础适配(1-2 天)
- 在
module.json5的deviceTypes中增加"tablet"和"2in1"设备类型 - 验证应用在 PC 窗口模式下的基本渲染和交互是否正常
- 调整
fontSize和padding等参数,适配大屏阅读距离
- 在
-
第二阶段:布局优化(2-3 天)
- 使用 ArkUI 的
BreakpointSystem检测屏幕尺寸 - 在大屏(宽度 >= 840vp)上将表单和结果改为左右分栏布局
- 左侧固定宽度放置输入面板,右侧自适应宽度展示结果
- 在超大屏(宽度 >= 1280vp)上限制内容最大宽度,居中显示
- 使用 ArkUI 的
-
第三阶段:交互增强(1-2 天)
- 适配键盘操作:Tab 键切换输入框,Enter 键触发生成,Ctrl+C 复制结果
- 添加鼠标悬停效果(如按钮 hover 状态)
- 支持窗口大小拖拽时的动态布局调整
-
第四阶段:鸿蒙Flutter框架评估(视需求而定)
- 如果 PC 端需要更丰富的动画、自定义渲染或复杂的图形效果,评估鸿蒙Flutter框架方案
- 将数据模型(
CopyResult接口)和 AI API Stub 逻辑迁移到 Dart 代码 - 使用 Flutter Widget 重写 UI 层,保持与移动端一致的用户体验
- 如果选择 Flutter 方案,移动端也可以逐步迁移,实现一套代码覆盖手机和 PC
6.6 总结
「种草笔杆子」作为 AI 智能应用工具箱中的一员,完整展现了 HarmonyOS 开发中 轻量级 AI 应用 的典型开发模式:简洁的输入表单 + AI 能力调用 + 结构化输出展示。整个应用仅 283 行 ArkTS 代码,但覆盖了数据建模、状态管理、条件渲染、列表渲染、路由导航、ArkTS 语法约束合规等核心知识点。
在鸿蒙生态快速发展的当下,开发者面临的核心问题不是"学什么",而是"怎么学和怎么选"。掌握 ArkTS + ArkUI 的原生开发能力,是进入 HarmonyOS 开发世界的基础门槛;理解与鸿蒙Flutter框架的协同关系,是在多平台策略中做出正确决策的关键能力。无论是选择原生 ArkUI 的性能优势和生态兼容性,还是选择鸿蒙Flutter框架的跨平台能力和丰富的 Widget 生态,核心的工程思维是一致的:清晰的架构分层、可扩展的数据模型、完善的状态管理、优雅的异常处理。
本文通过 6A 工作流(Align -> Architect -> Atomize -> Approve -> Automate -> Assess)完整记录了一个 HarmonyOS AI 应用的开发过程,从需求分析到架构设计,从任务分解到代码实现,从质量审查到评估总结。希望这套方法论和具体的代码实践,能为正在探索 HarmonyOS AI 应用开发的读者提供有价值的参考。
本文基于 HarmonyOS Next API 26 开发环境编写,所有代码均通过 ArkTS 编译器验证。项目源码位于 entry/src/main/ets/pages/app39/Index.ets,属于「AI 智能应用工具箱」项目(40 个 AI 应用)的第 39 号子应用。
更多推荐

所有评论(0)