鸿蒙地址选择库(ArkTs UI)

发布于:2025-08-31 ⋅ 阅读:(23) ⋅ 点赞:(0)

功能点:支持三级联动、点击确认返回省市区code及name(安心)、布局可以高度自定义

实现:TextPicker+读取本地json(也可用第三方的json 不过需要自行调整了)
先上图吧、废话下面再说:

凑和看吧、(可能是虚拟机卡、或者动画原因、联动会慢一些)

调用:
 

  方式一 1、//注意调用处是在ets @Component 组件中
  let areaData = await AreaDataUtil.loadJsonFile(getContext())
  GlobalAreaPicker.show(this.getUIContext(),{
      areaData: areaData,
      onConfirm: (province, city, county,addr) => {//或者直接用addr
        console.log(`选中:${province.name} ${city.name} ${county.name}`);
      },
      onCancel: () => {
        console.log('取消选择');
      }
   })


方式二 2、//调用注意调用处是在ets @Component 组件中
 showAreaPicker(this.getUIContext(), (province, city, county,addr) => {
      // this.companyAddress = `${province.name} ${city.name} ${county.name}`;
     
})

1、主要类说明看截图

补充代码截图

2、上代码 :
2.1 GlobalAreaPicker.ets(核心实现类)

// GlobalAreaPicker.ets
import { JsonAddr } from '../utils/AreaDataUtil';
import { ComponentContent } from '@kit.ArkUI';
import { FontDiy, TextStyleModifier } from './style/TextStyles';
import { IS_PREVIEW } from 'models';
import { DIVIDER_COLOR } from '../constant/ColorConstant';

//地址选择器参数传递
export interface AreaSelectOption {
  areaData: JsonAddr[],
  onConfirm?: (province: JsonAddr, city: JsonAddr, county: JsonAddr,addressInfo:string) => void,//addressInfo 拼接好的地址 默认空格隔开
  onCancel?: () => void// 反正我没用着、留着呗
}

//全局弹窗封装、不依赖沙雕Pager
export class GlobalAreaPicker {
  static contentNode?: ComponentContent<Object>

  static show(uiContext: UIContext, options: AreaSelectOption) {
    GlobalAreaPicker.contentNode = new ComponentContent(
      uiContext,
      wrapBuilder(buildAreaPicker),
      options
    );

    uiContext.getPromptAction().openCustomDialog(GlobalAreaPicker.contentNode, {
      alignment: DialogAlignment.Bottom,
      autoCancel: true,
    });
  }

  static close(uiContext: UIContext) {
    if (GlobalAreaPicker.contentNode !== null) {
      uiContext.getPromptAction().closeCustomDialog(GlobalAreaPicker.contentNode)
    }
  }
}

//全局弹窗需要、甲鱼的屁股-》规定
@Builder
function buildAreaPicker(params: AreaSelectOption) {
  AreaPickerContent({
    areaData: params.areaData,
    onConfirm: params.onConfirm,
    onCancel: params.onCancel
  })
}

// 弹窗内容组件(开业)
// @Preview 需要虚拟机运行才行、
@Component
struct AreaPickerContent {
  @Prop areaData: JsonAddr[] = [];
  @State selectedProvince: JsonAddr | null = null;
  @State selectedCity: JsonAddr | null = null;
  @State selectedCounty: JsonAddr | null = null;
  @State provinceIndex: number = 0;
  @State cityIndex: number = 0;
  @State countyIndex: number = 0;

  // TextPicker 数据
  @State provinceNames: string[] = [];
  @State cityNames: string[] = [];
  @State countyNames: string[] = [];

  onConfirm?: (province: JsonAddr, city: JsonAddr, county: JsonAddr,addressInfo:string) => void;
  onCancel?: () => void;

  aboutToAppear() {
    this.provinceNames = this.areaData.map(p => p.name);
    this.initDefaultSelection();
  }

  initDefaultSelection() {
    if (this.areaData.length > 0) {
      this.selectedProvince = this.areaData[0];
      this.provinceIndex = 0;
      this.updateCities();
    }
  }

  updateCities() {
    if (this.selectedProvince?.children && this.selectedProvince.children.length > 0) {
      this.cityNames = this.selectedProvince.children.map(c => c.name);
      this.selectedCity = this.selectedProvince.children[0];
      this.cityIndex = 0;
      this.updateCounties();
    }
  }

  updateCounties() {
    if (this.selectedCity?.children && this.selectedCity.children.length > 0) {
      this.countyNames = this.selectedCity.children.map(c => c.name);
      this.selectedCounty = this.selectedCity.children[0];
      this.countyIndex = 0;
    }
  }

