智能填充隐藏功能——自动补全地址表单所在地区

在应用程序使用过程中,用户经常需要填写各种表单,例如在寄送包裹时填写收货人信息、购买票务时填写购票人信息、参与调查时填写参与者信息等。这些重复且繁琐的信息填写过程,会直接影响用户的使用体验。为解决这一问题,HarmonyOS SDK融合场景服务(Scenario Fusion Kit)提供了智能填充功能,该功能可根据页面输入框类型、用户已输入内容,为用户提供输入建议,实现复杂表单一键填充。

然而,在填写表单时可能会遇到一个特殊的挑战:当表单中包含所在地区地址选择器时,智能填充不支持对地址选择器进行填充,为了实现地址信息的自动补全,开发者需要对表单中的地址字段进行开发。开发完成后,即使数据源中的"地址所在地区"信息不完整,智能填充服务也能够根据数据源中的详细地址内容,自动推断并补全地址选择器中的所在地区信息。

当"所在地区信息"自动补全后,如果补全内容不符合预期,用户也可以通过点击"地址选择器"重新选择修改。

下面,本文将详细讲解,如何对表单中的地址字段进行开发,实现自动补全地址表单所在地区。

开发准备

  1. 首先,我们需要在module.json5文件中设置模糊位置权限:ohos.permission.APPROXIMATELY_LOCATION,允许应用获取设备模糊位置信息。

  2. 其次,所在地区地址选择器需要开通地图服务。

  3. 最后,还需要配置应用签名证书指纹,可参见配置Client ID。

开发步骤

我们以北京天安门的经纬度为例进行讲解,在获得相关授权后调用获取位置信息的API,然后根据数据源中现有地址信息遍历当前地址的行政区划层级,自动补全地址表单所在地区,在填写完毕后将表单信息保存到历史表单输入。

复制代码
import { util } from '@kit.ArkTS';
import { i18n } from '@kit.LocalizationKit';
import { sceneMap, site } from '@kit.MapKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl, autoFillManager, common, PermissionRequestResult, Permissions } from '@kit.AbilityKit';

const AUTHED = 0;
const TIME_OUT = 100;
// Default longitude and latitude. The following uses the longitude and latitude of Tiananmen, Beijing as an example.
const INIT_LAT = 39.5;
const INIT_LON = 116.2;
const ENGLISH = 'en';
const SIMPLIFIED_CHINESE = 'zh_CN';
const PERMISSIONS: Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION'];
const ADMINISTRATIVE_REGION: Array<string> =
  ['countryName', 'adminLevel1', 'adminLevel2', 'adminLevel3', 'adminLevel4'];

interface PersonInfo {
  name?: string;
  phone?: string;
  email?: string;
  idCard?: string;
  region?: string;
  stressAddress?: string;
}

interface RequestParam {
  requestTag: string;
  requestText: string;
}

interface Location {
  latitude: number;
  longitude: number;
}

// Display the authorization pop-up.
async function reqPermissionsFromUser(permissions: Array<Permissions>,
  context: common.UIAbilityContext): Promise<PermissionRequestResult> {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  return await atManager.requestPermissionsFromUser(context, permissions);
}

// Throttle function.
function debounce(func: () => void, wait: number = TIME_OUT): Function {
  let timeout: number | null = null;
  return () => {
    timeout && clearTimeout(timeout);
    timeout = setTimeout(() => {
      func();
      clearTimeout(timeout);
    }, wait);
  };
}

@Extend(Text)
function textStyle() {
  .width(64)
  .textAlign(TextAlign.End)
}

@Entry
@Component
struct Index {
  @State personInfo: PersonInfo = {};
  @State isClicked: boolean = false;
  // Whether the user has triggered information input.
  private isUserInput: boolean = false;
  private location: Location = {
    latitude: INIT_LAT,
    longitude: INIT_LON,
  };
  private currentRequestTag: string = '';
  private handleAddressChange = (request: RequestParam) => {
    return debounce(async () => {
      this.autoCompleteAddress(request);
    });
  };

