news 2026/10/18 1:52:40

【化学实验室|08】HarmonyOS ArkTS 化学公式库实战:让方程式、条件与分类信息在长列表中可读

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【化学实验室|08】HarmonyOS ArkTS 化学公式库实战:让方程式、条件与分类信息在长列表中可读

化学公式页看起来只是一个List:名称放在上面,方程式放大,下面再补一行说明。真正运行后,问题往往不在“能不能显示”,而在“能不能快速读懂”。短式子PV = nRT很轻松,CaCO₃ + 2HCl → CaCl₂ + H₂O + CO₂↑已经开始占宽度;如果再把“加热”“电解”和沉淀、气体符号塞进同一个字符串,用户很难在长列表中区分反应主体、条件与现象。

本文基于化学实验室 1.0.5的真实 ArkTS 源码,拆解FormulaPage.ets、FormulaItem.ets、FavoritesPage.ets和DataStore.ets。当前项目共收录 15 条本地公式:基础化学 7 条、有机化学 2 条、电化学 1 条、分析化学 5 条。页面已经具备横向分类、公式卡片、收藏持久化和常见反应查询,数据量不大,但模型、排版、定位和失败反馈之间仍有几个明显断点。

本文唯一核验标记:CHEM-LAB-ONE12-FORMULA-LIBRARY-READABILITY-20260730。

最典型的是“配平”按钮。现有实现并没有求解任意方程式,而是识别Fe + O2、H2 + O2、CH4 + O2等 6 组关键词并返回预置结果;未知输入也会弹出“已生成配平结果”。收藏页打开一条公式时,又只跳回FormulaPage,没有携带公式 ID,用户还得在列表里重新寻找。

更稳的工程判断是:公式库不是把化学式放大显示,而是把方程式、条件、现象和入口身份组织成可扫描的阅读单元。内容模型保持化学语义,展示模型负责视觉层级,页面状态负责筛选与空结果,路由只使用稳定 ID。

本文会完成以下拆解:

  • 盘点 15 条真实公式及当前页面的数据流。
  • 把方程式、反应条件、现象和解释从单字符串中拆开。
  • 设计适合手机、平板和小窗口的公式卡片。
  • 让分类、搜索、收藏和空状态使用同一份结果列表。
  • 明确“常见反应查询”与“通用方程式配平”的产品边界。
  • 从全局搜索和收藏页按formulaId定位到具体公式。
  • 给出验证矩阵与故障排查顺序。

一、先看真实数据,而不是先改卡片样式

FormulaItem.ets当前模型只有五个字段:

export interface FormulaItem { id: string name: string formula: string description: string category: string }

它足以支撑第一版列表,但formula同时承载了方程式、反应条件和现象符号。例如:

{ id: 'oxygen_prepare', name: '氧气制取', formula: '2KMnO₄ →Δ K₂MnO₄ + MnO₂ + O₂↑', description: '高锰酸钾加热分解制取氧气', category: '基础化学' }

这里的Δ被写在箭头右侧,视觉上像生成物的一部分;O₂↑又同时表达物质与气体逸出。如果后续要按“加热反应”筛选、给条件单独着色,或者为无障碍朗读补充语义,只靠一个字符串会越来越难维护。

当前 15 条数据分布如下:

分类数量典型内容列表风险
基础化学7中和、置换、沉淀、气体制取长方程式较多
有机化学2甲烷燃烧、乙醇燃烧化学式字符密集
电化学1水的电解条件需要突出
分析化学5物质的量、浓度、理想气体符号含义需要说明

先统计数据能避免两个误判:一是不能因为目前只有 15 条就忽略目标定位;二是不能把规则公式和反应方程式强行用完全相同的视觉结构展示。

二、当前页面的数据流已经形成最小闭环

FormulaPage初始化时直接读取全部公式,并在出现时加载收藏:

@State selectedCategory: number = 0 @State favoriteKeys: string[] = [] @State formulas: FormulaItem[] = getAllFormulas() aboutToAppear(): void { this.reloadFavorites() } private async reloadFavorites(): Promise<void> { const items = await DataStore.loadAllFavorites() this.favoriteKeys = items .filter(item => item.type === 'formula') .map(item => item.id) }

