Comming

百度小程序中三级联动深入理解

最近百度小程序的项目中有一个三级联动了功能,就是省市区,由于以前没有接触过,卡了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);
  }
});
multiIndex:她是一个数组,用来表示选择的多维数组的每一项的第几个
range="{{multiArray}}":multiArray她是一个多维数组,用来保存要渲染的数据,这个案例中她用来放置所有名称
mode="multiSelector":用来声明我们创建的是一个多列选择器
bindchange:她是value改变时触发的事件
bindcolumnchange:她是每一列值改变时触发的事件

 

 

 在真机上测试可以发现滚动列和列中的值都会触发相应的事件,下面就由我这个老司机带你们解开她的衣服看本质!

 

 

 

 

 

 

 

 

事件触发之后,索引也应该跟着修改,所以需要重新设置她的索引为当前滚动位置的索引,当前索引就是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组件在百度开发者工具上无法触发事件,必须要到真机上才能触发,每次修改完代码都得重新编译扫码才能看,增加了调试成本,希望官方早日修复吧!

 

posted @ 2019-11-10 21:39  Sauron831  阅读(709)  评论(0编辑  收藏  举报