第一部分:门禁设备绑定与配网全流程解析

1.1 业务背景与核心价值

在益康养老场景中,门禁设备的智能化管理是保障老人安全、提升护理效率的关键环节。通过HarmonyOS APP实现蓝牙门禁设备的快速绑定与WiFi配网,使护理员能够:

  • 一键式设备管理:简化传统繁琐的硬件配置流程
  • 实时状态监控:随时掌握门禁设备在线状态
  • 远程控制能力:实现跨设备、跨场景的智能门禁控制
  • 安全认证机制:确保只有授权人员可操作特定门禁

1.2 整体架构与交互流程

WiFi路由器 后端服务器 蓝牙门禁设备 鸿蒙APP 护理员(用户) WiFi路由器 后端服务器 蓝牙门禁设备 鸿蒙APP 护理员(用户) 第一阶段:设备发现与绑定 第二阶段:WiFi网络配置 第三阶段:设备控制与验证 进入设备管理页面 启动蓝牙扫描(BLE) 广播设备信息(名称:JL-ITHEIMA_XXX) 过滤并显示门禁设备列表 选择目标设备 发起蓝牙连接请求 连接建立成功 调用设备绑定API 返回绑定成功与设备凭证 本地存储设备信息 点击"配网"按钮 扫描周边WiFi网络 返回WiFi列表(SSID,信号强度) 选择目标WiFi并输入密码 通过蓝牙发送WiFi凭证(加密) 尝试连接WiFi网络 返回连接结果 上报设备在线状态 确认配网完成 点击"开门"测试 验证用户权限 权限验证通过 发送开门指令(加密) 执行开门动作 返回执行结果 显示操作成功

1.3 详细实现方案

1.3.1 蓝牙设备发现与连接
// features/device/src/main/ets/bluetooth/AccessControlManager.ets
import { ble, connection, wifi } from '@ohos.bluetooth';
import { BusinessError } from '@ohos.base';
import { Logger } from '@elderly/basic';

/**
 * 门禁设备管理器
 * 负责蓝牙设备的扫描、连接、绑定和配网
 */
export class AccessControlManager {
  private static instance: AccessControlManager;
  private isScanning: boolean = false;
  private discoveredDevices: Array<BluetoothDevice> = [];
  private connectedDevice: BluetoothDevice | null = null;
  
  // 门禁设备名称前缀约定
  private static readonly DEVICE_NAME_PREFIX: string = 'JL-ITHEIMA';
  // 门禁设备服务UUID约定
  private static readonly SERVICE_UUID: string = '0000AE30-0000-1000-8000-00805F9B34FB';
  // 写入特征值UUID
  private static readonly WRITE_CHAR_UUID: string = '0000AE10-0000-1000-8000-00805F9B34FB';
  // 通知特征值UUID
  private static readonly NOTIFY_CHAR_UUID: string = '0000AE04-0000-1000-8000-00805F9B34FB';
  
  public static getInstance(): AccessControlManager {
    if (!AccessControlManager.instance) {
      AccessControlManager.instance = new AccessControlManager();
    }
    return AccessControlManager.instance;
  }
  
  /**
   * 开始扫描门禁设备
   * @param duration 扫描持续时间(毫秒),默认10秒
   */
  public async startDeviceScan(duration: number = 10000): Promise<Array<BluetoothDevice>> {
    if (this.isScanning) {
      Logger.warn('AccessControlManager', '设备扫描已在进行中');
      return this.discoveredDevices;
    }
    
    try {
      this.isScanning = true;
      this.discoveredDevices = [];
      
      Logger.info('AccessControlManager', '开始扫描门禁设备...');
      
      // 注册蓝牙扫描回调
      ble.on('BLEDeviceFind', (device: ble.ScanResult) => {
        this.handleDiscoveredDevice(device);
      });
      
      // 开始BLE扫描
      await ble.startBLEScan({
        interval: 0,
        dutyMode: ble.ScanDuty.SCAN_DUTY_LOW_POWER,
        matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE
      });
      
      // 设置扫描超时
      setTimeout(() => {
        this.stopDeviceScan();
        Logger.info('AccessControlManager', `扫描完成,共发现${this.discoveredDevices.length}个设备`);
      }, duration);
      
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('AccessControlManager', `设备扫描失败: ${err.code}, ${err.message}`);
      this.isScanning = false;
      throw new Error(`蓝牙扫描启动失败: ${err.message}`);
    }
    
    return this.discoveredDevices;
  }
  
  /**
   * 处理发现的设备
   */
  private handleDiscoveredDevice(scanResult: ble.ScanResult): void {
    // 过滤门禁设备:名称以特定前缀开头
    if (scanResult.deviceName && scanResult.deviceName.startsWith(AccessControlManager.DEVICE_NAME_PREFIX)) {
      const device: BluetoothDevice = {
        deviceId: scanResult.deviceId,
        deviceName: scanResult.deviceName,
        rssi: scanResult.rssi,
        isConnectable: scanResult.isConnectable
      };
      
      // 去重检查
      const existingIndex = this.discoveredDevices.findIndex(d => d.deviceId === device.deviceId);
      if (existingIndex === -1) {
        this.discoveredDevices.push(device);
        Logger.debug('AccessControlManager', `发现门禁设备: ${device.deviceName} (${device.rssi}dBm)`);
        
        // 通知UI更新
        emitter.emit({
          eventId: EventConstants.DEVICE_DISCOVERED
        }, { device });
      }
    }
  }
  
  /**
   * 连接门禁设备
   */
  public async connectDevice(deviceId: string): Promise<boolean> {
    try {
      Logger.info('AccessControlManager', `开始连接设备: ${deviceId}`);
      
      // 停止扫描
      if (this.isScanning) {
        this.stopDeviceScan();
      }
      
      // 建立蓝牙连接
      await connection.connect({
        deviceId,
        isAutoConnect: false
      });
      
      // 发现服务
      const services = await ble.getBLEDeviceServices(deviceId);
      const targetService = services.find(service => 
        service.serviceUuid === AccessControlManager.SERVICE_UUID
      );
      
      if (!targetService) {
        throw new Error('未找到门禁设备服务');
      }
      
      // 获取特征值
      const characteristics = await ble.getBLEDeviceCharacteristics(deviceId, targetService.serviceUuid);
      const writeChar = characteristics.find(char => 
        char.characteristicUuid === AccessControlManager.WRITE_CHAR_UUID
      );
      const notifyChar = characteristics.find(char => 
        char.characteristicUuid === AccessControlManager.NOTIFY_CHAR_UUID
      );
      
      if (!writeChar || !notifyChar) {
        throw new Error('未找到必要的特征值');
      }
      
      // 开启通知
      await ble.setBLECharacteristicChangeNotification(deviceId, {
        serviceUuid: targetService.serviceUuid,
        characteristicUuid: notifyChar.characteristicUuid,
        enable: true
      });
      
      // 监听通知
      ble.on('BLECharacteristicChange', (data: ble.NotifyCharacteristic) => {
        this.handleDeviceNotification(data);
      });
      
      this.connectedDevice = this.discoveredDevices.find(d => d.deviceId === deviceId) || null;
      Logger.info('AccessControlManager', '门禁设备连接成功');
      
      return true;
      
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('AccessControlManager', `设备连接失败: ${err.code}, ${err.message}`);
      return false;
    }
  }
  