这个实现有两个值得保留的点。第一,公式内容是本地静态事实,页面不需要为了显示列表引入网络状态。第二,收藏用type + id区分业务对象,不会因为两类内容恰好同名而互相覆盖。

页面的职责仍然偏多:它既保存内容数组,又做分类筛选、输入归一化、规则匹配、收藏持久化和卡片渲染。数据量小时不会造成性能问题,但会让每项交互难以单独验证。后续重构应先划分职责,不必先引入复杂框架。

三、把内容模型与展示模型分开

更适合公式库的内容模型,可以显式表达条件和现象:

export type FormulaKind = 'reaction' | 'law' export interface FormulaItem { id: string name: string equation: string description: string category: string kind: FormulaKind condition?: string phenomenon?: string aliases: string[] }

这里没有把一个方程式拆成完整的化学 AST,因为当前产品只需要浏览、筛选和定向查询。equation保存可直接显示的式子,condition保存“加热”“电解”等条件,phenomenon保存“放出气体”“生成沉淀”等观察信息,aliases服务于搜索和输入匹配。

页面再生成只读展示对象:

export interface FormulaDisplayItem { id: string title: string equation: string category: string description: string conditionText: string phenomenonText: string isFavorite: boolean } export function toFormulaDisplay( item: FormulaItem, favoriteIds: string[] ): FormulaDisplayItem { return { id: item.id, title: item.name, equation: item.equation, category: item.category, description: item.description, conditionText: item.condition ?? '常规条件', phenomenonText: item.phenomenon ?? '无额外现象标记', isFavorite: favoriteIds.indexOf(item.id) >= 0 } }

展示对象不修改业务数据,也不承担持久化。它只把页面真正需要的字段一次准备好,卡片无需反复判断可选字段。

四、迁移一条公式时先保持化学含义不变

以高锰酸钾制氧为例,迁移后不再把条件挤进箭头:

const oxygenPrepare: FormulaItem = { id: 'oxygen_prepare', name: '高锰酸钾制取氧气', equation: '2KMnO₄ → K₂MnO₄ + MnO₂ + O₂↑', description: '高锰酸钾受热分解,产生锰酸钾、二氧化锰和氧气', category: '基础化学', kind: 'reaction', condition: '加热', phenomenon: '有气体生成', aliases: ['氧气制取', '高锰酸钾分解', 'KMnO4'] }

迁移时要遵守一个边界:模型拆分是为了显示和检索,不应顺手修改化学计量数或反应结论。每条数据都应对照原始内容逐项确认equation、condition和phenomenon,避免“重构成功、知识错误”。

对于n = m / M、PV = nRT这类定律,kind设为law,条件栏可改为“适用范围”,不必伪装成反应方程式。

五、公式主视觉必须允许自然换行

当前页面使用 22vp 加粗文字显示公式:

Text(item.formula) .fontSize(22) .fontWeight(AppFonts.WEIGHT_BOLD) .fontColor(AppColors.PRIMARY) .margin({ top: 8 })

它没有固定宽度,这比强制单行更安全,但长式子换行后缺少行距,条件和说明又紧跟在后面。更稳的卡片应给公式独立的垂直区域:

Text(item.equation) .width('100%') .fontSize(21) .fontWeight(AppFonts.WEIGHT_BOLD) .fontColor(AppColors.PRIMARY) .lineHeight(32) .textAlign(TextAlign.Start) .margin({ top: 10 }) .constraintSize({ minHeight: 40 })

这里不使用单行省略。公式一旦被截断,可能正好丢失系数、生成物或状态符号,信息就不完整。真正超长的内容可以换行,并通过上下间距与说明文字建立层级。

六、条件与现象不应和分类标签抢位置

如果卡片底部同时放分类、条件、现象和收藏按钮,很容易变成一排密集小标签。可以把信息分成三层:

  1. 第一层:名称与收藏动作。
  2. 第二层:完整方程式,字号最大。
  3. 第三层:条件、现象和解释,字号较小但保持正文对比度。

示例 ArkUI 结构如下:

Column({ space: 8 }) { Row() { Text(item.title) .fontSize(AppFonts.BODY_SIZE) .fontWeight(AppFonts.WEIGHT_MEDIUM) .layoutWeight(1) this.FavoriteAction(item) } Text(item.equation) .width('100%') .fontSize(21) .lineHeight(32) .fontWeight(AppFonts.WEIGHT_BOLD) Row({ space: 8 }) { this.MetaChip(item.category) this.MetaChip(item.conditionText) } Text(item.description) .fontSize(AppFonts.CAPTION_SIZE) .fontColor(AppColors.TEXT_SECONDARY) .lineHeight(22) }

