HarmonyOS电阻色环工作台连载02:角色感知的颜色选项过滤【共创稿事节】
四环电阻的计算入口已经能够拒绝非法组合,但让用户先选错再读报错,并不是高效的编辑方式。第二位可以选黑色,第一位却不接受它;倍率位置可以选金银,误差位置又有另一套可用集合。本篇沿用棕黑红金的初始样本,把这些约束提前到颜色选择界面,同时让计算层继续承担最终校验。

位置是角色,颜色代码是身份
页面使用零至三的位置表示第一位、第二位、倍率和误差,颜色仍使用固定英文代码保存。点击角色只切换当前编辑位置,不改变任何色环;点击候选颜色才形成一次数据更新。把这两个动作分开,能够避免切换到倍率页时误把上一个位置的颜色写进去。
本篇支持范围保持不变:第一位为棕至白九种,第二位为黑至白十种,倍率为黑至白加金银共十二种,误差为棕、红、金、银四种。这里的误差集合是当前工具的配置,不是全部电阻误差等级的穷举。以后扩展规则表时,应同时调整可选集合和解码规则,避免界面出现能够选择却无法计算的颜色。
工程文件继续位于entry/src/main/ets下。下面给出完整的model/BandRule.ets,在既有规则表后增加角色判断、选项过滤和角色值格式化。倍率的负指数仍是有效数值,不能使用“大于等于零”作为所有字段共同的合法条件。
// model/BandRule.ets
export interface BandRule {
code: string;
label: string;
paint: string;
digit: number;
exponent: number;
tolerance: number;
}
export const RULES: BandRule[] = [
{ code: 'black', label: '黑', paint: '#222222', digit: 0, exponent: 0, tolerance: -1 },
{ code: 'brown', label: '棕', paint: '#805333', digit: 1, exponent: 1, tolerance: 1 },
{ code: 'red', label: '红', paint: '#CD4545', digit: 2, exponent: 2, tolerance: 2 },
{ code: 'orange', label: '橙', paint: '#EC8D36', digit: 3, exponent: 3, tolerance: -1 },
{ code: 'yellow', label: '黄', paint: '#E7C83D', digit: 4, exponent: 4, tolerance: -1 },
{ code: 'green', label: '绿', paint: '#429566', digit: 5, exponent: 5, tolerance: -1 },
{ code: 'blue', label: '蓝', paint: '#456EBD', digit: 6, exponent: 6, tolerance: -1 },
{ code: 'violet', label: '紫', paint: '#8D59A8', digit: 7, exponent: 7, tolerance: -1 },
{ code: 'gray', label: '灰', paint: '#888888', digit: 8, exponent: 8, tolerance: -1 },
{ code: 'white', label: '白', paint: '#F3F3F3', digit: 9, exponent: 9, tolerance: -1 },
{ code: 'gold', label: '金', paint: '#B7954C', digit: -1, exponent: -1, tolerance: 5 },
{ code: 'silver', label: '银', paint: '#ADB3B8', digit: -1, exponent: -2, tolerance: 10 }
];
export function findBand(code: string): BandRule {
for (const rule of RULES) {
if (rule.code === code) return rule;
}
throw new Error('颜色不在当前规则表中');
}
export function allowed(rule: BandRule, position: number): boolean {
if (!Number.isInteger(position) || position < 0 || position > 3) return false;
if (position === 0) return rule.digit > 0;
if (position === 1) return rule.digit >= 0;
if (position === 2) return true;
return rule.tolerance > 0;
}
export function optionsFor(position: number): BandRule[] {
return RULES.filter((rule: BandRule) => allowed(rule, position));
}
export function roleValue(rule: BandRule, position: number): string {
if (!allowed(rule, position)) throw new Error('颜色与角色不匹配');
if (position < 2) return rule.digit.toString();
if (position === 2) return `10^${rule.exponent}`;
return `±${rule.tolerance}%`;
}
allowed接收规则和位置,先拒绝小数、负数及越界位置,再按照角色检查字段。第一位的有效数字需要大于零,第二位允许零。当前表中每一种颜色都有倍率指数,所以倍率角色接受表内全部颜色;误差字段为负一的颜色不进入误差集合。这些判断没有读取中文色名,修改界面语言也不会改变规则。
optionsFor返回筛选后的数组,没有对共享规则表执行删除或排序。一个页面切换到误差角色,不应把全局颜色表裁成四项,否则回到倍率角色时金银以外的选项也可能丢失。候选项的身份来自颜色代码,不能使用筛选结果中的数组序号;不同角色下的序号相同,并不意味着它们指向同一种颜色。
一次选择生成完整的新结果
选择入口先查找颜色规则,再检查当前角色是否接受它,随后复制四个代码并替换目标位置。完整数组交给decodeFour继续检查,成功后返回新的输入与结果。这样可以防止外部传入的旧数组本身已经损坏,也可以避免只校验新颜色却漏过其他三个位置。
以下是完整的service/Resistance.ets。标称阻值、误差和上下限的算法保持上一章约定,新增selectColor负责编辑提交。它不会修改传入数组,计算失败时调用者仍持有原来的四环组合。新代码与原有入口共享同一份颜色规则,不另建一套界面专用数字表。
// service/Resistance.ets
import { BandRule, findBand, allowed } from '../model/BandRule';
export interface ResistanceResult {
ohms: number;
tolerance: number;
low: number;
high: number;
labels: string[];
}
export function resistance(first: number, second: number,
exponent: number): number {
return (first * 10 + second) * Math.pow(10, exponent);
}
export function decodeFour(codes: string[]): ResistanceResult {
if (codes.length !== 4) throw new Error('四环需要四个颜色');
const first: BandRule = findBand(codes[0]);
const second: BandRule = findBand(codes[1]);
const multiplier: BandRule = findBand(codes[2]);
const tolerance: BandRule = findBand(codes[3]);
if (first.digit <= 0 || second.digit < 0) {
throw new Error('有效数字位置不接受当前颜色');
}
if (tolerance.tolerance <= 0) throw new Error('误差位置不接受当前颜色');
const ohms: number = resistance(first.digit, second.digit, multiplier.exponent);
if (!Number.isFinite(ohms) || ohms <= 0) throw new Error('阻值超出计算范围');
const delta: number = ohms * tolerance.tolerance / 100;
return { ohms, tolerance: tolerance.tolerance,
low: ohms - delta, high: ohms + delta,
labels: [first.label, second.label, multiplier.label, tolerance.label] };
}
export function formatOhms(value: number): string {
if (value >= 1000000) return `${(value / 1000000).toFixed(2)} MΩ`;
if (value >= 1000) return `${(value / 1000).toFixed(2)} kΩ`;
return `${Number(value.toFixed(4))} Ω`;
}
export interface SelectionResult {
codes: string[];
result: ResistanceResult;
}
export function selectColor(codes: string[], position: number,
code: string): SelectionResult {
if (codes.length !== 4) throw new Error('四环需要四个颜色');
const rule: BandRule = findBand(code);
if (!allowed(rule, position)) throw new Error('当前角色不接受该颜色');
const next: string[] = codes.slice();
next[position] = code;
return { codes: next, result: decodeFour(next) };
}
切换角色时保留各自的选择
页面将当前编辑位置和已确认的颜色数组分别保存。默认打开误差位置,初始金色选中;四个候选项只显示棕、红、金、银。选择棕色后,前三环保持棕黑红,误差变为百分之一,标称阻值仍是一千欧姆,区间变为九百九十至一千零一十欧姆。回到第二位时,黑色仍然是该位置的已选值。
这次交互采用即时提交,点击候选颜色后立即更新色环与阻值,不再保留上一章用于两种倍率切换的按钮。页面没有未确认的草稿层,因此不会把新色带与旧结果同时展示。若以后增加需要多步确认的表单,可以单独引入草稿模型,但不能复用当前结果对象承担两种状态。
// pages/Index.ets
import { BandRule, findBand, optionsFor, roleValue } from '../model/BandRule';
import { ResistanceResult, SelectionResult, decodeFour,
formatOhms, selectColor } from '../service/Resistance';
@Entry
@Component
struct Index {
@State codes: string[] = ['brown', 'black', 'red', 'gold'];
@State result: ResistanceResult = decodeFour(['brown', 'black', 'red', 'gold']);
@State position: number = 3;
@State message: string = '颜色选择后自动计算';
private positions: number[] = [0, 1, 2, 3];
private roles: string[] = ['第一位', '第二位', '倍率', '误差'];
private choose(code: string): void {
try {
const selection: SelectionResult = selectColor(this.codes, this.position, code);
this.codes = selection.codes;
this.result = selection.result;
this.message = '颜色选择后自动计算';
} catch (error) {
this.message = error instanceof Error ? error.message : '颜色更新失败';
}
}
private reset(): void {
const codes: string[] = ['brown', 'black', 'red', 'gold'];
const result: ResistanceResult = decodeFour(codes);
this.codes = codes;
this.result = result;
this.position = 3;
this.message = '颜色选择后自动计算';
}
build() {
Scroll() {
Column({ space: 16 }) {
Text('电阻色环工作台').fontSize(25).fontWeight(FontWeight.Bold)
Text('按角色选择颜色').fontSize(16)
Row({ space: 4 }) {
ForEach(this.positions, (position: number) => {
Button(this.roles[position]).fontSize(14).layoutWeight(1)
.backgroundColor(this.position === position ? '#18796C' : '#829B94')
.onClick(() => { this.position = position; })
}, (position: number) => position.toString())
}.width('100%')
Text(`当前选择:${this.roles[this.position]}`).fontSize(18)
Grid() {
ForEach(optionsFor(this.position), (rule: BandRule) => {
GridItem() {
Button(`${rule.label} ${roleValue(rule, this.position)}`)
.fontSize(14).fontColor('#173B34').width('100%')
.backgroundColor(rule.code === this.codes[this.position] ? '#DDF3EC' : '#FFFFFF')
.border({ width: 1, color: '#18796C' })
.onClick(() => { this.choose(rule.code); })
}.height(72)
}, (rule: BandRule) => rule.code)
}.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(4).rowsGap(4).width('100%').height(224)
Text('当前色环').fontSize(18)
Row({ space: 16 }) {
ForEach(this.positions, (position: number) => {
Column({ space: 6 }) {
Text('').width(30).height(50)
.backgroundColor(findBand(this.codes[position]).paint)
Text(this.result.labels[position])
}
}, (position: number) => position.toString())
}
Text(`${formatOhms(this.result.ohms)} ±${this.result.tolerance}%`)
.fontSize(28).fontColor('#18796C')
Text(`${Number(this.result.low.toFixed(4))}–${Number(this.result.high.toFixed(4))} Ω`)
Button('恢复默认').onClick(() => { this.reset(); })
Text(this.message).fontSize(14)
}.padding(20).width('100%').constraintSize({ maxWidth: 520 })
}.width('100%').height('100%').backgroundColor('#F4F9F6')
}
}

