前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【愚公系列】2022年08月 微信小程序-省市区三联动功能实现

【愚公系列】2022年08月 微信小程序-省市区三联动功能实现

作者头像
愚公搬代码
发布2022-09-26 10:48:53
3.3K0
发布2022-09-26 10:48:53
举报
文章被收录于专栏:历史专栏

文章目录


前言

多级联动下拉菜单是前端常见的效果,省市区三级联动又属于其中最典型的案例。多级联动一般都是与数据相关联的,根据数据来生成和修改联动的下拉菜单。完成一个多级联动效果,有助于增强对数据处理的能力。

数据可以是后台从数据库读出来的数据,也可以是在JS里直接写的数据。但无论是哪种形式,三个数组的数据都是有关联的。

citys,市数组,里面每一项内容都有一个属性表示这个市是属于哪个省的,即对应的是省数组里的id。

同样areas,区数组,里面都有属性是对应市数组里的id,表示这个区是属于哪个市的。

相关json数据链接:https://download.csdn.net/download/aa2528877987/86504988

小程序是自带省市区选择器的,下面介绍三种方式实现省市区三联动

一、picker选择器

在小程序mode = region就代表是省市区选择器,不过这种选择器比较局限,无法自定义。

代码语言:javascript
复制
<view class="section">
  <view class="section__title">省市区选择器</view>
  <picker mode="region" bindchange="bindRegionChange" value="{{region}}" custom-item="{{customItem}}">
    <view class="picker">
      当前选择:{{region[0]}},{{region[1]}},{{region[2]}}
    </view>
  </picker>
</view>
代码语言:javascript
复制
Page({
  data: {
    region: ['广东省', '广州市', '海珠区'],
    customItem: '全部'
  },
  bindRegionChange: function (e) {
    console.log('picker发送选择改变,携带值为', e.detail.value)
    this.setData({
      region: e.detail.value
    })
  }
})
在这里插入图片描述
在这里插入图片描述

二、js滚动选择器实现

1.组件封装

city.js相关json数据链接:https://download.csdn.net/download/aa2528877987/86504988

组件index四件套

代码语言:javascript
复制
// 该组件参照了以下文章,感兴趣请前往查看原文
// https://developers.weixin.qq.com/community/develop/article/doc/0000643f674fa81a18a92b37455413
// 在此对原作者表示感谢~

var app = getApp()
var address = require('./city.js')