  build() {
    Column() {
      // 顶部操作栏
      Row() {
        Text('取消')
          .attributeModifier(new TextStyleModifier(16, '#999999', FontDiy.MEDIUM, IS_PREVIEW))
          .onClick(() => {
            GlobalAreaPicker.close(this.getUIContext());
            this.onCancel?.();
          })

        Blank()

        Text('确认')
          .attributeModifier(new TextStyleModifier(16, '#1778FF', FontDiy.MEDIUM, IS_PREVIEW))
          .onClick(() => {
            if (this.selectedProvince && this.selectedCity && this.selectedCounty) {

              this.onConfirm?.(
                this.selectedProvince,
                this.selectedCity,
                this.selectedCounty
                ,`${this.selectedProvince.name}${this.selectedCity.name.includes('市辖区')?' ':` ${this.selectedCity.name} `}${this.selectedCounty.name}`//拼接字符串地址(过滤掉市辖区)
              );
            }
            GlobalAreaPicker.close(this.getUIContext());
          })
      }
      .width('100%')
      .height(44)
      .padding({ left: 16, right: 16 })

      Divider().color('#f0f0f0')

      Row() {
        // 省份
        TextPicker({ range: this.provinceNames, selected: this.provinceIndex })
          .width('33.33%')
          .height(250)
          .canLoop(false)
          .selectTextStyle()//往下扒拉扒拉 能找到
          .onChange((value: string | string[], index: number | number[]) => {
            if (typeof index === 'number') {
              this.provinceIndex = index;
              this.selectedProvince = this.areaData[index];
              this.updateCities();
            }
          })

        // 城市
        TextPicker({ range: this.cityNames, selected: this.cityIndex })
          .width('33.33%')
          .height(250)
          .canLoop(false)
          .selectTextStyle()
          .onChange((value: string | string[], index: number | number[]) => {
            if (typeof index === 'number' && this.selectedProvince?.children) {
              this.cityIndex = index;
              this.selectedCity = this.selectedProvince.children[index];
              this.updateCounties();
            }
          })

        // 区县
        TextPicker({ range: this.countyNames, selected: this.countyIndex })
          .width('33.33%')
          .height(250)
          .canLoop(false)
          .selectTextStyle()
          .onChange((value: string | string[], index: number | number[]) => {
            if (typeof index === 'number' && this.selectedCity?.children) {
              this.countyIndex = index;
              this.selectedCounty = this.selectedCity.children[index];
            }
          })
      }
    }
    .backgroundColor(Color.White)
    .borderRadius({ topLeft: 12, topRight: 12 })
  }
}
//扩展属性
@Extend(TextPicker)
function selectTextStyle() {
  .selectedTextStyle({color: '#333333', font: {size: 18, family:$r('[base].media.pf_medium')}})//报错 删了family就行、自定义字体
  .textStyle({color: '#999999', font: {size: 16, family:$r('[base].media.pf_regular')}})
  .disappearTextStyle({color: '#999999', font: {size: 14, family:$r('[base].media.pf_regular')}})
  .divider({
    color: DIVIDER_COLOR,//#d5d5d5
  } as DividerOptions)
}

/*
// 使用方式
GlobalAreaPicker.show({
  areaData: yourAreaData,
  onConfirm: (province, city, county) => {
    console.log(`选中:${province.name} ${city.name} ${county.name}`);
  },
  onCancel: () => {
    console.log('取消选择');
  }
});*/

2.2 AreaDataUtil.ts(读取地址json)

import { logger } from "../../../mock/baseMock";
import { Context } from "@kit.AbilityKit";
import { util } from "@kit.ArkTS";

// utils/AreaDataUtil.ets
export class AreaDataUtil {
  private static JsonAddr: JsonAddr[] = []
  //读取本地json文件
  static async loadJsonFile(context: Context): Promise<JsonAddr[]> {
    if (this.JsonAddr.length > 0) {
      logger.info('loadJsonFile stop read reason: has address '+AreaDataUtil.JsonAddr.length)
      return AreaDataUtil.JsonAddr
    }
    try {
      logger.info('开始读取地址 ')
      const mgr = context.resourceManager;
      const value = await mgr.getRawFileContent('newprovince.json');
      const textDecoder = util.TextDecoder.create('utf-8');
      const jsonStr = textDecoder.decodeWithStream(value);
      logger.info('地址读取完成 ')
      let ja = JSON.parse(jsonStr) as JsonAddr[]
      AreaDataUtil.JsonAddr = ja
      return ja;
    } catch (error) {
      console.error('读取JSON失败', error);
      return null;
    }
  }
}

