如何自动识别并隐藏表单中的地区自动补全功能?

摘要:在应用程序使用过程中,用户经常需要填写各种表单,例如在寄送包裹时填写收货人信息、购买票务时填写购票人信息、参与调查时填写参与者信息等。这些重复且繁琐的信息填写过程,会直接影响用户的使用体验。为解决这一问题,HarmonyOS SDK融合场景
在应用程序使用过程中,用户经常需要填写各种表单,例如在寄送包裹时填写收货人信息、购买票务时填写购票人信息、参与调查时填写参与者信息等。这些重复且繁琐的信息填写过程,会直接影响用户的使用体验。为解决这一问题,HarmonyOS SDK融合场景服务(Scenario Fusion Kit)提供了智能填充功能,该功能可根据页面输入框类型、用户已输入内容,为用户提供输入建议,实现复杂表单一键填充。 然而,在填写表单时可能会遇到一个特殊的挑战:当表单中包含所在地区地址选择器时,智能填充不支持对地址选择器进行填充,为了实现地址信息的自动补全,开发者需要对表单中的地址字段进行开发。开发完成后,即使数据源中的"地址所在地区"信息不完整,智能填充服务也能够根据数据源中的详细地址内容,自动推断并补全地址选择器中的所在地区信息。 当"所在地区信息"自动补全后,如果补全内容不符合预期,用户也可以通过点击"地址选择器"重新选择修改。 下面,本文将详细讲解,如何对表单中的地址字段进行开发,实现自动补全地址表单所在地区。 开发准备 首先,我们需要在module.json5文件中设置模糊位置权限:ohos.permission.APPROXIMATELY_LOCATION,允许应用获取设备模糊位置信息。 其次,所在地区地址选择器需要开通地图服务。 最后,还需要配置应用签名证书指纹,可参见配置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%') } } 了解更多详情>> 访问融合场景服务联盟官网 获取智能填充能力的开发指导文档