App39「种草笔杆子」—— 小红书文案生成的 HarmonyOS 开发实践

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

在这里插入图片描述
在这里插入图片描述

目录

  1. Align —— 需求对齐与规范制定
  2. Architect —— 架构设计与技术选型
  3. Atomize —— 原子化任务分解
  4. Approve —— 审批与质量门控
  5. Automate —— 自动化执行与编码实现
  6. 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 的更新粒度更细,性能更好。

showResultfalse 变为 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.json5deviceTypes 仅配置了 "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框架的场景。关键是保持数据模型和业务逻辑的独立性:

  • 数据模型可移植CopyResultCopyScenario 接口定义的是纯数据结构,不依赖任何 ArkUI 或 ArkTS 特有类型。这些接口可以直接翻译为 Dart 的 class 或 map 结构。

  • Stub 函数可替换generateMockData()getMockData() 的逻辑是纯 TypeScript 代码,不涉及 UI 操作。在 Flutter 中,这些逻辑可以直接放入 Controller 或 ViewModel 中。

  • UI 层隔离build() 方法和所有 .fontSize().backgroundColor() 等样式链式调用是唯一需要重写的部分。在 Flutter 中,这些对应的是 Widget 树和 Theme 系统。


3. Atomize —— 原子化任务分解

将开发任务拆解为以下原子任务,按依赖关系排序。每个原子任务都足够小,可以在一个开发周期内完成并验证:

编号 原子任务 预估工时 依赖 验证标准
A1 定义数据模型接口 CopyResultCopyScenario 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) 而不是通过 visibilityopacity 隐藏结果面板。条件渲染意味着结果面板不展示时完全不创建 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:所有变量声明使用 letconst(实际上 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 生成结果。初始值中所有字符串字段为空,标签数组为空数组,确保在 showResultfalse 时不会渲染任何内容。

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 不支持 undefinednull 是唯一合法的空值标记。
  • 使用标准的 for 循环而非 for...offorEach。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() 完成数据匹配,currentResultshowResult 状态更新。由于 showResultfalse 变为 truebuild() 方法中的条件渲染 if (this.showResult) 会自动创建结果面板的 UI 树,用户无需手动刷新或等待。

当前流程是同步的(Mock 数据在内存中直接匹配),但在真实 AI 场景下,需要在这个位置添加异步处理:先设置一个 isLoading 状态显示加载动画,等待 AI API 返回后再更新 currentResultshowResult,同时处理可能的网络错误和超时。

5.6 结果展示面板

结果面板使用条件渲染,仅在 showResulttrue 时创建。整个面板分为四个功能区块,每个区块都有明确的视觉层级:

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 不支持 anyunknown 类型,所有变量必须显式标注类型。这是与标准 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 中类型判断必须使用 instanceofis 运算符不被支持。在当前代码中虽然未涉及,但这是 ArkTS 开发中需要牢记的规则。


6. Assess —— 评估与总结

6.1 成果回顾

「种草笔杆子」作为 AI 智能应用工具箱中的第 39 号应用,成功实现了以下核心功能:

  1. 三参数输入系统:主题、人设、情绪三位一体的输入模型,精准覆盖小红书文案的核心创作要素。三个参数的组合可以产生数十种不同的文案风格,满足不同用户的需求。

  2. 四情绪风格切换:种草、吐槽、干货、治愈四种情绪,覆盖了小红书平台主流的内容类型。每种情绪对应不同的文案语气、用词习惯和结构模式。

  3. 结构化四件套输出:封面文案 + 标题 + 正文 + 标签的完整输出,用户可以直接复制使用。封面文案适配图片制作场景,标签适配搜索优化需求,正文适配内容发布需求。

  4. 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 的性能和可维护性。

  • 响应式布局设计:使用百分比宽度、layoutWeightmargin 等相对单位,避免了固定像素值带来的适配问题。为后续向鸿蒙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 已知局限与改进方向

在完成当前版本后,我们识别出以下局限和改进方向:

  1. Mock 数据覆盖有限:当前仅 3 组预设场景,用户自定义输入(如"母婴用品推荐"、"数码产品测评"等)的匹配率很低,基本都会回退到默认场景。这是 Mock 策略的固有局限。后续应接入大语言模型 API(如华为盘古大模型、文心一言等),实现真正的 AI 动态生成。接入方式已在 generateMockData() 中预留。

  2. 无 loading 状态:当前 Mock 数据是同步返回的,用户点击生成后立即看到结果。接入真实 AI API 后,网络请求通常需要 2-5 秒,这期间需要添加 loading 动画或骨架屏(skeleton screen),避免用户以为应用卡住了。

  3. 无历史记录:每次生成结果会覆盖前一次。如果用户想回顾之前生成的文案,需要重新输入参数并生成。后续可添加历史记录列表,用 @State 数组存储多次生成的结果,让用户可以浏览和对比。

  4. 无一键复制功能:生成的文案需要用户手动长按选择复制。可添加各板块的复制按钮,使用 HarmonyOS 的 pasteboard API 实现一键复制到剪贴板。

  5. 鸿蒙PC适配未完成:虽然布局是响应式的,但未针对大屏做专门优化。PC 端适合的交互模式是左右分栏(左侧输入,右侧实时预览),而不是手机端的上下滚动模式。此外,PC 端需要支持键盘快捷键(如 Ctrl+Enter 触发生成)。

  6. 国际化待完善:当前文案全部硬编码为中文,未使用 $r 资源引用。如果未来要支持英文、日文等语言的小红书文案生成,需要将 UI 文案和 Mock 数据都迁移到资源文件中。

  7. 参数校验缺失:当前用户可以输入空字符串作为主题或人设,虽然不会导致崩溃,但生成结果可能不符合预期。后续应添加输入校验,至少要求主题不为空。

  8. 没有使用 @Builder 提取可复用组件:情绪选择按钮和输入面板的代码存在重复。可以使用 @Builder 装饰器提取可复用的子组件,提升代码的 DRY 程度。

6.5 向鸿蒙PC迁移的路线图

如果要将「种草笔杆子」扩展到鸿蒙PC平台,建议按以下路径分阶段推进:

  1. 第一阶段:基础适配(1-2 天)

    • module.json5deviceTypes 中增加 "tablet""2in1" 设备类型
    • 验证应用在 PC 窗口模式下的基本渲染和交互是否正常
    • 调整 fontSizepadding 等参数,适配大屏阅读距离
  2. 第二阶段:布局优化(2-3 天)

    • 使用 ArkUI 的 BreakpointSystem 检测屏幕尺寸
    • 在大屏(宽度 >= 840vp)上将表单和结果改为左右分栏布局
    • 左侧固定宽度放置输入面板,右侧自适应宽度展示结果
    • 在超大屏(宽度 >= 1280vp)上限制内容最大宽度,居中显示
  3. 第三阶段:交互增强(1-2 天)

    • 适配键盘操作:Tab 键切换输入框,Enter 键触发生成,Ctrl+C 复制结果
    • 添加鼠标悬停效果(如按钮 hover 状态)
    • 支持窗口大小拖拽时的动态布局调整
  4. 第四阶段:鸿蒙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 号子应用。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