给四环页面再加一个颜色按钮,并不能直接得到五环工具。原来第三环表示倍率,进入五环后却变成第三位有效数字;如果继续按旧下标计算,界面可能正常显示五种颜色,结果却少算一位。本篇将模式、角色布局与颜色数组共同保存,让四环和五环共用计算流程,同时维持各自的编辑内容。

四环与五环的角色布局

模式是记录的一部分

四环布局为两位有效数字、倍率和误差;五环布局为三位有效数字、倍率和误差。第一位仍不接受黑色,后续数字位接受黑至白,倍率接受规则表内全部颜色,误差仍采用棕、红、金、银四档。新增模式改变的是角色布局,不改变已有颜色的数字、指数或误差含义。

初始四环仍为棕黑红金,对应一千欧姆、百分之五;五环默认棕黑黑棕棕,前三位组成一百,棕色倍率为十,标称同样为一千欧姆,但误差为百分之一,上下限为九百九十和一千零一十欧姆。两个默认值标称相同,便于检查模式切换是否错误地改变了有效数字位数。

工程仍使用entry/src/main/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 type ResistorMode = 'four' | 'five';
export interface BandSelection { mode: ResistorMode; codes: string[]; }
export enum BandRole { FirstDigit, Digit, Multiplier, Tolerance }

export function rolesForMode(mode: ResistorMode): BandRole[] {
  if (mode === 'four') return [BandRole.FirstDigit, BandRole.Digit,
    BandRole.Multiplier, BandRole.Tolerance];
  if (mode === 'five') return [BandRole.FirstDigit, BandRole.Digit,
    BandRole.Digit, BandRole.Multiplier, BandRole.Tolerance];
  throw new Error('电阻模式无效');
}

export function roleAt(mode: ResistorMode, position: number): BandRole {
  const roles: BandRole[] = rolesForMode(mode);
  if (!Number.isInteger(position) || position < 0 || position >= roles.length) {
    throw new Error('色环位置无效');
  }
  return roles[position];
}

export function allowedForRole(rule: BandRule, role: BandRole): boolean {
  if (role === BandRole.FirstDigit) return rule.digit > 0;
  if (role === BandRole.Digit) return rule.digit >= 0;
  if (role === BandRole.Multiplier) return true;
  if (role === BandRole.Tolerance) return rule.tolerance > 0;
  return false;
}

export function optionsFor(mode: ResistorMode, position: number): BandRule[] {
  const role: BandRole = roleAt(mode, position);
  return RULES.filter((rule: BandRule) => allowedForRole(rule, role));
}

export function roleTitle(mode: ResistorMode, position: number): string {
  const role: BandRole = roleAt(mode, position);
  if (role === BandRole.Multiplier) return '倍率';
  if (role === BandRole.Tolerance) return '误差';
  return ['第一位', '第二位', '第三位'][position];
}

export function roleValue(rule: BandRule, mode: ResistorMode, position: number): string {
  const role: BandRole = roleAt(mode, position);
  if (!allowedForRole(rule, role)) throw new Error('颜色与角色不匹配');
  if (role === BandRole.Multiplier) return `10^${rule.exponent}`;
  if (role === BandRole.Tolerance) return `±${rule.tolerance}%`;
  return rule.digit.toString();
}

roleAt先检查模式,再检查位置范围,避免把一个不存在的第五位置解释为误差。上一章的选项函数接收单个位置,本篇升级为模式加位置;非法位置现在抛出明确异常,而不是返回空集合。页面始终根据当前模式生成位置列表,调用服务时仍保留完整校验,以保护导入记录等其他入口。

同一个颜色在不同模式、不同位置下可以具有不同角色。金色在四环第三位置可作倍率,在五环第三位置却不能作有效数字。不能因为颜色在旧页面可以选,就把它原封不动带到新位置。记录模式比根据颜色数组长度猜测用途更清楚,错误长度会被当作输入问题处理,不会触发自动模式识别。

累积有效数字,再读取倍率和误差

计算服务先逐位验证角色,再累积有效数字。每读取一位,旧值乘十并加上新数字。四环累积两次,五环累积三次;倍率位置紧随有效数字,误差位置再往后一位。所有阻值仍以欧姆保存,显示单位转换继续放在格式化函数中。

下面完整列出service/Resistance.ets。保留decodeFour和旧的四环选择入口,已有调用点可以继续明确处理四环;新页面使用带模式的decode与selectBand。兼容入口不会将四个颜色补成五个,也不会悄悄切换模式。

// service/Resistance.ets
import { BandRule, BandSelection, BandRole, roleAt, rolesForMode,
  allowedForRole, findBand } from '../model/BandRule';

export interface ResistanceResult {
  ohms: number;
  tolerance: number;
  low: number;
  high: number;
  labels: string[];
}
export interface SelectionResult { codes: string[]; result: ResistanceResult; }
export interface ModeSelectionResult { selection: BandSelection; result: ResistanceResult; }

