尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
ArkUI 搜索不到同义词食材怎么办:中式美食番茄、西红柿和土豆别名怎么统一命中
用户搜“番茄”结果页空了换成“西红柿”又能出来。用户搜“土豆”菜谱里却写着“马铃薯”。这种问题在菜谱、买菜清单、食材库里很常见表面看是搜索体验不好实际是数据写入时没有把“展示名”和“查询名”分开。中式美食后面做 HarmonyOS 版本时搜索框不能只把用户输入原样丢给列表过滤而要先把食材名归一化再让 RDB 按稳定字段查。应用名称中式美食这篇解决什么用户输入常用食材名但菜谱或购物清单里用了另一个叫法导致搜索不到谁会搜这篇做 HarmonyOS 菜谱、买菜清单、食材库、本地搜索的开发者点进来能拿到什么同义词字典、归一化字段、RDB 索引、Repository 查询和验收用例当前项目证据SearchViewModel、ShoppingListState、RecipeRepository、购物清单勾选状态HarmonyOS 目标能力ArkUI、ArkTS、relationalStore、RdbPredicates、CREATE INDEX本章导读这篇不讨论“大搜索架构”只处理一个开发时马上会遇到的小坑同一个食材有多个叫法时怎么让用户用任何一个常见叫法都能搜到结果。中式美食现在已经有搜索历史、热门词、搜索结果状态也有购物清单和菜谱仓储边界。后面迁到 HarmonyOS 时应该把这个坑提前收掉。章节重点问题从哪里来为什么只在页面里contains(keyword)会漏结果字段怎么拆displayName给用户看normalizedName给查询用RDB 怎么建哪些字段值得建索引哪些字段先不要动Repository 怎么写页面不直接碰 RDB只拿已经整理好的结果ArkUI 怎么验收搜索、清空、返回、购物清单合并都要能复现当前验证环境和技术栈项目说明DevEco Studio6.x 系列HarmonyOS SDKAPI 12/ArkTS 工程UI 层ArkUI 声明式页面数据层ohos.data.relationalStore、RDB、RdbPredicates当前项目参考中式美食 Flutter 模块中的搜索、购物清单、菜谱仓储边界验收方式固定几组食材别名验证搜索页、菜谱详情和购物清单结果一致当前中式美食项目里搜索 ViewModel 已经有三个状态历史词、热门词、搜索结果。这个拆法是对的因为页面不用猜现在应该显示什么。但如果下一步只是把keyword直接拿去查菜谱标题就会漏掉食材别名。classSearchViewModel{SearchViewModel(this._repository);finalSearchRepository_repository;SearchStatestateconstSearchState();Futurevoidsearch(Stringkeyword,{int page1})async{statestate.copyWith(keyword:keyword,results:constUiState.loading());try{finalresultawait_repository.search(keyword,page:page);statestate.copyWith(results:result.items.isEmpty?constUiState.empty(没有找到相关菜谱):UiState.success(result),);}catch(error){statestate.copyWith(results:UiState.error(error.toString()));}}}这段代码暴露出一个很实际的问题keyword进入 Repository 之前还没有被整理。用户输入“番茄”数据里如果存的是“西红柿炒蛋”那就要看 Repository 有没有能力把两者对上。先别急着做全文搜索先把名字拆清楚菜谱类应用里食材名至少有两种用途一种是给用户看的另一种是给查询、合并、排序用的。混在一起写前期省事后期很难收。字段给谁用例子要不要参与查询displayName用户看西红柿、土豆、鸡蛋可以展示不作为唯一查询字段normalizedName查询和合并番茄、土豆、鸡蛋应该建索引aliasText辅助匹配西红柿/番茄/圣女果可选看数据量category筛选蔬菜、肉类、调味料高频筛选时建索引recipeId回到菜谱tomato-egg-001必须稳定我会把归一化放在写入和搜索入口两边都做一遍。写入时保证数据干净搜索时保证用户输入也走同一套规则。constingredientAlias:Recordstring,string{西红柿:番茄,小番茄:番茄,圣女果:番茄,马铃薯:土豆,洋芋:土豆,土鸡蛋:鸡蛋,姜葱蒜:葱姜蒜};exportfunctionnormalizeIngredientName(raw:string):string{constvalueraw.trim();if(!value){return;}returningredientAlias[value]??value;}这里有个取舍别一开始就搞很复杂的 NLP。中式美食现阶段更需要一套可控、可验收、可维护的别名字典。等真实搜索词积累多了再把高频失败词补进去。RDB 表结构要为“搜得到”服务如果只存一个name字段后面一定会纠结它到底是展示字段还是查询字段我更倾向于一开始就拆开。constCREATE_RECIPE_INGREDIENT_TABLECREATE TABLE IF NOT EXISTS recipe_ingredient ( id TEXT PRIMARY KEY, recipe_id TEXT NOT NULL, display_name TEXT NOT NULL, normalized_name TEXT NOT NULL, category TEXT, amount_text TEXT, updated_at INTEGER NOT NULL );constCREATE_SHOPPING_ITEM_TABLECREATE TABLE IF NOT EXISTS shopping_item ( id TEXT PRIMARY KEY, plan_id TEXT NOT NULL, recipe_id TEXT, display_name TEXT NOT NULL, normalized_name TEXT NOT NULL, category TEXT, checked INTEGER DEFAULT 0, updated_at INTEGER NOT NULL );这两张表都保留display_name和normalized_name原因很简单菜谱详情里要尊重原始叫法搜索和购物清单合并时要用稳定叫法。场景用哪个字段为什么菜谱详情展示display_name用户看到的是自然菜谱文案搜索食材normalized_name同义词要命中同一个标准名购物清单合并normalized_name西红柿和番茄不能分成两条分类筛选category让肉类、蔬菜、调料能分组返回菜谱recipe_id搜索结果要能跳回详情页索引只给高频查询不要见字段就建别名搜索最常用的是normalized_name购物清单还会按plan_id和checked查。这个时候索引应该跟着查询方式来。constCREATE_INGREDIENT_INDEXES[CREATE INDEX IF NOT EXISTS idx_ingredient_normalized ON recipe_ingredient(normalized_name),CREATE INDEX IF NOT EXISTS idx_ingredient_recipe ON recipe_ingredient(recipe_id),CREATE INDEX IF NOT EXISTS idx_shopping_plan_checked ON shopping_item(plan_id, checked, updated_at DESC),CREATE INDEX IF NOT EXISTS idx_shopping_normalized ON shopping_item(normalized_name)];我不会一开始就给amount_text、display_name、updated_at全部建索引。索引不是越多越好写入、迁移、调试都会变重。中式美食这种本地应用先让核心查询稳定后面根据真实数据再补。查询推荐索引说明搜番茄相关菜谱normalized_name解决番茄/西红柿命中查某个菜谱食材recipe_id详情页和购物清单都要用查某个计划未买食材plan_id checked updated_at购物清单高频入口按分类看食材category只有分类筛选变高频时再加按展示名模糊搜暂不作为主索引展示名不稳定容易误伤Repository 负责把用户输入变成查询条件ArkUI 页面不应该知道“西红柿等于番茄”。页面只负责拿用户输入ViewModel 负责触发动作Repository 负责把输入转成数据层能懂的条件。importrelationalStorefromohos.data.relationalStore;exportinterfaceIngredientSearchHit{recipeId:string;displayName:string;normalizedName:string;category:string;}exportclassIngredientSearchRepository{constructor(privatereadonlystore:relationalStore.RdbStore){}asyncsearchIngredient(keyword:string):PromiseIngredientSearchHit[]{constnormalizednormalizeIngredientName(keyword);if(!normalized){return[];}constpredicatesnewrelationalStore.RdbPredicates(recipe_ingredient);predicates.equalTo(normalized_name,normalized);predicates.orderByDesc(updated_at);constresultSetawaitthis.store.query(predicates,[recipe_id,display_name,normalized_name,category]);consthits:IngredientSearchHit[][];while(resultSet.goToNextRow()){hits.push({recipeId:resultSet.getString(resultSet.getColumnIndex(recipe_id)),displayName:resultSet.getString(resultSet.getColumnIndex(display_name)),normalizedName:resultSet.getString(resultSet.getColumnIndex(normalized_name)),category:resultSet.getString(resultSet.getColumnIndex(category)),});}resultSet.close();returnhits;}}这段代码的重点不是 API 多复杂而是边界清楚页面输入“西红柿”Repository 先转成“番茄”再查normalized_name。这样同一套逻辑可以同时服务搜索页、购物清单页和推荐页。ArkUI 页面只关心三种结果页面不用知道具体查了哪张表。它只需要知道当前是加载中、空结果、还是有结果。中式美食的搜索页后面可以继续保留历史词和热门词但结果列表要从统一状态里拿。ObservedexportclassSearchAliasViewModel{keyword:string;loading:booleanfalse;emptyText:string;hits:IngredientSearchHit[][];constructor(privatereadonlyrepository:IngredientSearchRepository){}asyncsearch(keyword:string){this.keywordkeyword;this.loadingtrue;this.emptyText;try{constnextawaitthis.repository.searchIngredient(keyword);this.hitsnext;this.emptyTextnext.length0?没有找到相关食材可以换个叫法试试:;}finally{this.loadingfalse;}}}页面状态越少问题越好查。不要让组件自己去判断“是不是同义词”“是不是要展示空结果”“是不是要从购物清单再查一遍”。这些都应该在 ViewModel 和 Repository 之间处理掉。页面状态用户看到什么开发检查什么loading搜索中或骨架占位旧结果是否被错误清空empty明确告诉用户没找到输入是否先归一化过ready食材和菜谱结果recipeId是否能跳详情error可重试提示RDB 异常是否被吞掉cleared回到热门词/历史词清空后是否还有残留过滤条件购物清单也要吃这套规则搜索不是唯一入口。购物清单更容易暴露这个问题两个菜谱分别用了“西红柿”和“番茄”买菜时如果出现两条用户会觉得应用很不聪明。exportfunctionmergeShoppingItems(items:ShoppingItemEntity[]):ShoppingItemEntity[]{constmapnewMapstring,ShoppingItemEntity();for(constitemofitems){constkeynormalizeIngredientName(item.displayName);constexistedmap.get(key);if(!existed){map.set(key,{...item,normalizedName:key});continue;}map.set(key,{...existed,amountText:[existed.amountText,item.amountText].filter(Boolean).join( / ),recipeId:existed.recipeId,updatedAt:Math.max(existed.updatedAt,item.updatedAt)});}returnArray.from(map.values());}这里不要把合并逻辑写在 ArkUI 列表里。页面只负责渲染合并后的结果避免每个入口都重复写一套“西红柿和番茄算同一个”的判断。失败和边界要提前写进验收同义词搜索最容易出现“看起来能搜其实漏了一半”的情况。验收时不能只搜一个正常词要专门准备几组容易出错的词。验收项操作通过标准同义词命中搜“番茄”能命中displayName西红柿的食材反向命中搜“西红柿”和“番茄”返回同一批核心结果购物清单合并两个菜谱分别加入番茄/西红柿清单里只出现一条归一化食材空输入输入空格后搜索不查库回到历史词或热门词清空搜索点击清空按钮结果、关键词、空文案都回到默认态返回详情从结果点进菜谱再返回搜索词和结果列表不丢asyncfunctionverifyAliasSearch(repository:IngredientSearchRepository){consttomatoawaitrepository.searchIngredient(番茄);constxihongshiawaitrepository.searchIngredient(西红柿);if(tomato.length0||xihongshi.length0){thrownewError(番茄/西红柿至少有一组没有命中);}consttomatoIdstomato.map(itemitem.recipeId).sort().join(,);constxihongshiIdsxihongshi.map(itemitem.recipeId).sort().join(,);if(tomatoIds!xihongshiIds){thrownewError(同义词返回结果不一致);}}这类验收比“页面能打开”更有用。页面能打开只能说明 UI 没崩番茄和西红柿返回一致才说明搜索链路真的收住了。工程验收验收点检查方式结论标准字段边界看表结构展示名和查询名分开查询边界看 Repository页面不直接拼 RDB 查询索引边界看建表脚本高频查询字段有索引低频字段不乱建状态边界看 ViewModelloading、empty、ready、error 分清用户路径真实操作搜索、清空、返回搜索词和结果不乱跳购物清单加入同义食材不重复显示相同食材常见问题和取舍问题我的处理要不要直接用模糊搜索可以作为补充但不能替代归一化字段要不要一开始接 AI 识别同义词先不要业务字典更可控也更容易验收别名字典放哪里早期可以放本地常量后期可以进 RDB 或配置表用户输入错别字怎么办那是下一层容错不要和同义词归一化混在一起为什么不把所有字段都建索引写入和迁移会变重先服务高频入口本章小结中式美食的搜索问题不只是“能不能搜到菜名”。真正影响用户的是他用自己的叫法输入食材应用能不能理解。番茄、西红柿、圣女果土豆、马铃薯、洋芋这些词如果不提前收口后面购物清单、推荐、搜索历史都会跟着乱。我的做法很直接展示名给用户看归一化名给查询和合并用ArkUI 页面只负责展示状态ViewModel 负责触发搜索Repository 负责把用户输入转成 RDB 查询。这样写看起来多了一层但后面加搜索历史、热门词、购物清单合并、详情页返回时不会每个页面都补一堆临时判断。如果你也在做菜谱、清单、内容库或本地工具类 HarmonyOS 应用可以先挑三组用户最可能输入的别名写进验收用例。只要这些词能稳定命中搜索体验就会比单纯做一个漂亮输入框更可靠。
RELATED

