基于HarmonyOS的智能单位转换器开发与跨设备同步实现 原创

进修的泡芙
发布于 2025-6-18 21:05
浏览
0收藏

基于HarmonyOS的智能单位转换器开发与跨设备同步实现

一、项目概述

本项目基于HarmonyOS的ArkUI框架和分布式能力,开发一个支持多设备同步的单位转换器应用,包含长度和重量单位的相互转换功能。参考《鸿蒙跨端U同步:同一局游戏中多设备玩家昵称/头像显示》中的分布式数据同步技术,实现转换记录在多设备间的实时共享。

!https://example.com/unit-converter-arch.png
图1:单位转换器架构(包含UI层、转换逻辑层和分布式数据同步层)

二、核心功能实现
数据模型与转换逻辑(ArkTS)

// 单位类型枚举
enum UnitType {
LENGTH = ‘长度’,
WEIGHT = ‘重量’
// 单位定义接口

interface Unit {
name: string;
symbol: string;
toBase: (value: number) => number; // 转换为基本单位
fromBase: (value: number) => number; // 从基本单位转换
// 转换记录模型

class ConversionRecord {
id: string;
value: number;
fromUnit: Unit;
toUnit: Unit;
result: number;
timestamp: number;
deviceId: string;

constructor(value: number, fromUnit: Unit, toUnit: Unit, result: number) {
this.id = generateUUID();
this.value = value;
this.fromUnit = fromUnit;
this.toUnit = toUnit;
this.result = result;
this.timestamp = Date.now();
this.deviceId = deviceInfo.deviceId;
}

// 单位转换管理器
class UnitConverter {
private static instance: UnitConverter;
private lengthUnits: Unit[] = [];
private weightUnits: Unit[] = [];
private conversionHistory: ConversionRecord[] = [];

static getInstance(): UnitConverter {
if (!UnitConverter.instance) {
UnitConverter.instance = new UnitConverter();
return UnitConverter.instance;

constructor() {

this.initializeUnits();
this.loadHistory();

// 初始化单位定义

private initializeUnits() {
// 长度单位
this.lengthUnits = [
name: ‘毫米’,

    symbol: 'mm',
    toBase: (v) => v,
    fromBase: (v) => v
  },

name: ‘厘米’,

    symbol: 'cm',
    toBase: (v) => v * 10,
    fromBase: (v) => v / 10
  },

name: ‘米’,

    symbol: 'm',
    toBase: (v) => v * 1000,
    fromBase: (v) => v / 1000
  },

name: ‘千米’,

    symbol: 'km',
    toBase: (v) => v * 1000000,
    fromBase: (v) => v / 1000000
  },

name: ‘英寸’,

    symbol: 'in',
    toBase: (v) => v * 25.4,
    fromBase: (v) => v / 25.4
  },

name: ‘英尺’,

    symbol: 'ft',
    toBase: (v) => v * 304.8,
    fromBase: (v) => v / 304.8

];

// 重量单位
this.weightUnits = [

name: ‘克’,

    symbol: 'g',
    toBase: (v) => v,
    fromBase: (v) => v
  },

name: ‘千克’,

    symbol: 'kg',
    toBase: (v) => v * 1000,
    fromBase: (v) => v / 1000
  },

name: ‘吨’,

    symbol: 't',
    toBase: (v) => v * 1000000,
    fromBase: (v) => v / 1000000
  },

name: ‘磅’,

    symbol: 'lb',
    toBase: (v) => v * 453.592,
    fromBase: (v) => v / 453.592
  },

name: ‘盎司’,

    symbol: 'oz',
    toBase: (v) => v * 28.3495,
    fromBase: (v) => v / 28.3495

];

// 执行单位转换