export function resistance(first: number, second: number, exponent: number): number {
  return (first * 10 + second) * Math.pow(10, exponent);
}

export function decode(selection: BandSelection): ResistanceResult {
  const roles: BandRole[] = rolesForMode(selection.mode);
  if (selection.codes.length !== roles.length) throw new Error('颜色数量与模式不一致');
  for (let i: number = 0; i < roles.length; i++) {
    if (!allowedForRole(findBand(selection.codes[i]), roles[i])) {
      throw new Error('色环颜色与角色不匹配');
    }
  }
  const digitCount: number = selection.mode === 'four' ? 2 : 3;
  let digits: number = 0;
  for (let i: number = 0; i < digitCount; i++) {
    digits = digits * 10 + findBand(selection.codes[i]).digit;
  }
  const multiplier: BandRule = findBand(selection.codes[digitCount]);
  const tolerance: BandRule = findBand(selection.codes[digitCount + 1]);
  const ohms: number = digits * Math.pow(10, 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: selection.codes.map((code: string) => findBand(code).label) };
}

export function decodeFour(codes: string[]): ResistanceResult {
  return decode({ mode: 'four', codes });
}

export function selectBand(selection: BandSelection, position: number,
  code: string): ModeSelectionResult {
  const role: BandRole = roleAt(selection.mode, position);
  if (!allowedForRole(findBand(code), role)) throw new Error('当前角色不接受该颜色');
  const codes: string[] = selection.codes.slice();
  if (codes.length !== rolesForMode(selection.mode).length) {
    throw new Error('颜色数量与模式不一致');
  }
  codes[position] = code;
  const next: BandSelection = { mode: selection.mode, codes };
  return { selection: next, result: decode(next) };
}

