【OpenHarmony/HarmonyOs 】单位换算引擎实战:长度、面积、体积、温度、速度的端侧计算方案

📅 2026/7/6 1:41:47 👁️ 阅读次数 📝 编程学习
【OpenHarmony/HarmonyOs 】单位换算引擎实战:长度、面积、体积、温度、速度的端侧计算方案

【OpenHarmony/HarmonyOs 】单位换算引擎实战:长度、面积、体积、温度、速度的端侧计算方案

项目类型:OpenHarmony / HarmonyOS ArkTS 数学学习应用
项目名称:数学视界
对应主题:端侧 AI、全场景智慧生活、精细化权限管控
关键词:ArkTS、单位换算、本地计算、端侧能力、历史记录、零网络依赖 🔄

一、单位换算为什么符合主题?

“单位换算”看起来是一个小工具,但它非常符合 HarmonyOS 全场景和端侧能力的思路:

  • 用户可能在学习、作业、生活、实验中随时使用;
  • 不需要网络;
  • 不需要权限;
  • 结果需要即时反馈;
  • 适合拆成元服务轻量入口;
  • 也适合保留历史记录,方便复查。

数学视界项目中的UnitConverter.ets支持:

  • 📏 长度;
  • ⬜ 面积;
  • 📦 体积;
  • ⚖️ 重量;
  • 🌡️ 温度;
  • 🚀 速度。

这篇文章就围绕它的本地换算引擎展开。

二、页面状态:换算由四个核心变量驱动

单位换算页的核心状态如下:

@StateselectedCategory: string ='长度'@StateinputValue: string =''@StatefromUnit: string ='米'@StatetoUnit: string ='厘米'@Stateresult: string =''@StateshowHistory: boolean = false@StateunitPickerTarget: string ='none'

这几个状态组合起来就能表达一个完整换算任务:

输入值inputValuefromUnit换算到toUnit所属类别selectedCategory输出result

比如:

2.5千米 -> 米 =250036摄氏度 -> 华氏度 =96.8华氏度

三、分类配置:让工具具备扩展性

页面顶部的类别不是写死 UI,而是配置数组:

private categories: CategoryInfo[] = [ { id:'length',name:'长度', icon:'📏',color:'#5B9FFF', bgColor:'#E8F5FF'}, { id:'area',name:'面积', icon:'⬜',color:'#3CD68C', bgColor:'#E0F8EE'}, { id:'volume',name:'体积', icon:'📦',color:'#B06FFF', bgColor:'#F3E8FF'}, { id:'weight',name:'重量', icon:'⚖️',color:'#FF9A3C', bgColor:'#FFF3E0'}, { id:'temperature',name:'温度', icon:'🌡️',color:'#FF7755', bgColor:'#FFE8E0'}, { id:'speed',name:'速度', icon:'🚀',color:'#FF7A8A', bgColor:'#FFE8F0'}, ]

这样如果后续要增加:

  • 时间;
  • 压强;
  • 能量;
  • 角度;
  • 数据存储单位;

只需要补充分类和单位组,不需要重写整个页面。

四、单位组设计:统一转成基准单位

项目中通过UnitGroup保存单位列表和换算倍率:

class UnitGroup { units: string[]=[]toBase: number[]=[]}

长度单位示例:

constUNIT_LENGTH:UnitGroup= { units: ['米','厘米','毫米','千米','英里','英尺','英寸','码'], toBase: [1,0.01,0.001,1000,1609.344,0.3048,0.0254,0.9144], }

这里的toBase表示“换算到基准单位”的比例。长度的基准单位是米:

  • 1 米 = 1 米;
  • 1 厘米 = 0.01 米;
  • 1 千米 = 1000 米;
  • 1 英里 = 1609.344 米。

这种设计非常通用。任意两个单位互转,都可以先转成基准单位,再从基准单位转成目标单位。

五、核心换算逻辑:from -> base -> to

普通单位换算逻辑如下:

constbaseValue: number = inputNum * group.toBase[fromIdx]constfinalValue: number = baseValue / group.toBase[toIdx]this.result =this.formatNumber(finalValue) +' '+this.toUnit

完整流程:

convert(): void {if(this.inputValue ==='') {this.result =''return}constinputNum: number = parseFloat(this.inputValue)if(isNaN(inputNum)) {this.result ='请输入有效数字'return}if(this.selectedCategory ==='温度') {this.result =this.convertTemperature(inputNum,this.fromUnit,this.toUnit)return} let group: UnitGroup = UNIT_LENGTHif(this.selectedCategory ==='面积') group = UNIT_AREAelseif(this.selectedCategory ==='体积') group = UNIT_VOLUMEelseif(this.selectedCategory ==='重量') group = UNIT_WEIGHTelseif(this.selectedCategory ==='速度') group = UNIT_SPEEDconstunits: string[] =this.getCurrentUnits()constfromIdx: number = units.indexOf(this.fromUnit)consttoIdx: number = units.indexOf(this.toUnit)constbaseValue: number = inputNum * group.toBase[fromIdx]constfinalValue: number = baseValue / group.toBase[toIdx]this.result =this.formatNumber(finalValue) +' '+this.toUnit }