// components/region-picker-view/index.js
Component({
  options: {
    multipleSlots: false // 在组件定义时的选项中启用多slot支持
  },
  /**
   * 组件的属性列表
   */
  properties: {

  },

  /**
   * 组件的初始数据
   */
  data: {
    address: '', //详细收货地址(四级)
    value: [0, 0, 0], // 地址选择器省市区 暂存 currentIndex
    region: '', //所在地区
    regionValue: [0, 0, 0], // 地址选择器省市区 最终 currentIndex
    provinces: [], // 一级地址
    citys: [], // 二级地址
    areas: [], // 三级地址
    visible: false
  },

  ready(){
    // 默认联动显示北京
    var id = address.provinces[0].id 
    this.setData({
      provinces: address.provinces, // 34省
      citys: address.citys[id], //默认北京市辖区
      areas: address.areas[address.citys[id][0].id]
    })
  },

  /**
   * 组件的方法列表
   */
  methods: {
    closePopUp() {
      this.setData({
        visible: false
      })
    },
    pickAddress() {
      this.setData({
        visible: true,
        value: [...this.data.regionValue]
      })
    },
    // 处理省市县联动逻辑 并保存 value
cityChange(e) {
  var value = e.detail.value
  let {
    provinces,
    citys
  } = this.data
  var provinceNum = value[0]
  var cityNum = value[1]
  var areaNum = value[2]

  if (this.data.value[0] !== provinceNum) {
    var id = provinces[provinceNum].id
    this.setData({
      value: [provinceNum, 0, 0],
      citys: address.citys[id],
      areas: address.areas[address.citys[id][0].id]
    })
  } else if (this.data.value[1] !== cityNum) {
    var id = citys[cityNum].id
    this.setData({
      value: [provinceNum, cityNum, 0],
      areas: address.areas[citys[cityNum].id]
    })
  } else {
    this.setData({
      value: [provinceNum, cityNum, areaNum]
    })
  }
},
    preventTouchmove() {},
    // 城市选择器
    // 点击地区选择取消按钮
    cityCancel(e) {
      var id = address.provinces[0].id 
      this.setData({
        citys: this.data.lastCitys ||  address.citys[id], //默认北京市辖区,
        areas: this.data.lastAreas || address.areas[address.citys[id][0].id],
        value: [...this.data.regionValue],
        visible: false
      })
    },
    // 提交时由序号获取省市区id
    getRegionId(type) {
      let value = this.data.regionValue
      let provinceId = address.provinces[value[0]].id
      let townId = address.citys[provinceId][value[1]].id
      let areaId = ''
      if (address.areas[townId][value[2]].id) {
        areaId = address.areas[townId][value[2]].id
      } else {
        areaId = 0
      }
  
      if (type === 'provinceId') {
        return provinceId
      } else if (type === 'townId') {
        return townId
      } else {
        return areaId
      }
    },
    // 点击地区选择确定按钮
    citySure(e) {
      var value = this.data.value
      this.setData({
        visible: false
      })
      // 将选择的城市信息显示到输入框
      try {
        var region = (this.data.provinces[value[0]].name || '') + (this.data.citys[value[1]].name || '')
        if (this.data.areas.length > 0) {
          region = region + this.data.areas[value[2]].name || ''
        } else {
          this.data.value[2] = 0
        }
      } catch (error) {
        console.log('adress select something error')
      }
  
      this.setData({
        region: region,
        lastCitys: this.data.citys,
        lastAreas: this.data.areas,
        regionValue: [...this.data.value]
      }, () => {
        console.log(`省份ID:${this.getRegionId('provinceId')}: 市区ID:${this.getRegionId('townId')}:城区ID:${this.getRegionId('areas')}`)
        this.triggerEvent('change',{value:{
          region,
          province:{
            id:this.getRegionId('provinceId'),
            name:this.data.provinces[value[0]].name
          },
          town:{
            id:this.getRegionId('townId'),
            name:this.data.citys[value[1]].name
          },
          area:{
            id:this.getRegionId('areas'),
            name:this.data.areas[value[2]].name
          }
        }})
      })
    }
  }
})
代码语言:javascript
复制
{
  "component": true,
  "usingComponents": {
    "pop-up": "../pop-up/index"
  }
}
代码语言:javascript
复制
<!--components/region-picker-view/index.wxml-->
<view class="address-item" bindtap="pickAddress">
  <view class="item-title">所在地区:</view>
  <view class="item-content arrow {{region ? '' : 'item-content_shadow'  }}">{{region||"请选择"}}</view>
</view>
<pop-up visible="{{visible}}" onClose="closePopUp">
  <view slot="content">
    <view class="picker-view">
      <view class="picker-view__pane">
        <text catchtap="cityCancel">取消</text>
        <text catchtap="citySure">确定</text>
      </view>
<picker-view class="pick-view__group" bindchange="cityChange" value="{{value}}" wx:key="*this">
  <picker-view-column indicator-class="item_active">
    <view wx:for="{{provinces}}" class="picker-item" wx:key="index">{{item.name}}</view>
  </picker-view-column>
  <picker-view-column>
    <view wx:for="{{citys}}" class="picker-item" wx:key="index">{{item.name}}</view>
  </picker-view-column>
  <picker-view-column>
    <view wx:for="{{areas}}" class="picker-item" wx:key="index">{{item.name}}</view>
  </picker-view-column>
</picker-view>
    </view>
  </view>
</pop-up>
代码语言:javascript
复制
/* components/region-picker-view/index.wxss */
.address-item {
  min-height: 98rpx;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  border-bottom: 1px solid #f1f1f1;
  padding: 0 32rpx
}