  aboutToAppear() {
    reqPermissionsFromUser(PERMISSIONS, getContext(this) as common.UIAbilityContext)
      .then((permissionRequestResult: PermissionRequestResult) => {
        if (permissionRequestResult.authResults[0] === AUTHED) {
          // The API for obtaining location information can be called only under authorization.
          geoLocationManager.getCurrentLocation((err, location: geoLocationManager.Location) => {
            if (err) {
              hilog.error(0x0000, 'testTag', `Failed to get location, code: ${err?.code}, message: ${err?.message}`);
              return;
            }
            hilog.info(0x0000, 'testTag', `Succeeded in obtaining the current location of the user`);
            this.location.latitude = location.latitude;
            this.location.longitude = location.longitude;
          })
        }
      })
      .catch((err: BusinessError) => {
        hilog.error(0x0000, 'testTag', `Failed request permissions, code: ${err?.code}, message: ${err?.message}`);
      })
  }

  public isUsLanguage(): boolean {
    let result: string = '';
    try {
      result = i18n.System.getSystemLanguage();
    } catch (error) {
      hilog.error(0x0000, 'testTag', 'Failed to get system language');
    }
    return result.toLowerCase() === 'en-latn-us';
  }

  async autoCompleteAddress(request: RequestParam): Promise<void> {
    try {
      let params: site.SearchByTextParams = {
        query: request.requestText,
        // Longitude and latitude to which search results need to be biased.
        location: {
          latitude: this.location.latitude,
          longitude: this.location.longitude
        },
        language: this.isUsLanguage() ? ENGLISH : SIMPLIFIED_CHINESE,
        isChildren: true
      };
      const result = await site.searchByText(params);
      if (result.sites) {
        let region: string = '';
        let addressComponent = result.sites[0].addressComponent;
        // Traverse the administrative region level of the current address.
        for (let item of ADMINISTRATIVE_REGION) {
          if (addressComponent[item] === undefined) {
            break;
          }
          region += addressComponent[item];
        }
        // Prevent repeated searches that may lead to inconsistent results.
        if (request.requestTag === this.currentRequestTag) {
          this.personInfo.region = region;
        }
      }
    } catch (error) {
      hilog.error(0x0000, 'testTag', `Failed to search location, code: ${error.code}, message: ${error.message}`);
    }
    hilog.info(0x0000, 'testTag', 'Succeeded in searching location');
  }

