【鸿蒙应用开发系列】实现省市地区三级联动的选择器组件

前言

在购物类App中,当用户购买商品下单的时候,需要提供收货地址,地址管理对于用户体验和商城的运营至关重要。

省市区联动组件为用户提供了一种直观、高效的方式来选择地理位置信息。通过联动的方式,用户只需点击或滑动即可浏览和选择相关地区,避免了手动输入的繁琐和错误。

通过本文,读者将了解到实现省市区联动组件的关键技术和注意事项,并能够根据自身需求进行开发和定制。

案例效果

需求分析

在本示例中,用户可以选择省份,然后根据所选省份展示相应的城市列表。当用户选择城市后,应显示该城市对应的区域列表。 业务逻辑应确保省、市、区之间的关联正确,即用户只能选择对应省份和城市的区域。

数据来源和数据结构

数据来源

在初始化地址选择组件时,需要传入省、市、区数据。数据来源可以是多种多样的,例如内部数据库、外部 API 或静态数据文件。 我们可以结合项目的实际情况选择合适的数据来源。

示例中,我们通过外部 API的方式获取省、市、区的数据。如下图所示。 第一个接口,是获取一级数据。

第二个接口,是获取二级数据。其接收一个一级数据的code。例如,使用山东省的code值370000作为参数,获取二级数据,获取山东省下的所有市区结构。

第三个接口,是获取三级数据。其接收一个二级数据的code。例如,使用济南市的code值370100作为参数,获取三级数据。 根据以上3个接口,通过code参数的筛选得到结果。将获取的结果赋值给currentArea变量,代码如下所示。

typescript 复制代码
//当前选择器区域显示的数据
@State currentArea: AreaModel[] = []
//点击选择的数据
@State selectData: SelAreaModel = new SelAreaModel();

//获取省数据
getProvince() {
   AreaPickerViewModel.getProvince().then((data: AreaModel[]) => {
     this.currentArea = data
   }).catch((error) => {
     promptAction.showToast({ message: error })
   })
}

//获取市数据
getCity(code: number) {
   AreaPickerViewModel.getCity(code).then((data: AreaModel[]) => {
     this.currentArea = data
   }).catch((error) => {
     promptAction.showToast({ message: error })
   })
}

//获取区数据
getDistrict(code: number) {
   AreaPickerViewModel.getDistrict(code).then((data: AreaModel[]) => {
     this.currentArea = data
   }).catch((error) => {
     promptAction.showToast({ message: error })
   })
}

数据结构

其中,AreaModel是选择器所需的数据,属性如下:

typescript 复制代码
export class AreaModel {
  id: number  //主键id
  name: string //省市区的名称
  code: number //省市区的地址编码

  constructor(id: number, name: string, code: number) {
    this.id = id
    this.name = name
    this.code = code
  }
}

SelAreaModel是当前选择器区域选择保存的数据,属性如下:

typescript 复制代码
export class SelAreaModel {
  provinceName: string
  provinceCode: number
  cityName: string
  cityCode: number
  districtName: string
}

界面设计

在本示例中,地址选择组件使用Panel 组件,其为可滑动面板,提供一种轻量的内容展示窗口,方便在不同尺寸中切换。 地址内容使用Grid组件,其为网格容器,由"行"和"列"分割的单元格所组成,通过指定"项目"所在的单元格做出各种各样的布局。代码如下所示。

scss 复制代码
@Builder GridLayout() {
    Grid() {
        ForEach(this.currentArea, (item: AreaModel) => {
        GridItem() 
        })
    }
    .columnsTemplate('1fr 1fr 1fr')
    .rowsGap($r('app.float.float10'))
    .width('100%')
    .padding($r('app.float.float10'))
}
scss 复制代码
Panel(this.show) {
    Column({ space: CommonConstants.FLOAT_NUMBER_10 }) {
    Row() {
    CommonText({ text: '选择区域' })
    Blank()
    Image($r('app.media.icon_close')).width($r('app.float.float20')).onClick(() => {
        this.closePanel()
    })
    }.width('100%').padding({ left: $r('app.float.float20'), right: $r('app.float.float20') })

    Divider().strokeWidth(1).color($r('app.color.top_bg'))

    if (this.currentArea.length > 0) {
        this.GridLayout()
    }

    }.width('100%').padding({ top: $r('app.float.float10'), bottom: $r('app.float.float10') })
}
.type(PanelType.Foldable)
.mode(PanelMode.Full)
.dragBar(false)
.backgroundColor($r('app.color.white'))