.item-title {
  width: 140rpx;
  color: #4d4c4c;
  font-size: 28rpx;
  height: 98rpx;
  line-height: 98rpx;
}
.item-content {
  width: 520rpx;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 28rpx;
  height: 98rpx;
  line-height: 98rpx;
  color: #4d4c4c;
}
/* 地区级联选择器 */

.picker-view {
  width: 100%;
  display: flex;
  background-color: #fff;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  bottom: 0rpx;
  left: 0rpx;
}

.picker-item {
  line-height: 70rpx;
  margin-left: 5rpx;
  margin-right: 5rpx;
  text-align: center;
}

.picker-view__pane {
  height: 100rpx;
  width: 100%;
  padding: 20rpx 32rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-sizing: border-box;
}

.picker-view__pane text{
  color: #00cc88;
  font-size: 30rpx;
}

.pick-view__group {
  width: 96%;
  height: 450rpx;
}

2.使用

代码语言:javascript
复制
<region-picker-view bindchange="onRegionChange"></region-picker-view>
代码语言:javascript
复制
onRegionChange(e){
  console.log('选择了',e.detail);
},

3.效果

在这里插入图片描述
在这里插入图片描述

三、wxs滚动选择器实现

1.组件封装

city.wxs相关json数据链接:https://download.csdn.net/download/aa2528877987/86504988

代码语言:javascript
复制
// 该组件参照了以下文章,感兴趣请前往查看原文
// https://developers.weixin.qq.com/community/develop/article/doc/0000643f674fa81a18a92b37455413
// 在此对原作者表示感谢~

// var address = require('./city')
Component({
  options: {
    multipleSlots: false 
  },
  properties: {},
  data: {
    // value: [0, 0, 0], // 地址选择器省市区 暂存 currentIndex
    // regionText: '', //所在地区
    // provinces: null, // 一级地址
    // citys: null, // 二级地址
    // areas: null, // 三级地址
    visible: false
  },
  ready(){},
  methods: {}
})
代码语言:javascript
复制
{
  "component": true,
  "usingComponents": {
    "pop-up": "../pop-up/index"
  }
}
代码语言:javascript
复制
<!--components/region-picker-view2/index.wxml-->
<wxs module="region" src="./region.wxs"></wxs>
<view change:class="{{region.onPropSigned}}" class="address-item" bindtap="{{region.pickAddress}}">
	<view class="item-title">所在地区:</view>
	<view class="item-content arrow {{regionText ? '' : 'item-content_shadow'  }}">{{regionText||"请选择"}}</view>
</view>
<pop-up visible="{{visible}}" onClose="closePopUp" bindready="{{region.onComponentReady}}">
	<view slot="content">
		<view class="picker-view">
			<view class="picker-view__pane">
				<text catchtap="{{region.onCancel}}">取消</text>
				<text catchtap="{{region.onSure}}">确定</text>
			</view>
			<picker-view class="pick-view__group" bindpickstart="{{region.onPickStart}}" bindpickend="{{region.onPickEnd}}" bindchange="{{region.onChange}}" value="{{value}}" wx:key="*this">
<picker-view-column indicator-class="item_active">
  <view wx:for="{{provinces}}" class="picker-item" wx:key="index">{{item.name}}</view>
</picker-view-column>
				<picker-view-column>
					<view wx:for="{{citys}}" class="picker-item" wx:key="index">{{item.name}}</view>
				</picker-view-column>
				<picker-view-column>
					<view wx:for="{{areas}}" class="picker-item" wx:key="index">{{item.name}}</view>
				</picker-view-column>
			</picker-view>
		</view>
	</view>
</pop-up>
代码语言:javascript
复制
/* components/region-picker-view/index.wxss */
.address-item {
  min-height: 98rpx;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  border-bottom: 1px solid #f1f1f1;
  padding: 0 32rpx
}

