尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【华夏二十四节气|07】HarmonyOS 6.0.2(22) ArkTS 节气搜索实战:多字段匹配与四态闭环

【华夏二十四节气|07】HarmonyOS 6.0.2(22) ArkTS 节气搜索实战:多字段匹配与四态闭环 【华夏二十四节气07】HarmonyOS 6.0.2(22) ArkTS 节气搜索实战多字段匹配与四态闭环本文唯一核验标记AGC18-HMOS-15-07-SEARCH-POEM-GAP证据边界本文的“当前实现”以本轮复核的SearchPage.ets、SolarTermModel.ets、SolarTermService.ets与solar_terms.json为准文中构建通过记录属于历史证据本轮未重新执行构建不将其表述为新的构建结果。架构扩展和性能优化段落均为建议实现不代表当前工程已经具备对应能力。二十四节气只有 24 条主记录搜索不需要服务器、倒排索引或复杂数据库但“小数据”并不等于可以忽略搜索合同。用户输入“饺子”时希望命中冬至美食输入诗句时希望找到对应节气输入空格时不应看到莫名其妙的空页加载失败、无结果和返回详情也都需要明确状态。本文基于真实 HarmonyOS 工程D:\huawei\one5华夏二十四节气复核SearchPage.ets、SolarTermModel.ets与SolarTermService.ets。审计开始时源码只检索名称、摘要、描述、习俗、食物和养生字段本轮已经把诗词、物候、加载态、错误态、空状态、清除入口和结果数量合入SearchPage.ets并以assembleHap通过 ArkTS 编译。下文保留修复前证据同时给出修复后的复核输出。一、搜索对象来自本地 rawfileSolarTermService首次加载const buf: Uint8Array await rm.getRawFileContent(solar_terms.json); const decoder util.TextDecoder.create(utf-8); const text decoder.decodeToString(buf); this.bundle JSON.parse(text) as SolarTermBundle;搜索页不访问网络而是从应用包内的solar_terms.json取得数据。加载完成后使用内存数组过滤因此断网不会阻止搜索。这也限定了结果范围搜索只能命中当前安装包携带的节气内容不能声称检索互联网诗词库或实时资讯。二、数据模型决定可搜索字段真实SolarTerm包含export interface SolarTerm { id: string; order: number; name: string; season: Season; summary: string; description: string; phenology: string[]; customs: string[]; food: string[]; health: string[]; poems: SolarTermPoem[]; }搜索字段应该根据用户意图选择而不是把对象序列化后做一次粗糙匹配。名称、简介、习俗、美食、养生、物候与诗词都有不同展示价值。id、顺序和季节是结构字段是否参与搜索需要明确产品需求。当前页面没有检索这些字段。三、页面加载完成后保存全部节气SearchPage 维护State keyword: string ; State loading: boolean true; State allTerms: SolarTerm[] [];在aboutToAppear()调用loadData()等待服务加载后把all()结果赋给allTerms。关键词变化触发重新构建matchedTerms()从这份内存数组计算结果。当前记录只有 24 条直接保存全部内容并同步过滤是合适的最小方案不需要分页接口或后台 Worker。四、真实搜索入口是 TextInput搜索栏使用TextInput({ placeholder: 搜索节气、习俗、美食…, text: this.keyword }) .onChange((value: string) { this.keyword value; })用户每次输入都会更新State keyword。页面没有“搜索”按钮也没有提交动作体验属于即时过滤。对于 24 条本地记录即时过滤延迟很小。若以后数据增长到数万条再考虑防抖、预索引或后台计算而不是提前增加复杂度。五、空关键词返回全部 24 条真实逻辑先执行const k this.keyword.trim(); if (!k) return this.allTerms;空字符串或只包含空格的输入都会展示全部节气。这个行为使搜索页也可以作为节气索引页而不是空白初始页。如果产品希望初始状态显示“输入关键词开始搜索”就需要单独定义 idle 状态。不能一边返回全部数据一边在文案中声称默认不展示内容。六、当前匹配采用精确子串每个字段通过text.indexOf(k) 0判断关键词是否是原字符串的连续子串。中文名称、短语和食物名适合这种策略行为直观、可预测。它不支持拼音、错别字纠正、同义词、分词组合或相关度排序。输入“清 明”也不会自动命中“清明”这些都不是当前源码能力。七、名称、摘要和描述已经覆盖真实条件首先检查t.name.indexOf(k) 0 || t.summary.indexOf(k) 0 || t.description.indexOf(k) 0名称适合精确定位节气摘要和描述则允许用户用内容概念搜索。例如数据里出现的气候、农事或季节性词汇可以通过简介命中。但结果行只展示name与summary若命中发生在 description用户未必知道为什么出现。高亮或命中来源提示可以改善可解释性。八、习俗、食物和养生使用some数组字段通过t.customs.some(s s.indexOf(k) 0) || t.food.some(s s.indexOf(k) 0) || t.health.some(s s.indexOf(k) 0)任意元素命中就保留整个节气。搜索“踏青”“春饼”或某种养生建议时不需要先把数组拼成大字符串。some()在找到第一个匹配项后即可停止语义也清楚。对 24 条数据这种写法比建立复杂索引更容易维护和测试。九、初版诗词搜索缺口与修复结果SolarTerm明确定义export interface SolarTermPoem { title: string; author: string; content: string; }初版matchedTerms()没有读取t.poems。因此输入诗名、作者或诗句不会因为诗词字段而命中除非同一关键词偶然出现在摘要或描述中。本轮已经完成代码改造并通过补丁后的源码静态复核与assembleHap构建。这里保留初版缺口是为了让读者理解修复动机而不是把它描述成当前版本的未完成项。十、补齐诗名、作者和正文可复用的纯函数写法如下private poemMatches( poems: SolarTermPoem[], keyword: string ): boolean { return poems.some((poem: SolarTermPoem) poem.title.indexOf(keyword) 0 || poem.author.indexOf(keyword) 0 || poem.content.indexOf(keyword) 0 ); }当前源码采用同等语义的内联some()条件已经把诗名、作者与正文加入过滤。诗词是结构化对象数组必须分别检查标题、作者和正文不能把对象直接转字符串。回归用例应选择只出现在诗句而不出现在其他字段的关键词避免测试出现假阳性。十一、物候字段已经纳入匹配合同模型里有phenology: string[]。初版过滤条件遗漏了它本轮已经将物候数组纳入匹配合同用户输入真实物候词时可以返回对应节气。当前实现与习俗字段一致|| t.phenology.some( (item: string) item.indexOf(k) 0 )代码已经支持物候匹配。后续若在占位文案中直接写出“物候”还应结合窄屏宽度验证文字是否截断这属于界面文案优化不影响本轮搜索合同。十二、把搜索合同集中到一个方法继续把条件写在页面里会越来越长。可以将单条匹配提取为private matches(t: SolarTerm, k: string): boolean { return this.includes(t.name, k) || this.includes(t.summary, k) || this.includes(t.description, k) || this.arrayIncludes(t.customs, k) || this.arrayIncludes(t.food, k) || this.arrayIncludes(t.health, k) || this.arrayIncludes(t.phenology, k) || this.poemMatches(t.poems, k); }这样字段清单成为可审查合同单元测试也能逐字段覆盖。页面的matchedTerms()只负责 trim 与 filter。十三、当前搜索没有大小写归一化节气内容主要是中文但模型中可能出现英文、拉丁符号或 ID。indexOf()区分大小写Spring与spring不等价。如果决定支持拉丁文本可以在比较两端执行const normalized value.toLocaleLowerCase().normalize(NFKC);归一化策略要保持一致并验证目标 HarmonyOS ArkTS 运行环境支持的字符串方法。中文不需要无意义地转小写但全角半角与兼容字符仍可能受益。十四、不要擅自删除正文中的空格trim()只移除关键词两端空白不会删除中间空格。这个行为保护了诗句和短语的原始结构。如果为了“更宽松匹配”把所有空格都删除可能产生意外命中也会改变英文作者名和标点语义。更稳妥的方式是明确提供“连续子串”合同。需要支持多关键词时应先定义 AND、OR 和排序规则再实现分词不能简单split( )后随意组合。验收时应准备含前后空格、连续空格、中文标点和英文作者名的关键词分别记录标准化前后结果确保规则没有破坏诗词原文或产生无法解释的宽泛命中。十五、当前结果顺序保持原始节气顺序filter()不改变未删除元素的相对顺序因此结果沿用solar_terms.json的数组顺序通常对应节气序。当前没有相关度评分名称命中与描述命中的位置一样不会把更精确结果置顶。对于 24 条数据这可能足够若要排序可定义名称精确匹配、名称包含、数组字段、长描述的分值。排序必须稳定分值相同的结果仍应按节气顺序展示。若加入相关度排序测试应同时断言分值与次级排序不要只看首条结果否则数据内容微调后同分结果可能在不同构建中出现顺序漂移。十六、匹配函数在每次构建时执行ResultList 直接调用ForEach(this.matchedTerms(), ...)关键词状态变化会触发构建并重新扫描所有字段。24 条数据与短文本下计算量很小无需缓存。若内容扩大应该先用性能分析确认重建成本再考虑把结果维护为State、添加防抖或预先生成规范化搜索文档。过早缓存会增加状态同步风险。十七、loading 状态已经进入四态渲染虽然页面有State loading: boolean true;初版loadData()会切换它但ResultList()没有对应分支。修复后ResultList()首先判断this.loading显示LoadingProgress和“正在加载节气数据”不会把首次加载误呈现为空结果。当前实现让状态变量与可见 UI 保持一致。即使 rawfile 很小也保留了可测试的加载态为未来数据量增长和解析耗时变化留下明确行为。十八、try/catch/finally建立错误与重试闭环初版加载只有try/finally。本轮实现调整为try { await SolarTermService.instance.ensureLoaded(getContext(this)); this.allTerms SolarTermService.instance.all().slice(); } catch (_) { this.allTerms []; this.errorMessage 节气数据加载失败请重试; } finally { this.loading false; }JSON 解析或资源读取失败时页面进入 error 分支并提供“重新加载”入口。这样loading / error / empty / content四态拥有独立判定加载失败不会伪装成“没有搜索结果”。十九、无结果空状态与恢复入口修复后关键词无法命中任何节气时会进入专用空状态if (this.matchedTerms().length 0) { EmptyView({ text: 未找到相关节气 }) }当前页面同时显示查询词和“清除关键词”入口用户可以一步恢复全部 24 条结果。若后续扩大数据规模可把结果在一个方法中计算后传给 Builder减少同一轮构建中的重复过滤。二十、搜索框清除与结果数量闭环当前TextInput右侧会在关键词非空时显示“清除”点击后重置keyword与导航提示。用户无需逐字删除即可回到全部节气。结果区域显示“共 N 条结果”用户可以直接判断关键词是否过宽提示位于列表顶部没有增加额外卡片层级。清除操作应同时恢复输入、结果和键盘焦点不能只把视觉文本清空却保留旧状态。清除按钮还应具备可访问性说明与稳定触控尺寸并只在关键词非空时显示或启用自动化测试可断言清除后keyword为空且结果数量恢复为全部节气。二十一、命中原因需要可解释结果行只显示Text(t.name) Text(t.summary)若关键词命中food或诗词正文summary 中可能没有该词。用户会疑惑结果为何出现。可以返回interface SearchHit { term: SolarTerm; field: name | custom | food | poem | other; snippet: string; }列表第二行显示命中片段并高亮关键词。这样补齐诗词后诗句命中才真正可见而不只是过滤数组里的隐藏逻辑。二十二、高亮必须处理重复和边界简单高亮可以用indexOf()找到第一个位置拆成前、中、后三段。关键词为空时不能进入拆分否则会产生无意义片段。多次出现时要决定只高亮首处还是全部长文本应先生成围绕命中的短 snippet再设置maxLines和省略。ArkUI 可以用 Span 组合不同样式但构造富文本应放在 helper 中保持 Builder 声明式。高亮测试应包含关键词位于开头、结尾、重复出现以及包含正则特殊字符的情况实现若只使用indexOf()就不应引入不必要的正则转义风险。二十三、结果摘要缺少溢出约束当前Text(t.summary)没有显式.maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis })真实数据摘要若变长列表行高度会扩大影响扫描效率。在手机横屏、小窗口或系统字体放大时更明显。名称列使用layoutWeight(1)的父容器是正确基础再补充行数与溢出策略即可提升适配稳定性。二十四、路由参数使用稳定 ID点击结果行时构造const params: TermDetailParams { termId: t.id }; router.pushUrl({ url: Routes.TermDetail, params })页面不传整份对象而是让详情页用稳定 ID 回查内容。这与收藏、历史模块使用同一身份策略。结果排序或高亮变化不会影响路由合同rawfile 内容更新也不需要修改页面间大对象传递。二十五、路由异常已经提供可见反馈初版源码末尾使用空catch点击失败时没有反馈。本轮改为设置页面状态router.pushUrl({ url: Routes.TermDetail, params }).catch(() { this.navigationMessage 详情页打开失败请重试; });结果数量栏会同步显示提示用户可以继续搜索或重试。后续若接入日志应只记录公共错误码、路由名与termId不写入完整节气内容。二十六、ForEach Key 使用t.id(t: SolarTerm) t.id稳定 ID 作为 Key 能让 ArkUI 在过滤变化时识别同一节气行。它优于数组索引因为删除前部结果不会改变其他行身份。这要求数据源保证 ID 唯一。加载solar_terms.json时应验证重复 ID否则列表复用和详情路由都会出现不确定行为。二十七、all()暴露了服务内部数组SolarTermService.all()当前返回return this.bundle ? this.bundle.terms : [];SearchPage 只读取并过滤没有修改它因此真实路径没有破坏缓存。但公共调用者若执行sort()或splice()会直接改变服务内部顺序。更安全的服务可以返回slice()或只读数组。小数据复制成本低能加强数据所有权。二十八、数据加载也需要结构校验JSON.parse(text) as SolarTermBundle只是类型断言运行时不会验证terms是否数组也不会验证customs、food和poems是否存在。搜索大量调用字符串方法和some()坏数据可能直接导致构建异常。加载边界应校验每个可搜索字段缺失的可选数组可规范化为空数组。错误应停留在 Service 层并映射为页面 error 状态而不是让某一个异常节气拖垮整个页面。二十九、字段权重比模糊算法更重要若要改善排序先建立简单可解释的权重名称完全相等 100 名称包含 80 习俗/食物/诗词 50 摘要/描述/养生 30分值相同按order排序。用户能理解“清明”排在名称只包含“清”的结果前也方便测试。在只有 24 条数据时没有必要先引入编辑距离或向量检索。可解释、可复现的排序更适合离线文化内容。三十、最小单元测试按字段组织至少准备以下互斥用例只在名称出现的关键词只在习俗数组出现的关键词只在食物数组出现的关键词只在养生数组出现的关键词只在诗名出现的关键词只在作者出现的关键词只在诗句正文出现的关键词空字符串与纯空格完全无结果的关键词。其中 5 至 7 在修复前源码应失败用来证明诗词缺口本轮补齐诗词与物候条件后这些用例已经具备进入通过集的代码基础。测试记录能力演进而不是只挑现有代码能过的案例。三十一、页面手工验收关注状态转换打开搜索页时应看到全部节气或明确 loading输入名称后结果收窄输入食物与习俗词时出现可解释结果输入不存在词时显示无结果状态。补齐诗词后输入作者和诗句验证对应节气。点击结果进入详情再返回时关键词是否保留需要按页面路由生命周期实际确认。还要测试快速输入、清除、系统字体放大、横屏、小窗口、键盘弹出与返回键确保搜索栏和结果都可达。每一步都应记录关键词、结果数量、首条结果、命中来源和页面状态返回详情后若关键词重置也要明确这是当前生命周期行为还是回归缺陷。三十二、性能优化先守住 24 条事实每次输入扫描 24 条记录即使检查多个短数组成本仍很低。当前最重要的问题是能力缺口和状态表达而不是 CPU 优化。如果未来节气扩展成大规模传统文化库可以在加载时生成规范化文档interface SearchDocument { term: SolarTerm; normalizedText: string; }但文档拼接会丢失命中字段信息因此还需保存字段索引。数据规模没有增长前不必支付这份复杂度。三十三、隐私与网络边界关键词只保存在State keyword源码没有把搜索词写入 Preferences、日志或网络。页面离开后短期 UI 状态随组件生命周期结束。隐私材料不应声称收集搜索历史也不能把本地搜索包装成在线推荐。若以后保存最近搜索要提供清除入口、容量上限和本地处理说明。搜索词可能反映用户兴趣即使是文化应用也应遵守最小化原则。验证时可在工程内搜索 Preferences、日志、HTTP 与分析 SDK 调用并在断网环境操作搜索页只有代码证据与运行观察都一致才能写出“本地搜索且不保存关键词”的隐私结论。三十四、上架审核需要真实描述当前可准确描述为“支持按节气名称、简介、习俗、美食和养生内容搜索”。在诗词条件补齐并通过真实数据测试前不应在应用商店声称已支持诗名、作者和诗句搜索。如果版本说明写“多字段搜索”审核人员应能从页面快速进入看到无结果提示并通过可复查关键词验证字段命中。页面加载失败、点击无响应和文本溢出都可能影响运行稳定与布局审核应在发布前覆盖。发布素材若展示诗词关键词应使用已经补齐诗词匹配的同一构建包并保留测试关键词与命中截图否则应从描述、截图和标签中移除该能力避免审核素材超前于代码。三十五、推荐的搜索职责分层层职责SolarTermService加载并校验本地数据SearchMatcher规范化关键词、字段匹配与评分SearchPage输入、状态和结果展示Router用稳定 termId 打开详情匹配逻辑从页面抽出后可以用纯数据测试诗词、习俗和食物不需要启动 ArkUI。页面只负责 loading、error、empty、content 四态。这不是为 24 条数据制造框架而是让“搜索哪些字段”成为一个清晰、可回归的业务合同。三十六、发布前检查清单rawfile 加载失败时显示错误与重试空关键词行为与产品说明一致名称、摘要、描述、习俗、食物和养生可命中诗名、作者、诗句在补齐代码后可命中物候是否参与搜索有明确决定无结果时显示关键词和清除入口命中片段能解释结果来源长摘要在小窗口与大字体下不溢出结果 Key 唯一稳定路由失败有反馈搜索词不被无声明地持久化或上传24 条数据下无不必要防抖和后台任务应用介绍不超出当前安装包真实能力。这份清单把功能、性能、隐私和审核口径放在同一条验收链上。三十七、可直接落地的状态闭环补丁针对源码审计发现的诗词字段和页面状态边界可以在不引入网络、数据库或第三方依赖的前提下完成一次小范围改造。补丁仍以SolarTermService提供的 24 条本地数据为唯一输入只把匹配合同和页面状态显式化type SearchPageState loading | content | empty | error; interface SearchHit { term: SolarTerm; fields: string[]; } private normalize(value: string): string { return value.trim().toLocaleLowerCase(zh-CN); } private poemMatches(term: SolarTerm, keyword: string): boolean { const poems term.poems ?? []; return poems.some((poem: SolarTermPoem) { return this.normalize(poem.title).includes(keyword) || this.normalize(poem.author).includes(keyword) || this.normalize(poem.content).includes(keyword); }); }页面加载时使用try/catch/finally收敛状态而不是让异常和零结果共享空白区域private async loadTerms(): Promisevoid { this.pageState loading; try { await SolarTermService.instance.ensureLoaded(getContext(this)); this.allTerms SolarTermService.instance.all().slice(); this.pageState this.allTerms.length 0 ? content : empty; } catch (error) { this.pageState error; } }搜索结果使用SearchHit返回命中字段。名称、简介、习俗、食物、养生和诗词分别记录来源页面既能显示“共 3 条”也能展示“命中诗句”避免用户面对结果却不知道为何命中。空关键词继续返回 24 条完整数据规范化后的非空关键词没有结果时进入emptyrawfile 解析失败进入error并提供重试只有正常匹配才进入content。这次改造的验收矩阵可以固定为四组名称输入“冬至”命中 1 条食物输入真实 JSON 中的已有词命中对应节气诗名或诗句输入命中包含该诗词的节气不存在的随机词进入无结果状态。再补充纯空格、快速连续输入、加载失败和详情返回四个边界就形成从数据加载、字段匹配、状态渲染到路由的完整闭环。本轮补丁已经合入当前源码并通过构建但没有执行真机输入法、滚动和路由交互测试因此不把“编译通过”扩大成“真机全链路通过”。三十八、总结华夏二十四节气的搜索页已经具备一个轻量离线搜索的核心从 rawfile 加载 24 条节气TextInput 实时更新关键词名称、摘要、描述、习俗、食物和养生通过indexOf()与some()进行同步过滤稳定 ID 用于列表复用与详情路由。本轮已经补齐诗名、作者、诗句与物候匹配并建立 loading/error/empty/content 四态、清除入口、结果数量和路由失败提示。仍未实现的是命中来源标签、文本高亮、拼音搜索、错别字纠正和相关度排序这些能力不在本文通过项中。标题中的多字段搜索由真实源码与构建结果支撑不再是一句超出源码的宣传。本文部分内容由 AI 辅助整理所有源码结论均以文中所列本地工程文件为复核依据。三十九、2026-07-27 修复后复核源码与构建双证据本轮复核固定在应用版本1.0.1、targetSdkVersion 6.0.2(22)、compatibleSdkVersion 6.0.2(22)。数据源仍是entry/src/main/resources/rawfile/solar_terms.json实际包含 24 条节气搜索实现仍以SearchPage.ets为准。下面的检查在补丁合入后再次执行用来确认当前源码覆盖范围。$search Get-Content -Raw -Encoding UTF8 entry/src/main/ets/pages/SearchPage.ets $terms Get-Content -Raw -Encoding UTF8 entry/src/main/resources/rawfile/solar_terms.json | ConvertFrom-Json [pscustomobject]{ termCount $terms.terms.Count hasTextInput $search.Contains(TextInput) matchesPoems $search.Contains(t.poems) matchesPhenology $search.Contains(t.phenology) declaresLoading $search.Contains(State loading) rendersLoading $search.Contains(if (this.loading)) hasCatch $search.Contains(catch () swallowsRouteError $search.Contains(.catch(() {})) } | ConvertTo-Json在上述版本源码上执行实际输出如下{ termCount: 24, hasTextInput: true, matchesPoems: true, matchesPhenology: true, declaresLoading: true, rendersLoading: true, hasCatch: true, swallowsRouteError: false }这组结果给出了一条可复查的边界当前版本已经有TextInput和 24 条本地数据poems与phenology已进入匹配loading已渲染为页面分支数据加载具有catch错误态详情路由也不再使用空回调吞错。清除关键词、结果数量和无结果提示同样位于当前SearchPage.ets。构建命令使用 DevEco Studio 26.0.0.461 自带 hvigorhvigorw.bat --no-daemon assembleHap原工程目录包含命令行校验不接受的全角括号因此将同一份源码复制到临时 ASCII 路径后执行实际结果为BUILD SUCCESSFUL in 17 s 400 msCompileArkTS、PackageHap与SignHap均完成。构建仍有项目既存的废弃 API 和兼容性警告但没有新增 ArkTS 编译错误。复核时还应把“纯逻辑可验证”和“真机才能验证”分开。字段匹配、空关键词返回 24 条、稳定 ID 和命中来源可以通过纯数据测试输入法联想、返回键行为、滚动手感、错误态视觉以及详情路由失败提示必须在 HarmonyOS 真机或模拟器中检查。本轮没有执行真机交互测试所以不写“真机已通过”。这一限制同样是复核结果的一部分。唯一复核标记AGC18-HMOS-15-07-STATIC-AUDIT-V2。AI 辅助声明本文在人工核对真实工程源码、数据文件和页面状态后使用 AI 辅助整理结构、润色表达并生成配图代码能力、工程状态与验证边界均以文中列出的本地证据为准。CSDN-SERIES:ALL-163252527
返回列表