数据绑定与联动逻辑

通过@State装饰器和@Builder装饰器,实现数据绑定,通过@Link装饰器,进行父子组件的通信

在省份选择控件GridItem上设置监听器,当用户选择一个省份时,根据选中的省份在数据源中获取对应的城市列表。然后,将城市列表绑定到城市选择控件的数据源。类似地,在城市选择控件上设置监听器,当用户选择一个城市时,根据选中的城市在数据源中获取对应的区域列表。最后,将区域列表绑定到区域选择控件的数据源。代码如下所示。

less 复制代码
//是否显示
@Link show: boolean
//获取选择的省市区
@Link areaName: string
//省
@Link province: string
//市
@Link city: string
//区
@Link county: string

//当前省市区选择的下标
@State selectIndex: number = 0;
//点击选择的数据
@State selectData: SelAreaModel = new SelAreaModel();
@State currentArea: AreaModel[] = []
//显示市
@State showCity: boolean = false
//显示区
@State showDistrict: boolean = false

Row({ space: CommonConstants.FLOAT_NUMBER_12 }) {
    //显示省
    if (this.selectIndex == 0 && !this.selectData.provinceName) {
        Column({ space: CommonConstants.FLOAT_NUMBER_2 }) {
        Text('请选择')
                .fontColor(this.selectIndex == 0 ? $r('app.color.logo_color') : $r('app.color.text_color'))
                .fontSize($r('app.float.font14'))
        Rect({ width: CommonConstants.FLOAT_NUMBER_18, height: CommonConstants.FLOAT_NUMBER_2 })
                .fill(this.selectIndex == 0 ? $r('app.color.logo_color') : $r('app.color.white'))
    }
    } else {
        Column({ space: CommonConstants.FLOAT_NUMBER_2 }) {
        Text(this.selectData.provinceName)
                .fontColor(this.selectIndex == 0 ? $r('app.color.logo_color') : $r('app.color.text_color'))
        .fontSize($r('app.float.font14'))
        .onClick(() => {
            this.selectIndex = 0
            this.showCity = false
            this.selectData.cityName = ''
            this.selectData.cityCode = 0
            this.currentArea = []
            this.getProvince()
        })
        Rect({ width: CommonConstants.FLOAT_NUMBER_18, height: CommonConstants.FLOAT_NUMBER_2 })
                .fill(this.selectIndex == 0 ? $r('app.color.logo_color') : $r('app.color.white'))
}
}
    //显示市
    if (this.showCity) {
        if (this.selectIndex == 1 && !this.selectData.cityName) {
            Column({ space: CommonConstants.FLOAT_NUMBER_2 }) {
            Text('请选择')
                  .fontColor(this.selectIndex == 1 ? $r('app.color.logo_color') : $r('app.color.text_color'))
                  .fontSize($r('app.float.font14'))
            Rect({ width: CommonConstants.FLOAT_NUMBER_18, height: CommonConstants.FLOAT_NUMBER_2 })
                  .fill(this.selectIndex == 1 ? $r('app.color.logo_color') : $r('app.color.white'))
        }
        } else {
              Column({ space: CommonConstants.FLOAT_NUMBER_2 }) {
                Text(this.selectData.cityName)
                  .fontColor(this.selectIndex == 1 ? $r('app.color.logo_color') : $r('app.color.text_color'))
                  .fontSize($r('app.float.font14'))
                  .onClick(() => {
                    this.selectIndex = 1
                    this.currentArea = []
                    this.showDistrict = false
                    this.selectData.districtName = ''
                    this.getCity(this.selectData.provinceCode)
                  })
                Rect({ width: CommonConstants.FLOAT_NUMBER_18, height: CommonConstants.FLOAT_NUMBER_2 })
                  .fill(this.selectIndex == 1 ? $r('app.color.logo_color') : $r('app.color.white'))
}
}
}

        //显示区
        if (this.showDistrict) {
        if (this.selectIndex == 2 && !this.selectData.districtName) {
            Column({ space: CommonConstants.FLOAT_NUMBER_2 }) {
            Text('请选择')
                  .fontColor(this.selectIndex == 2 ? $r('app.color.logo_color') : $r('app.color.text_color'))
                  .fontSize($r('app.float.font14'))
        Rect({ width: CommonConstants.FLOAT_NUMBER_18, height: CommonConstants.FLOAT_NUMBER_2 })
                  .fill(this.selectIndex == 2 ? $r('app.color.logo_color') : $r('app.color.white'))
        }
        } else {
            Column({ space: CommonConstants.FLOAT_NUMBER_2 }) {
            Text(this.selectData.districtName)
                  .fontColor(this.selectIndex == 2 ? $r('app.color.logo_color') : $r('app.color.text_color'))
            .fontSize($r('app.float.font14'))
            .onClick(() => {
                this.selectIndex = 2
                this.currentArea = []
                this.getDistrict(this.selectData.cityCode)
            })
            Rect({ width: CommonConstants.FLOAT_NUMBER_18, height: CommonConstants.FLOAT_NUMBER_2 })
                  .fill(this.selectIndex == 2 ? $r('app.color.logo_color') : $r('app.color.white'))
}
}
}

}.width('100%').padding({ left: $r('app.float.float10'), right: $r('app.float.float10') })
kotlin 复制代码
.onClick(() => {
if (this.selectIndex === 0) {
this.selectData.provinceName = item.name;
this.selectData.provinceCode = item.code;
this.selectData.cityName = "";
this.selectData.districtName = "";
this.currentArea = []
this.showCity = true;
setTimeout(() => {
this.selectIndex = 1;
this.getCity(item.code)
}, 200)
} else if (this.selectIndex === 1) {
this.selectData.cityName = item.name;
this.selectData.cityCode = item.code;
this.selectData.districtName = "";
this.currentArea = []
this.showDistrict = true;
setTimeout(() => {
this.selectIndex = 2;
this.getDistrict(item.code)
}, 200)

} else if (this.selectIndex === 2) {
this.selectData.districtName = item.name;
this.province = this.selectData.provinceName
this.city = this.selectData.cityName
this.county = this.selectData.districtName
this.areaName = this.selectData.provinceName + "/" + this.selectData.cityName + "/" +   this.selectData.districtName;
this.reset()
}
})