页面中的ForEach用颜色代码作为候选项键,用固定位置作为角色按钮和结果色带的键。角色切换会改变候选集合,但不会改变色环位置身份。背景色判断读取当前角色对应的已选颜色,不是读取一个全局“最后点击颜色”。因此同一种颜色出现在第一位和误差位时,各自的选中状态仍由位置确定。
候选项同时显示颜色名称和角色值。例如金色在倍率位置显示负一次幂,在误差位置显示百分之五;黑色在第二位显示零,在倍率位置显示零次幂。不能把这种差异压缩成一个没有单位的数字,否则使用者看见“金色等于负一”时,仍然不知道该值代表倍率还是误差。界面文字解释角色,计算模型保存原始代码,二者各自承担明确职责。
被过滤掉的入口仍需要服务校验
按钮集合限制了当前页面能发出的输入,却没有限制历史记录、外部导入或其他调用点。服务函数因此仍要检查颜色代码、位置和整个四环数组。通过界面隐藏黑色不能替代第一位不能为零的入口条件,也不能假定用户点击按钮时页面状态永远没有变化。
当前选择动作是同步计算,目标位置来自调用时的this.position,结果立即提交。以后增加异步加载选项时,回调应绑定发起时的目标位置,并在提交前核对是否仍属于同一编辑请求;不能在迟到回调中重新读取当前角色后盲目写入。本篇保持同步数据源,使这一条提交链路可以先被完整检查。