相关推荐

2026年政企类网站建设与改造全流程指南

2026年政企类网站建设与改造全流程指南

政企官网是单位形象展示、政务公开、便民利企、政企互动的核心数字化载体,相较于普通商业网站,更强调权威性、合规性、安全性、实用性。当下多数存量政企网站存在架构杂乱、合规不足、体验老旧、运维繁琐、安全薄弱等问题。依托PageAdmin政企专属建站系统…

📅 2026/8/25 15:56:56
Unity UGUI自定义美术字体全流程:从资源规范到性能优化

Unity UGUI自定义美术字体全流程:从资源规范到性能优化

1. 项目概述:为什么我们需要自定义美术字体?在Unity UGUI项目里,尤其是那些对视觉表现有较高要求的项目,比如二次元、国风或者特定风格的独立游戏,你肯定遇到过这样的问题:UI设计师给了一套超酷的美术字&am…

📅 2026/8/23 23:15:28
Unity HDRP Custom Pass实现边缘检测与扫描特效:原理、优化与工程实践

Unity HDRP Custom Pass实现边缘检测与扫描特效:原理、优化与工程实践

1. 项目概述:从屏幕特效到核心原理在Unity的高清渲染管线(HDRP)中,Custom Pass(自定义通道)是一个强大到令人兴奋的工具,它允许我们直接介入渲染流程,在特定的渲染阶段注入自己的着色…

