文章目录
- 前言
- 一、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;
}