.item-title {
  width: 140rpx;
  color: #4d4c4c;
  font-size: 28rpx;
  height: 98rpx;
  line-height: 98rpx;
}
.item-content {
  width: 520rpx;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 28rpx;
  height: 98rpx;
  line-height: 98rpx;
  color: #4d4c4c;
}
/* 地区级联选择器 */

.picker-view {
  width: 100%;
  display: flex;
  background-color: #fff;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  bottom: 0rpx;
  left: 0rpx;
}

.picker-item {
  line-height: 70rpx;
  margin-left: 5rpx;
  margin-right: 5rpx;
  text-align: center;
}

.picker-view__pane {
  height: 100rpx;
  width: 100%;
  padding: 20rpx 32rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-sizing: border-box;
}

.picker-view__pane text{
  color: #00cc88;
  font-size: 30rpx;
}

.pick-view__group {
  width: 96%;
  height: 450rpx;
}
代码语言:javascript
复制
var address = require('./city.wxs')
var region = {
  provinces: []
  , citys: []
  , areas: []
  , value: [-1, 0, 0] //选定的值,
  , selectedValue: [0, 0, 0]//当前选择的值
}
var dataIsDirty = false
var defaultProvinceId = address.provinces[0].id 
region.provinces = address.provinces
region.citys = address.citys[defaultProvinceId] //默认北京市辖区
region.areas = address.areas[address.citys[defaultProvinceId][0].id]

// 选择省与城市,触发数据变动
function onSelectedValueChanged(ownerInstance) {
  var selectedValue = region.selectedValue
    , value = region.value
  var provinceNum = selectedValue[0]
  var cityNum = selectedValue[1]

  if (value[0] !== provinceNum) {
    var id = region.provinces[provinceNum].id
    region.selectedValue = [provinceNum, 0, 0]
    region.citys = address.citys[id]
    region.areas = address.areas[address.citys[id][0].id]
  } else if (value[1] !== cityNum) {
    var id = region.citys[cityNum].id
    region.selectedValue = [provinceNum, cityNum, 0]
    region.areas = address.areas[region.citys[cityNum].id]
  }

  ownerInstance.callMethod("setData", {
    citys: region.citys
    , areas: region.areas
  })
}
function getRegionId(value, type) {
  var provinceId = address.provinces[value[0]].id
  var townId = address.citys[provinceId][value[1]].id
  var areaId = ''
  if (address.areas[townId][value[2]].id) {
    areaId = address.areas[townId][value[2]].id
  } else {
    areaId = 0
  }

  if (type === 'provinceId') {
    return provinceId
  } else if (type === 'townId') {
    return townId
  } else {
    return areaId
  }
}

region.onPickStart = function (e, owner) {
  console.log(e.type, e.detail)
}
region.onPickEnd = function (e, owner) {
  if (dataIsDirty) {
    onSelectedValueChanged(owner)
    dataIsDirty = false
  }
}
region.onChange = function (e, owner) {
  console.log(e.type, e.detail)
  if ('' + region.selectedValue != '' + e.detail.value) {
    dataIsDirty = true
    region.selectedValue = e.detail.value
  }
}
region.onCancel = function (e, owner) {
  // console.log(e.type, e.detail)
  var value = region.value 
  var provinceId = address.provinces[value[0]].id 
  owner.callMethod("setData",{
    citys:address.citys[provinceId]
    ,areas: address.areas[address.citys[provinceId][value[1]].id]
    ,visible:false 
  })
}
region.onSure = function (e, owner) {
  // console.log(e.type, e.detail)
  var value = region.value = region.selectedValue
  var regionTextArr = ['','','']

  // 将选择的城市信息显示到输入框
  regionTextArr[0] = region.provinces[value[0]].name || ''
  if (region.citys[value[1]]){
    regionTextArr[1] = region.citys[value[1]].name || ''
  } 
  if (region.areas[value[2]]) {
    regionTextArr[2] = region.areas[value[2]].name || ''
  } else {
    value[2] = 0
  }
  var regionText = regionTextArr.join('')

  owner.callMethod("setData",{
    regionText:regionText
    ,value:value 
    ,visible:false 
  })
  owner.triggerEvent("change", {
    value: {
      region: regionText,
      province: {
        id: getRegionId(region.value, 'provinceId'),
        name: regionTextArr[0]
      },
      town: {
        id: getRegionId(region.value, 'townId'),
        name: regionTextArr[1]
      },
      area: {
        id: getRegionId(region.value, 'areas'),
        name: regionTextArr[2]
      }
    }
  })
}
region.pickAddress = function(e, owner) {
  owner.callMethod("setData",{
    visible: true
  })
},
// Cannot use wxs function to handle custom event "ready"
region.onComponentReady = function (e, owner) {
  console.log(e, "onComponentReady");
  // onSelectedValueChanged(owner)
}
// ownerInstance不一定是页面对象
region.onPropSigned = function(newValue, oldValue, ownerInstance, instance){
  console.log("onPropSigned",newValue, oldValue, ownerInstance, instance)
  ownerInstance.callMethod("setData", {
    provinces: region.provinces
    , citys: region.citys
    , areas: region.areas
    , value: [0, 0, 0] // 地址选择器省市区 暂存 currentIndex
    , regionText: ''
  })
}

