百度小程序中三级联动深入理解
最近百度小程序的项目中有一个三级联动了功能,就是省市区,由于以前没有接触过,卡了2天,各种找资料看别人写的代码才解决,特此记录一下。
前戏
如果用手百小程序官方的组件做三级联动,那么久绕不过使用" picker 底部弹起的滚动选择器",它的官方文档:https://smartprogram.baidu.com/docs/develop/component/formlist_picker/
官方介绍的这几个mode类型就不细说了,除了multiSelector(多列选择器),其他的我都没有用过,这里着重讲解下multiSelector的使用姿势!
划线的几个属性都是必须要熟练掌握的,因为一定会用到!
下面看一段code
<view class="wrap"> <view class="title">多列选择器</view> <view class="section date-section"> <picker mode="multiSelector" bindchange="bindMultiPickerChange" bindcolumnchange="bindMultiPickerColumnChange" value="{{multiIndex}}" range="{{multiArray}}" title="多列选择器"> <view class="picker"> <text class='chooseItem'>当前选择:</text> {{multiArray[0][multiIndex[0]]}} {{multiArray[1][multiIndex[1]]}} {{multiArray[2][multiIndex[2]]}} </view> </picker> </view> </view>
Page({ data: { multiIndex: [0, 0, 0], multiArray: [ ["无脊柱动物", "脊柱动物"], ["扁性动物", "线形动物", "环节动物", "软体动物", "节肢动物"], ["猪肉绦虫", "吸血虫"] ] }, bindMultiPickerChange: function(e) { console.log("picker-multiSelector changed,值为", e.detail.value); this.setData("multiIndex", e.detail.value); }, bindMultiPickerColumnChange: function(e) { console.log("修改的列为", e.detail.column, ",值为", e.detail.value); var data = { multiArray: this.getData("multiArray"), multiIndex: this.getData("multiIndex") }; data.multiIndex[e.detail.column] = e.detail.value; switch (e.detail.column) { case 0: switch (data.multiIndex[0]) { case 0: data.multiArray[1] = [ "扁性动物", "线形动物", "环节动物", "软体动物", "节肢动物" ]; data.multiArray[2] = ["猪肉绦虫", "吸血虫"]; break; case 1: data.multiArray[1] = ["鱼", "两栖动物", "爬行动物"]; data.multiArray[2] = ["鲫鱼", "带鱼"]; break; } data.multiIndex[1] = 0; data.multiIndex[2] = 0; break; case 1: switch (data.multiIndex[0]) { case 0: switch (data.multiIndex[1]) { case 0: data.multiArray[2] = ["猪肉绦虫", "吸血虫"]; break; case 1: data.multiArray[2] = ["蛔虫"]; break; case 2: data.multiArray[2] = ["蚂蚁", "蚂蟥"]; break; case 3: data.multiArray[2] = ["河蚌", "蜗牛", "蛞蝓"]; break; case 4: data.multiArray[2] = [ "昆虫", "甲壳动物", "蛛形动物", "多足动物" ]; break; } break; case 1: switch (data.multiIndex[1]) { case 0: data.multiArray[2] = ["鲫鱼", "带鱼"]; break; case 1: data.multiArray[2] = ["青蛙", "娃娃鱼"]; break; case 2: data.multiArray[2] = ["蜥蜴", "龟", "壁虎"]; break; } break; } data.multiIndex[2] = 0; break; } this.setData("multiArray", data.multiArray); this.setData("multiIndex", data.multiIndex); } });
在真机上测试可以发现滚动列和列中的值都会触发相应的事件,下面就由我这个老司机带你们解开她的衣服看本质!
事件触发之后,索引也应该跟着修改,所以需要重新设置她的索引为当前滚动位置的索引,当前索引就是e.detail.value这个数组
注意区分bindMultiPickerChange事件和bindMultiPickerColumnChange事件的默认对象的值,前者是改变值触发的事件,她的e.detail.value是一个数组,映射着multiArray,后者是改变列的时候触发的事件,她的e.detail里面有2个key,一个是column,表示修改的列的索引,另一个是value,表示修改之后的值,这里可能理解起来比较绕,最好自己把demo copy到开发者工具中,用手机预览调试一下,理解每一个值是什么意思,怎么来的就行了。
这个参考上面的第1,2张图片,我从无脊椎动物滚动到脊椎动物,改变的是multiArray中的第0项数组的值吧,那么e.detail.column的值就是0,e.detail.value的值就是1,因此multiIndex数组中的值就为:[1,0,0],接着往后看!
以前学习JS条件判断的时候一直觉得switch是个废物,没用,这里百度的同学啪啪啪打我的脸了。。。
到这里应该理清楚了整个流程了,如果还没理解,重看几遍,认真思考下,下面进入高潮部分!
高潮
接口返回的数据:
// 导入验证js import WxValidate from "../../utils/WxValidate"; import { axios } from "../../utils/request"; const app = getApp(); Page({ data: { multiArray: [], // 三维数组数据 multiIndex: [0, 0, 0], // 默认的下标 [省、市、区] step: 0, // 默认显示请选择 // 设置默认收货地址按钮选中状态 value: "", }, onLoad() { // 初始化表单验证规则 this.initValidate(); // 获取省的数据 this.getProvince(); }, // 提交表单 formSubmit(e) { let params = e.detail.value; // 省的名字 let DeliveryName = this.data.provinceList[params['receivingAddress'][0]].AreaName; // 市的名字 let CityName = this.data.cityList[params['receivingAddress'][1]].AreaName; // 区的名字 let storeName = this.data.storeList[params['receivingAddress'][2]].AreaName; let DeliveryAddress = DeliveryName + CityName + storeName let storeNumber = params['receivingAddress'][2]; let storeCode = this.data.storeList[storeNumber].AreaCode; const { openid } = app.globalData; if (!this.WxValidate.checkForm(params)) { //表单元素验证不通过,此处给出相应提示 let error = this.WxValidate.errorList[0]; this.showToast(error); return false; } else { console.log("准备发送请求了..."); // 构建请求数据结构 let addressObj = { openid: openid, Name: params.Name, phone: params.phone, IsDefault: this.data.value, // 详细地址 CustomArea: params.CustomArea, // 省市区+详细地址 DeliveryAddress: DeliveryAddress + params.CustomArea, RegionCode: storeCode }; axios({ url: '/api/Account/AddAddress', method: 'POST', data: addressObj }).then(res=>{ if(res.data.IsSuccess) { // 跳转到我的收获地址页面 swan.navigateTo({ url: '/pages/address/address', success: res=>{ // 添加成功 swan.showToast({ title: "添加收货地址成功", icon: "none", duration: 4000, mask: false }); }, fail: ()=>{ console.log("跳转失败,请重试!"); } }) }else { // 添加失败 this.showToast(res.data.Msg); } }).catch(error=>{ console.log(error); }) } }, // checkedbox状态改变 checkboxChange(e) { const { value } = e.detail; // value是一个数组,如果length不等于0说明选中了 value.length != 0 ? this.setData({ value: "true" }) : this.setData({ value: "false" }); }, // 添加 addAddress() { // 构建数据对象 }, /* 表单验证函数 */ initValidate() { let rules = { Name: { required: true, maxlength: 10 }, phone: { required: true, tel: true }, CustomArea: { required: true }, receivingAddress: { required: true } }; let message = { Name: { required: "请输入姓名", rangelength: "请输入2~4个汉字个汉字" }, phone: { required: "请输入11位手机号码", tel: "请输入正确的手机号码" }, CustomArea: { required: "请输入详细地址" }, receivingAddress: { required: "请选择收货地区" } }; //实例化当前的验证规则和提示消息 this.WxValidate = new WxValidate(rules, message); }, // 错误消息提示框 showToast(error) { swan.showToast({ title: error.msg, icon: "none", duration: 4000, mask: false }); }, // 获取省的数据 getProvince() { axios({ url: "/api/Account/area", method: "POST", data: { parentCode: "0", openid: "" } }) .then(res => { if (res.data.IsSuccess) { let data = res.data.ResultObject; let provinceList = [...data]; // 将返回的数据放在一个数组里面,现在的数据结构是一个数组对象 let provinceArr = data.map(item => item.AreaName); // 获取数据里面的value值,只用数据的名称 this.setData({ multiArray: [provinceArr, [], []], // 更新三维数组 provinceList, // 省原始数据 provinceArr // 省级所有名称 }); let defaultCode = this.data.provinceList[0].AreaCode; // 使用第一项当做参数获取市级数据 if (defaultCode) { this.setData({ currentProviceKey: defaultCode // 保存当前省级的key(也就是AreaCode) }); this.getCity(defaultCode); // 通过省级的AreaCode获取当前省级下面的市级数据 } } }) .catch(error => { console.log(error); }); }, getCity(code) { // 获取市级的数据 this.setData({ currentProviceKey: code // 保存当前选择的市级code }); axios({ url: "/api/Account/area", method: "POST", data: { parentCode: code, openid: "" } }) .then(res => { if (res.data.ResultObject.length != 0) { let data = res.data.ResultObject; let cityArr = data.map(item => item.AreaName); // 市级所有名称 let cityList = [...data]; // 市级所有数据 this.setData({ multiArray: [this.data.provinceArr, cityArr, []], // 更新三维数组 cityArr, // 保存下市级所有名称 cityList // 保存下市级所有数据 }); let defaultCode = this.data.cityList[0].AreaCode; // 用第一个code获取区数据 if (defaultCode) { this.setData({ currentCityKey: defaultCode // 存下当前选择的城市的key }); // 通过市级的AreaCode获取当前市级下面的区级数据 this.getStore(defaultCode, code); } } }) .catch(error => { console.log(error); }); }, getStore(code, CityKey) { this.setData({ // 获取区级数据 currentCityKey: code // 更新当前选择的市级Key }); // 处理直辖市和特殊的区,获取区的时候使用市的code 如果不是直辖市就通过市级的AreaCode获取当前市级下面的区级数据 if(CityKey == '110000' || CityKey == '120000' || CityKey == '310000' || CityKey == '500000' || CityKey == '540000' || CityKey == '810000' || CityKey == '820000' || CityKey == '830000'){ axios({ url: "/api/Account/area", method: "POST", data: { parentCode: CityKey, openid: "" } }) .then(res => { let data; if (res.data.IsSuccess && res.data.ResultObject.length !== 0) { data = res.data.ResultObject; let storeArr = data.map(item => item.AreaName); // 区级所有名称 let storeList = [...data]; this.setData({ multiArray: [this.data.provinceArr, this.data.cityArr, storeArr], // 重新赋值三级数组 storeList, // 保存区级原始数据 storeArr // 保存区级名称 }); } }) .catch(error => { console.log(error); }); }else { axios({ url: "/api/Account/area", method: "POST", data: { parentCode: code, openid: "" } }) .then(res => { let data; if (res.data.IsSuccess && res.data.ResultObject.length !== 0) { data = res.data.ResultObject; let storeArr = data.map(item => item.AreaName); // 区级所有名称 let storeList = [...data]; this.setData({ multiArray: [this.data.provinceArr, this.data.cityArr, storeArr], // 重新赋值三级数组 storeList, // 保存区级原始数据 storeArr // 保存区级名称 }); } }) .catch(error => { console.log(error); }); } }, columnchange(e) { // 滚动选择器触发的事件 let column = e.detail.column; // column表示改变了第几列(下标从0开始) let value = e.detail.value; // value的值表示变更值得下标 let data = { multiIndex: JSON.parse(JSON.stringify(this.data.multiIndex)), multiArray: JSON.parse(JSON.stringify(this.data.multiArray)) }; data.multiIndex[column] = e.detail.value; // 第几列改变了就是对应multiIndex的第几个,更新他 switch ( column // 处理不同的逻辑 ) { case 0: // 第一列更改 就是省级的更改 let currentProviceKey = this.data.provinceList[e.detail.value].AreaCode; // 重新赋值省级编号 if (currentProviceKey != this.data.currentProviceKey) { // 判断当前key是不是正真的更新 this.getCity(currentProviceKey); // 获取当前key下面的市级数据 } data.multiIndex[1] = 0; // 将市默认选择第1个 break; case 1: // 市发生变化 let currentCityKey = this.data.cityList[e.detail.value].AreaCode; let currentCityParentCode = this.data.cityList[e.detail.value].ParentCode; if (currentCityKey != this.data.currentCityKey) { this.getStore(currentCityKey, currentCityParentCode); // 获取区 } data.multiIndex[2] = 0; // 区默认选择第一个 break; } this.setData(data); // 更新数据 }, pickchange(e) { this.setData({ step: 1, // 更新用于是否默认显示地区的状态值 multiIndex: e.detail.value }); } });
参考资料:
https://juejin.im/post/5aa2289e518825557207f675
https://smartprogram.baidu.com/docs/develop/component/formlist_picker/
根据上面这位大佬写的代码进行二次开发即可,还有一个需要特别注意的坑,手百小程序中的picker组件在百度开发者工具上无法触发事件,必须要到真机上才能触发,每次修改完代码都得重新编译扫码才能看,增加了调试成本,希望官方早日修复吧!