  /**
   * 处理设备通知
   */
  private handleDeviceNotification(data: ble.NotifyCharacteristic): void {
    Logger.debug('AccessControlManager', `收到设备通知: ${Array.from(data.characteristicValue).join(',')}`);
    
    // 根据协议解析设备状态
    const value = new Uint8Array(data.characteristicValue);
    
    // 示例:根据数据长度判断消息类型
    switch (value.length) {
      case 1: // 连接状态
        if (value[0] === 0x01) {
          emitter.emit({
            eventId: EventConstants.DEVICE_CONNECTED
          }, { status: 'connected' });
        }
        break;
      case 2: // WiFi连接状态
        const wifiStatus = value[0];
        const signalStrength = value[1];
        emitter.emit({
          eventId: EventConstants.WIFI_STATUS_UPDATE
        }, { wifiStatus, signalStrength });
        break;
      default:
        Logger.debug('AccessControlManager', `未知通知数据长度: ${value.length}`);
    }
  }
  
  /**
   * 停止设备扫描
   */
  public stopDeviceScan(): void {
    if (!this.isScanning) {
      return;
    }
    
    try {
      ble.off('BLEDeviceFind');
      ble.stopBLEScan();
      Logger.info('AccessControlManager', '设备扫描已停止');
    } catch (error) {
      Logger.error('AccessControlManager', '停止扫描失败');
    } finally {
      this.isScanning = false;
    }
  }
}

// 设备数据类型定义
export interface BluetoothDevice {
  deviceId: string;
  deviceName: string;
  rssi: number;
  isConnectable: boolean;
}
1.3.2 WiFi配网模块实现
// features/device/src/main/ets/wifi/DeviceNetworkingManager.ets
import { wifi, connection } from '@ohos.wifi';
import { BusinessError } from '@ohos.base';
import { Logger } from '@elderly/basic';
import { AccessControlManager } from '../bluetooth/AccessControlManager';

/**
 * 设备配网管理器
 * 负责WiFi网络扫描、密码配置和网络连接状态管理
 */
export class DeviceNetworkingManager {
  private static instance: DeviceNetworkingManager;
  private accessControlManager: AccessControlManager;
  private isScanningWifi: boolean = false;
  private wifiList: Array<WifiScanInfo> = [];
  
  private constructor() {
    this.accessControlManager = AccessControlManager.getInstance();
  }
  
  public static getInstance(): DeviceNetworkingManager {
    if (!DeviceNetworkingManager.instance) {
      DeviceNetworkingManager.instance = new DeviceNetworkingManager();
    }
    return DeviceNetworkingManager.instance;
  }
  
  /**
   * 检查WiFi状态
   */
  public async checkWifiStatus(): Promise<WifiStatus> {
    try {
      const isActive = await wifi.isWifiActive();
      if (!isActive) {
        return {
          isEnabled: false,
          message: 'WiFi未开启,请先打开WiFi'
        };
      }
      
      const connectionInfo = await wifi.getLinkedInfo();
      return {
        isEnabled: true,
        isConnected: connectionInfo !== null,
        ssid: connectionInfo?.ssid,
        signalLevel: this.calculateSignalLevel(connectionInfo?.rssi || -100)
      };
      
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('DeviceNetworkingManager', `检查WiFi状态失败: ${err.message}`);
      return {
        isEnabled: false,
        message: '无法获取WiFi状态'
      };
    }
  }
  
  /**
   * 扫描周边WiFi网络
   */
  public async scanWifiNetworks(): Promise<Array<WifiScanInfo>> {
    if (this.isScanningWifi) {
      Logger.warn('DeviceNetworkingManager', 'WiFi扫描已在进行中');
      return this.wifiList;
    }
    
    try {
      this.isScanningWifi = true;
      this.wifiList = [];
      
      Logger.info('DeviceNetworkingManager', '开始扫描WiFi网络...');
      
      // 请求WiFi扫描权限(需在module.json5中声明)
      await this.requestWifiPermissions();
      
      // 开始扫描
      await wifi.scan();
      
      // 获取扫描结果
      const scanResults = await wifi.getScanInfoList();
      
      // 过滤和排序:按信号强度降序排列
      this.wifiList = scanResults
        .filter(wifi => !wifi.ssid.includes('<unknown ssid>') && wifi.ssid.trim() !== '')
        .sort((a, b) => (b.rssi || -100) - (a.rssi || -100))
        .map(wifi => ({
          ssid: wifi.ssid,
          bssid: wifi.bssid,
          securityType: wifi.securityType,
          rssi: wifi.rssi,
          band: wifi.band,
          frequency: wifi.frequency,
          timestamp: wifi.timestamp
        }));
      
      Logger.info('DeviceNetworkingManager', `扫描完成,发现${this.wifiList.length}个WiFi网络`);
      
      return this.wifiList;
      
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('DeviceNetworkingManager', `WiFi扫描失败: ${err.message}`);
      throw new Error(`WiFi扫描失败: ${err.message}`);
    } finally {
      this.isScanningWifi = false;
    }
  }
  
  /**
   * 为设备配置WiFi网络
   */
  public async configureDeviceWifi(ssid: string, password: string): Promise<NetworkConfigResult> {
    try {
      // 验证输入
      if (!ssid || ssid.trim().length === 0) {
        return {
          success: false,
          message: 'SSID不能为空'
        };
      }
      
      if (!password || password.length < 8) {
        return {
          success: false,
          message: '密码至少需要8个字符'
        };
      }
      
      Logger.info('DeviceNetworkingManager', `开始为设备配置WiFi: ${ssid}`);
      
      // 构建配网指令数据
      const configData = this.buildWifiConfigData(ssid, password);
      
      // 通过蓝牙发送配网指令
      const sendResult = await this.sendWifiConfigToDevice(configData);
      
      if (!sendResult) {
        return {
          success: false,
          message: '发送配网指令失败'
        };
      }
      
      // 等待设备连接WiFi(轮询状态)
      const connectionResult = await this.waitForDeviceConnection(ssid, 30000);
      
      if (connectionResult.success) {
        Logger.info('DeviceNetworkingManager', `设备成功连接到WiFi: ${ssid}`);
        
        // 上报设备网络状态到服务器
        await this.reportDeviceNetworkStatus(ssid, 'connected');
        
        return {
          success: true,
          message: '配网成功',
          ssid,
          ipAddress: connectionResult.ipAddress
        };
      } else {
        return {
          success: false,
          message: `设备连接WiFi失败: ${connectionResult.message}`
        };
      }
      
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('DeviceNetworkingManager', `配网过程出错: ${err.message}`);
      return {
        success: false,
        message: `配网失败: ${err.message}`
      };
    }
  }
  