module.exports = region

2.使用

代码语言:javascript
复制
<region-picker-view bindchange="onRegionChange"></region-picker-view>
代码语言:javascript
复制
onRegionChange(e){
  console.log('选择了',e.detail);
},

3.效果

在这里插入图片描述
在这里插入图片描述

四、相关组件pop-up四件套

代码语言:javascript
复制
Component({
  options: {
    multipleSlots: true // 在组件定义时的选项中启用多slot支持
  },
  /**
   * 组件的属性列表
   */
  properties: {
    visible: {
      type: Boolean,
      value: false
    }
  },

  /**
   * 组件的初始数据
   */
  data: {},

  ready(){
    this.triggerEvent('ready')
  },

  /**
   * 组件的方法列表
   */
  methods: {
    popPreventTouchmove() { },
    popPreventTouchmove2() { },
    popPreventTouchmove3() { },
    cityChange() { },
    close() {
      this.triggerEvent('close')
    },
    handleClickMask(e) {
      // console.log(e)
      if (e.target.dataset.type !== 'unclose') this.close()
    }
  }
})
代码语言:javascript
复制
{
  "component": true,
  "usingComponents": {}
}
代码语言:javascript
复制
<view catchtouchmove="popPreventTouchmove">
  <view class="q-pp-mask  {{ visible ? 'q-pp-mask-show' : '' }} ptp_exposure" bindtap="handleClickMask" catchtouchmove="popPreventTouchmove">
    <view class=" q-pp {{ visible ? 'q-pp-show' : '' }}" catchtouchmove="popPreventTouchmove">
      <slot name="content" data-type="unclose"></slot>
    </view>
  </view>
</view>
代码语言:javascript
复制
.q-pp {
  position: fixed;
  width: 100%;
  box-sizing: border-box;
  left: 0;
  right: 0;
  bottom: 0;
  background: #f7f7f7;
  transform: translate3d(0, 100%, 0);
  transform-origin: center;
  transition: all 0.2s ease-in-out;
  z-index: 900;
  visibility: hidden;
}

.q-pp-show {
  transform: translate3d(0, 0, 0);
  visibility: visible;
}

.q-pp-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.7);
  z-index: 900;
  transition: all 0.2s ease-in-out;
  opacity: 0;
  visibility: hidden;
}

.q-pp-mask-show {
  opacity: 1;
  visibility: visible;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/08/31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 前言
  • 一、picker选择器
  • 二、js滚动选择器实现
    • 1.组件封装
      • 2.使用
        • 3.效果
        • 三、wxs滚动选择器实现
          • 1.组件封装
            • 2.使用
              • 3.效果
              • 四、相关组件pop-up四件套
              相关产品与服务
              云开发 CloudBase
              云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档