HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(三十):【社区分享】本地社区功能——让菜谱从“独享”走向“共享”
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(三十):【社区分享】本地社区功能——让菜谱从“独享”走向“共享”
摘要:一个人做饭是生活,一群人分享是社区。前面 29 篇中,《灵犀厨房》的菜谱从推荐、收藏到烹饪,都围绕着“我一个人”。但烹饪的乐趣有一半在于分享——把自己拿手的番茄牛腩煲分享出去,看看别人做了什么。本篇为《灵犀厨房》新增“社区广场”——用户可在菜谱详情页一键分享菜谱到社区,其他用户可浏览所有分享,无需后端服务器,纯本地数据库实现。
一、引言:从“一个人”到“一群人”
打开《灵犀厨房》第 29 篇的版本,浏览菜谱、收藏、烹饪——一切围绕“我”。但你有没有想过:隔壁老王做的宫保鸡丁可能比菜谱库里的还正宗?同事小张的低脂健身餐才是减肥必备?
目前的设计无法满足一个基本需求:用户生成内容(UGC)。所有菜谱都来自我们预设的 10 道模拟数据,用户只能消费,不能生产。这既限制了内容生态的扩展,也缺少了社区互动带来的活跃度。
| 功能现状 | 用户需求 | 差距 |
|---|---|---|
| 菜谱来自预设 MockData | 用户想分享自己的拿手菜 | 缺少 UGC 入口 |
| 菜谱详情只读 | 用户想浏览他人的分享 | 缺少社区展示页 |
| 数据在本地 | 分享需要持久化存储 | 缺少存储表 |
🎯本篇目标:用
shared_recipes持久化表(已在第 28 篇预建)和约 130 行新代码,让用户可以分享菜谱到社区广场、浏览所有分享的菜谱。不需要后端服务器,纯本地数据库实现,为后续的云端社区打下基础。
二、功能设计:两个页面,一套数据
菜谱详情页 社区广场 ┌──────────────────┐ ┌──────────────────┐ │ 🍳 番茄牛腩煲 │ │ 👤 灵犀大厨 │ │ 📋 食材清单 │ │ 🍳 番茄牛腩煲 │ │ 第1步 焯水 │ │ 食材:牛腩、番茄... │ │ 第2步 炒香 │ 点击分享 │ 步骤:焯水; 炒香... │ │ ... │────────→│ 5/28 14:30 │ │ │ │ │ │ [🔄分享] [❤收藏] │ │ 👤 小明 │ └──────────────────┘ │ 🍳 宫保鸡丁 │ │ ... │ └──────────────────┘交互模型:
- 用户在菜谱详情页点击分享按钮 → 菜谱信息写入
shared_recipes表 - 用户在“我的”Tab 进入社区广场 → 从数据库按时间倒序加载所有分享
- 点击社区广场中的某条分享 → 进入该菜谱的详情页(复用现有 RecipeDetailPage)
三、数据层:第 28 篇已预建的表
在第 28 篇中,我们已经预建了shared_recipes表:
CREATETABLEIFNOTEXISTSshared_recipes(idINTEGERPRIMARYKEYAUTOINCREMENT,user_nameTEXTNOTNULL,recipe_nameTEXTNOTNULL,ingredientsTEXT,stepsTEXT,shared_atINTEGERDEFAULT(strftime('%s','now')));本篇新增的用途:在第 28 篇中,这张表只是“预留字段”,没有写入逻辑。本篇将实现完整的 INSERT(分享)和 SELECT(广场加载)操作。
| 字段 | 类型 | 存储格式 | 用途 |
|---|---|---|---|
user_name | TEXT | 固定值“灵犀大厨”(后续对接登录系统) | 分享者标识 |
ingredients | TEXT | 、分隔(如“牛腩、番茄、洋葱”) | 食材展示 |
steps | TEXT | ;分隔(如“焯水:…; 炒香:…”) | 步骤展示 |
shared_at | INTEGER | Unix 时间戳 | 按时间排序 |
设计考量:为什么
ingredients和steps用分隔符字符串而非 JSON?因为这两个字段在本篇中只用于展示,不需要结构化查询。分隔符字符串可以直接展示(牛腩、番茄、洋葱),不需要JSON.parse再join。如果后续需要查询“包含牛腩的所有分享”,再迁移到 JSON 不迟。
四、分享按钮:从详情页到数据库的“一键传送”
4.1 入口位置
在RecipeDetailPage底部操作栏中,收藏按钮之前新增橙色分享按钮:
// ★ 分享到社区Button({type:ButtonType.Circle}){SymbolGlyph($r('sys.symbol.square_and_arrow_up')).fontSize(16).fontColor([Color.White])}.width(38).height(38).backgroundColor('#FF8C5A').onClick(()=>this.shareToCommunity())4.2 shareToCommunity 方法
privateasyncshareToCommunity():Promise<void>{try{awaitstoreHelper.insert('shared_recipes',{user_name:'灵犀大厨',recipe_name:this.recipe.name,ingredients:this.recipe.ingredients.join('、'),steps:this.recipe.steps.join('; '),shared_at:Date.now()});ToastUtil.showToast(this.getUIContext(),'✅ 已分享到社区');}catch(err){ToastUtil.showToast(this.getUIContext(),'分享失败,请重试');}}图一解读:分享流程极简——用户点击按钮,数据直接写入 SQLite,返回成功提示。没有网络请求、没有队列、没有审核。这不是阉割版,是 MVP(最小可行产品)的哲学:先验证用户是否愿意分享,再考虑内容审核和云端同步。
4.3 设计考量:为什么固定用户名“灵犀大厨”?
当前版本还没有完整的用户登录与身份管理功能,用户名使用固定值“灵犀大厨”占位。当后续完成登录功能对接后,只需将此处替换为authViewModel.username,即可实现真实用户名分享。
五、社区广场页面:SQLite 的“流式读取”
5.1 CommunityPage.ets
新建页面entry/src/main/ets/pages/CommunityPage.ets:
@Entry@ComponentV2struct CommunityPage{@Localrecipes:SharedRecipe[]=[];@LocalisLoading:boolean=true;asyncaboutToAppear():Promise<void>{awaitthis.loadSharedRecipes();}privateasyncloadSharedRecipes():Promise<void>{try{constrs=awaitstoreHelper.querySql('SELECT * FROM shared_recipes ORDER BY shared_at DESC LIMIT 50');this.recipes=[];while(rs.goToNextRow()){this.recipes.push({id:rs.getLong(rs.getColumnIndex('id')),user_name:rs.getString(rs.getColumnIndex('user_name')),recipe_name:rs.getString(rs.getColumnIndex('recipe_name')),ingredients:rs.getString(rs.getColumnIndex('ingredients')),steps:rs.getString(rs.getColumnIndex('steps')),shared_at:rs.getLong(rs.getColumnIndex('shared_at'))});}rs.close();this.isLoading=false;}catch(err){console.error('[Community] 加载失败:',JSON.stringify(err));this.isLoading=false;}}}5.2 UI 布局
每个 ListItem 展示:
┌──────────────────────────────────┐ │ 👤 灵犀大厨 5/28 14:30 │ │ 🍳 番茄牛腩煲 │ │ 食材:牛腩、番茄、洋葱、胡萝卜... │ │ 步骤:焯水:牛腩切块...; 炒香... │ └──────────────────────────────────┘- 分享者名 + 时间戳在顶部
- 菜谱名用主题色高亮
- 食材和步骤各占两行,超出省略
- 卡片背景为
$r('app.color.bg_card'),自动适配深色模式
5.3 路由注册
需在main_pages.json中添加路由:
{"src":["pages/CommunityPage"]}5.4 入口位置
在“我的”Tab 中新增社区广场入口:
// MainContainer.ets → ProfileTabContentRow(){Row({space:10}){Text('🌐').fontSize(18)Text('社区广场').fontSize(15).fontColor('#333')}Blank()SymbolGlyph($r('sys.symbol.chevron_right')).fontSize(14).fontColor(['#CCC'])}.onClick(()=>{this.getUIContext().getRouter().pushUrl({url:'pages/CommunityPage'})})六、代码交付清单
| 文件 | 新增/修改 | 行数 | 说明 |
|---|---|---|---|
RecipeDetailPage.ets | 修改 | +15 | 新增分享按钮 +shareToCommunity()方法 |
CommunityPage.ets | 新文件 | +105 | 社区广场页面 |
MainContainer.ets | 修改 | +10 | “我的”Tab 新增社区广场入口 |
main_pages.json | 修改 | +1 | 新增路由注册 |
RelationalStoreHelper.ets | 无需修改 | 0 | shared_recipes表已在第 28 篇预建 |
七、设计决策
| 决策 | 选择 | 理由 |
|---|---|---|
| 纯本地数据库 | 不依赖后端 | 当前阶段无需服务器;后续可扩展为云端同步 |
ingredients/steps用分隔符字符串 | 、分隔食材,;分隔步骤 | 只展示不查询,分隔符比 JSON 更直观 |
| 固定用户名“灵犀大厨” | 占位值 | 登录系统对接后替换为authViewModel.username,当前保证功能可跑通 |
shared_at用 Unix 时间戳 | 整数排序比字符串快 | 展示时用formatDate()转换即可 |
| 查询 LIMIT 50 | 限制单次加载量 | 本地数据库中数据量有限,50 条足够展示全部历史分享 |
八、设计哲学:MVP 的“先跑通再完善”
本篇的分享功能是一个典型的 MVP(最小可行产品)实现。它有三个“不完美”:
- 用户名是固定的:“灵犀大厨”不是真实用户名,但现阶段没有登录系统,真实用户名需要等登录功能完善后对接。
- 没有内容审核:用户可以分享任何内容,但当前是本地数据库,数据只在用户自己的设备上,不存在合规风险。
- 没有云端同步:分享只在本地,其他设备看不到,但后续接入后端 API 后,只需将
storeHelper.insert替换为apiService.shareRecipe(),其他代码零改动。
这三个“不完美”是有意为之,不是能力不够。MVP 的核心哲学是:先验证核心行为(用户是否愿意分享),再逐步完善周边设施(身份、审核、同步)。如果用户根本不愿意分享,你把审核系统做得再完善也是浪费。
九、本阶段总结与下篇预告
本篇用约 130 行新代码,为《灵犀厨房》打开了从“独享”到“共享”的大门:
- 分享按钮:在菜谱详情页一键分享菜谱到社区,食材和步骤自动格式化
- 社区广场:按时间倒序展示所有分享,卡片式布局清晰展示菜谱信息
- 纯本地实现:不依赖后端服务器,
shared_recipes表在第 28 篇已预建 - MVP 哲学:先验证分享行为,再逐步完善身份和审核
现在的社交体验:
🍳 做了一道拿手的番茄牛腩煲 → 点击分享 → 出现在社区广场 → 其他人打开 App 就能看到!
下篇预告:第 31 篇《应用权限管理与隐私保护最佳实践》。我们将系统梳理《灵犀厨房》需要的权限,按“最小权限原则”清理不必要的权限声明,并讲解 Health Kit 授权和 OAuth 隐私合规的最佳实践。
📚 本系列持续更新中:下一篇将让 App 在权限和隐私上合规,为发布到 AppGallery 做最后的准备。
🔗专栏入口:[《HarmonyOS6.1全场景实战》合集]
📦 获取基线版本源码包:包括第1-15篇所有代码 + 架构文档 + Flask 后端
如果你觉得这篇文章对您有所帮助,麻烦您动动发财之手点赞 👍、收藏 ⭐ 和评论 💬。谢谢大家!!
纯血鸿蒙,用心造厨。我们下一篇见!
