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

2022-09-26 10:48:53 浏览数 (1)

文章目录

  • 前言
  • 一、picker选择器
  • 二、js滚动选择器实现
    • 1.组件封装
    • 2.使用
    • 3.效果
  • 三、wxs滚动选择器实现
    • 1.组件封装
    • 2.使用
    • 3.效果
  • 四、相关组件pop-up四件套

前言

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

数据可以是后台从数据库读出来的数据,也可以是在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;
}

0 人点赞