
茶器艺科智造HarmonyOS应用实战-23-六个标签和六个presetKey靠索引对齐新增杯型为何容易串位改成类型化配置表先设想一个尚未在当前工程执行的扩展场景产品计划在“方盏”和“高足杯”之间加入“水波杯”。如果开发者只在标签数组插入一个中文名界面会多出按钮点击它却可能套用原高足杯位置的参数后面的项目也可能拿到错误的 presetKey。重启后表现还可能变化因为草稿保存的是索引Web 端又用另一套 key 分支选择轮廓。这个场景用于暴露索引耦合风险不代表当前源码已经加入水波杯或完成过运行验证。指定工程现在有六个PRESET_LABELS和六个CUP_PRESET_KEYS注释要求“一一对应”。HSP 的按钮、applyPreset(i)、深链 key 反查、对话关键词、订单名称和 3D 同步都依赖这个数字 iArkWeb 内嵌 HTML 再根据 presetKey 走自己的轮廓分支。任何一处插入、重排或漏改类型系统都看不见数组之间的关系。本文将完成四项工程收口沿源码还原 label、key、默认参数、对话与 Web 轮廓的真实依赖链。用一个CupPresetSpec同时承载稳定 key、标签、默认参数和几何类型。让 UI、深链、草稿与 Web 都按 key 查表不再传递裸索引。单独建立工艺参数证据边界配置一致不等于尺寸与轮廓已经获得工艺认可。一、两组数组的等长只是约定不是类型关系libraryhar/src/main/ets/model/ChaqiModels.ets:56与:72当前分别声明export const PRESET_LABELS: string[] [ 罗汉杯, 悟空杯, 方盏, 高足杯, 螺旋瓶, 竹节杯 ]; export type CupPresetKey luohan | wukong | fang | zhujie | spiral | generic; export const CUP_PRESET_KEYS: CupPresetKey[] [ luohan, wukong, fang, generic, spiral, zhujie ];第四项最能说明问题标签是“高足杯”key 却是generic。这不一定是错误。源码注释明确说高足杯、悟空杯和螺旋瓶走通用轮廓它们可通过不同默认尺寸形成不同外观。真正风险是这种语义只存在于数组位置和注释中。编译器能保证 key 属于联合类型却不能保证PRESET_LABELS[3]就应配CUP_PRESET_KEYS[3]。数组长度相等也不足以证明对齐。两边各六项但顺序不同程序仍能编译若只加标签不加 key取值还可能成为 undefined再被 Web 的p.presetKey || luohan回退成罗汉杯。二、索引从按钮一路传播到参数、订单和深链HSP 的ChaqiExperiencePage.ets让ForEach(PRESET_LABELS)把 index 交给applyPreset(index)。该函数用 if/else 按 0–5 写入五个尺寸private applyPreset(i: number): void { this.presetIndex i; if (i 0) { this.heightMm 70; this.diameterMm 85; this.mouthMm 85; this.bottomMm 41; this.waistPct 45; } else if (i 1) { this.heightMm 88; this.diameterMm 76; this.mouthMm 68; this.bottomMm 34; this.waistPct 58; } // 2 方盏、3 高足、4 螺旋瓶、5 竹节杯继续分支 this.syncCupLatheFromUi(); }syncCupLatheFromUi又执行const k CUP_PRESET_KEYS[this.presetIndex]。切片下单使用PRESET_LABELS[this.presetIndex]拼订单名深链 presetKey 通过遍历CUP_PRESET_KEYS反查 index对话关键词则直接写applyPreset(4)、applyPreset(5)等数字。这条链共有六个隐式索引消费者按钮显示、参数赋值、模型 key、订单名称、深链反查、对话快捷规则。新增杯型不是改两个数组而是必须同步审查所有消费者。三、Web 分支再次解释 key形成第二份映射ArkTS 的CupLatheSilhouette.buildLatheProfilePoints对zhujie/luohan/fang走专用函数其他 key 走 generic。libraryhsp/.../rawfile/cup3d/index.html:342-364也有一份近似逻辑另外保留了roundfunctiongenerateCupProfileMm(p){varkeyp.presetKey||luohan;if(keyzhujie)returngenerateZhujieProfile(/* ... */);if(keyluohan)returngenerateLuohanProfile(/* ... */);if(keyround)returngenerateWaterWaveProfile(/* ... */);if(keyfang)returngenerateFangProfile(/* ... */);returngenerateGenericProfile(/* ... */);}因此“key 被正确传到 Web”仍不等于“Web 知道新 key 的几何含义”。新增waterwave后若只改 ArkTS 联合类型和数组Web 会落到 generic若只在 Web 加roundHAR 联合类型与深链白名单又可能不承认它。更稳的做法是区分杯型身份与几何算法类型。高足、悟空、螺旋瓶可以拥有不同稳定 key但共同声明profileKind: generic。Web 接收经过白名单化的 profileKind不再自行猜业务 key 对应哪种几何。四、类型化配置表把同一杯型的字段放在同一行建议在 HAR 定义一个配置接口。默认尺寸和算法类型与 key 同处一个对象插入新杯型时不会让平行数组错位export type CupProfileKind luohan | fang | zhujie | generic; export interface CupPresetDefaults { heightMm: number; diameterMm: number; mouthMm: number; bottomMm: number; waistPct: number; } export interface CupPresetSpec { readonly key: CupPresetKey; readonly label: string; readonly profileKind: CupProfileKind; readonly defaults: CupPresetDefaults; readonly aliases: readonly string[]; }配置表示例可按当前源码值迁移而不是趁重构改数字export const CUP_PRESETS: readonly CupPresetSpec[] [ { key: luohan, label: 罗汉杯, profileKind: luohan, defaults: { heightMm: 70, diameterMm: 85, mouthMm: 85, bottomMm: 41, waistPct: 45 }, aliases: [罗汉杯, 罗汉] }, { key: generic, label: 高足杯, profileKind: generic, defaults: { heightMm: 88, diameterMm: 72, mouthMm: 64, bottomMm: 36, waistPct: 52 }, aliases: [高足杯, 高足] } ];这里暴露了当前模型里的身份问题generic被用作高足杯 key同时注释又称它为“其他通用杯型”。若未来需要多个通用轮廓预设稳定身份应新增gaozuprofileKind 仍为 generic并为旧草稿做迁移。不要直接把现有 key 改名后让持久化数据失联。五、页面按 spec 应用不再维护数字 if/else查找函数集中处理 keyUI 与路由都复用它export function presetByKey(key: CupPresetKey): CupPresetSpec | null { for (let i 0; i CUP_PRESETS.length; i) { if (CUP_PRESETS[i].key key) { return CUP_PRESETS[i]; } } return null; } private applyPreset(spec: CupPresetSpec): void { this.selectedPresetKey spec.key; this.heightMm spec.defaults.heightMm; this.diameterMm spec.defaults.diameterMm; this.mouthMm spec.defaults.mouthMm; this.bottomMm spec.defaults.bottomMm; this.waistPct spec.defaults.waistPct; this.syncCupLatheFromUi(); }按钮遍历CUP_PRESETS点击直接传 spec订单名使用当前 spec.label深链使用 presetByKey对话匹配 aliases 后传 spec。数字 index 只可作为 UI 遍历位置不能再充当持久身份。ForEach(CUP_PRESETS, (spec: CupPresetSpec) { Button(spec.label) .onClick(() this.applyPreset(spec)); }, (spec: CupPresetSpec) spec.key)稳定 key 也适合做 ForEach 身份键。新增或重排显示顺序不会让组件身份跟着数组位置漂移。六、草稿应保存 key旧 presetIndex 需要显式迁移当前ChaqiDraftState保存presetIndexload/save 都把它夹在 0–5。即使运行时配置表完全正确重排数组仍会改变旧索引含义。建议新 schema 保存presetKey读取旧版本时用冻结的历史映射迁移const V1_PRESET_KEYS: readonly CupPresetKey[] [ luohan, wukong, fang, generic, spiral, zhujie ]; function migratePresetIndex(index: number): CupPresetKey { if (index 0 index V1_PRESET_KEYS.length) { return V1_PRESET_KEYS[index]; } return luohan; }历史映射必须固定不能引用会继续变化的 CUP_PRESETS。迁移完成后写新 schema若遇到未知 key记录问题并采用受控默认。第 06 篇已经讨论 schemaVersion 读取缺口这里只强调预设身份不要继续依赖位置。七、把 profileKind 明确传给 Web减少两端分支分叉CupWeb3d 当前把 presetKey 传入 rawfile。建议由 native 配置表解析出 profileKind同时传 key 用于诊断与身份Web 只对白名单 geometry 分派const spec presetByKey(this.selectedPresetKey); const command: CupWebCommand { presetKey: this.selectedPresetKey, profileKind: spec?.profileKind ?? generic, heightMm: this.heightMm, diameterMm: this.diameterMm, mouthMm: this.mouthMm, bottomMm: this.bottomMm, waistPct: this.waistPct, waveLevel: this.waveLevel };functiongenerateCupProfileMm(p){varkindnormalizeProfileKind(p.profileKind);if(kindzhujie)returngenerateZhujieProfile(/* ... */);if(kindluohan)returngenerateLuohanProfile(/* ... */);if(kindfang)returngenerateFangProfile(/* ... */);returngenerateGenericProfile(/* ... */);}Web 仍要对 profileKind 做白名单化因为桥接消息是运行时输入。ArkTS 切片轮廓也应使用同一个profileKindForPreset结果确保 3D 与切片分派一致。若确实要保留round应把它提升为双方正式的 CupProfileKind而不是只留在 HTML 的隐藏分支。八、结构一致不等于工艺参数正确类型化表能证明“罗汉杯标签与 luohan key、当前默认参数和 luohan 算法被绑在一起”却不能证明heightMm70、diameterMm85或腹部公式符合真实制陶工艺。当前源码注释使用“阔腹底稳”“细高”“黄金档案最优参数”等描述但项目内没有看到参数来源、测量样本、陶艺师签审、打印收缩率、材料批次或烧成结果记录。因此每个 spec 还需要一份独立证据账本而不是在代码注释里写“已验证”证据项可接受材料当前源码能否证明尺寸来源实物测量记录、设计图、版本号不能轮廓合理性专家复核、采样点对照不能可打印性切片器参数与结果文件不能材料收缩材料批次、打印与烧成前后测量不能UI/切片/3D一致同输入点集或截图对照只能看到同源意图未运行配置重构应先原样迁移现有数值避免把“修接线”和“改工艺”混成一次变更。后续若证据支持调参应单独提交 spec 数值变化并绑定证据编号。九、回归矩阵、故障排查与证据边界it(keeps every preset key unique, 0, () { const seen: string[] []; CUP_PRESETS.forEach((spec: CupPresetSpec) { expect(seen.includes(spec.key)).assertFalse(); seen.push(spec.key); expect(spec.label.length 0).assertTrue(); }); }); it(resolves every route key to one spec, 0, () { CUP_PRESETS.forEach((spec: CupPresetSpec) { expect(presetByKey(spec.key)?.label).assertEqual(spec.label); }); });场景静态/单元预期集成观察新增杯型一个 spec 完成 key/label/default/profile按钮、订单、深链同名重排显示顺序key 与默认参数不变旧草稿仍选同一 key高足杯身份 key 与 generic profile 分离3D/切片均走 generic未知深链 key查表失败并留问题码不切到错误预设未知 Web kind白名单回退并记录不执行任意分支旧 presetIndex用 V1 固定表迁移重启前后身份一致现象首查常见根因修正按钮名对、形状错spec.profileKind 与桥接消息Web 仍按旧 key 猜显式传 geometry 类型新杯型后旧草稿变杯型是否仍存 index显示顺序成为身份迁移并保存稳定 key对话选错杯型是否还调用 applyPreset(数字)硬编码索引未清aliases 查 spec切片与 3D 分叉两端算法分派Web/ArkTS 各维护 key switch共享 profileKind 语义编译通过但标签串位是否仍有平行数组类型只约束单数组一行一个 CupPresetSpec参数被称为工艺最优是否有外部证据编号把注释当验证单独建立工艺证据账本当前源码能确认六组标签、六组 key、按索引的 applyPreset、深链反查、对话数字索引、presetIndex 草稿和 Web/ArkTS 分支也能确认高足等 key 会走 generic 轮廓。本文的 CUP_PRESETS、稳定 key 迁移、profileKind 桥接和测试是建议实现没有修改只读项目。本文未运行单测、HAP 构建、ArkWeb、切片、真机或打印流程也没有陶艺参数来源材料因此不能声称新增配置已生效更不能把现有尺寸称为经工艺验证的最优值。