功能点:支持三级联动、点击确认返回省市区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库
github:https://github.com/ibestservices/area-data 这两个是同一个库、地址维护比较新