convert(value: number, fromUnit: Unit, toUnit: Unit): number {
if (fromUnit === toUnit) return value;

// 转换为基本单位,再转换为目标单位
const baseValue = fromUnit.toBase(value);
const result = toUnit.fromBase(baseValue);

// 记录转换历史
const record = new ConversionRecord(value, fromUnit, toUnit, result);
this.addToHistory(record);

return result;

// 添加历史记录

addToHistory(record: ConversionRecord) {
this.conversionHistory.unshift(record);
this.saveHistory();
// 获取历史记录

getHistory(): ConversionRecord[] {
return […this.conversionHistory];
// 清空历史记录

clearHistory() {
this.conversionHistory = [];
this.saveHistory();
// 加载历史记录

private loadHistory() {
const history = localStorage.get(‘conversionHistory’);
if (history) {
this.conversionHistory = JSON.parse(history);
}

// 保存历史记录
private saveHistory() {
localStorage.set(‘conversionHistory’, JSON.stringify(this.conversionHistory));
// 获取单位列表

getUnits(type: UnitType): Unit[] {
return type === UnitType.LENGTH ? […this.lengthUnits] : […this.weightUnits];
}

分布式数据同步实现(ArkTS)

// 分布式转换记录同步服务
class ConversionSyncService {
private static instance: ConversionSyncService;
private distObject: distributedDataObject.DataObject;
private deviceList: deviceManager.DeviceInfo[] = [];

static getInstance(): ConversionSyncService {
if (!ConversionSyncService.instance) {
ConversionSyncService.instance = new ConversionSyncService();
return ConversionSyncService.instance;

constructor() {

// 初始化分布式数据对象
this.distObject = distributedDataObject.create({
  conversionHistory: [],
  deviceList: []
});

// 监听设备连接变化
deviceManager.on('deviceStateChange', () => {
  this.updateDeviceList();
});

// 监听数据变化
this.distObject.on('change', (fields: string[]) => {
  if (fields.includes('conversionHistory')) {
    this.handleHistoryUpdate();

});

this.updateDeviceList();

// 更新设备列表

private updateDeviceList() {
this.deviceList = deviceManager.getConnectedDevices();
this.distObject.deviceList = this.deviceList.map(d => ({
id: d.deviceId,
name: d.deviceName,
lastSync: Date.now()
}));

this.distObject.setDistributed(this.getConnectedDeviceIds());

// 同步转换记录

syncConversion(record: ConversionRecord) {
const history = this.distObject.conversionHistory as ConversionRecord[];
this.distObject.conversionHistory = […history, record];
this.distObject.setDistributed(this.getConnectedDeviceIds());
// 处理历史记录更新

private handleHistoryUpdate() {
const remoteHistory = this.distObject.conversionHistory as ConversionRecord[];
const localHistory = UnitConverter.getInstance().getHistory();

// 合并策略:保留最新的100条记录
const mergedHistory = [...localHistory, ...remoteHistory]
  .sort((a, b) => b.timestamp - a.timestamp)
  .filter((v, i, a) => a.findIndex(t => t.id = v.id) = i)
  .slice(0, 100);

UnitConverter.getInstance().updateHistory(mergedHistory);

// 获取已连接设备ID

private getConnectedDeviceIds(): string[] {
return this.deviceList
.map(d => d.deviceId)
.filter(id => id !== deviceInfo.deviceId);
// 获取设备名称

getDeviceName(deviceId: string): string {
if (deviceId === deviceInfo.deviceId) return ‘本机’;

const device = this.deviceList.find(d => d.deviceId === deviceId);
return device?.deviceName || '其他设备';

}

UI界面实现(ArkTS)

// 主页面组件
@Entry
@Component
struct UnitConverterPage {
@State currentTab: UnitType = UnitType.LENGTH;
@State inputValue: string = ‘’;
@State fromUnit: Unit = UnitConverter.getInstance().getUnits(UnitType.LENGTH)[0];
@State toUnit: Unit = UnitConverter.getInstance().getUnits(UnitType.LENGTH)[1];
@State result: number = 0;
@State showHistory: boolean = false;

private converter = UnitConverter.getInstance();
private syncService = ConversionSyncService.getInstance();

build() {
Column() {
// 标题栏
Row() {
Text(‘单位转换器’)
.fontSize(24)
.fontWeight(FontWeight.Bold)

    // 设备同步状态
    DeviceSyncIndicator()

.width(‘100%’)

  .justifyContent(FlexAlign.SpaceBetween)
  .padding(16)
  
  // 标签页
  Tabs({ barPosition: BarPosition.Start }) {
    TabContent() {
      this.buildConverterContent(UnitType.LENGTH)
    }.tabBar('长度')
    
    TabContent() {
      this.buildConverterContent(UnitType.WEIGHT)
    }.tabBar('重量')

.onChange((index: number) => {

    this.currentTab = index === 0 ? UnitType.LENGTH : UnitType.WEIGHT;
    this.resetUnits();
  })
  .width('100%')
  .layoutWeight(1)

.height(‘100%’)

.backgroundColor('#F5F5F5')

// 构建转换器内容

@Builder
buildConverterContent(type: UnitType) {
Column() {
// 输入区域
Row() {
TextInput({ placeholder: ‘输入数值’ })
.width(‘40%’)
.type(InputType.Number)
.onChange((value: string) => {
this.inputValue = value;
this.calculateResult();
})

    UnitPicker({
      units: this.converter.getUnits(type),
      selectedUnit: this.fromUnit,
      onUnitChange: (unit: Unit) => {
        this.fromUnit = unit;
        this.calculateResult();

})

    Text('→')
      .fontSize(20)
      .margin({ left: 8, right: 8 })
      
    UnitPicker({
      units: this.converter.getUnits(type),
      selectedUnit: this.toUnit,
      onUnitChange: (unit: Unit) => {
        this.toUnit = unit;
        this.calculateResult();

})

.width(‘100%’)

  .padding(16)
  
  // 结果展示
  if (this.inputValue) {
    Column() {
      Text({this.inputValue} {this.fromUnit.symbol} =)
        .fontSize(18)
        
      Text({this.result.toFixed(6)} {this.toUnit.symbol})
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 8 })

.width(‘100%’)

    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)

// 操作按钮

  Row() {
    Button('历史记录')
      .width('40%')
      .onClick(() => {
        this.showHistory = true;
      })
      
    Button('交换单位')
      .width('40%')
      .margin({ left: 16 })
      .onClick(() => {
        const temp = this.fromUnit;
        this.fromUnit = this.toUnit;
        this.toUnit = temp;
        this.calculateResult();
      })

.width(‘100%’)

  .padding(16)
  .justifyContent(FlexAlign.Center)

// 历史记录弹窗

if (this.showHistory) {
  HistoryPanel({
    history: this.converter.getHistory(),
    onClose: () => {
      this.showHistory = false;

})

}

// 计算转换结果
private calculateResult() {
if (!this.inputValue) return;

const value = parseFloat(this.inputValue);
if (isNaN(value)) return;

this.result = this.converter.convert(value, this.fromUnit, this.toUnit);
this.syncService.syncConversion(
  new ConversionRecord(value, this.fromUnit, this.toUnit, this.result)
);

// 重置单位选择

private resetUnits() {
const units = this.converter.getUnits(this.currentTab);
this.fromUnit = units[0];
this.toUnit = units[1];
this.result = 0;
}

// 单位选择器组件
@Component
struct UnitPicker {
@Prop units: Unit[];
@Prop selectedUnit: Unit;
@Link onUnitChange: (unit: Unit) => void;

@State showPicker: boolean = false;

build() {
Column() {
Button(this.selectedUnit.name)
.width(120)
.onClick(() => {
this.showPicker = true;
})

  if (this.showPicker) {
    Column() {
      ForEach(this.units, (unit: Unit) => {
        Button(unit.name)
          .width('100%')
          .backgroundColor(unit === this.selectedUnit ? '#E3F2FD' : '#FFFFFF')
          .onClick(() => {
            this.onUnitChange(unit);
            this.showPicker = false;
          })
      })

.width(120)

    .border({ width: 1, color: '#E0E0E0' })
    .borderRadius(4)
    .margin({ top: 4 })

}

}

// 历史记录面板组件
@Component
struct HistoryPanel {
@Prop history: ConversionRecord[];
@Link onClose: () => void;

private syncService = ConversionSyncService.getInstance();

build() {
Column() {
// 标题栏
Row() {
Text(‘转换历史’)
.fontSize(20)
.fontWeight(FontWeight.Bold)

    Button('关闭')
      .onClick(() => {
        this.onClose();
      })

.width(‘100%’)

  .justifyContent(FlexAlign.SpaceBetween)
  .padding(16)
  
  // 历史记录列表
  List({ space: 8 }) {
    ForEach(this.history, (record: ConversionRecord) => {
      ListItem() {
        HistoryItem({ record: record })

})

.layoutWeight(1)

  .width('100%')
  
  // 清空按钮
  Button('清空历史记录')
    .width('80%')
    .margin(16)
    .onClick(() => {
      UnitConverter.getInstance().clearHistory();
    })

.width(‘90%’)

.height('70%')
.backgroundColor('#FFFFFF')
.borderRadius(16)
.position({ x: '5%', y: '15%' })

}

// 历史记录项组件
@Component
struct HistoryItem {
@Prop record: ConversionRecord;

private syncService = ConversionSyncService.getInstance();

build() {
Row() {
Column() {
Text({this.record.value} {this.record.fromUnit.symbol} → {this.record.result.toFixed(4)} {this.record.toUnit.symbol})
.fontSize(16)

    Row() {
      Text(formatTime(this.record.timestamp))
        .fontSize(12)
        .fontColor('#757575')
        
      Text(来自: ${this.syncService.getDeviceName(this.record.deviceId)})
        .fontSize(12)
        .fontColor('#757575')
        .margin({ left: 8 })

.margin({ top: 4 })

.layoutWeight(1)

.width(‘100%’)

.padding(16)
.backgroundColor('#FAFAFA')
.borderRadius(8)

}

// 设备同步状态指示器组件
@Component
struct DeviceSyncIndicator {
@State connectedDevices: number = 0;

aboutToAppear() {
deviceManager.on(‘deviceStateChange’, () => {
this.connectedDevices = deviceManager.getConnectedDevices().length;
});

this.connectedDevices = deviceManager.getConnectedDevices().length;

build() {

Row() {
  Image($r('app.media.ic_sync'))
    .width(16)
    .height(16)
    
  Text(${this.connectedDevices})
    .fontSize(16)
    .margin({ left: 4 })

}

// 辅助函数:格式化时间

function formatTime(timestamp: number): string {
const date = new Date(timestamp);
return {date.getMonth()+1}/{date.getDate()} {date.getHours()}:{date.getMinutes().toString().padStart(2, ‘0’)};

三、关键功能说明
单位转换核心算法

// 转换算法实现
convert(value: number, fromUnit: Unit, toUnit: Unit): number {
if (fromUnit === toUnit) return value;

// 转换为基本单位,再转换为目标单位
const baseValue = fromUnit.toBase(value);
const result = toUnit.fromBase(baseValue);

return result;

分布式数据同步流程

数据同步触发:

  // 转换完成后同步记录

this.syncService.syncConversion(
new ConversionRecord(value, fromUnit, toUnit, result)
);

数据接收处理:

  // 处理远程更新

private handleHistoryUpdate() {
const remoteHistory = this.distObject.conversionHistory as ConversionRecord[];
// 合并到本地历史…

数据类型转换实践

转换场景 实现方式 注意事项

字符串→数字 parseFloat(inputValue) 处理NaN情况
数字→字符串 result.toFixed(6) 控制小数位数
单位间转换 通过基本单位中转 保持精度

四、项目扩展与优化
功能扩展建议

更多单位类型:

  enum UnitType {
 LENGTH = '长度',
 WEIGHT = '重量',
 TEMPERATURE = '温度',
 VOLUME = '体积'

收藏常用转换:

  interface FavoriteConversion {
 fromUnit: Unit;
 toUnit: Unit;

实时货币转换:

  // 集成汇率API实现货币转换

性能优化建议

历史记录分页:

  getHistory(page: number, pageSize: number): ConversionRecord[] {
 return this.conversionHistory.slice(
   (page - 1) * pageSize,
   page * pageSize
 );

数据同步优化:

添加节流机制

实现差异同步

使用二进制格式传输

五、测试方案
测试用例设计

测试类型 测试场景 验证点

功能测试 单位转换 结果计算准确
功能测试 历史记录 正确保存和显示
同步测试 多设备转换 历史记录同步
性能测试 大数据量 列表滚动流畅
兼容测试 不同设备 布局适配正常

自动化测试示例

// 单位转换测试
describe(‘UnitConverter Tests’, () => {
let converter: UnitConverter;

before(() => {
converter = UnitConverter.getInstance();
});

it(‘should convert meters to centimeters’, () => {
const meters = converter.getUnits(UnitType.LENGTH)[2]; // 米
const centimeters = converter.getUnits(UnitType.LENGTH)[1]; // 厘米
const result = converter.convert(1, meters, centimeters);
expect(result).toBe(100);
});

it(‘should sync conversion records’, () => {
const syncService = ConversionSyncService.getInstance();
const record = new ConversionRecord(1, meters, centimeters, 100);

syncService.syncConversion(record);
expect(syncService['distObject'].conversionHistory.length).toBe(1);

});
});

六、总结

本项目基于HarmonyOS开发了一个功能完善的单位转换器,主要特点包括:
精准的单位转换:支持多种长度和重量单位的相互转换

完整的历史记录:保存每次转换记录并支持跨设备同步

直观的用户界面:清晰的输入输出和单位选择

分布式能力集成:实现多设备间的转换记录共享

通过参考《鸿蒙跨端U同步:同一局游戏中多设备玩家昵称/头像显示》的技术方案,我们验证了HarmonyOS在分布式数据同步方面的强大能力,为开发者提供了构建跨设备协同应用的实践参考。

注意事项:
实际开发中需要处理浮点数精度问题

考虑添加单位换算公式说明

生产环境需要更完善的错误处理

可根据需求扩展更多单位类型

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