  /**
   * 构建WiFi配置数据
   */
  private buildWifiConfigData(ssid: string, password: string): Uint8Array {
    // 协议示例:数据头(0xAA) + 指令(0x01) + SSID长度 + SSID + 密码长度 + 密码
    const encoder = new TextEncoder();
    const ssidBytes = encoder.encode(ssid);
    const passwordBytes = encoder.encode(password);
    
    const data = new Uint8Array(4 + ssidBytes.length + 1 + passwordBytes.length);
    let offset = 0;
    
    // 数据头
    data[offset++] = 0xAA;
    // 指令:WiFi配置
    data[offset++] = 0x01;
    // SSID长度
    data[offset++] = ssidBytes.length;
    // SSID内容
    data.set(ssidBytes, offset);
    offset += ssidBytes.length;
    // 密码长度
    data[offset++] = passwordBytes.length;
    // 密码内容
    data.set(passwordBytes, offset);
    
    return data;
  }
  
  /**
   * 等待设备连接WiFi
   */
  private async waitForDeviceConnection(ssid: string, timeout: number): Promise<DeviceConnectionResult> {
    return new Promise((resolve) => {
      const startTime = Date.now();
      let connectionChecked = false;
      
      // 监听设备WiFi状态通知
      const handler = emitter.on(EventConstants.WIFI_STATUS_UPDATE, (data: any) => {
        if (data.wifiStatus === 0x01 && !connectionChecked) { // 0x01表示连接成功
          connectionChecked = true;
          emitter.off(handler);
          
          // 模拟获取设备IP地址(实际应从设备获取)
          const ipAddress = this.generateRandomIP();
          
          resolve({
            success: true,
            ssid,
            ipAddress,
            message: '连接成功'
          });
        }
      });
      
      // 超时检查
      const checkTimeout = () => {
        if (!connectionChecked && (Date.now() - startTime) > timeout) {
          emitter.off(handler);
          resolve({
            success: false,
            ssid,
            message: '连接超时'
          });
        } else if (!connectionChecked) {
          setTimeout(checkTimeout, 1000);
        }
      };
      
      checkTimeout();
    });
  }
  
  /**
   * 上报设备网络状态
   */
  private async reportDeviceNetworkStatus(ssid: string, status: string): Promise<void> {
    try {
      // 这里调用后端API上报设备状态
      // 示例代码,实际应使用HttpService
      Logger.info('DeviceNetworkingManager', `上报设备网络状态: ${ssid} - ${status}`);
      
      // 模拟API调用
      await new Promise(resolve => setTimeout(resolve, 500));
      
    } catch (error) {
      Logger.warn('DeviceNetworkingManager', '上报设备状态失败,将在下次同步时重试');
    }
  }
}

// 类型定义
export interface WifiStatus {
  isEnabled: boolean;
  isConnected?: boolean;
  ssid?: string;
  signalLevel?: number;
  message?: string;
}

export interface WifiScanInfo {
  ssid: string;
  bssid: string;
  securityType: number;
  rssi: number;
  band: number;
  frequency: number;
  timestamp: number;
}

export interface NetworkConfigResult {
  success: boolean;
  message: string;
  ssid?: string;
  ipAddress?: string;
}

export interface DeviceConnectionResult {
  success: boolean;
  ssid?: string;
  ipAddress?: string;
  message: string;
}
1.3.3 设备绑定页面实现
// features/device/src/main/ets/pages/DeviceBindingPage.ets
import { AccessControlManager } from '../bluetooth/AccessControlManager';
import { DeviceNetworkingManager } from '../wifi/DeviceNetworkingManager';
import { Logger } from '@elderly/basic';
import { Router } from '@elderly/basic';
import { HttpService } from '@elderly/basic';

@Component
export struct DeviceBindingPage {
  @State deviceList: Array<BluetoothDevice> = [];
  @State selectedDevice: BluetoothDevice | null = null;
  @State currentStep: number = 1; // 1: 扫描设备, 2: 绑定设备, 3: 配置WiFi, 4: 完成
  @State isLoading: boolean = false;
  @State statusMessage: string = '点击开始扫描设备';
  
  private accessControlManager: AccessControlManager = AccessControlManager.getInstance();
  private networkingManager: DeviceNetworkingManager = DeviceNetworkingManager.getInstance();
  private httpService: HttpService = new HttpService('https://api.elderlycare.com');
  
  aboutToAppear(): void {
    // 监听设备发现事件
    emitter.on(EventConstants.DEVICE_DISCOVERED, this.handleDeviceDiscovered.bind(this));
    // 监听设备连接事件
    emitter.on(EventConstants.DEVICE_CONNECTED, this.handleDeviceConnected.bind(this));
  }
  
  aboutToDisappear(): void {
    // 清理事件监听
    emitter.off(EventConstants.DEVICE_DISCOVERED);
    emitter.off(EventConstants.DEVICE_CONNECTED);
    // 停止扫描
    this.accessControlManager.stopDeviceScan();
  }
  
  /**
   * 开始扫描设备
   */
  private async startScanning(): Promise<void> {
    if (this.isLoading) return;
    
    this.isLoading = true;
    this.statusMessage = '正在扫描附近的设备...';
    this.deviceList = [];
    
    try {
      const devices = await this.accessControlManager.startDeviceScan(15000);
      this.deviceList = devices;
      
      if (devices.length === 0) {
        this.statusMessage = '未发现门禁设备,请确保设备已开启并在范围内';
      } else {
        this.statusMessage = `发现 ${devices.length} 个设备,请选择要绑定的设备`;
      }
    } catch (error) {
      this.statusMessage = '扫描失败,请检查蓝牙权限和开关';
      Logger.error('DeviceBindingPage', `扫描失败: ${error.message}`);
    } finally {
      this.isLoading = false;
    }
  }
  
  /**
   * 选择设备
   */
  private selectDevice(device: BluetoothDevice): void {
    this.selectedDevice = device;
    this.currentStep = 2;
    this.statusMessage = `已选择设备: ${device.deviceName}`;
  }
  
