美食生活助手:基于 HarmonyOS ArkTS 的 AI 菜谱推荐应用开发实践
美食生活助手:基于 HarmonyOS ArkTS 的 AI 菜谱推荐应用开发实践

一、项目背景与问题定义
1.1 厨房里的"选择困难症"
每个人在做饭前都经历过这样的时刻:打开冰箱,看着零零散散的食材,脑子里一片空白——"今天吃什么?"这个看似简单的问题,却困扰着无数家庭厨师。
从技术角度看,这个问题背后有三个核心难点:
第一,决策疲劳。 现代人每天面临大量决策,到了做饭时间,大脑的处理能力已经接近耗尽。面对冰箱里有限的食材,用户需要的是一个能快速给出建议的"军师",而不是一个需要自己主动搜索的搜索引擎。
第二,信息过载。 下厨房、美食杰等传统菜谱平台,虽然菜谱数量庞大,但用户需要先"知道自己想吃什么"才能搜索。当用户处于"不知道吃什么"的状态时,这些平台提供的价值有限——它们解决的是"怎么做"的问题,而不是"做什么"的决策。
第三,菜谱质量参差不齐。 互联网上的菜谱大多来自用户投稿,食材用量模糊(“适量”“少许”),步骤描述不清晰,缺少关键的烹饪技巧提示。对于烹饪新手来说,按这样的菜谱操作,成功率很低。
针对这三个痛点,我们用 HarmonyOS ArkTS 开发了「美食生活助手」——一款专注于"帮你决定吃什么"和"教你做好一道菜"的 AI 对话应用。它覆盖六类美食场景,根据用户输入动态切换"推荐模式"和"菜谱模式",提供从决策到执行的完整烹饪指导。
1.2 技术选型:为什么选择 HarmonyOS
美食助手是一个典型的"AI 对话 + 内容展示"应用,理论上可以用任何前端框架开发。我们选择 HarmonyOS + ArkTS 的技术栈,基于以下四个考量:
第一,声明式 UI 对长文本内容的天然友好。 菜谱推荐和菜谱详情都是长文本内容,需要良好的排版支持。ArkTS 的 Text 组件原生支持 Markdown 风格的富文本渲染(粗体、换行、emoji),不需要额外引入富文本库,就能实现清晰的排版效果。
第二,零依赖的纯原生实现。 整个应用约 500 行代码,没有引入任何第三方 npm 包。安装包体积不足 5MB,冷启动时间低于 300ms。对于工具类应用来说,轻量本身就是竞争力。
第三,冷启动场景的体验要求。 用户打开美食助手的场景通常是"在厨房里,马上要做饭了"。这个场景下,应用慢一秒打开,用户的耐心就少一分。HarmonyOS 原生应用的启动速度远优于 WebView 方案,能在用户打开冰箱的瞬间完成加载。
第四,多端扩展的前瞻性。 HarmonyOS 的分布式能力为未来在平板、智慧屏等设备上运行预留了空间。想象一下,在厨房的智慧屏上打开美食助手,跟着步骤一步步做菜——这个场景的体验会远好于在手机上切换应用。
1.3 项目概览
| 维度 | 数据 |
|---|---|
| 开发语言 | ArkTS(声明式 UI) |
| 架构模式 | MVVM(Model-View-Service) |
| 核心代码量 | 约 500 行 |
| 美食分类 | 6 个(随便推荐/中式/西式/素食/低卡/烘焙) |
| 回复模式 | 2 种(推荐模式 + 菜谱模式) |
| 具体菜谱 | 3 道精确到克的详细菜谱 |
| 第三方依赖 | 0 个 |
| 页面数量 | 1 个(单页对话) |
| 安装包大小 | < 5MB |
| 冷启动时间 | < 300ms |
二、产品设计:两种回复模式的智能切换
2.1 推荐模式 vs 菜谱模式
美食助手的产品设计核心在于两种回复模式的智能切换。这是它区别于传统菜谱应用的关键差异。
推荐模式:当用户选择话题标签(如"随便推荐"“中式料理”)或输入类似"不知道吃什么"的内容时,AI 返回 4-5 道菜的推荐列表。每道菜的推荐包含四个要素:
菜名 → 推荐理由 → 简易做法概要 → 所需食材清单
这种结构化的推荐格式,让用户快速浏览多道菜,在比较中做出选择。推荐理由给用户一个"为什么选这道菜"的心理锚点,做法概要让用户判断"这道菜我做得了吗",食材清单让用户检查"我有没有这些食材"。
菜谱模式:当用户输入具体的菜名(如"番茄炒蛋"“红烧肉”“牛排”)时,AI 返回完整详细的菜谱,包含:
食材清单(精确到克)→ 分步做法 → 烹饪技巧提示 → 营养价值说明
菜谱模式的特点是精确、可执行、有深度。食材用量精确到克,不做"适量"“少许"这种模糊描述;每个步骤标注了预计时间,让用户有心理预期;烹饪技巧提示是"菜谱之外的加分项”,是经验丰富的厨师才知道的细节;营养价值说明为用户提供健康参考。
2.2 系统提示词:产品灵魂的代码化
美食助手的系统提示词是产品设计文档的代码化表达。它用不到 200 字定义了产品的核心行为:
你是一个美食生活助手,精通中西方烹饪,了解各地饮食文化。
你的任务是根据用户需求推荐菜品或提供菜谱:
1. 如果用户说"不知道吃什么",根据用户口味偏好、食材库存或季节推荐3-5道菜
2. 如果用户点明具体菜名,提供详细的做法步骤
3. 考虑 dietary restrictions(素食、过敏、低卡等)
回答结构:
- 推荐菜品时:菜名 → 推荐理由 → 简易做法概要 → 所需食材清单
- 提供菜谱时:食材清单(精确到克)→ 分步做法 → 烹饪技巧提示 → 营养价值说明
回复风格:热情、生动,让用户感受到美食的诱惑,但步骤要清晰可执行。
这段提示词的设计有几个值得注意的决策:
第一,角色定义精准。 “美食生活助手"而非"AI 厨师”——这个定位意味着应用是"助手"而非"替代者"。助手帮助你做决策、提供参考,但最终做菜的人还是你。这个定位避免了用户对 AI 产生不切实际的期望。
第二,任务边界清晰。 两条核心任务(推荐 + 菜谱)用 if-else 逻辑明确定义,没有模糊地带。这确保了回复的一致性——无论用户以什么方式表达"不知道吃什么",都能被引导到推荐模式。
第三,考虑了特殊需求。 第三条明确提到了 dietary restrictions(饮食限制),这意味着素食者、过敏者、减脂者也能获得适合自己的推荐。这是一个容易被忽略但实际影响很大的设计点。
第四,格式要求结构化。 两种回复模式都有明确的输出格式定义。这不是为了限制 AI 的创造性,而是为了确保用户无论何时发起请求,都能获得一致的、可预期的回答结构。格式的一致性直接转化为用户的可信度。
第五,风格要求平衡了"热情"和"可执行"。 这是最难把握的平衡——太热情显得浮夸,太干巴显得冷漠。提示词用"热情、生动,但步骤要清晰可执行"来约束,既给了 AI 发挥的空间,又划定了底线。
2.3 六类美食场景的覆盖策略
六个话题标签的设计不是随机的,而是经过分类组合的:
| 话题 | 覆盖场景 | 用户画像 |
|---|---|---|
| 🎲 随便推荐 | 选择困难、无偏好 | 所有用户 |
| 🥢 中式料理 | 日常家常菜 | 中式饮食习惯用户 |
| 🍝 西式料理 | 换口味、周末大餐 | 追求新鲜感的用户 |
| 🥬 素食 | 饮食限制、健康选择 | 素食者、弹性素食者 |
| 🥗 低卡健康 | 减脂、健身、轻食 | 健身人群、减脂用户 |
| 🍰 烘焙甜品 | 甜品、烘焙兴趣 | 烘焙爱好者 |
这六个话题的设计遵循了 MECE 原则(Mutually Exclusive, Collectively Exhaustive,即相互独立、完全穷尽):
- "随便推荐"覆盖了"无明确偏好"的场景
- "中式/西式"按菜系覆盖了地域偏好
- "素食/低卡"按饮食限制覆盖了特殊需求
- "烘焙甜品"覆盖了非正餐场景
六个话题的组合,基本覆盖了"一个人在家做饭"可能遇到的所有决策场景。用户不需要思考"我该选哪个分类",因为分类本身就是对用户需求的引导。
2.4 菜谱内容设计:从"能看"到"能用"
菜谱的质量决定了应用的核心价值。我们为三道经典菜谱(番茄炒蛋、红烧肉、香煎牛排)设计了精确到克的详细做法,每条菜谱包含四个模块:
食材清单:精确到克,附带"约X汤匙""约X小勺"的日常换算,让没有厨房秤的用户也能操作。
分步做法:每步标注预计时间,关键的步骤有 ⚠️ 警告标记(如"炒糖色不要过头"“热水不能用冷水”),这些是新手最容易犯错的地方。
烹饪技巧提示:4-5 条经验性建议,每条都是"菜谱上不会写但老厨师都知道"的干货。比如"番茄选熟透的"“焯水后用温水洗肉”“煎牛排后静置5分钟再切”。
营养价值说明:热量、蛋白质、微量元素等关键数据,帮助用户了解这道菜的营养构成。
以红烧肉为例,这条菜谱揭示了几个关键烹饪原理:
- 焯水后必须用温水洗肉(冷水会导致肉质收缩变硬)
- 炖肉必须加热水(冷水会让蛋白质凝固,炖不烂)
- 小火慢炖 1 小时是底线,急不得
- 收汁时不能离开,最后阶段最容易糊锅
这些技巧不是 AI 从菜谱网站上"复制"来的,而是烹饪经验的总结。在接入真实大模型后,这些内容将由 AI 根据菜名动态生成,而不需要手动编写。
三、架构设计:MVVM 在美食场景中的实践
3.1 整体架构
美食助手延续了我们在 HarmonyOS 项目中积累的 MVVM 简化架构——Model-View-Service 三层结构。这种架构在中小型应用中经过验证,在开发效率和代码可维护性之间取得了良好平衡。
┌─────────────────────────────────────────┐
│ View (视图层) │
│ Index.ets — 声明式 UI + 状态管理 │
│ 304 行,包含 5 个 @Builder 方法 │
├─────────────────────────────────────────┤
│ Service (服务层) │
│ FoodAssistantService.ets — 业务逻辑 │
│ - 系统提示词管理(200 字) │
│ - 6 类话题回复 + 3 道详细菜谱 │
│ - Map 关键词匹配引擎 │
│ - AI 调用封装(降级策略) │
├─────────────────────────────────────────┤
│ Model (模型层) │
│ FoodAssistantModel.ets — 数据结构 │
│ - ChatMessage / TopicOption │
│ - MessageRole / FoodTopic 枚举 │
│ - RecipeRecommendation 菜谱推荐模型 │
├─────────────────────────────────────────┤
│ Constants (常量层) │
│ Constants.ets — 全局配置 │
│ - 暖橙美食配色系统 │
│ - 6 个话题常量 │
│ - 欢迎文案 / 加载文案 │
└─────────────────────────────────────────┘
这种分层的核心优势在于关注点分离:
- View 层只关心 UI 渲染和用户交互,不包含任何业务逻辑
- Service 层封装了所有业务规则(提示词、回复匹配、AI 调用),完全不涉及 UI
- Model 层定义了各层之间的数据"契约"
- Constants 层集中管理所有可配置项,避免散落在代码中的魔法值
当未来需要接入真实大模型时,只需修改 Service 层的 callAI() 方法。当需要调整配色时,只需修改 Constants 中的颜色值。每一层的变化不会影响其他层。
3.2 为什么不用更复杂的架构
在项目启动阶段,我们也考虑过几种常见方案。但最终选择 MVVM 简化架构的原因很简单:架构的复杂度应该与项目的复杂度匹配。
对于大约 500 行代码的单页面应用,引入 Redux 状态管理、Clean Architecture 多层抽象、或者 RxJS 响应式编程,都属于"过度设计"。这些架构在大型项目中能显著提升可维护性,但在小型项目中反而会增加理解和修改的成本。
我们遵循一个原则:先让代码跑起来,再在需要的地方引入抽象,而不是一开始就构建一个"完美"的架构。随着项目的发展,如果后续需要多页面管理、登录鉴权、数据持久化等功能,可以逐步引入更复杂的架构模式。这种"渐进式"的架构演进,比"一步到位"的重型架构更适合中小型项目。
3.3 目录结构
entry/src/main/ets/
├── common/
│ └── Constants.ets # 全局常量:配色、文案、话题定义
├── models/
│ └── FoodAssistantModel.ets # 数据模型:消息、话题、菜谱推荐
├── services/
│ └── FoodAssistantService.ets # 业务服务:系统提示词、回复匹配、AI 调用
└── pages/
└── Index.ets # 主页面:对话界面 + 全部交互逻辑
目录结构刻意保持扁平,只有四个文件。每个文件的职责单一且明确,打开任何一个文件都能立刻知道它的作用。对于这个规模的项目,深层嵌套的目录反而会增加导航成本。
四、Model 层:数据模型的设计
4.1 为什么用 class 而不是 interface
在 ArkTS 中,定义数据结构有两种方式:interface 和 class。我们选择了 class,原因有三:
第一,构造函数带来的便利。 消息对象需要在创建时自动记录时间戳,用 class 的构造函数可以一行代码完成:
export class ChatMessage {
role: MessageRole
content: string
timestamp: number
constructor(role: MessageRole, content: string) {
this.role = role
this.content = content
this.timestamp = Date.now()
}
}
调用方不需要关心时间戳的生成逻辑,new ChatMessage(...) 即可。如果用 interface,每次创建都要手动写 { role: ..., content: ..., timestamp: Date.now() },重复且容易遗漏。
第二,类型安全。 class 创建的实例支持 instanceof 运行时检查,虽然当前项目中没有用到,但保留这个能力成本很低,未来可能有用。
第三,风格一致性。 ArkTS 是面向对象的语言,Service 是 class,UI 组件是被 @Component 装饰的 struct,数据模型用 class 保持整体风格统一。
4.2 枚举的三大价值
项目中有两个枚举:MessageRole 和 FoodTopic。用枚举而不是字符串字面量的好处在之前的项目中已经验证过,这里只做简要总结:
防拼写错误。 MessageRole.USR 编译不通过,'usr' 编译通过但运行时才发现问题。编译期拦截 > 运行时排查。
IDE 智能提示。 输入 MessageRole. 之后,IDE 自动列出所有可用值,不需要记忆。
重构友好。 改枚举定义一处,全局自动生效。散落的字符串改起来需要全局搜索替换,容易漏改误改。
4.3 RecipeRecommendation:为菜谱推荐定制的数据模型
美食助手特有的一个数据模型是 RecipeRecommendation:
export class RecipeRecommendation {
dishName: string
reason: string
summary: string
ingredients: string
constructor(dishName: string, reason: string, summary: string, ingredients: string) {
this.dishName = dishName
this.reason = reason
this.summary = summary
this.ingredients = ingredients
}
}
这个模型对应了"推荐模式"回复的四个要素:菜名、推荐理由、做法概要、食材清单。虽然当前版本中,推荐内容是以纯文本形式嵌入在回复字符串中的,但定义了 RecipeRecommendation 类为未来的结构化数据处理预留了空间。
假设未来要接入真实大模型,AI 返回的推荐内容很可能是一个 JSON 数组,每条推荐包含菜品名称、推荐理由、做法步骤、食材用量等结构化字段。到那时,RecipeRecommendation 这个模型就能直接承载这些数据,Service 层将其转换为富文本卡片,View 层渲染出更美观的菜品卡片——而不是现在的一大段纯文本。
这就是面向未来设计的含义:今天多加一个类,未来少改很多代码。
五、Service 层:核心匹配引擎与 AI 调用封装
5.1 Map 匹配引擎的设计
美食助手的 Service 层使用 Map<string, string> 实现关键词匹配。这个设计看似简单,但有几个关键决策:
第一,匹配顺序决定优先级。 Map 的遍历顺序是插入顺序,因此可以通过调整 initMockResponses() 中 set 的调用顺序来控制匹配优先级。例如,具体菜名(“番茄炒蛋”“红烧肉”“牛排”)放在话题分类(“中式料理"等)之后插入,但话题分类又放在"默认"之前。这个顺序确保了:如果用户输入"番茄炒蛋”,优先匹配到详细菜谱,而不是"中式料理"的推荐列表。
第二,first-match 策略。 找到第一个匹配就返回,不继续搜索。这个策略简单、高效、可预测。如果每次都遍历所有 key 找"最佳匹配",不仅增加复杂度,而且"最佳"的定义本身就模糊——是"最长匹配"还是"最多匹配"?first-match 避免了这种模糊性。
第三,默认兜底。 如果所有关键词都不匹配,返回一个引导性的默认回复。这个回复不是"我听不懂"这种挫败感反馈,而是列出了用户可以尝试的几种方式——点击话题标签、说出具体菜名、描述口味偏好。这种设计把"我不知道"变成了"我帮你试试这些方法",保持了对话的延续性。
5.2 关键词匹配的局限性
关键词匹配有明显的局限性——它不理解语义,只做字面匹配。比如用户输入"我想吃番茄炒鸡蛋",不会匹配到"番茄炒蛋"(因为字符串不完全包含)。用户输入"做份红烧排骨",不会匹配到"红烧肉"。
这些局限性在当前版本中是可以接受的,因为:
- 话题标签提供了确定性的入口——用户点击标签不会受到匹配问题的影响
- 默认兜底回复引导用户说出更明确的需求
- 未来接入真实大模型后,关键词匹配将作为降级方案保留,主流走 AI 语义理解
5.3 降级策略:从 Mock 到 AI 的平滑过渡
generateReply 方法使用了 try-catch 降级策略:
async generateReply(userMessage: string): Promise<ChatMessage> {
try {
const replyContent = await this.callAI(userMessage)
return new ChatMessage(MessageRole.ASSISTANT, replyContent)
} catch (e) {
return this.generateMockReply(userMessage)
}
}
这个设计有两个作用:
开发阶段:callAI 方法目前直接返回模拟回复,让整个应用不依赖外部服务就能跑起来。UI 开发、交互调试、功能验证都不受 AI 接口进度的影响。
生产阶段:当接入真实大模型后,如果 AI 服务出现网络错误、限流、超时等异常,会自动降级到本地模拟回复。用户不会看到错误页面,体验是连贯的。
这种"前后端分离 + Mock 驱动"的开发模式在 Web 开发中已经非常成熟,但在移动端开发中还没有被普遍采用。我们把这种实践引入到 HarmonyOS 开发中,显著提升了开发效率。
5.4 系统提示词的管理
系统提示词存储在 Service 类的私有属性中,而不是 Constants 文件中。这个决策有一个考量:提示词是业务逻辑的一部分,不是界面常量。提示词的内容、结构、措辞直接影响 AI 的回复质量,应该和业务逻辑放在一起,方便开发者在修改 Service 代码时同步调整提示词。
如果未来需要支持多语言或 A/B 测试不同的提示词版本,可以将提示词抽象为独立的配置,但当前项目规模下,放在 Service 类中是最简洁的选择。
六、View 层:声明式 UI 的实践
6.1 页面结构概览
整个应用只有一个页面 [Index.ets](file:///c:/Users/l/DevEcoStudioProjects/MyApplication/entry/src/main/ets/pages/Index.ets),约 300 行代码,分为四个区域:
┌───────────────────────────────────────┐
│ 🍳 美食生活助手 [清除对话] │ ← Header
│ 精通中西烹饪 · 让每顿饭都成为享受 │
├───────────────────────────────────────┤
│ 🎲随便推荐 🥢中式 🍝西式 🥬素食 ... │ ← TopicBar(横向滚动)
├───────────────────────────────────────┤
│ │
│ 🍳 美食助手 │ ← 消息列表
│ 你好!我是你的美食生活助手... │
│ │
│ ┌──────────────┐ │
│ │ 番茄炒蛋 │ │
│ └──────────────┘ │
│ │
│ 🍳 美食助手 │
│ 🍳 番茄炒蛋 · 详细菜谱 │
│ 📋 食材清单(精确到克):... │
│ │
│ [正在为你准备美味方案...] │ ← 加载状态
│ │
├───────────────────────────────────────┤
│ [不知道吃什么?...] [发送] │ ← InputArea
└───────────────────────────────────────┘
四个区域对应四个 @Builder 方法,build() 方法作为总目录,清晰展示页面结构。
6.2 @State:响应式状态管理
页面使用四个 @State 装饰的变量管理所有动态数据:
| 状态变量 | 作用 | 影响的 UI |
|---|---|---|
messages: ChatMessage[] |
对话消息列表 | 消息气泡数量与内容(核心状态) |
inputText: string |
输入框文本 | 输入框内容、发送按钮颜色和启用状态 |
isLoading: boolean |
加载状态 | 加载气泡显示/隐藏、发送按钮禁用 |
selectedTopic: string |
选中话题 | 话题标签的选中/未选中样式 |
数据修改 → UI 自动更新,这是 ArkTS 声明式 UI 的核心范式。开发者只需关注数据逻辑,不需要手动操作 DOM 或调用刷新方法。
6.3 暖橙配色:美食主题的视觉表达
美食助手的配色方案以暖橙色(#EA580C)为主色调,配合浅米色背景(#FFF7ED)和橙色边框(#FED7AA),整体呈现温暖、诱人的视觉感受。
配色选择遵循了美食类应用的视觉惯例:
-
暖色系激发食欲。 橙色和红色是餐饮行业最常用的颜色,研究表明暖色系能刺激食欲。这不同于我们之前开发的生活管家(蓝色系,传达信任感)和健康顾问(绿色系,传达安全感),每个应用都有自己独特的色彩语言。
-
60-30-10 法则。 60% 浅米色背景(#FFF7ED),30% 白色卡片和深色文字,10% 橙色强调(按钮、选中态、标题)。这种比例让界面看起来温暖但不刺眼,层次分明但不杂乱。
-
高对比度保证可读性。 深色文字(#1E293B)在浅色背景上有足够的对比度,确保菜谱长文本的可读性。辅助文字(#64748B)降低对比度,用于不重要的信息。
6.4 话题标签的交互设计
话题栏使用横向滚动的 Scroll + Row 组合,包含 6 个话题标签。选中态从浅米色变为橙色,文字从灰色变为白色,视觉反馈清晰明确。
话题标签的交互流程是:点击标签 → 自动填入话题关键词 → 自动发送 → 匹配对应回复。用户不需要手动输入文字,一键即可获得推荐。这个设计降低了使用门槛,也在无声地告诉用户:“我擅长这些领域,你可以这样问我。”
6.5 消息气泡的细节打磨
消息气泡是对话应用的核心 UI 元素,看似简单,实则有很多细节:
左右分布。 用户消息在右侧(橙色),AI 回复在左侧(白色),符合行业通用设计模式。
最大宽度 85%。 避免气泡撑满屏幕,两边留出呼吸空间。
圆角 12vp + 阴影。 圆角让气泡柔和,轻微阴影(radius: 4, offsetY: 2, color: rgba(0,0,0,0.06))增加层次感。
行高 22 + 内边距 12。 在视觉舒适度和信息密度之间找到平衡点,确保菜谱长文本的阅读体验。
6.6 输入区的交互细节
按钮状态联动。 发送按钮的颜色和启用状态与输入框内容绑定——空输入时灰色不可点击,有内容时橙色可点击。额外绑定 !isLoading 条件,AI 回复中禁止发送新消息。
字数限制。 maxLength(500) 限制输入长度。500 字足以描述清楚一个菜名或口味偏好。
圆角 20vp。 与整体设计语言保持一致,柔和现代。
6.7 自动滚动到最新消息
发送消息后和 AI 回复完成后,各触发一次 scrollEdge(Edge.Bottom),确保用户始终看到最新内容。使用 setTimeout 延迟 200ms,给 UI 渲染留出时间,避免在气泡还没渲染出来时就滚动导致位置不对。
七、核心技术亮点
7.1 双模式回复的智能切换
美食助手最核心的技术亮点是"推荐模式"和"菜谱模式"的智能切换。这不是两个独立的页面或功能,而是同一个对话流中,根据用户输入动态切换的两种回复策略。
实现原理很简单——关键词匹配。当用户输入包含"随便推荐"“中式料理"等话题关键词时,返回推荐列表;当用户输入包含"番茄炒蛋”"红烧肉"等具体菜名时,返回详细菜谱;当用户输入不匹配任何关键词时,返回引导性默认回复。
这个看似简单的匹配逻辑,支撑了一个完整的用户决策流程:
用户感到选择困难
→ 点击"随便推荐"
→ AI 返回 5 道菜推荐
→ 用户对其中一道感兴趣
→ 输入"番茄炒蛋"
→ AI 返回精确到克的详细菜谱
→ 用户跟着步骤做菜
每一步都有明确的引导,用户不会感到迷茫。这种"由粗到细、由推荐到执行"的渐进式对话流,是产品设计中最重要的决策。
7.2 零第三方依赖的纯原生实现
整个应用没有引入任何 npm 包、第三方组件库或网络请求框架。所有功能都用 HarmonyOS SDK 自带 API 实现。
收益:
- 安装包不足 5MB,用户秒下载
- 冷启动低于 300ms,打开即用
- 零兼容性风险,不依赖第三方版本更新
- 零维护成本,没有
package.json要管理
代价:
- 某些高级功能需要自己实现(如 Markdown 解析、网络请求封装)
- 但在这个项目规模下,代价几乎为零
7.3 设计系统与常量管理
所有颜色、文案、话题定义集中在 [Constants.ets](file:///c:/Users/l/DevEcoStudioProjects/MyApplication/entry/src/main/ets/common/Constants.ets) 中统一管理。改主色调只改一个值,全局自动生效。这个实践在多个项目中持续验证了其价值——好的工程实践是跨项目复用的。
7.4 完整的降级策略
generateReply 的 try-catch 降级机制,不仅在开发阶段实现了前后端并行开发,在生产阶段也保障了用户体验的连续性。AI 服务不可用时,自动降级到本地模拟回复,用户不会感知到故障。
7.5 精确到克的菜谱设计
三条详细菜谱(番茄炒蛋、红烧肉、香煎牛排)的食材用量精确到克,附带了日常换算(“约 1 汤匙”“约 半小勺”)。这种"精确 + 直观"的双重表述,兼顾了严谨性和可操作性。
八、开发经验与踩坑总结
8.1 坑一:Map 的遍历顺序依赖
问题现象: 在某些情况下,Map.keys() 返回的迭代器顺序与预期不一致,导致匹配结果不符合预期。
原因分析: ArkTS 的 Map 在规范上保证遍历顺序为插入顺序,但 forEach 和 keys() 迭代器的行为略有不同。我们使用了 keys() 迭代器 + next() 手动遍历,确保了顺序的确定性。
解决方案: 显式使用 keys() 迭代器遍历,而不是 forEach,确保 first-match 策略的确定性。
经验教训: 在依赖顺序的场景中,使用显式的迭代器比使用 forEach 更可控。forEach 的回调函数执行顺序虽然也遵循插入顺序,但显式迭代器的语义更清晰。
8.2 坑二:Markdown 在 Text 组件中的渲染差异
问题现象: 菜谱回复中使用了 **粗体**、--- 分隔线、> 引用等 Markdown 语法,在不同版本的 HarmonyOS 上渲染效果不一致。
原因分析: ArkTS 的 Text 组件对 Markdown 的支持程度因系统版本而异。部分旧版本不支持 --- 分隔线或 > 引用块的渲染。
解决方案: 使用 emoji 和空行代替 Markdown 分隔线,用视觉符号(如 📋、👨🍳、💡、📊)代替 Markdown 的标题层级。这样即使 Markdown 渲染不完整,纯文本的视觉效果也不会差。
经验教训: 在跨版本的移动端开发中,不要过度依赖富文本渲染能力。用 emoji 和符号构建的"伪富文本"在兼容性上远优于 Markdown。
8.3 坑三:emoji 的跨平台一致性
问题现象: 不同设备上,同一个 emoji 的显示效果不同(颜色、大小、甚至形态)。
原因分析: emoji 的渲染依赖于系统的字体和渲染引擎,不同厂商、不同版本的 HarmonyOS 可能有不同的 emoji 实现。
解决方案: 选择最通用的 emoji 符号(如 🍳 🥢 🍝 🥬 🥗 🍰),避免使用较新的、可能不被支持的 emoji。在关键位置(如话题标签),emoji 的作用是辅助识别,即使显示效果有差异,文字标签仍然能保证功能的可用性。
经验教训: emoji 是"增强体验"而非"核心功能"。不要依赖 emoji 传达关键信息,始终保留文字标签作为备选。
8.4 坑四:ForEach 的 key 稳定性
问题现象: 在消息列表中插入新消息时,旧的 key 可能因为索引变化而失效,导致列表重新渲染。
解决方案: 使用 ${index}_${msg.timestamp} 作为 key,既保证唯一性(时间戳毫秒级),又保持稳定性(同一条消息的时间戳不变)。
经验教训: ForEach 的 key 生成函数是性能优化的关键。好的 key 能显著减少不必要的重新渲染,尤其在消息量大的场景下。
九、完整对话示例
场景一:选择困难,寻求推荐
用户点击「🎲 随便推荐」
系统自动发送:“随便推荐”
AI 回复(推荐模式):
🍳 今天为你推荐这几道美味:
---
1. 番茄炒蛋 🥚🍅
> 推荐理由:国民家常菜第一名!酸甜开胃、营养均衡,10分钟就能上桌。
> 做法概要:鸡蛋打散炒熟盛出 → 番茄切块炒出汁 → 鸡蛋回锅翻炒 → 加盐糖调味出锅
> 食材清单:鸡蛋3个、番茄2个、葱花少许、盐、糖、食用油
2. 蒜蓉西兰花 🥦
> 推荐理由:清爽脆嫩,蒜香浓郁,是一道完美的低卡配菜。
> 做法概要:西兰花焯水一分钟 → 蒜末爆香 → 下西兰花快炒 → 加盐蚝油调味
> 食材清单:西兰花1颗、大蒜5瓣、蚝油、盐、食用油
3. 可乐鸡翅 🍗
> 推荐理由:甜咸适口、肉质嫩滑,新手也能做出惊艳效果。
> 做法概要:鸡翅划刀腌制 → 煎至两面金黄 → 倒入可乐和酱油 → 大火收汁
> 食材清单:鸡翅中8个、可乐1罐、生抽、老抽、姜片、料酒
4. 酸辣土豆丝 🥔
> 推荐理由:酸辣脆爽,下饭神器!成本不到5块钱。
> 做法概要:土豆切丝泡水去淀粉 → 干辣椒花椒爆香 → 土豆丝大火快炒 → 加醋盐调味
> 食材清单:土豆2个、干辣椒、花椒、醋、盐、蒜
5. 紫菜蛋花汤 🍜
> 推荐理由:简单快手,清淡鲜美,搭配任何菜都合适。
> 做法概要:水烧开 → 放入紫菜 → 淋入蛋液 → 加盐香油葱花
> 食材清单:紫菜、鸡蛋2个、葱花、盐、香油
🍽️ 以上五道菜可以搭配成一顿丰盛的家常晚餐!想了解哪道菜的详细做法,直接告诉我菜名就好~
场景二:获取具体菜谱
用户输入:“番茄炒蛋”
AI 回复(菜谱模式):
🍳 番茄炒蛋 · 详细菜谱
---
📋 食材清单(精确到克):
- 鸡蛋:3个(约150g)
- 番茄:2个(约300g,选熟透的)
- 葱花:5g
- 食用油:15ml(约1汤匙)
- 白糖:5g(约1小勺,提鲜用)
- 盐:3g(约半小勺)
- 水淀粉:淀粉3g + 水10ml(可选,让汤汁更浓稠)
---
👨🍳 分步做法:
Step 1:备料(3分钟)
番茄洗净,顶部划十字,用开水烫30秒后去皮,切小块。鸡蛋打入碗中,加一小撮盐,充分搅打至起泡。
Step 2:炒鸡蛋(1分钟)
大火热锅,倒油,油温七成热时倒入蛋液。看到蛋液边缘开始凝固,用筷子快速划散,炒至八成熟(还有点湿润)立刻盛出。
Step 3:炒番茄(2分钟)
锅中余油,倒入番茄块,中火翻炒,用铲子按压番茄帮助出汁。炒到番茄变软、汤汁浓稠。
Step 4:合炒调味(1分钟)
鸡蛋回锅,加白糖和盐,大火快速翻炒均匀。如果喜欢汤汁浓稠,淋入水淀粉勾芡。
Step 5:出锅
撒上葱花,装盘出锅!
---
💡 烹饪技巧提示:
- 番茄选熟透的,汁水多、酸味够,炒出来才浓郁
- 鸡蛋不要炒太老,八成熟盛出,回锅后刚好嫩滑
- 加一点白糖可以中和番茄的酸味,让口感更柔和
- 番茄去皮口感更好,如果懒可以不去,但会影响口感
---
📊 营养价值说明:
- 热量:约280大卡(两人份)
- 蛋白质:约18g(鸡蛋提供优质蛋白)
- 番茄红素:番茄富含番茄红素,加热后更易吸收
- 维生素C:番茄提供丰富的维生素C
🍽️ 配上一碗热米饭,就是最温暖的家常味道!
场景三:特殊饮食需求
用户点击「🥗 低卡健康」
AI 回复(推荐模式,每道菜标注热量):
🥗 低卡健康餐推荐(每道菜均低于400大卡):
---
1. 鸡胸肉藜麦沙拉碗 🥗(约350大卡)
> 推荐理由:高蛋白+优质碳水+膳食纤维,健身减脂的完美一餐。
> 做法概要:藜麦煮熟 → 鸡胸肉煎熟切片 → 蔬菜混合 → 淋油醋汁
> 食材清单:鸡胸肉150g、藜麦50g、混合生菜、小番茄、黄瓜、柠檬汁、橄榄油、黑胡椒
2. 清蒸龙利鱼配西兰花 🐟(约280大卡)
> 推荐理由:龙利鱼低脂高蛋白,清蒸最能保留营养,鲜嫩无比。
> 做法概要:龙利鱼抹盐姜丝 → 上锅蒸8分钟 → 西兰花焯水 → 淋蒸鱼豉油和热油
> 食材清单:龙利鱼柳200g、西兰花100g、姜丝、蒸鱼豉油、橄榄油
3. 番茄菌菇豆腐汤 🍅(约180大卡)
> 推荐理由:低卡饱腹,番茄的酸和菌菇的鲜完美融合,晚餐喝一碗刚好。
> 做法概要:番茄炒出汁 → 加水煮开 → 加入菌菇和豆腐 → 煮5分钟 → 加盐和葱花
> 食材清单:番茄2个、嫩豆腐半盒、金针菇100g、香菇3朵、盐、葱花
4. 虾仁滑蛋 🍤🥚(约300大卡)
> 推荐理由:虾仁高蛋白低脂肪,鸡蛋嫩滑,5分钟快手菜。
> 做法概要:虾仁料酒腌制 → 鸡蛋打散加盐 → 虾仁炒至变色 → 倒入蛋液推炒至凝固
> 食材清单:虾仁150g、鸡蛋3个、料酒、盐、葱花、橄榄油
💪 健康饮食不等于吃草!想知道哪道菜的详细做法,尽管问我~
十、文件清单与代码统计
10.1 文件清单
| 文件路径 | 行数 | 作用 |
|---|---|---|
common/Constants.ets |
36 | 全局常量:暖橙配色、6 话题文案、欢迎语 |
models/FoodAssistantModel.ets |
51 | 数据模型:ChatMessage、TopicOption、RecipeRecommendation、枚举 |
services/FoodAssistantService.ets |
331 | 核心服务:系统提示词、6 类话题回复、3 道详细菜谱、Map 匹配引擎、降级策略 |
pages/Index.ets |
304 | 主页面:完整对话界面 + 交互逻辑 |
| 合计 | 约 722 行 |
10.2 代码特点总结
- 零第三方依赖:全部使用 HarmonyOS 原生 API,包体小、启动快
- 双模式回复引擎:推荐模式(4-5 道菜概览)和菜谱模式(精确到克的详细菜谱)智能切换
- Map 关键词匹配:first-match 策略,顺序可控,确定性高
- 暖橙美食配色:60-30-10 法则,专业但不冰冷的视觉风格
- 完整降级策略:AI 不可用时无缝降级到本地模拟回复
- 精确到克的菜谱:3 条经典菜谱,食材精确克数,步骤带时间预估
- 6 类话题覆盖:MECE 原则设计,覆盖日常烹饪全场景
- 类型安全:class + enum 替代字符串和接口,编译期拦截错误
10.3 与之前项目的对比
| 维度 | AI 生活管家 | 健康生活顾问 | 美食生活助手 |
|---|---|---|---|
| 主色调 | 蓝色(信任) | 绿色(安全) | 橙色(食欲) |
| 话题数 | 6 个 | 5 个 | 6 个 |
| 回复模式 | 单一模式 | 多模式 | 双模式(推荐+菜谱) |
| 内容深度 | 中等 | 深度 | 深度(精确克数) |
| 核心资产 | 五条原则 | 症状分析框架 | 精确菜谱数据 |
十一、如何运行与扩展
11.1 运行环境
- DevEco Studio 5.0 及以上
- HarmonyOS SDK API 12 及以上
- HarmonyOS 4.0 及以上设备或模拟器
11.2 运行步骤
- 用 DevEco Studio 打开项目根目录
- 等待同步完成
- 连接设备或启动模拟器
- 点击运行按钮(Shift+F10)
11.3 扩展方向
方向一:接入真实大模型。 修改 FoodAssistantService.callAI() 方法,接入大模型 API。推荐从华为盘古大模型或通义千问开始尝试。接入后,用户输入任意菜名,AI 都能生成精确到克的详细菜谱,不再局限于预设的 3 道菜。
方向二:食材识别。 利用 HarmonyOS 的图片识别能力,用户拍摄冰箱里的食材,AI 自动识别并推荐可以做的菜。这是"食材库存 → 推荐菜品"的完整闭环。
方向三:语音交互。 接入 HarmonyOS 语音识别,用户在厨房里可以一边做菜一边语音询问下一步操作,不用湿手去操作手机。
方向四:用户偏好学习。 记录用户的历史查询和偏好(喜欢的口味、常做的菜、饮食限制),逐步优化推荐结果。同一个用户用久了,AI 越来越懂他的口味。
方向五:多端协同。 利用 HarmonyOS 分布式能力,手机和平板/智慧屏协同。在手机上选好菜谱,智慧屏上显示步骤,手机作为遥控器控制翻页。
十二、结语
美食生活助手是一个专注于"吃"的 HarmonyOS AI 应用。它用约 700 行 ArkTS 代码,实现了从"不知道吃什么"到"做出一道菜"的完整烹饪辅助链路。核心的双模式回复引擎(推荐模式 + 菜谱模式)让用户在一个对话流中完成从决策到执行的过渡,无需切换应用或页面。
从技术角度看,这个项目验证了几个关键实践:
- MVVM 简化架构在美食类 AI 应用中的适用性:Model-View-Service 三层结构清晰、扩展性好
- Map 匹配引擎的可行性:在无 AI 接入阶段,用关键词匹配模拟智能对话,前后端并行开发
- 降级策略的工程价值:从 Mock 到真实 AI 的平滑过渡,开发者体验和用户体验都得到保障
- 精确数据的价值:菜谱中精确到克的食材用量,是"能用"和"好用"的分水岭
从产品角度看,美食助手的价值在于解决了"做什么"和"怎么做"两个核心问题。它不是传统菜谱平台的替代品,而是一个专注于"决策辅助"的轻量工具。它帮用户迈出"下厨房"的第一步——当你站在冰箱前犹豫不决的时候,打开它,点一下,答案就有了。
对于 HarmonyOS 开发者来说,这个项目是一个很好的学习参考——它涵盖了数据建模、状态管理、UI 构建、用户交互、AI 集成(预留)等应用开发的核心环节,代码量适中(约 700 行),结构清晰,零编译错误,可直接作为学习参考和二次开发的基础。
希望这个项目不仅能帮你学会 HarmonyOS 开发,也能帮你在"今天吃什么"这个问题上,少一点纠结,多一点享受。
更多推荐



所有评论(0)