从集合、提交和结果三个层次检查
集合检查分别统计四个角色的候选项数量,应为九、十、十二、四。每个候选项都应通过对应位置的allowed判断,颜色代码不可重复。倍率集合必须含金银,误差集合不能含黑色。把位置改为负一、小数或四时,集合应为空,角色值格式化也不应返回似是而非的数字。
提交检查从棕黑红金开始,将误差改为棕,确认只有第四个代码变化,旧数组仍保持原值;再将第二位改为红,标称值应为一千二百欧姆。让第一位选择黑色或金色,应在角色校验处失败;传入不存在的颜色代码,应在查表时失败。两种失败都不能留下部分更新的输入数组。
结果检查要观察区间与百分比一起变化。金色误差切到银色时,标称一千欧姆保持不变,区间扩展为九百至一千一百欧姆;倍率从红变金时,标称值变为一欧姆,误差比例保持不变。若只改变标称值而区间没有变化,应检查页面是否缓存了一份旧的范围文本,或者色带与结果是否分两次使用了不同输入。
适配时优先保持角色含义可见
手机上的四个角色入口保留固定顺序,候选区域使用四列网格,最多十二个候选项可以分成三行。小高度窗口由外层滚动容器承载完整内容,不能隐藏误差字段来缩短页面。较宽窗口可以增加每个候选项的文字空间,但角色顺序、颜色代码和计算结果都应与手机保持一致。
颜色块始终伴随色名,选中态还通过背景差异表现,避免把操作依赖完全放在颜色本身。完整的朗读描述、焦点顺序和键盘交互将在后续多设备篇扩展;数据层现在已经能够用角色名、颜色名和角色值表达选项,不需要再从绘制颜色反向猜测业务含义。
过滤集合让选择过程更清楚,服务校验让全部入口遵守同一规则。下一章将扩展五环模式,位置对应的角色会发生变化,当前以位置驱动的函数需要升级为明确的模式与角色模型;已有四环记录仍应保持原来的解释方式。
相关官方资料
更多推荐

所有评论(0)