  /**
   * 绑定设备
   */
  private async bindDevice(): Promise<void> {
    if (!this.selectedDevice) {
      return;
    }
    
    this.isLoading = true;
    this.statusMessage = '正在连接并绑定设备...';
    
    try {
      // 1. 连接设备
      const connected = await this.accessControlManager.connectDevice(this.selectedDevice.deviceId);
      
      if (!connected) {
        this.statusMessage = '设备连接失败';
        return;
      }
      
      // 2. 调用绑定API
      const bindResult = await this.httpService.post('/api/v1/devices/bind', {
        deviceId: this.selectedDevice.deviceId,
        deviceName: this.selectedDevice.deviceName,
        deviceType: 'access_control'
      });
      
      if (bindResult.code === 200) {
        Logger.info('DeviceBindingPage', '设备绑定成功');
        this.currentStep = 3; // 进入配网步骤
        this.statusMessage = '设备绑定成功,请配置WiFi网络';
      } else {
        this.statusMessage = `绑定失败: ${bindResult.message}`;
      }
      
    } catch (error) {
      this.statusMessage = `绑定过程出错: ${error.message}`;
      Logger.error('DeviceBindingPage', `绑定失败: ${error.message}`);
    } finally {
      this.isLoading = false;
    }
  }
  
  /**
   * 处理设备发现事件
   */
  private handleDeviceDiscovered(data: any): void {
    const device: BluetoothDevice = data.device;
    
    // 更新设备列表
    const existingIndex = this.deviceList.findIndex(d => d.deviceId === device.deviceId);
    if (existingIndex === -1) {
      this.deviceList = [...this.deviceList, device];
    }
  }
  
  /**
   * 处理设备连接事件
   */
  private handleDeviceConnected(data: any): void {
    Logger.debug('DeviceBindingPage', '设备连接状态更新');
  }
  