分类和条件可以使用短标签,现象与解释保持普通文本。这样用户扫列表时先识别式子,再决定是否阅读说明。

七、用独立卡片组件限制页面复杂度

卡片不应直接访问DataStore,否则列表里的每个子组件都可能发起持久化操作。它只接收数据和点击回调:

@Component struct FormulaCard { @Prop item: FormulaDisplayItem onFavorite: (id: string) => void = () => {} build() { Column() { Row() { Text(this.item.title) .layoutWeight(1) .maxLines(2) Button({ type: ButtonType.Circle }) { Text(this.item.isFavorite ? '★' : '☆') .fontSize(20) } .width(44) .height(44) .accessibilityText(this.item.isFavorite ? '取消收藏' : '收藏公式') .onClick(() => this.onFavorite(this.item.id)) } Text(this.item.equation) .width('100%') .fontSize(21) .lineHeight(32) Text(this.item.description) .width('100%') .fontSize(14) .lineHeight(22) } .width('100%') .alignItems(HorizontalAlign.Start) .padding(16) } }

44vp 的圆形动作区比单独一个星号字符更容易点击,也能提供明确的无障碍文本。页面仍负责真正的收藏切换和重新加载。

八、ForEach 必须使用公式 ID 作为稳定键

当前公式列表没有显式 key 生成器:

