鸿蒙Next 利用半模态弹窗实现省市区三级联动

835 阅读2分钟

先看效果

弹窗利用了半模态弹窗,绑定到了button上面,选择器使用了官方的TextPicker, 具体实现看下面代码,关键部分加了注释, 需要注意的点就是数据源,省市区传入TextPicker的格式要正确
代码仓库链接

import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
import { CommonConstants } from '../common/CommonConstants';

/**
 * 三级联动地址选择器
 */

@Component
export struct AddressSelectView {
  // 平板或折叠屏展开态在中间显示
  @State isCenter: boolean = true;
  // 是否显示半屏模态页面
  @State isPresent: boolean = false;
  // 半模态高度
  @State sheetHeight: number = 300;
  // 是否显示控制条
  @State showDragBar: boolean = true;
  // 省市区数据下标
  addressSelectIndex: number | number [] = [0, 0, 0];
  //选中的省市区名字
  addressSelectText: Array<string> = []
  // 省市区数据存放文件地址
  fileName: string = 'regionsdata.json';
  // 存放省市区数据
  cascade: Array<TextCascadePickerRangeContent> = [];
  @State selectText: string = ''

  aboutToAppear(): void {
    this.loadRegion();
  }

  build() {

    Column({ space: 10 }) {
      Text(this.selectText).margin(10).padding(10)
      Button('选择地址').bindSheet($$this.isPresent, this.halfModalAddressSelectView(), {
        // TextInput绑定半模态转场
        height: this.sheetHeight, // 半模态高度
        dragBar: this.showDragBar, // 是否显示控制条
        // 平板或折叠屏展开态在中间显示
        // preferType: this.isCenter ? SheetType.CENTER : SheetType.POPUP,
        backgroundColor: $r('app.color.editaddress_btn_bgc'),
        showClose: false, // 是否显示关闭图标
        shouldDismiss: ((sheetDismiss: SheetDismiss) => { // 半模态页面交互式关闭回调函数
          sheetDismiss.dismiss();
        })
      })
        .onBlur(() => {
          if (!this.isPresent) { // 当省市区输入框失焦且半模态关闭,将点击状态置为false
          }
        })
        .onClick(() => {
          this.isPresent = true;
        })
    }
  }

  @Builder
  halfModalAddressSelectView() { // 半模态窗口页面
    Column() {
      Row() {
        Text('取消')
          .titleBtnStyle()
          .onClick(() => {
            this.isPresent = !this.isPresent;
          })

        Text('地区选择')
          .fontColor(Color.Black)
          .fontSize(CommonConstants.HALF_FONTSIZE_TITLE)

        Text('确认')
          .titleBtnStyle()
          .onClick(() => {
            this.isPresent = !this.isPresent; // 关闭半模态
            // this.getSelectedPlace();
            this.getSelectedCityName();
          })
      }
      .padding({
        left: 50,
        right: 50,
        top: 15,
        bottom: 15
      })
      .alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.SpaceBetween)
      .backgroundColor($r('app.color.editaddress_bind_sheet_title_bgc'))
      .width(CommonConstants.FULL_PERCENT)

      TextPicker({
        range: this.cascade,//省市区数据
        selected: this.addressSelectIndex//默认选中的index
      })
        .onChange((value: string | string[], index: number | number[]) => {
          //返回选择项下标数组
          if (index instanceof Array) {
            this.addressSelectIndex = index;
          }
          //返回选择项文本数组
          if (value instanceof Array) {
            this.addressSelectText = value
          }
        })// 设置所有选项中除了最上、最下及选中项以外的文本颜色、字号、字体粗细。
        .textStyle({
          color: $r('app.color.editaddress_textStyle_font_color'),
          font: {
            size: 16,
            weight: FontWeight.Regular
          }
        })// 设置选中项的文本颜色、字号、字体粗细。
        .selectedTextStyle({
          color: $r('app.color.editaddress_selectedTextStyle_font_color'),
          font: {
            size: $r('app.string.editaddress_selectedTextStyle_font_size'),
            weight: FontWeight.Medium
          }
        })
        .padding({ top: CommonConstants.PADDING })
    }
  }

  /**
   * 从文件中读取省市区json数据
   */
  async loadRegion(): Promise<void> {
    try {
      // 通过getRawFileContent()获取resources/rawfile目录下对应的文件内容,得到一个字节数组
      getContext(this).resourceManager.getRawFileContent(this.fileName, (_: BusinessError, value: Uint8Array) => {
        let rawFile = value;
        let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
        let retStr =
          textDecoder.decodeToString(rawFile, { stream: false }); // 再用@ohos.util (util工具函数)的TextDecoder给它解析出来
        this.cascade = JSON.parse(retStr);
      })
    } catch (error) {
      let code = (error as BusinessError).code;
      let message = (error as BusinessError).message;
      console.error(`callback getRawFileContent failed, error code: ${code}, message: ${message}.`);
    }
  }

  /**
   * 从TextPicker返回选中的数据中逐级查找省、市、区的名称,并将其组合成一个完整的地址字符串。
   */
  getSelectedPlace() {
    if (this.addressSelectIndex instanceof Array) {
      let province = this.cascade[this.addressSelectIndex[0]]; // 获取省信息
      let areaName = ""; // 存储最终构建的省市区名称
      if (province) {
        areaName += this.cascade[this.addressSelectIndex[0]].text; // 省的名称添加到容器里
        if (province.children) { // 检查是否有市的信息
          let city = province.children[this.addressSelectIndex[1]]; // 市的名称添加到容器里
          if (city) {
            areaName += city.text;
            if (city.children) { // 检查是否有区的信息
              areaName += city.children[this.addressSelectIndex[2]].text; // 区的名称添加到容器里
            }
          }
        }
      }
      this.selectText = areaName; // 将取出的省市区拼接的字符串回填给TextInput
      return;
    }
  }

  /**
   * 遍历返回数据,组合成地址
   */
  getSelectedCityName() {
    let nameTemp = ''
    this.addressSelectText.forEach(element => {
      nameTemp += element
    });
    this.selectText = nameTemp
  }
}

@Extend(Text)
function titleBtnStyle() {
  .fontColor($r('app.color.editaddress_font_color'))
  .fontSize(CommonConstants.HALF_FONTSIZE)
  .fontWeight(CommonConstants.HALF_FONT_WEIGHT)
}

参考:
编辑收货地址案例
TextPicker