📅 2026/8/23 12:41:43
MORE NEWS

更多资讯

📰

在 Ember 应用中运行 tsParticles 粒子动画 Demo:安装、启动与源码解析

在 Ember 应用中运行 tsParticles 粒子动画 Demo:安装、启动与源码解析 【免费下载链接】tsparticles tsParticles - Easily create highly customizable JavaScript particles effects, confetti explosions and fireworks animations and use them as animated ba…

📰

craft-agents-oss v0.4.8 版本解析:`call_llm` 工具、Skills 插件解析修复与 Codex 事件队列竞态修复

craft-agents-oss v0.4.8 版本解析:call_llm 工具、Skills 插件解析修复与 Codex 事件队列竞态修复 【免费下载链接】craft-agents-oss 项目地址: https://gitcode.com/GitHub_Trending/cr/craft-agents-oss 本篇文章基于 craft-agents-oss 仓库 apps/elect…

📰

Unity建筑场景模型的工程化落地指南

1. 为什么“类型多样的建筑场景Unity3D模型素材”不是普通资源包,而是项目落地的加速器你有没有过这样的经历:在Unity里搭一个城市街区,花三天调材质、改UV、修法线,结果发现路灯模型的碰撞体是空的,玻璃窗没做透明度混…

📰

3天从85%降到20%!这3个降AIGC工具让我顺利毕业

还记得上周三凌晨两点,当我第三次收到知网AIGC检测报告时,手心都在冒汗——85%的AI相似度,40%的查重率,这意味着我的毕业论文根本达不到盲审要求。导师直接在我的初稿上批注“学术合规性存疑,建议重写”。距离最终答辩…

📰

前端事件机制实战:7个必踩的坑与解决思路

上个月我在改造一个后台管理页面,遇到了一连串非常“诡异”的问题:列表里点“编辑”按钮,结果弹窗刚打开就被行点击事件关掉;页面里嵌了个iframe,鼠标怎么点里面的内容,外层容器的点击事件都毫无反应&#…

📰

智能灌溉系统实战:从传感器选型到执行闭环的完整设计

简介:面向嵌入式系统开发、单片机应用及农业信息化方向的学习者与研究者,这份文献为智能灌溉系统设计提供完整参照。系统以STC89C52单片机为核心控制模块,配合DHT11温湿度传感器采集环境温湿度与植物相关数据,经A/D转换后在LCD160…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