export function selectColor(codes: string[], position: number, code: string): SelectionResult {
  const selected: ModeSelectionResult = selectBand({ mode: 'four', codes }, position, code);
  return { codes: selected.selection.codes, result: selected.result };
}

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))} Ω`;
}

两种模式分别保存编辑内容

切换模式是一项查看与编辑上下文的变化,不是转换阻值的动作。页面分别保存四环与五环选择,切换后读取对应记录,并将编辑位置重置为该模式的误差位。当前四环的第三位置是倍率,不能把它的数字下标直接延续为五环第三位的焦点含义。

选择颜色时仍先计算成功再一起更新输入与结果,随后复制到对应模式的保存字段。不同模式之间不共享颜色数组,界面状态与保存字段也分别复制;修改五环记录不会通过数组引用改写四环记录。“恢复当前模式”只恢复正在查看的那一份默认组合,另一份编辑结果保留。

// pages/Index.ets
import { BandRule, BandSelection, ResistorMode, findBand, optionsFor,
  rolesForMode, roleTitle, roleValue } from '../model/BandRule';
import { ResistanceResult, ModeSelectionResult, decode,
  selectBand, formatOhms } from '../service/Resistance';

@Entry
@Component
struct Index {
  @State selection: BandSelection = { mode: 'four', codes: ['brown', 'black', 'red', 'gold'] };
  @State result: ResistanceResult = decode({ mode: 'four', codes: ['brown', 'black', 'red', 'gold'] });
  @State position: number = 3;
  @State message: string = '四环与五环分别保留选择';
  private four: BandSelection = { mode: 'four', codes: ['brown', 'black', 'red', 'gold'] };
  private five: BandSelection = { mode: 'five', codes: ['brown', 'black', 'black', 'brown', 'brown'] };

  private positions(): number[] {
    const result: number[] = [];
    for (let i: number = 0; i < rolesForMode(this.selection.mode).length; i++) result.push(i);
    return result;
  }

  private publish(selection: BandSelection, resetPosition: boolean): void {
    const result: ResistanceResult = decode(selection);
    const codes: string[] = selection.codes.slice();
    this.selection = { mode: selection.mode, codes };
    this.result = result;
    if (selection.mode === 'four') this.four = { mode: 'four', codes: codes.slice() };
    else this.five = { mode: 'five', codes: codes.slice() };
    if (resetPosition) this.position = codes.length - 1;
  }

  private openMode(mode: ResistorMode): void {
    this.publish(mode === 'four' ? this.four : this.five, true);
  }

  private choose(code: string): void {
    try {
      const next: ModeSelectionResult = selectBand(this.selection, this.position, code);
      this.publish(next.selection, false);
      this.message = '四环与五环分别保留选择';
    } catch (error) {
      this.message = error instanceof Error ? error.message : '颜色更新失败';
    }
  }

  private reset(): void {
    const next: BandSelection = this.selection.mode === 'four' ?
      { mode: 'four', codes: ['brown', 'black', 'red', 'gold'] } :
      { mode: 'five', codes: ['brown', 'black', 'black', 'brown', 'brown'] };
    this.publish(next, true);
  }

  build() {
    Scroll() {
      Column({ space: 14 }) {
        Text('电阻色环工作台').fontSize(25).fontWeight(FontWeight.Bold)
        Text(this.selection.mode === 'four' ? '四环模式' : '五环模式')
        Row({ space: 12 }) {
          Button('四环').onClick(() => { this.openMode('four'); })
          Button('五环').onClick(() => { this.openMode('five'); })
        }
        Row({ space: 3 }) {
          ForEach(this.positions(), (position: number) => {
            Button(roleTitle(this.selection.mode, position)).fontSize(12).layoutWeight(1)
              .backgroundColor(this.position === position ? '#18796C' : '#829B94')
              .onClick(() => { this.position = position; })
          }, (position: number) => `${this.selection.mode}:${position}`)
        }.width('100%')
        Row({ space: 12 }) {
          ForEach(this.positions(), (position: number) => {
            Column({ space: 6 }) {
              Text('').width(26).height(44)
                .backgroundColor(findBand(this.selection.codes[position]).paint)
              Text(this.result.labels[position])
            }
          }, (position: number) => `${this.selection.mode}:${position}`)
        }
        Text(`当前选择:${roleTitle(this.selection.mode, this.position)}`)
        Grid() {
          ForEach(optionsFor(this.selection.mode, this.position), (rule: BandRule) => {
            GridItem() {
              Button(`${rule.label} ${roleValue(rule, this.selection.mode, this.position)}`)
                .fontSize(13).width('100%').fontColor('#173B34')
                .backgroundColor(rule.code === this.selection.codes[this.position] ? '#DDF3EC' : '#FFFFFF')
                .onClick(() => { this.choose(rule.code); })
            }.height(68)
          }, (rule: BandRule) => `${this.selection.mode}:${this.position}:${rule.code}`)
        }.columnsTemplate('1fr 1fr 1fr 1fr').columnsGap(4).rowsGap(4)
          .width('100%').height(212)
        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')
  }
}

模式解码服务与五环开发预览

候选项和位置按钮的键都包含模式信息。虽然四环和五环中都存在位置二,但它们分别表示倍率和第三位有效数字;切换模式时保留同一个组件身份容易把旧选中态带入新角色。当前代码让身份包含模式、位置和颜色,在模式或角色变化时重新绑定对应选项,普通颜色变化仍使用稳定代码。

五个角色按钮保持完整顺序,手机上字号和间距适当收紧,不能把第三位藏进不明确的“更多”按钮。颜色预览始终带色名,结果卡同步显示标称值与误差。外层纵向滚动容器承载较长内容,使小高度窗口仍能到达恢复操作;后续多设备篇可以将角色选择与结果展示并列,但不改变数据契约。

用同值不同编码检查角色偏移

打开四环默认记录,确认一千欧姆、百分之五以及九百五十至一千零五十欧姆。再切到五环默认记录,确认标称值保持一千欧姆,误差改为百分之一。若结果变成一百或一万,应检查有效数字循环次数及倍率位置,不能通过调整格式化单位掩盖计算错误。

在五环中把第三位黑色改为红色,前三位变为一百零二,棕色倍率不变,标称值应为一千零二十欧姆。回到四环,记录仍应是棕黑红金;再返回五环,应保留刚才的第三位红色。恢复五环默认后,四环选择也不能发生变化。这个过程同时检查角色布局、按模式保存及数组引用隔离。

五环默认组合与百分之一误差

数据损坏不能靠自动补位处理

带有四环模式却保存五个颜色的记录,应在数量检查处失败;五环记录只有四个颜色也同样失败。不能删除末尾颜色或复制某一环来凑长度,因为末尾承担误差角色,自动修补会改变业务含义。不存在的模式、颜色代码以及越界位置,都需要沿明确的失败路径返回调用者。

入口还应检查第一位黑色、数字位金色和误差位黑色等不匹配组合。倍率的金银仍然合法,其负指数不能被有效数字的规则误伤。对于同一选择,选项过滤接受的颜色应能被解码服务接受;如果两者不一致,应检查是否有某个页面还在调用上一章仅依据位置的过滤函数。

模式切换与阻值转换需要分开设计。如果以后提供“将四环换成等值五环编码”,它应当是独立功能,需要处理有效数字位数、倍率与可表达范围,并明确保留或调整误差。本篇的模式按钮只是恢复各自编辑上下文,不承担等值转换,因此不会替用户选择新的颜色组合。

为后续反向查色保留数值与编码

输出继续同时保留原始模式与颜色代码,以及派生的欧姆数值和误差。只保存1.00 kΩ这样的文本,会丢失有效数字位数和模式信息;只保存一个颜色数组,又缺少解释位置的契约。后续反向查色应从数值和目标模式出发寻找可用编码,不能从显示字符串的舍入结果直接决定色环。

四环和五环因此共享一套角色校验与数值计算,同时保持独立记录。新增模式不再依赖散落的下标判断,而是由明确的角色布局驱动。下一章将继续处理显示精度、单位归一与阻值反向查色,沿用本篇已经固定的模式与选择记录。

相关官方资料

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