  /**
   * 渲染扫描步骤
   */
  @Builder
  private renderScanStep() {
    Column({ space: 20 }) {
      // 扫描状态显示
      Text(this.statusMessage)
        .fontSize(18)
        .fontColor(this.isLoading ? Color.Blue : Color.Gray)
        .textAlign(TextAlign.Center)
        .width('100%')
      
      // 扫描按钮
      Button(this.isLoading ? '扫描中...' : '开始扫描')
        .width('80%')
        .height(50)
        .backgroundColor(this.isLoading ? Color.Gray : Color.Blue)
        .enabled(!this.isLoading)
        .onClick(() => this.startScanning())
      
      // 设备列表
      if (this.deviceList.length > 0) {
        List({ space: 10 }) {
          ForEach(this.deviceList, (device: BluetoothDevice) => {
            ListItem() {
              this.renderDeviceItem(device);
            }
          })
        }
        .height(300)
        .width('100%')
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
  
  /**
   * 渲染设备项
   */
  @Builder
  private renderDeviceItem(device: BluetoothDevice) {
    Row({ space: 15 }) {
      // 设备图标
      Image($r('app.media.ic_device_lock'))
        .width(40)
        .height(40)
      
      Column({ space: 5 }) {
        Text(device.deviceName)
          .fontSize(16)
          .fontColor(Color.Black)
        
        Text(`信号强度: ${device.rssi}dBm`)
          .fontSize(12)
          .fontColor(Color.Gray)
      }
      .layoutWeight(1)
      
      // 选择按钮
      Button('选择')
        .width(60)
        .height(30)
        .onClick(() => this.selectDevice(device))
    }
    .width('100%')
    .padding(10)
    .backgroundColor(Color.White)
    .borderRadius(8)
    .shadow({ radius: 2, color: Color.Gray })
  }
  
  build() {
    Column({ space: 20 }) {
      // 进度指示器
      this.renderProgressIndicator()
      
      // 步骤内容
      if (this.currentStep === 1) {
        this.renderScanStep();
      } else if (this.currentStep === 2) {
        this.renderBindStep();
      } else if (this.currentStep === 3) {
        this.renderNetworkStep();
      } else {
        this.renderCompleteStep();
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F7FA')
  }
}

第二部分:头像上传与昵称修改功能实现

2.1 用户信息管理架构设计

权限与系统服务

数据持久层

业务逻辑层

前端展现层

我的页面

个人信息编辑页面

头像选择弹窗

昵称编辑组件

UserViewModel

ImagePickerService

UserApiService

Preferences - 本地存储

后端API服务器

华为云OBS/本地文件

相册权限

相机权限

文件系统

2.2 头像上传功能完整实现

2.2.1 图片选择与处理服务
// common/basic/src/main/ets/services/ImagePickerService.ets
import { picker, photoAccessHelper } from '@ohos.file.picker';
import { image } from '@ohos.multimedia.image';
import { fileIo } from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';
import { Logger } from '../utils/Logger';

/**
 * 图片选择与处理服务
 * 支持拍照、相册选择、图片压缩和格式转换
 */
export class ImagePickerService {
  private static instance: ImagePickerService;
  private maxFileSize: number = 5 * 1024 * 1024; // 5MB限制
  private supportedFormats: Array<string> = ['image/jpeg', 'image/png', 'image/webp'];
  
  public static getInstance(): ImagePickerService {
    if (!ImagePickerService.instance) {
      ImagePickerService.instance = new ImagePickerService();
    }
    return ImagePickerService.instance;
  }
  
  /**
   * 显示图片选择器
   */
  public async showImagePicker(options: ImagePickerOptions = {}): Promise<ImagePickerResult> {
    const defaultOptions: ImagePickerOptions = {
      sourceType: 'both', // both, camera, gallery
      maxSelectCount: 1,
      enableCrop: true,
      cropAspectRatio: [1, 1], // 正方形
      maxWidth: 1024,
      maxHeight: 1024,
      quality: 85
    };
    
    const finalOptions = { ...defaultOptions, ...options };
    
    try {
      let result: ImagePickerResult;
      
      if (finalOptions.sourceType === 'camera') {
        result = await this.pickFromCamera(finalOptions);
      } else if (finalOptions.sourceType === 'gallery') {
        result = await this.pickFromGallery(finalOptions);
      } else {
        // 显示选择对话框
        const source = await this.showSourceSelector();
        if (source === 'camera') {
          result = await this.pickFromCamera(finalOptions);
        } else if (source === 'gallery') {
          result = await this.pickFromGallery(finalOptions);
        } else {
          return {
            success: false,
            message: '用户取消了选择'
          };
        }
      }
      
      return result;
      
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('ImagePickerService', `图片选择失败: ${err.message}`);
      return {
        success: false,
        message: `图片选择失败: ${err.message}`
      };
    }
  }
  
  /**
   * 从相机获取图片
   */
  private async pickFromCamera(options: ImagePickerOptions): Promise<ImagePickerResult> {
    try {
      const cameraPicker = new picker.CameraPicker();
      const pickerOptions: picker.CameraSelectOptions = {
        maxSelectNumber: options.maxSelectCount || 1,
        MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE
      };
      
      const result = await cameraPicker.select(pickerOptions);
      
      if (!result || result.photoUris.length === 0) {
        return {
          success: false,
          message: '未选择图片'
        };
      }
      
      const imageUri = result.photoUris[0];
      
      // 验证图片
      const validation = await this.validateImage(imageUri);
      if (!validation.valid) {
        return {
          success: false,
          message: validation.message
        };
      }
      
      // 处理图片(压缩、裁剪等)
      const processedImage = await this.processImage(imageUri, options);
      
      return {
        success: true,
        uri: processedImage.uri,
        base64: processedImage.base64,
        width: processedImage.width,
        height: processedImage.height,
        size: processedImage.size,
        mimeType: processedImage.mimeType
      };
      
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      throw new Error(`相机选择失败: ${err.message}`);
    }
  }
  
  /**
   * 从相册获取图片
   */
  private async pickFromGallery(options: ImagePickerOptions): Promise<ImagePickerResult> {
    try {
      const photoPicker = new picker.PhotoViewPicker();
      const pickerOptions: picker.PhotoSelectOptions = {
        maxSelectNumber: options.maxSelectCount || 1,
        MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE,
        isPreview: true
      };
      
      const result = await photoPicker.select(pickerOptions);
      
      if (!result || result.photoUris.length === 0) {
        return {
          success: false,
          message: '未选择图片'
        };
      }
      
      const imageUri = result.photoUris[0];
      
      // 验证图片
      const validation = await this.validateImage(imageUri);
      if (!validation.valid) {
        return {
          success: false,
          message: validation.message
        };
      }
      
      // 处理图片
      const processedImage = await this.processImage(imageUri, options);
      
      return {
        success: true,
        uri: processedImage.uri,
        base64: processedImage.base64,
        width: processedImage.width,
        height: processedImage.height,
        size: processedImage.size,
        mimeType: processedImage.mimeType
      };
      
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      throw new Error(`相册选择失败: ${err.message}`);
    }
  }
  
  /**
   * 处理图片(压缩、裁剪等)
   */
  private async processImage(uri: string, options: ImagePickerOptions): Promise<ProcessedImage> {
    try {
      // 创建图片源
      const imageSource = image.createImageSource(uri);
      
      // 获取图片信息
      const imageInfo = await imageSource.getImageInfo();
      
      // 计算目标尺寸
      const targetSize = this.calculateTargetSize(
        { width: imageInfo.size.width, height: imageInfo.size.height },
        options
      );
      
      // 创建PixelMap
      const decodingOptions: image.DecodingOptions = {
        desiredSize: targetSize,
        editable: true
      };
      
      const pixelMap = await imageSource.createPixelMap(decodingOptions);
      
      // 如果需要裁剪
      let croppedPixelMap = pixelMap;
      if (options.enableCrop && options.cropAspectRatio) {
        croppedPixelMap = await this.cropImage(pixelMap, options.cropAspectRatio);
      }
      
      // 保存处理后的图片到临时文件
      const tempUri = await this.saveToTempFile(croppedPixelMap, options);
      
      // 转换为base64
      const base64Data = await this.convertToBase64(tempUri);
      
      // 释放资源
      imageSource.release();
      pixelMap.release();
      if (croppedPixelMap !== pixelMap) {
        croppedPixelMap.release();
      }
      
      return {
        uri: tempUri,
        base64: base64Data,
        width: croppedPixelMap.getImageInfo().size.width,
        height: croppedPixelMap.getImageInfo().size.height,
        size: await this.getFileSize(tempUri),
        mimeType: 'image/jpeg'
      };
      
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      throw new Error(`图片处理失败: ${err.message}`);
    }
  }
  
  /**
   * 计算目标尺寸
   */
  private calculateTargetSize(
    originalSize: Size,
    options: ImagePickerOptions
  ): Size {
    const maxWidth = options.maxWidth || 1024;
    const maxHeight = options.maxHeight || 1024;
    
    let width = originalSize.width;
    let height = originalSize.height;
    
    // 如果图片尺寸大于最大限制,则进行缩放
    if (width > maxWidth || height > maxHeight) {
      const widthRatio = maxWidth / width;
      const heightRatio = maxHeight / height;
      const ratio = Math.min(widthRatio, heightRatio);
      
      width = Math.floor(width * ratio);
      height = Math.floor(height * ratio);
    }
    
    return { width, height };
  }
  
  /**
   * 裁剪图片
   */
  private async cropImage(
    pixelMap: image.PixelMap,
    aspectRatio: [number, number]
  ): Promise<image.PixelMap> {
    const imageInfo = pixelMap.getImageInfo();
    const width = imageInfo.size.width;
    const height = imageInfo.size.height;
    
    // 计算裁剪区域
    const targetAspect = aspectRatio[0] / aspectRatio[1];
    const currentAspect = width / height;
    
    let cropWidth, cropHeight, cropX, cropY;
    
    if (currentAspect > targetAspect) {
      // 图片过宽,裁剪宽度
      cropHeight = height;
      cropWidth = Math.floor(height * targetAspect);
      cropX = Math.floor((width - cropWidth) / 2);
      cropY = 0;
    } else {
      // 图片过高,裁剪高度
      cropWidth = width;
      cropHeight = Math.floor(width / targetAspect);
      cropX = 0;
      cropY = Math.floor((height - cropHeight) / 2);
    }
    
    const cropOptions: image.InitializationOptions = {
      size: {
        height: cropHeight,
        width: cropWidth
      },
      editable: true
    };
    
    return pixelMap.crop(cropOptions);
  }
  
  /**
   * 保存到临时文件
   */
  private async saveToTempFile(
    pixelMap: image.PixelMap,
    options: ImagePickerOptions
  ): Promise<string> {
    const tempDir = globalThis.cacheDir + '/temp_images/';
    const fileName = `avatar_${Date.now()}.jpg`;
    const filePath = tempDir + fileName;
    
    // 创建目录
    try {
      await fileIo.mkdir(tempDir);
    } catch (error) {
      // 目录可能已存在
    }
    
    // 创建图片打包器
    const imagePacker = image.createImagePacker();
    
    // 打包选项
    const packOptions: image.PackingOption = {
      format: 'image/jpeg',
      quality: options.quality || 85
    };
    
    // 打包图片
    const arrayBuffer = await imagePacker.packing(pixelMap, packOptions);
    
    // 写入文件
    const file = await fileIo.open(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    await fileIo.write(file.fd, arrayBuffer);
    await fileIo.close(file.fd);
    
    return filePath;
  }
  
  /**
   * 转换为base64
   */
  private async convertToBase64(filePath: string): Promise<string> {
    const file = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
    const stat = await fileIo.stat(filePath);
    const buffer = new ArrayBuffer(stat.size);
    
    await fileIo.read(file.fd, buffer);
    await fileIo.close(file.fd);
    
    // ArrayBuffer转base64
    const bytes = new Uint8Array(buffer);
    let binary = '';
    for (let i = 0; i < bytes.length; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    
    return btoa(binary);
  }
  
  /**
   * 获取文件大小
   */
  private async getFileSize(filePath: string): Promise<number> {
    const stat = await fileIo.stat(filePath);
    return stat.size;
  }
}

// 类型定义
export interface ImagePickerOptions {
  sourceType?: 'camera' | 'gallery' | 'both';
  maxSelectCount?: number;
  enableCrop?: boolean;
  cropAspectRatio?: [number, number];
  maxWidth?: number;
  maxHeight?: number;
  quality?: number;
}

export interface ImagePickerResult {
  success: boolean;
  message?: string;
  uri?: string;
  base64?: string;
  width?: number;
  height?: number;
  size?: number;
  mimeType?: string;
}

export interface ProcessedImage {
  uri: string;
  base64: string;
  width: number;
  height: number;
  size: number;
  mimeType: string;
}

export interface Size {
  width: number;
  height: number;
}
2.2.2 头像上传页面组件
// features/mine/src/main/ets/pages/AvatarUploadPage.ets
import { ImagePickerService, ImagePickerResult } from '@elderly/basic';
import { UserViewModel } from '../viewmodel/UserViewModel';
import { Logger } from '@elderly/basic';
import { Router } from '@elderly/basic';

@Component
export struct AvatarUploadPage {
  @State currentAvatar: string = $r('app.media.default_avatar');
  @State selectedImage: string = '';
  @State isLoading: boolean = false;
  @State uploadProgress: number = 0;
  @State errorMessage: string = '';
  
  private imagePicker: ImagePickerService = ImagePickerService.getInstance();
  private userViewModel: UserViewModel = new UserViewModel();
  
  aboutToAppear(): void {
    this.loadCurrentAvatar();
  }
  
  /**
   * 加载当前头像
   */
  private async loadCurrentAvatar(): Promise<void> {
    try {
      const userInfo = this.userViewModel.getUserInfo();
      if (userInfo.avatarUrl) {
        this.currentAvatar = userInfo.avatarUrl;
      }
    } catch (error) {
      Logger.warn('AvatarUploadPage', '加载当前头像失败,使用默认头像');
    }
  }
  
  /**
   * 显示图片选择器
   */
  private async showImagePicker(): Promise<void> {
    if (this.isLoading) {
      return;
    }
    
    this.isLoading = true;
    this.errorMessage = '';
    
    try {
      const result: ImagePickerResult = await this.imagePicker.showImagePicker({
        sourceType: 'both',
        enableCrop: true,
        cropAspectRatio: [1, 1],
        maxWidth: 800,
        maxHeight: 800,
        quality: 90
      });
      
      if (result.success && result.uri) {
        this.selectedImage = result.uri;
        this.errorMessage = '';
      } else {
        this.errorMessage = result.message || '选择图片失败';
      }
      
    } catch (error) {
      this.errorMessage = `选择图片时出错: ${error.message}`;
      Logger.error('AvatarUploadPage', `图片选择失败: ${error.message}`);
    } finally {
      this.isLoading = false;
    }
  }
  
  /**
   * 上传头像
   */
  private async uploadAvatar(): Promise<void> {
    if (!this.selectedImage) {
      this.errorMessage = '请先选择图片';
      return;
    }
    
    this.isLoading = true;
    this.uploadProgress = 0;
    this.errorMessage = '';
    
    try {
      // 模拟上传进度(实际应使用分块上传和进度回调)
      const progressInterval = setInterval(() => {
        if (this.uploadProgress < 90) {
          this.uploadProgress += 10;
        }
      }, 200);
      
      // 实际上传逻辑
      const result = await this.userViewModel.updateAvatar(this.selectedImage);
      
      clearInterval(progressInterval);
      this.uploadProgress = 100;
      
      if (result.success) {
        // 更新本地显示
        this.currentAvatar = this.selectedImage;
        
        // 延迟返回,让用户看到上传成功
        setTimeout(() => {
          Router.back();
        }, 1000);
      } else {
        this.errorMessage = result.message;
        this.uploadProgress = 0;
      }
      
    } catch (error) {
      this.errorMessage = `上传失败: ${error.message}`;
      this.uploadProgress = 0;
      Logger.error('AvatarUploadPage', `头像上传失败: ${error.message}`);
    } finally {
      this.isLoading = false;
    }
  }
  
  /**
   * 取消上传
   */
  private cancelUpload(): void {
    this.selectedImage = '';
    this.uploadProgress = 0;
    this.errorMessage = '';
  }
  
  build() {
    Column({ space: 30 }) {
      // 页面标题
      Text('修改头像')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.Black)
        .width('100%')
        .textAlign(TextAlign.Center)
      
      // 头像显示区域
      Column({ space: 15 }) {
        Stack({ alignContent: Alignment.Center }) {
          // 当前头像
          Image(this.selectedImage || this.currentAvatar)
            .width(200)
            .height(200)
            .borderRadius(100)
            .border({ width: 4, color: Color.White })
            .shadow({ radius: 10, color: '#00000020' })
          
          // 上传进度指示器
          if (this.isLoading && this.uploadProgress > 0) {
            LoadingProgress()
              .width(60)
              .height(60)
              .color(Color.Blue)
            
            Text(`${this.uploadProgress}%`)
              .fontSize(14)
              .fontColor(Color.Blue)
              .margin({ top: 70 })
          }
        }
        .width(200)
        .height(200)
        
        // 选择图片按钮
        if (!this.selectedImage) {
          Button('选择图片')
            .width(150)
            .height(40)
            .backgroundColor(Color.Blue)
            .fontColor(Color.White)
            .enabled(!this.isLoading)
            .onClick(() => this.showImagePicker())
        }
      }
      .alignItems(HorizontalAlign.Center)
      .width('100%')
      
      // 错误信息
      if (this.errorMessage) {
        Text(this.errorMessage)
          .fontSize(14)
          .fontColor(Color.Red)
          .textAlign(TextAlign.Center)
          .width('90%')
          .padding(10)
          .backgroundColor('#FFF5F5')
          .borderRadius(8)
      }
      
      // 操作按钮
      if (this.selectedImage) {
        Row({ space: 20 }) {
          Button('取消')
            .width(120)
            .height(45)
            .backgroundColor('#F0F0F0')
            .fontColor(Color.Black)
            .enabled(!this.isLoading)
            .onClick(() => this.cancelUpload())
          
          Button(this.isLoading ? '上传中...' : '确认上传')
            .width(120)
            .height(45)
            .backgroundColor(Color.Blue)
            .fontColor(Color.White)
            .enabled(!this.isLoading)
            .onClick(() => this.uploadAvatar())
        }
        .margin({ top: 20 })
      }
      
      // 使用说明
      Column({ space: 10 }) {
        Text('温馨提示')
          .fontSize(16)
          .fontColor(Color.Gray)
          .fontWeight(FontWeight.Medium)
        
        Text('• 建议使用正方形图片')
          .fontSize(14)
          .fontColor(Color.Gray)
        
        Text('• 图片大小不超过5MB')
          .fontSize(14)
          .fontColor(Color.Gray)
        
        Text('• 支持JPG、PNG、WebP格式')
          .fontSize(14)
          .fontColor(Color.Gray)
      }
      .width('90%')
      .padding(15)
      .backgroundColor('#F8F9FA')
      .borderRadius(10)
      .margin({ top: 30 })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor(Color.White)
  }
}

2.3 昵称修改功能实现

2.3.1 昵称编辑组件
// features/mine/src/main/ets/components/NicknameEditor.ets
import { UserViewModel } from '../viewmodel/UserViewModel';
import { Logger } from '@elderly/basic';
import { Prompt } from '@ohos.prompt';

@Component
export struct NicknameEditor {
  @State currentNickname: string = '';
  @State editingNickname: string = '';
  @State isEditing: boolean = false;
  @State isLoading: boolean = false;
  @State validationError: string = '';
  
  private userViewModel: UserViewModel = new UserViewModel();
  private readonly NICKNAME_MIN_LENGTH: number = 2;
  private readonly NICKNAME_MAX_LENGTH: number = 20;
  
  aboutToAppear(): void {
    this.loadCurrentNickname();
  }
  
  /**
   * 加载当前昵称
   */
  private async loadCurrentNickname(): Promise<void> {
    try {
      const userInfo = this.userViewModel.getUserInfo();
      this.currentNickname = userInfo.nickname || '';
      this.editingNickname = this.currentNickname;
    } catch (error) {
      Logger.error('NicknameEditor', `加载昵称失败: ${error.message}`);
    }
  }
  
  /**
   * 开始编辑
   */
  private startEditing(): void {
    this.isEditing = true;
    this.validationError = '';
    this.editingNickname = this.currentNickname;
  }
  
  /**
   * 取消编辑
   */
  private cancelEditing(): void {
    this.isEditing = false;
    this.validationError = '';
    this.editingNickname = this.currentNickname;
  }
  
  /**
   * 验证昵称
   */
  private validateNickname(nickname: string): ValidationResult {
    const trimmedNickname = nickname.trim();
    
    // 检查空值
    if (trimmedNickname.length === 0) {
      return {
        valid: false,
        message: '昵称不能为空'
      };
    }
    
    // 检查长度
    if (trimmedNickname.length < this.NICKNAME_MIN_LENGTH) {
      return {
        valid: false,
        message: `昵称至少需要${this.NICKNAME_MIN_LENGTH}个字符`
      };
    }
    
    if (trimmedNickname.length > this.NICKNAME_MAX_LENGTH) {
      return {
        valid: false,
        message: `昵称不能超过${this.NICKNAME_MAX_LENGTH}个字符`
      };
    }
    
    // 检查字符范围(中文、英文、数字、下划线、减号)
    const nicknameRegex = /^[\u4e00-\u9fa5a-zA-Z0-9_\-]+$/;
    if (!nicknameRegex.test(trimmedNickname)) {
      return {
        valid: false,
        message: '昵称只能包含中文、英文、数字、下划线和减号'
      };
    }
    
    // 检查敏感词(养老场景特殊要求)
    if (this.containsSensitiveWords(trimmedNickname)) {
      return {
        valid: false,
        message: '昵称包含不适当的词汇'
      };
    }
    
    return {
      valid: true,
      message: ''
    };
  }
  
  /**
   * 保存昵称
   */
  private async saveNickname(): Promise<void> {
    // 验证昵称
    const validation = this.validateNickname(this.editingNickname);
    if (!validation.valid) {
      this.validationError = validation.message;
      return;
    }
    
    // 检查是否与当前昵称相同
    if (this.editingNickname === this.currentNickname) {
      this.isEditing = false;
      return;
    }
    
    this.isLoading = true;
    this.validationError = '';
    
    try {
      Logger.info('NicknameEditor', `开始保存昵称: ${this.editingNickname}`);
      
      // 调用ViewModel更新昵称
      const result = await this.userViewModel.updateNickname(this.editingNickname);
      
      if (result.success) {
        this.currentNickname = this.editingNickname;
        
        // 显示成功提示
        Prompt.showToast({
          message: '昵称修改成功',
          duration: 2000,
          bottom: '50vp'
        });
        
        this.isEditing = false;
      } else {
        this.validationError = result.message || '保存失败';
      }
      
    } catch (error) {
      Logger.error('NicknameEditor', `保存昵称失败: ${error.message}`);
      this.validationError = '保存失败,请检查网络连接';
    } finally {
      this.isLoading = false;
    }
  }
  
  /**
   * 渲染显示模式
   */
  @Builder
  private renderDisplayMode() {
    Row({ space: 15 }) {
      Column({ space: 5 }) {
        Text('昵称')
          .fontSize(16)
          .fontColor('#666666')
        
        Text(this.currentNickname || '未设置')
          .fontSize(18)
          .fontColor(this.currentNickname ? Color.Black : '#999999')
      }
      .layoutWeight(1)
      
      Button('修改')
        .width(70)
        .height(32)
        .fontSize(14)
        .backgroundColor(Color.Transparent)
        .borderColor(Color.Blue)
        .borderWidth(1)
        .fontColor(Color.Blue)
        .onClick(() => this.startEditing())
    }
    .width('100%')
    .padding({ top: 15, bottom: 15 })
    .border({ bottom: { width: 1, color: '#EEEEEE' } })
  }
  
  /**
   * 渲染编辑模式
   */
  @Builder
  private renderEditMode() {
    Column({ space: 15 }) {
      // 输入框
      TextInput({
        text: this.editingNickname,
        placeholder: `请输入${this.NICKNAME_MIN_LENGTH}-${this.NICKNAME_MAX_LENGTH}位字符`
      })
      .width('100%')
      .height(50)
      .fontSize(16)
      .padding({ left: 15, right: 15 })
      .backgroundColor('#F8F9FA')
      .borderRadius(8)
      .onChange((value: string) => {
        this.editingNickname = value;
        this.validationError = ''; // 清空错误信息
      })
      
      // 字符计数
      Row() {
        Text(`${this.editingNickname.length}/${this.NICKNAME_MAX_LENGTH}`)
          .fontSize(12)
          .fontColor(
            this.editingNickname.length > this.NICKNAME_MAX_LENGTH ? 
            Color.Red : '#999999'
          )
      }
      .width('100%')
      .justifyContent(FlexAlign.End)
      
      // 错误提示
      if (this.validationError) {
        Text(this.validationError)
          .fontSize(12)
          .fontColor(Color.Red)
          .width('100%')
      }
      
      // 操作按钮
      Row({ space: 20 }) {
        Button('取消')
          .width(100)
          .height(40)
          .backgroundColor('#F0F0F0')
          .fontColor(Color.Black)
          .enabled(!this.isLoading)
          .onClick(() => this.cancelEditing())
        
        Button(this.isLoading ? '保存中...' : '保存')
          .width(100)
          .height(40)
          .backgroundColor(
            this.editingNickname === this.currentNickname || this.isLoading ?
            '#CCCCCC' : Color.Blue
          )
          .fontColor(Color.White)
          .enabled(
            this.editingNickname !== this.currentNickname && 
            !this.isLoading
          )
          .onClick(() => this.saveNickname())
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .margin({ top: 10 })
    }
    .width('100%')
    .padding({ top: 15, bottom: 15 })
    .border({ bottom: { width: 1, color: '#EEEEEE' } })
  }
  
  build() {
    Column() {
      if (this.isEditing) {
        this.renderEditMode();
      } else {
        this.renderDisplayMode();
      }
    }
    .width('100%')
  }
}

// 类型定义
interface ValidationResult {
  valid: boolean;
  message: string;
}

2.4 用户信息整合页面

// features/mine/src/main/ets/pages/UserProfilePage.ets
import { UserViewModel } from '../viewmodel/UserViewModel';
import { NicknameEditor } from '../components/NicknameEditor';
import { Router } from '@elderly/basic';
import { Logger } from '@elderly/basic';

@Component
export struct UserProfilePage {
  @State userInfo: UserInfo = {
    id: '',
    nickname: '',
    avatarUrl: '',
    phone: '',
    email: '',
    role: '',
    joinDate: ''
  };
  
  @State isLoading: boolean = true;
  
  private userViewModel: UserViewModel = new UserViewModel();
  
  aboutToAppear(): void {
    this.loadUserProfile();
    
    // 订阅用户信息更新
    this.userViewModel.subscribeToUpdates((updatedInfo: UserInfo) => {
      this.userInfo = updatedInfo;
    });
  }
  
  /**
   * 加载用户信息
   */
  private async loadUserProfile(): Promise<void> {
    this.isLoading = true;
    
    try {
      await this.userViewModel.loadUserInfo();
      this.userInfo = this.userViewModel.getUserInfo();
    } catch (error) {
      Logger.error('UserProfilePage', `加载用户信息失败: ${error.message}`);
    } finally {
      this.isLoading = false;
    }
  }
  
  /**
   * 跳转到头像上传页面
   */
  private navigateToAvatarUpload(): void {
    Router.pushUrl({
      url: 'pages/AvatarUploadPage'
    });
  }
  
  /**
   * 渲染用户信息项
   */
  @Builder
  private renderInfoItem(label: string, value: string, editable: boolean = false) {
    Row({ space: 15 }) {
      Text(label)
        .fontSize(16)
        .fontColor('#666666')
        .width(80)
      
      Text(value || '未设置')
        .fontSize(16)
        .fontColor(value ? Color.Black : '#999999')
        .layoutWeight(1)
      
      if (editable) {
        Image($r('app.media.ic_arrow_right'))
          .width(16)
          .height(16)
      }
    }
    .width('100%')
    .padding({ top: 18, bottom: 18 })
    .border({ bottom: { width: 0.5, color: '#F0F0F0' } })
    .onClick(() => {
      if (editable && label === '头像') {
        this.navigateToAvatarUpload();
      }
    })
  }
  
  /**
   * 渲染头像项(特殊处理)
   */
  @Builder
  private renderAvatarItem() {
    Row({ space: 15 }) {
      Text('头像')
        .fontSize(16)
        .fontColor('#666666')
        .width(80)
      
      Image(this.userInfo.avatarUrl || $r('app.media.default_avatar'))
        .width(60)
        .height(60)
        .borderRadius(30)
        .border({ width: 2, color: Color.White })
        .shadow({ radius: 2, color: '#00000010' })
        .layoutWeight(1)
      
      Image($r('app.media.ic_arrow_right'))
        .width(16)
        .height(16)
    }
    .width('100%')
    .padding({ top: 18, bottom: 18 })
    .border({ bottom: { width: 0.5, color: '#F0F0F0' } })
    .onClick(() => {
      this.navigateToAvatarUpload();
    })
  }
  
  build() {
    Column({ space: 0 }) {
      // 页面标题
      Text('个人信息')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.Black)
        .width('100%')
        .textAlign(TextAlign.Center)
        .padding({ top: 20, bottom: 30 })
      
      // 加载状态
      if (this.isLoading) {
        Column() {
          LoadingProgress()
            .color(Color.Blue)
            .width(40)
            .height(40)
          
          Text('加载中...')
            .fontSize(14)
            .fontColor('#666666')
            .margin({ top: 15 })
        }
        .width('100%')
        .height(300)
        .justifyContent(FlexAlign.Center)
      } else {
        // 用户信息列表
        List() {
          // 头像
          ListItem() {
            this.renderAvatarItem()
          }
          
          // 昵称(使用自定义编辑器)
          ListItem() {
            Column() {
              Text('昵称')
                .fontSize(16)
                .fontColor('#666666')
                .margin({ bottom: 10 })
              
              NicknameEditor()
            }
            .width('100%')
            .padding({ top: 18, bottom: 18 })
            .border({ bottom: { width: 0.5, color: '#F0F0F0' } })
          }
          
          // 手机号
          ListItem() {
            this.renderInfoItem('手机号', this.userInfo.phone)
          }
          
          // 邮箱
          ListItem() {
            this.renderInfoItem('邮箱', this.userInfo.email)
          }
          
          // 角色
          ListItem() {
            this.renderInfoItem('角色', this.userInfo.role)
          }
          
          // 加入日期
          ListItem() {
            this.renderInfoItem('加入日期', this.userInfo.joinDate)
          }
        }
        .width('100%')
        .layoutWeight(1)
        .divider({ 
          strokeWidth: 0.5, 
          color: '#F0F0F0',
          startMargin: 20,
          endMargin: 20
        })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.White)
  }
}

// 类型定义
interface UserInfo {
  id: string;
  nickname: string;
  avatarUrl: string;
  phone: string;
  email: string;
  role: string;
  joinDate: string;
}

总结

本文详细阐述了益康养老HarmonyOS APP中两个核心功能模块的实现方案:

  1. 门禁设备绑定与配网流程:通过蓝牙发现、连接、绑定,再通过WiFi配网,实现了物联网设备的智能化管理。该方案充分利用了HarmonyOS的分布式能力和跨设备协同特性。

  2. 用户信息管理功能:实现了完整的头像上传(支持拍照、相册选择、图片处理)和昵称修改功能,提供了良好的用户体验和健壮的错误处理机制。

这两个功能模块都严格遵循了鸿蒙官方开发规范,采用了三层架构设计,确保了代码的可维护性、可扩展性和性能优化。通过本文的实现方案,可以为养老行业提供一套稳定、易用、安全的智能设备管理和用户信息管理解决方案。

Logo

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

更多推荐