  onRegionClick(): void {
    // After a user selects an administrative region, display only search results from the selected region to prevent prolonged queries.
    this.currentRequestTag = util.generateRandomUUID();
    let districtSelectOptions: sceneMap.DistrictSelectOptions = {
      countryCode: 'CN',
    };
    sceneMap.selectDistrict(getContext(this), districtSelectOptions).then((data) => {
      hilog.info(0x0000, 'testTag', 'SelectDistrict', 'Succeeded  in selecting district.');
      let region = '';
      for (let i = 0; i < data?.districts?.length; i++) {
        region += data.districts[i].name;
      }
      this.personInfo.region = region;
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, 'testTag', `Failed to select district, code: ${err.code}, message: ${err.message}`);
    });
  }

  searchRegionByAddress(val: string): void {
    let tag: string = util.generateRandomUUID();
    this.currentRequestTag = tag;
    let param: RequestParam = {
      requestTag: tag,
      requestText: val
    }
    // For the manual user input scenario, dithering processing is required. For the automatic input scenario of SmartFill, only the query processing is required.
    if (this.isUserInput) {
      this.handleAddressChange(param)();
    } else {
      this.autoCompleteAddress(param);
    }
  }

  build() {
    Column({ space: 8 }) {
      Row({ space: 8 }) {
        Text('姓名').textStyle()
        TextInput({ text: this.personInfo.name, placeholder: '姓名' })
          .layoutWeight(1)
          .contentType(ContentType.PERSON_FULL_NAME)
          .onChange((val: string) => {
            this.personInfo.name = val;
          })
      }

      Row({ space: 8 }) {
        Text('联系电话').textStyle()
        TextInput({ text: this.personInfo.phone, placeholder: '手机号码' })
          .layoutWeight(1)
          .contentType(ContentType.PHONE_NUMBER)
          .onChange((val: string) => {
            this.personInfo.phone = val;
          })
      }

      Row({ space: 8 }) {
        Text('身份证号').textStyle()
        TextInput({ text: this.personInfo.idCard, placeholder: '身份证信息' })
          .layoutWeight(1)
          .contentType(ContentType.ID_CARD_NUMBER)
          .onChange((val: string) => {
            this.personInfo.idCard = val;
          })
      }

      Row({ space: 8 }) {
        Text('邮件地址').textStyle()
        TextInput({ text: this.personInfo.email, placeholder: '电子邮件信息' })
          .layoutWeight(1)
          .contentType(ContentType.EMAIL_ADDRESS)
          .onChange((val: string) => {
            this.personInfo.email = val;
          })
      }

      Row({ space: 8 }) {
        Text('所在地区').textStyle()
        TextArea({ text: this.personInfo.region, placeholder: '地区信息' })
          .layoutWeight(1)
          .backgroundColor($r('sys.color.ohos_id_color_card_bg'))
          .placeholderColor($r('sys.color.ohos_id_color_text_secondary'))
          .fontSize($r('sys.float.ohos_id_text_size_body1'))
          .fontColor($r('sys.color.ohos_id_color_text_primary'))
          .onClick(() => this.onRegionClick())
          .focusable(false)
      }

      Row({ space: 8 }) {
        Text('详细地址').textStyle()
        TextInput({ text: this.personInfo.stressAddress, placeholder: '小区门牌信息' })
          .layoutWeight(1)
          .contentType(ContentType.DETAIL_INFO_WITHOUT_STREET)
          .onDidInsert(() => {
            // Triggered when a user inputs data through an input method.
            this.isUserInput = true;
          })
          .onDidDelete((val: DeleteValue) => {
            // Triggered when a user deletes data through an input method.
            if (val?.deleteValue?.length > 0) {
              this.isUserInput = true;
            }
          })
          .onChange((val: string) => {
            this.personInfo.stressAddress = val;
            if (val && val.trim().length > 0) {
              this.searchRegionByAddress(val);
            } else {
              this.currentRequestTag = util.generateRandomUUID();
              this.personInfo.region = '';
            }
            this.isUserInput = false;
          })
      }

      Button('保存')
        .width('50%')
        .onClick(() => {
          if (!this.isClicked) {
            this.isClicked = true;
            autoFillManager.requestAutoSave(this.getUIContext(), {
              onSuccess: () => {
                hilog.info(0x0000, 'testTag', 'Succeeded in saving request');
              },
              onFailure: () => {
                hilog.info(0x0000, 'testTag', 'Failed to save request');
              }
            });
            setTimeout(() => {
              this.isClicked = false;
            }, 2000);
          }
        })
    }
    .padding({ left: 16, right: 16 })
    .backgroundColor($r('sys.color.ohos_id_color_list_card_bg'))
    .alignItems(HorizontalAlign.Center)
    .height('100%')
    .width('100%')
  }
}

了解更多详情>>

访问融合场景服务联盟官网

获取智能填充能力的开发指导文档

相关推荐
不老刘2 小时前
Sherpa-onnx 离线 TTS 集成解决 openharmony 下语音播报完整方案
harmonyos·鸿蒙·tts·sherpa
音浪豆豆_Rachel3 小时前
Flutter跨平台通信的实战演练:复杂数据结构与单元测试在鸿蒙生态中的完美实现
数据结构·flutter·单元测试·harmonyos
坚果派·白晓明3 小时前
【鸿蒙开发者跨平台开发可选工具】Windows 11 安装 Android Studio 完整指南
windows·android studio·harmonyos·开发者可选工具·开源项目可选ide·鸿蒙跨平台开发
音浪豆豆_Rachel4 小时前
Flutter跨平台通信的类型安全艺术:枚举与复杂对象在鸿蒙生态中的映射与序列化
flutter·harmonyos
昼-枕4 小时前
【鸿蒙Flutter入门】10分钟快速上手开发天气应用
flutter·华为·harmonyos
前端世界4 小时前
鸿蒙应用能耗优化实战:如何避免引用不当引发的后台运行
华为·harmonyos
鸿蒙开发工程师—阿辉4 小时前
HarmonyOS 5 上下文的使用:理清“上下文”的关系
华为·harmonyos
音浪豆豆_Rachel4 小时前
Flutter鸿蒙文件选择器内核解析:从Dart调用到ArkTS系统级对话
flutter·harmonyos