这段代码没有任何网络请求,全部在端侧完成。

六、温度为什么要单独处理?

长度、面积、重量、速度都可以通过比例换算,但温度不一样。摄氏度、华氏度、开尔文之间有偏移量,不是简单乘除。

项目中单独实现了温度换算:

convertTemperature(val:number,from:string,to:string):string{ let celsius:number=0if(from=== '摄氏度') celsius = valelseif(from=== '华氏度') celsius = (val -32) *5/9elseif(from=== '开尔文') celsius = val -273.15letresult:number=0if(to=== '摄氏度')result= celsiuselseif(to=== '华氏度')result= celsius *9/5+32elseif(to=== '开尔文')result= celsius +273.15returnthis.formatNumber(result) + ' ' +to}

这里采用“先统一转摄氏度,再转目标单位”的思路,和普通单位的“先转基准单位”是一致的。

七、结果格式化:小数和科学计数法

换算结果可能很大或很小,比如平方千米转平方厘米、立方米转毫升。项目中通过formatNumber()控制显示:

formatNumber(val:number):string{if(Math.abs(val) <0.0001||Math.abs(val) >1e10) { returnval.toExponential(6)} const rounded: number =Math.round(val*1e8)/1e8return rounded.toString()}

这让结果兼顾可读性和精度:

  • 普通结果显示小数;
  • 极大/极小结果使用科学计数法;
  • 小数保留到合理范围。

八、单位选择面板:移动端更好操作

项目没有把所有单位都挤在横向芯片里,而是用底部选择面板:

@StateunitPickerTarget: string ='none'openFromUnitPicker(): void {this.unitPickerTarget ='from'} openToUnitPicker(): void {this.unitPickerTarget ='to'} applyPickedUnit(unit: string): void {if(this.unitPickerTarget ==='from') {this.fromUnit = unit }elseif(this.unitPickerTarget ==='to') {this.toUnit = unit }this.unitPickerTarget ='none'this.convert() }

这个交互比一排小按钮更适合手机:

  • 单位列表可滚动;
  • 当前选中项有勾选状态;
  • 选择后自动换算;
  • 源单位和目标单位共用一套面板。

九、历史记录:换算也能形成学习轨迹

换算结果可以保存到历史:

saveToHistory(): void {if(this.inputValue ===''||this.result ===''||this.result.indexOf('请') >=0)returnconstrecord: ConversionRecord = { id: Date.now().toString(), category:this.selectedCategory, from:this.inputValue +' '+this.fromUnit, to:this.result, time: new Date().toLocaleTimeString('zh-CN', { hour:'2-digit', minute:'2-digit'}), } AppState.addConvertHistory(record)this.showToast('✅ 已保存换算记录') }

AppState.addConvertHistory()会限制历史数量:

addConvertHistory(record: ConversionRecord): void {if(this.convertHistory.length>=50) { this.convertHistory.pop() } this.convertHistory.unshift(record) this.recordUnitConversion() }

这样既能保留最近换算,又不会无限增长。

十、隐私与全场景意义

单位换算是一个非常适合本地化的能力:

  • 🔐 不需要上传输入值;
  • 📶 离线可用;
  • ⚡ 结果即时反馈;
  • 🧮 可作为元服务轻量工具;
  • 📚 可接入学习统计;
  • 📱 手机和平板都适合使用。

如果后续做全场景扩展,可以把“单位换算”拆成桌面卡片或元服务入口:输入数值、选择单位、直接得到结果。

十一、总结

这篇文章贴合“端侧 AI / 全场景智慧生活 / 精细化权限管控”主题,重点是数学视界中的单位换算引擎。

核心实现包括:

  • 🔄 用UnitGroup统一描述单位和基准倍率;
  • 🧮 通过from -> base -> to完成普通单位换算;
  • 🌡️ 单独处理温度偏移;
  • 🔢 用formatNumber()控制结果格式;
  • 📜 用convertHistory保留最近 50 条记录;
  • 📱 用底部单位选择面板提升移动端体验;
  • 🔐 全程端侧计算,零网络依赖。

一个好用的学习 App,不一定每个功能都复杂。像单位换算这种高频小工具,只要做得快、准、稳,就能显著提升应用的实用价值。🚀