总结

目前,鸿蒙 4.0 及其对应 SDK API 9.0 版本尚处于磨合阶段,可能会出现某些瑕疵,或者在使用官方 API 时存在一些不便之处。通过本文,您了解了实现省市区联动组件的关键技术和注意事项,您可以根据自身需求进行开发和定制。

相关推荐
等什么君!3 分钟前
学习vue3:监听器
前端·vue.js·学习
患得患失94916 分钟前
【HTML】【面试提问】HTML面试提问总结
前端·html
天天打码1 小时前
Nuxt.js一个基于 Vue.js 的通用应用框架
前端·javascript·vue.js
Dnn011 小时前
前端读取本地项目中 public/a.xlsx 文件中的数据 vue3
前端·javascript·vue.js·读取xlsx数据
一块小砖头儿1 小时前
HTML向四周扩散背景
前端·javascript·html
杨超越luckly1 小时前
HTML应用指南:利用POST请求获取全国申通快递服务网点位置信息
大数据·前端·信息可视化·数据分析·html
Allen Bright1 小时前
【HTML-1】HTML骨架标签:构建网页的基础框架
前端·html
OneT1me2 小时前
SN生成流水号并且打乱
java·linux·前端
眼镜chen2 小时前
luckysheet的使用——17.将表格作为pdf下载到本地
前端·javascript·vue.js·chrome·pdf
_oP_i2 小时前
python实现pdf转图片(针对每一页)
前端·数据库·python