博客 / 詳情

返回

智能填充隱藏功能——自動補全地址表單所在地區

在應用程序使用過程中,用户經常需要填寫各種表單,例如在寄送包裹時填寫收貨人信息、購買票務時填寫購票人信息、參與調查時填寫參與者信息等。這些重複且繁瑣的信息填寫過程,會直接影響用户的使用體驗。為解決這一問題,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%')
  }
}

瞭解更多詳情>>

訪問融合場景服務聯盟官網

獲取智能填充能力的開發指導文檔

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.