//地址选择
/*export interface AreaList {
  province_list: Record<string, string>
  city_list: Record<string, string>
  county_list: Record<string, string>
  test: CascaderOption[]
}*/

//映射第三库的地址
export interface CascaderOption {
  text: string
  value: string
  children?: CascaderOption[]
}

//本地地址json newprovince.json
export interface JsonAddr {
  name: string
  code: string
  children?: JsonAddr[]
}

2.3 TextStyleModifier.ets 自定义字体 (不重要、可以不用加、只需要删除text中的.attributeModifier(xxxxx)属性即可)

export class TextStyleModifier implements AttributeModifier<TextAttribute> {
  fontSize?: number | string | Resource
  fontColor?: ResourceColor
  fontFamily?: string | Resource | FontDiy = FontDiy.UNSET
  isPreview :boolean = false

  constructor(fontSize: number | string | Resource,
    fontColor?: ResourceColor,
    fontFamily?: string | Resource| FontDiy,
    isPreview:boolean = false
  ) {
    this.fontSize = fontSize
    this.fontColor = fontColor
    this.fontFamily = fontFamily
    this.isPreview = isPreview
  }

  applyNormalAttribute(instance: TextAttribute): void {
    if (this.fontSize !== undefined) {
      instance.fontSize(this.fontSize)
    }

    if (this.fontColor !== undefined) {
      instance.fontColor(this.fontColor)
    }

    if (this.fontFamily !== undefined) {
      //枚举类型
      if (this.isFontDiy(this.fontFamily)) {
        if(this.isPreview) {
          return
        }
        switch (this.fontFamily) {
          case FontDiy.REGULAR:
            // 处理 REGULAR
            instance.fontFamily($r('[base].media.pf_regular'))
            break
          case FontDiy.MEDIUM:
            instance.fontFamily($r('[base].media.pf_medium'))
            // 处理 MEDIUM
            break
          case FontDiy.SEMI_BOLD:
            instance.fontFamily($r('[base].media.pf_semibold'))
            // ...
            break
          default:
        // 默认处理
            instance.fontFamily($r('[base].media.youshebiaotihei'))
          break
        }

      } else {
        //不确定是否有问题
        instance.fontFamily(`${this.fontFamily}`)
      }
    }
  }

  // 类型守卫函数
  isFontDiy(value: string | Resource | FontDiy): boolean {
    return Object.values(FontDiy).includes(value as FontDiy);
  }
}

export enum FontDiy {
  UNSET,
  REGULAR,
  MEDIUM,
  SEMI_BOLD,
  YOU_SHE_BIAO_TIHEI
}

2.4 PickerGlobalFun.ets

//地址选择器
export async function showAreaPicker(uiContext: UIContext, call?: (province: JsonAddr, city: JsonAddr,
  county: JsonAddr,addressInfo:string) => void) {
  //去读本地数据数据
  let areaData = await AreaDataUtil.loadJsonFile(getContext())
  GlobalAreaPicker.show(uiContext, {
    areaData: areaData,
    onConfirm:call,
  })
}

相关代码基本就这么多、加到自己工程里、需要自己稍微调整下 部分变量如IS_PREVIEW找不到可以使用false代替

别大意=》(注意文件类型、注意文件类型、注意文件类型 如ets/ts

3、json地址文件

文件地址:https://download.csdn.net/download/BirdEatBug/91801354

至此基本over

扩展:

1、环境

硬件环境:DevEco Studio 5.0.0

鸿蒙sdk版本(Compatible Sdk):12 

2、json数据源是否可以自定义?

可以
放心目前用的也就是最近两三年的地址json、当然有的需求不一样需要用自己的服务器的地址json

只需要调整JsonAddr 就行了(同时修改AreaPickerContent 所涉及的JsonAddr 字段即可)

修改JsonAddr参考AreaDataUtils 中

let ja = JSON.parse(jsonStr) as JsonAddr[]//改成自己定义的接口就行。
//原来数据源定义如下、可按照需求调整
export interface JsonAddr {
  name: string// 省市区 名称
  code: string//对应如上的地址编码
  children?: JsonAddr[]//市区/市县
}

3、是否有推荐的第三方地址json库

这个:OpenHarmony三方库中心仓 

github:https://github.com/ibestservices/area-data 这两个是同一个库、地址维护比较新        
 


网站公告

今日签到

点亮在社区的每一天
去签到