【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'这几个状态组合起来就能表达一个完整换算任务:
输入值inputValue从fromUnit换算到toUnit所属类别selectedCategory输出result比如:
2.5千米 -> 米 =2500米36摄氏度 -> 华氏度 =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,不一定每个功能都复杂。像单位换算这种高频小工具,只要做得快、准、稳,就能显著提升应用的实用价值。🚀