ForEach(this.filteredFormulas(), (item: FormulaItem) => { ListItem() { // 卡片内容 } })

切换分类后,同一条公式可能在不同结果数组中出现。使用稳定 ID 可以让 ArkUI 更准确地识别节点身份:

ForEach(this.visibleItems, (item: FormulaDisplayItem) => { ListItem() { FormulaCard({ item: item, onFavorite: (id: string) => this.toggleFavorite(id) }) } }, (item: FormulaDisplayItem) => item.id)

key 不能使用数组下标。分类变化后下标会改变,收藏状态或局部组件状态可能附着到错误卡片。

九、分类状态应使用业务值,而不是数组位置

当前状态是selectedCategory: number,筛选时再从categories[index]读取文字。只要数组顺序变化、插入新分类或者从配置中动态生成分类,索引就容易失去原来的含义。

可以直接保存分类值:

const ALL_CATEGORY = '全部' @State selectedCategory: string = ALL_CATEGORY private filterByCategory(items: FormulaItem[]): FormulaItem[] { if (this.selectedCategory === ALL_CATEGORY) { return items } return items.filter((item: FormulaItem) => { return item.category === this.selectedCategory }) }

这样按钮选中态、筛选条件和埋点文本都使用同一个业务值,不需要在多个位置解释数字2到底代表什么。

十、分类列表最好从目录数据中校验出来

当前分类数组手写为“全部、基础化学、有机化学、电化学、分析化学”。它与模型数据恰好一致,但以后新增“无机化学”时,开发者可能只加数据忘记加标签。

不需要在页面每次构建时去重,可以在模型层提供稳定目录:

export interface FormulaCategory { name: string count: number } export function getFormulaCategories(items: FormulaItem[]): FormulaCategory[] { const order: string[] = ['基础化学', '有机化学', '电化学', '分析化学'] return order.map((name: string) => ({ name, count: items.filter((item: FormulaItem) => item.category === name).length })).filter((item: FormulaCategory) => item.count > 0) }

界面可以显示“基础化学 7”,用户切换前就知道分类规模。若出现目录外分类,单元测试应直接失败,提醒内容维护者补齐顺序定义。

十一、分类与关键词需要合并成一个筛选管线

长列表真正可用时,分类和搜索不能各自维护一份结果。推荐统一计算:

private buildVisibleItems(): FormulaItem[] { const categoryItems = this.filterByCategory(this.formulas) const query = normalizeFormulaQuery(this.query) if (query.length === 0) { return categoryItems } return categoryItems.filter((item: FormulaItem) => { const source = normalizeFormulaQuery([ item.name, item.equation, item.description, item.condition ?? '', item.aliases.join(' ') ].join(' ')) return source.indexOf(query) >= 0 }) }

执行顺序是先分类、再关键词,结果数量更容易解释。15 条数据无需引入全文检索引擎,但必须让用户输入KMnO4时也能命中显示为KMnO₄的内容。

十二、化学输入归一化要处理上下标字符

现有normalizeInput()只删除空格、替换箭头和等号并转小写:

return value.replace(/\s/g, '') .replace(/=/g, '+') .replace(/→/g, '+') .toLowerCase()

用户粘贴H₂ + O₂时,Unicode 下标仍然是₂,与规则里的h2不相等。可以建立显式字符映射:

export function normalizeFormulaQuery(value: string): string { return value .toLowerCase() .replace(/₀/g, '0') .replace(/₁/g, '1') .replace(/₂/g, '2') .replace(/₃/g, '3') .replace(/₄/g, '4') .replace(/₅/g, '5') .replace(/₆/g, '6') .replace(/₇/g, '7') .replace(/₈/g, '8') .replace(/₉/g, '9') .replace(/[=→]/g, '+') .replace(/\s+/g, '') }

归一化只服务检索和规则匹配,展示时仍保留正确的上下标、箭头和状态符号,不能把用户看到的公式改成纯 ASCII。

十三、“配平工具”要先说清能力边界

当前balanceEquation()通过indexOf()判断反应物,能覆盖 6 个预置场景。它不是根据元素守恒建立线性方程组,也不会验证用户输入的生成物。因此界面更准确的名称应是“常见反应查询”或“配平示例”。

把规则抽成数据后,边界更清楚:

interface BalanceRule { id: string reactants: string[] result: string } const BALANCE_RULES: BalanceRule[] = [ { id: 'iron_oxygen', reactants: ['fe', 'o2'], result: '3Fe + 2O₂ → Fe₃O₄' }, { id: 'hydrogen_oxygen', reactants: ['h2', 'o2'], result: '2H₂ + O₂ → 2H₂O' }, { id: 'methane_oxygen', reactants: ['ch4', 'o2'], result: 'CH₄ + 2O₂ → CO₂ + 2H₂O' } ]

这段代码没有冒充通用算法。新增规则需要明确 ID、反应物和已核对结果,便于测试每条规则。

十四、查询结果必须区分成功、空输入和未收录

无论命中与否都弹“已生成配平结果”,会让用户误以为未知反应也已被正确计算。可以返回显式状态:

type LookupStatus = 'matched' | 'empty' | 'unsupported' interface LookupResult { status: LookupStatus message: string formulaId?: string } function findBalanceExample(input: string): LookupResult { const key = normalizeFormulaQuery(input) if (key.length === 0) { return { status: 'empty', message: '请先输入反应物' } } const rule = BALANCE_RULES.find((item: BalanceRule) => { return item.reactants.every((reactant: string) => key.indexOf(reactant) >= 0) }) if (!rule) { return { status: 'unsupported', message: '暂未收录该反应,请从公式目录继续查找' } } return { status: 'matched', message: rule.result, formulaId: rule.id } }

页面只有在matched时提示成功;empty聚焦输入框;unsupported使用中性说明,不暗示系统完成了实际计算。

十五、收藏操作要防止连续点击造成状态抖动

当前点击星号后先写入DataStore,再重新读取全部收藏。流程正确,但用户连续点击时可能发起重叠异步操作。可以记录正在处理的 ID:

@State pendingFavoriteIds: string[] = [] private async toggleFavorite(id: string): Promise<void> { if (this.pendingFavoriteIds.indexOf(id) >= 0) return const item = this.formulas.find((value: FormulaItem) => value.id === id) if (!item) return this.pendingFavoriteIds = this.pendingFavoriteIds.concat(id) try { await DataStore.toggleFavorite({ id: item.id, type: 'formula', title: item.name, subtitle: `${item.category} · ${item.equation}`, route: 'views/learning/FormulaPage', createdAt: Date.now() }) await this.reloadFavorites() } finally { this.pendingFavoriteIds = this.pendingFavoriteIds.filter((value: string) => value !== id) } }

卡片在 pending 状态禁用收藏按钮,并保留原图标,避免短时间内星标来回闪烁。

十六、收藏和全局搜索必须携带 formulaId

当前收藏页对公式执行:

router.pushUrl({ url: 'views/learning/FormulaPage' })

这只能打开公式页,无法定位收藏对象。调用方应传递稳定 ID:

router.pushUrl({ url: 'views/learning/FormulaPage', params: { formulaId: item.id } })

全局搜索中的公式结果也使用相同协议。不要传数组下标,因为新增、删除或重新排序公式后,下标不再代表同一条内容;也不要传标题,因为标题可能改名或重复。

十七、公式页接收 ID 后先验证再滚动

List可以绑定Scroller,在页面出现后定位目标:

interface FormulaRouteParams { formulaId?: string } private listScroller: Scroller = new Scroller() @State targetFormulaId: string = '' aboutToAppear(): void { const params = router.getParams() as FormulaRouteParams this.targetFormulaId = params.formulaId ?? '' this.reloadFavorites() } private scrollToTarget(): void { if (this.targetFormulaId.length === 0) return const index = this.visibleItems.findIndex((item: FormulaDisplayItem) => { return item.id === this.targetFormulaId }) if (index < 0) { this.showToast('未找到目标公式') return } this.listScroller.scrollToIndex(index, true, ScrollAlign.CENTER) }

如果当前分类不是“全部”,应先切回全部并刷新visibleItems,再滚动。目标不存在时留在页面并给出说明,不能因为错误参数白屏或崩溃。

十八、目标卡片需要短暂高亮,而不只是滚到附近

长列表滚动后,用户仍可能不知道哪一条是入口目标。可以让卡片根据targetFormulaId显示短暂边框:

.border({ width: item.id === this.targetFormulaId ? 2 : 0, color: AppColors.PRIMARY }) .backgroundColor( item.id === this.targetFormulaId ? AppColors.PRIMARY_BG : AppColors.CARD_BG )

高亮不是永久选中状态。页面完成首次定位后,可以延迟清除targetFormulaId,让卡片回到普通样式,同时不改变收藏状态或分类状态。

十九、空结果是筛选流程的一部分

当前任一分类都有数据,所以开发阶段不容易看到空列表。但加入关键词后,“电化学 + 乙醇”必然为空。页面应区分三种情况:

状态页面内容用户下一步
无查询当前分类全部公式浏览或切换分类
有结果结果数量与卡片打开、收藏或修改查询
无结果当前关键词与分类说明清除关键词或查看全部

ArkUI 可以直接使用条件分支:

if (this.visibleItems.length === 0) { Column({ space: 12 }) { Text('没有找到相关公式') .fontSize(18) .fontWeight(FontWeight.Medium) Text(`分类:${this.selectedCategory},关键词:${this.query}`) .fontSize(14) .fontColor(AppColors.TEXT_SECONDARY) .textAlign(TextAlign.Center) Button('清除筛选') .onClick(() => this.resetFilters()) } .layoutWeight(1) .justifyContent(FlexAlign.Center) } else { this.FormulaList() }

空状态必须提供恢复动作,不能只显示一句“暂无数据”。

二十、不要让硬编码颜色破坏深浅色适配

当前分类未选中背景和输入框背景分别使用#F0F0F0、#F3F4F6。在浅色模式下没有明显问题,但如果主题跟随系统,硬编码浅灰可能与深色页面形成刺眼色块,文字对比也可能失效。

这些颜色应进入统一主题:

Text(category.name) .fontColor( this.selectedCategory === category.name ? AppColors.TEXT_ON_PRIMARY : AppColors.TEXT_SECONDARY ) .backgroundColor( this.selectedCategory === category.name ? AppColors.PRIMARY : AppColors.CONTROL_BG )

公式正文、辅助说明、禁用收藏按钮和目标高亮都要在浅色、深色两种资源下检查。正文与背景需要保持足够对比,不能只靠颜色深浅表达是否收藏。

二十一、多设备下优先保住公式宽度

手机竖屏、横屏小窗口和平板对卡片宽度的需求不同,但公式区域都应尽量占满内容宽度。建议策略如下:

设备/窗口列表布局公式字号分类入口
手机竖屏单列20-22vp横向滚动
手机横屏/小窗口单列紧凑18-20vp横向滚动
平板双列仅用于短公式;默认仍单列20-22vp顶部固定
2in1限制内容最大宽度20-22vp支持鼠标点击

公式库不适合为了“充分利用平板空间”强制多列。双列会明显压缩长方程式的可读宽度,只有在按公式长度分组并经过验证后才值得采用。

二十二、无障碍与触控细节不能留到最后

星号字符虽然直观,但屏幕阅读器只可能读成“星形”,并不知道当前动作。改成有 44vp 触控区的按钮后,还需要动态说明:

.accessibilityText( item.isFavorite ? `取消收藏,${item.title}` : `收藏公式,${item.title}` )

方程式本身可以补充可朗读文本,例如把↑解释为“气体生成”、↓解释为“沉淀生成”。不要把无障碍文本直接显示在卡片上,而是由模型提供spokenText,交给组件的可访问属性。

二十三、用查询样例验证规则,而不是只看界面

本地规则最适合做确定性测试。至少覆盖以下输入:

输入预期状态预期结果
Fe + O2matched3Fe + 2O₂ → Fe₃O₄
H₂ + O₂matchedUnicode 下标归一化后命中
CH4+O2matched甲烷燃烧
空字符串empty提示输入反应物
Na + H2Ounsupported明确“暂未收录”
KMnO₄matched高锰酸钾分解规则

分类测试还应确认 7、2、1、5 的数量不因筛选重构发生变化。收藏测试要确认相同formulaId连续切换后最终只保留一条记录。

二十四、页面验收要覆盖完整交互链路

建议按以下顺序做手工验收:

  1. 首次进入显示全部 15 条公式,卡片没有横向溢出。
  2. 四个分类数量分别为 7、2、1、5。
  3. 输入KMnO4能找到显示为KMnO₄的公式。
  4. 未收录反应不会显示成功提示。
  5. 收藏任一公式后返回“我的收藏”,标题和方程式一致。
  6. 从收藏页重新打开,列表滚到对应公式并短暂高亮。
  7. 从全局搜索打开公式,使用同一formulaId协议。
  8. 重启应用后收藏仍存在,取消收藏后不残留。
  9. 系统深色模式下分类、卡片、输入框和辅助文字可读。
  10. 手机横屏、小窗口和平板下没有被状态栏或底部导航遮挡。

这组验收同时检查内容正确性、交互可达性和持久化,不会只得到一张“看起来不错”的静态截图。

二十五、常见故障与排查顺序

现象优先检查常见原因修复方向
H₂无法命中归一化结果未转换 Unicode 下标补齐上下标映射
切换分类后卡片状态错位ForEachkey使用数组下标或无稳定 key使用item.id
收藏后星标不刷新异步链路写入后未回读或重复点击pending 状态 + reload
从收藏页打开仍在列表顶部路由参数未传formulaId统一深链协议
公式被截断Text 约束单行省略或固定高度允许换行并设置行距
未知反应也提示成功查询状态所有分支共用成功 Toast区分 matched/unsupported
深色模式出现浅灰块主题资源页面硬编码颜色使用语义色 token
新分类没有标签分类目录数据与手写数组漂移模型层生成并校验分类

排查时先确认内容模型,再看筛选结果,最后检查组件布局和路由。若一开始就调字号,很容易掩盖“目标 ID 丢失”或“查询状态错误”这类真正的业务问题。

二十六、按最小风险顺序落地

这次改造不需要一次性重写整个页面,可以分成四步:

  1. 先为ForEach增加稳定 key,并让收藏、搜索都传formulaId。
  2. 再拆分condition、phenomenon和aliases,逐条迁移 15 条数据。
  3. 抽取FormulaCard与统一筛选管线,补齐空状态和 pending 状态。
  4. 最后调整“配平工具”的命名与返回类型,并完成多设备、深浅色验证。

每一步都能单独回归。第一步不改数据,第二步不改交互,第三步才改页面结构,第四步处理产品文案和规则边界,出问题时更容易定位。

二十七、源码事实与建议实现的边界

公式页同时包含数据目录、筛选、收藏、配平和跨页面定位,最容易把“计划中的完整工具”误写成“当前已经具备”。下面把可直接复核的实现与本文建议分开列出。

类别当前源码可直接确认本文建议但未声称已合入
公式目录FormulaItem.ets当前有 15 条公式和 4 个业务分类增加条件、现象、关键词与展示优先级字段
分类状态页面维护 5 个标签,selectedCategory保存数组下标使用稳定分类值并校验目录数据
列表筛选“全部”返回完整数组,其余按category精确过滤将分类与关键词合并为同一筛选管线
列表身份当前ForEach没有提供第三个稳定键函数使用item.id作为稳定键
公式展示公式文本固定22fp、粗体,未声明换行或缩放策略用约束宽度、自然换行与可测试字号保证长式可读
收藏点击后调用DataStore.toggleFavorite(),随后重新加载收藏增加进行中状态,阻止同一公式连续点击
配平输入去空白,并把=、→替换为+后转小写明确 token 化与上下标归一化规则
配平范围当前仅识别 6 组固定关键词分支并返回预置结果若扩展为通用能力,应采用经过验证的化学方程式解析与配平引擎
未收录结果返回“暂未收录”文本,但仍显示“已生成配平结果”Toast区分成功、空输入和未收录状态
跨入口定位收藏 route 和全局搜索当前只打开FormulaPage携带formulaId,验证后滚动并短暂高亮
颜色适配分类未选中背景、输入背景和阴影含硬编码色值迁移到深浅色资源与主题令牌

本文给出的展示模型、统一筛选、目标高亮、输入归一化和交互防抖属于针对现有断点的建议设计,并不表示这些结构已经合入源码,也没有声称完成 ArkTS 编译、自动化滚动定位或真机大字体验证。落地后仍需按第二十三、二十四节逐项记录真实查询、收藏、路由和多设备结果。

总结

化学公式库的难点不是List组件本身,而是让内容语义、视觉层级和入口身份保持一致。当前项目的 15 条公式、四类筛选和收藏持久化已经形成可用基础,但单字符串公式、索引式分类、预置规则伪装成通用配平,以及公式入口不携带 ID,都会在实际使用中增加理解与寻找成本。

更可靠的方案是把方程式、条件、现象和别名拆成明确字段;用展示模型组织卡片;允许长公式自然换行;用稳定 ID 驱动列表、收藏和深链;把未知输入视为正常失败状态,而不是成功结果。完成这些边界后,公式库才真正从“能列出来”变成“能快速看懂、能准确找到、能再次回到原位置”的学习工具。

AI 辅助声明:本文由 AI 辅助完成源码梳理、结构校对与配图设计,技术结论均以文中列出的本地 ArkTS 源码为依据,并由作者复核后发布。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 1:51:21

从零构建AI Skill:用可运行代码封装大模型经验的完整实战

1. 项目概述&#xff1a;为什么你需要一个“AI Skill”做开发的这两年&#xff0c;我越来越发现一个尴尬的现状&#xff1a;大模型本身的能力已经非常强了&#xff0c;但绝大多数人还停留在“聊个天、写个稿、翻译一段话”的阶段。真正能把自己手头那套重复性高、规则明确的流程…

作者头像 李华
网站建设 2026/10/18 1:51:08

geckolib开发包实战:1.12.2实体动画开发与避坑指南

简介&#xff1a;面向Minecraft模组开发者的geckolib集成开发包&#xff0c;适配Forge 1.12.2环境&#xff0c;适合希望为自定义物品或实体加入流畅动画的Mod作者。包体将geckolib源代码与编译产物一并整合&#xff0c;共10189个文件&#xff0c;包含约3780个Java源码、6115个c…

作者头像 李华
网站建设 2026/10/18 1:50:33

C++实战:从零开发飞机大战游戏,掌握面向对象与游戏循环

1. 从零上手&#xff1a;为什么用C写飞机大战是个靠谱的练手项目很多人第一次接触编程语言&#xff0c;学完变量、循环、函数之后&#xff0c;最大的困惑就是&#xff1a;接下来该干什么&#xff1f;语法书翻完了&#xff0c;练习题也做了不少&#xff0c;但真要自己从零写一个…

作者头像 李华
网站建设 2026/10/18 1:48:45

3分钟拿到带时间戳的歌词文件:网易云QQ音乐歌词下载实操

3分钟拿到带时间戳的歌词文件&#xff1a;网易云QQ音乐歌词下载实操 【免费下载链接】163MusicLyrics 云音乐歌词获取处理工具【网易云、QQ音乐】 项目地址: https://gitcode.com/GitHub_Trending/16/163MusicLyrics 你在网易云或QQ音乐里听到一首好歌&#xff0c;想下载…

作者头像 李华