微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.7 滚动选择器组件,使用两种方式自定义实现省、市、区三级联动的选择器

一、关于pick组件的相关问题

1.1 object array 这个属性值是怎么回事?如何使用它?
array: ['美国', '中国', '巴西', '日本'],
objectArray: [
	{
	 id: 0,
	 name: '美国'
	},
	{
	 id: 1,
	 name: '中国'
	},
	{
	 id: 2,
	 name: '巴西'
	},
	{
	 id: 3,
	 name: '日本'
	}
],
对于picker组件, 
range属性,可以是数组,也可以是对象数组
如果要选择的值和显示的值不一样,不是一个值,这个时候就使用 object array 
range-key适用于我们在界面组件渲染的时候,通知小程序去用哪个字段去作为文本去呈现
1.2 如何去用户选择的值?
要从事件中去取。event.detail.value可以取到这个值,这个值是索引值,数据源是数组。

对于picker组件,当mode = multiSelector的时候,多一个bindcolumnchange事件,event.detail里面
有两个字段,一个是 column ,代表的是当前滑动选择的是第几列,另一个字段是value,代表改变的这一列
在改变之后,它新选择的值是什么。

picker-view 、picker-view-column组件中,change事件得到的event.detail为数组
1.3 如何使用多项选择器,如何从多项选择器里面取得值,并使用取到的值?

在这里插入图片描述

<view class="section">
    <view class="section__title">多列选择器</view>
    <picker mode="multiSelector" bindchange="bindMultiPickerChange" bindcolumnchange="bindMultiPickerColumnChange"
        value="{{multiIndex}}" range="{{multiArray}}">
        <view class="picker">
            当前选择:{{multiArray[0][multiIndex[0]]}},{{multiArray[1][multiIndex[1]]}},{{multiArray[2][multiIndex[2]]}}
        </view>
    </picker>
</view>

Page({
  data: {
 
    multiArray: [
      ['无脊柱动物', '脊柱动物'], 
      ['扁性动物', '线形动物', '环节动物', '软体动物', '节肢动物'], 
      ['猪肉绦虫', '吸血虫']],
    multiIndex: [0, 0, 0],
  },
 
  bindMultiPickerChange: function (e) {
    console.log('picker发送选择改变,携带值为', e.detail.value)
    this.setData({
      multiIndex: e.detail.value
    })
  },
 
})

1.4 如何理解选择器的mode属性?它都有哪些可以用的值?
1.selector  普通单列选择器
2.multiSelector  多列选择器
3.time   时间选择器
4.date  日期选择器
5.region   省市区选择器
1.5 如何在选择器中直接选择月?如何处理选择器的粒度?

当picker组件的mode属性为date时,fields 属性为默认为 day 。

在这里插入图片描述

二、微信小程序的编码规范是什么?事件名如何写、函数名如何写、变量如何写、文件名如何写?等等

2.1 编码规范

主要指各种命名方式

2.1.1 四种常用的命名方法
小驼峰命名法  displaySreenSize
大驼峰命名法  DisplaySreenSize
连字符命名法  picker-view-column
匈牙利命名法  iphone、iOS、iPad、m_lpszStr 
2.1.2 WXSS 命名规范
BEM命名法   
模块_ _模块元素-描述符
组件名称_ _模块元素-状态、功能         双下划线

weui-cell__radio                     √
weui-cell__radio-selected       √
weui-cell__radio-light              ×
weui-cell__radio-dark             ×
boy-student__icon-checked    √
girl-student__icon-checked    √
2.1.3 WXML 命名规范
用简单连字符间隔
标签名称、属性、组件名   同样的 


picker-view
picker-view-column

ext-class
indicator-style
indicator-class

在组件的事件属性上绑定的js事件函数句柄的名称,要遵从js命名规范,小驼峰命名法。
单词的组成上,一般使用 "on +  名称 + 状态或动词 " 这样的形式。 
onRegionChanged   √
onTap                       ×
2.1.4 JavaScript 命名规范
类名  大驼峰命名法 
函数名  方法名  变量名  函数参数名  小驼峰命名法
2.1.5 文件与目录的命名规范
文件名、文件夹名称    连字符法

三、使用picker-view组件在开发中会遇到什么问题?

3.1 picker 与 picker-view 有什么区别?
picker 是从手机底部有滑起的选择器
picker-view 是嵌入小程序页面里边的选择器,
picker-view只可以放置picker-view-column组件 ,放其他的子组件是不会显示的
picker-view-column是滚动选择器的子项,它只可以放置在picker-view中,作为它的一个子节点。
它的高度是自动设置成与picker-view选中框的高度一致的
3.2 我们已经选择了值,为什么还要给选择器的 value 属性绑定值呢?

单向绑定

<!-- 单向绑定 -->
<view class="section">
    <view class="section__title">普通选择器</view>
    <picker bindchange="bindPickerChange" value="{{index}}" range="{{array}}">
        <view class="picker">
            当前选择:{{array[index]}}
        </view>
    </picker>
</view>

index是从bindPickerChange函数里面取到的,同时又把它绑定到value的属性上去,

在小程序开发中,属性的绑定默认它不是双向绑定的。
我们可以在change函数中取到新的值
但是这个值怎么在界面中去展示    当前选择:{{array[index]}}
需要开发者手动去完成

Page({
  data: {
    array: ['美国', '中国', '巴西', '日本'],
    index: 0,
  },
  bindPickerChange: function (e) {
    console.log('picker发送选择改变,携带值为', e.detail.value)
    this.setData({
      index: e.detail.value
    })
  },

})

双向绑定

<!-- 双向绑定  model:value  -->

<view class="section">
    <view class="section__title">双向绑定 普通选择器</view>

    {{index1}}

    <picker model:value="{{index1}}" range="{{array}}">
        <view class="picker">
            当前选择:{{array[index1]}}
        </view>
    </picker>
</view>

.picker{
  padding: 13px;
  background-color: #FFFFFF;
}

在这里插入图片描述

3.3 在 picker-view 中,indicator-style 与 indicator-class 这两个属性是做什么用的?

在这里插入图片描述
indicator-style 与 indicator-class 这两个属性。主要是用于控制选择项的样式

在这里插入图片描述
被选择的永远在中间那一列当中,
indicator-style属性直接写的是内联样式,
indicator-class 是写在样式文件中的类样式名称。


或者重写子组件的类样式名称,
也可以达到控制内部样式的目的,
这种控制方式它具有一定的通用性,更加灵活。

3.4 如何使用 picker-view 实现一个多列选择器?例如三级联动的省市区选择器。

问: picker 有一个 mode 是 region ,region 本身就支持省、市、区三级联动,为什么我们还要自己去写组件,并且还要使用 picker-view 去写,而不是使用 picker 的 multiSelector 的多级选择的模式去实现?

答:

从架构来看,虽然 picker 支持多列选择这种方式, 但是它的多列方式是通过数据源绑定的方式去实现的,我们对它的样式很难控制。

而 picker-view,它是基于子组件 picker-view-column 这种松耦合的这种架构去实现的,它本身没有数据源,
它的所有数据都是在 picker-view-column 这个子组件里面,由开发者我们自己通过 wx:for 循环绑定出来的。
对于picker-view的每一列,我们都可以自己去控制它的样式,还有它的数据。
对于我们自定义开发提供了很大的方便。
基于这一点,自定义实现多列的选择器,优先选择肯定是 picker-view 而不是 picker。

问:pick是从底部滑出来的,而picker-view是页面嵌入的?
答:

picker-view 也可以实现同样的底部滑出效果,把 picker-view 放在滑出的面板上,就可以实现这样的效果。

问:蒙层效果呢?
答:

至于嵌入组件的蒙层这样的样式效果,可以通过 mask-style 或者 mask-class 控制,
这两个属性是专门用于控制蒙层效果的。

示例1 js滚动选择器

效果
在这里插入图片描述

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

使用

index/index.wxml

<!-- js滚动选择器 -->
<region-picker-view bindchange="onRegionChange"></region-picker-view>

index/index.js


//三级联动省市区
Page({
  onRegionChange(e){
    console.log('选择了',e.detail);
  },
})


index/index.json

{
  "usingComponents": {
    "region-picker-view": "../../components/region-picker-view/index"
  }
}

components/region-picker-view/index.wxml

<!--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>
      <!-- bindchange  数据联动的处理 -->
      <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>

components/region-picker-view/index.js

// 该组件参照了以下文章,感兴趣请前往查看原文
// 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  // picker-view的选择值
      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  //  先通过索引取得对象,在取到对象的id 
        this.setData({
          value: [provinceNum, 0, 0],
          citys: address.citys[id], //address ---> city.js,查对应的city的城市列表是哪一个
          areas: address.areas[address.citys[id][0].id] // 默认对应的第一个城市,再取它的id,再以id在areas里面去看对应的数据源
        })
      } 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
            }
          }
        })
      })
    }
  }
})

components/region-picker-view/index.json

{
  "component": true,
  "usingComponents": {
    "pop-up": "../pop-up/index"
  }
}

components/region-picker-view/index.wxss

/* 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;
}

components/pop-up/index.wxml

<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>

components/pop-up/index.js


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()
    }
  }
})

components/pop-up/index.json

{
  "component": true,
  "usingComponents": {}
}

components/pop-up/index.wxss

.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;
}

对于目前实现的示例,它有两个问题,

第一个问题,我们最好不要在它的change事件里面去改变视图,
因为我们滑动的时候会涉及到连续的多次滑动,在用户没有选到目标值之前,可能会涉及多次的change事件派发,我们最好在用户选择结束之后,再去判断有没有变化,如果有变化的话我们再去做改变数据源的这些事情。

我们通过测试发现关于 picker 组件,当 mode 为 multiSelector 时,当手指滑动的时候,它的 columnchange 事件会有多次派发。
picker 组件的 change 事件 是在单击 " 确定 " 按钮之后才派发的。
而 picker-view 组件在滑动选择的过程当中,它的 change 事件是不派发的,change 事件只在选定之后派发了一次。
在 picker-view 组件中,有一个 pickstart 和 一个 pickend 事件,分别代表的是滚动选择的开始和结束,有这两个事件,我们可以从 change 事件拿到 value 值,先存储起来,然后在 pickend 事件里,在选择结束的时候再去做这个逻辑的处理。

从以上两点来看,picker-view 组件的设计,是优于 picker 组件的,picker 组件的功能,其实使用picker-view也是完全可以实现的。


第二个问题,我们这个关于交互的操作,最好是写在 WXS 模块里面,而不是写在js里面去做这件事情,减少视图层与逻辑层之间的通讯,可以显著提高我们界面的流畅性

基于以上两点,我们接下来使用 WXS 脚本,开始尝试把自定义组件去改写一下。

// 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: {}
})

改写以后 js 代码,只剩下数据源的定义,只是数据定义并没有数据。
数据是在 WXS 模块里面去设置的。

在 WXS 模块里面没有办法触发视图更新,
唯一的办法就是通过页面实例对象的 callMethod 方法
间接调用setData进行更新,但是在调用之前,我们首先要有一个组件描述对象
因为只有在这个对象里面它才有 callMethod 方法

而这个组件描述对象,目前我们又只能从绑定的事件函数的参数列表里面才可以拿到这个对象,
在开始的时候用户还没有互动没有事件,这个时候我们怎么把这个数据绑定上去

方法1:把这个数据分别写成 js 和 wxs 两种格式,存成两个文件,分别在两个地方进行处理,
也就是说在 js 文件里面,页面加载的时候,先设置省市区的初始值
后续数据更新的时候,我们再在 WXS 里面去更新新的数据

问题:给维护带来了麻烦,同样的数据要写成两个文件,他们的格式并不是完全一样的,对维护是一种格外的负担
这种方式行得通,但是蹩脚,不是一种好方法

----------------------------------------------------------------------

方法2:自定义组件 popup ,它的作用是为了创造一个底部滑出的窗口,
在 popup 自定义组件的 ready 周期函数里面,我们派发出来一个 ready 事件,
然后我们在 WXML 代码中去监听这个事件,把这个事件绑定到 WXS 模块的 js 代码中,
这样我们不就可以达到一个组件描述对象了吗?

但是这个方法行不通,
Cannot use wxs function to handle custom event "ready"

我们不能使用我们在 WXS 里面声明定义的函数,去监听自定义的事件
bindready="{{region.onComponentReady}} 所以说这种方式是行不通的,怎么解决的

用逻辑或 ||


<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  ||  region.provinces   }}" class="picker-item" wx:key="index">{{item.name}}</view>
	</picker-view-column>

	<picker-view-column>
		<view wx:for="{{citys  ||  region.citys  }}" class="picker-item" wx:key="index">{{item.name}}</view>
	</picker-view-column>

	<picker-view-column>
		<view wx:for="{{areas   ||  region.areas   }}" class="picker-item" wx:key="index">{{item.name}}</view>
	</picker-view-column>

</picker-view>

provinces  ||  region.provinces 

虽然 WXS 不能触发更新,但是她可以提供第一次的数据绑定
第一次的数据是由 WXS 模块提供的
当我们开始互动以后,我们会通过 setData 去设置互动之后的新数据,
这个时候 provinces 就已经有数据了

这样我们将数据的维护和管理放在一个地方去进行

----------------------------------------------------------------------

目前在我们的 WXS 模块里面以及js代码里面,现在存了两份省、市、区数据源的定义,
这个不太合理,一个信息只能在一个地方定义,这样维护起来才比较简单。

对于 WXS 模块,小程序还有一个 WxsPropObserver 机制,
这种机制它允许我们在 wxml 组件上,以 WXS 模块里定义的js函数监听属性的变化
并且不是变化以后才可以触发这个函数的执行,
它第一次渲染的时候,也可以触发

<view change:class="{{region.onPropSigned}}" class="address-item" bindtap="{{region.pickAddress}}">
	......
</view>

ownerInstance不一定是页面对象,它是触发事件的组件的父组件 ComponentDescriptor 的实例
只有当触发事件的父组件是页面时,它才是页面对象
但是无论是不是页面对象都没有关系,它都有 ComponentDescriptor 描述,都有 callMethod 这个方法

在这个事件函数里面,将js里面的数据源默认值都给绑定好了

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: ''
  })
}

逻辑或||不需要了
<picker-view-column indicator-class="item_active">
	<view wx:for="{{provinces  }}" class="picker-item" wx:key="index">{{item.name}}</view>
</picker-view-column>

----------------------------------------------------------------------
这两种实现方法,它们的运行效果是一样的,后面这种方式的实现效率上应该是优于前面那种纯js的实现方式的,
小程序自定义组件优先选择在 WXS 模块里面,完成主要逻辑甚至全部逻辑的编写

示例2 wxs 滚动选择器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

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

index/index.wxml

<!-- wxs滚动选择器 -->
<region-picker-view2 bindchange="onRegionChange"></region-picker-view2>

index/index.json

{
  "usingComponents": {
    "region-picker-view2": "/components/region-picker-view2/index",
  }
}

components/region-picker-view2/index.json

{
  "component": true,
  "usingComponents": {
    "pop-up": "../pop-up/index"
  }
}

components/region-picker-view2/index.wxml

<!--components/region-picker-view2/index.wxml-->
<wxs module="region" src="./region.wxs"></wxs>
<!-- 
change:class
这个WxsPropObserver这种监听,我们要用change开头,后面跟:,后面跟属性的名称
这个地方我们选择class,其实选择任何一个属性都可以

class="address-item"  这个属性第一次设置了一次,渲染一次
这一次就足以让我们去触发定义的这个onPropSigned函数了


如果绑定在   <pop-up visible="{{visible}}" >  会有问题
visible 这个涉及到 真假的 一个反复的一个绑定,如果绑定在这上面监听这个属性的话,我们onPropSigned这个函数它可能会执行多次

onPropSigned  这个函数的主要目的是
第一次去设置数据源的默认值,所以只需要执行一次就可以了,不需要执行多次
这样的情况下我们就需要随便找一个就是一次性的属性,然后去监听就可以了

先设置数据源,在渲染的

 -->


<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>




<!-- bindready 去尝试监听我们派发的事件, -->
<!-- visible 可视属性的绑定  -->
<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>


components/region-picker-view2/index.wxss

/* 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;
}

components/region-picker-view2/index.js

// 该组件参照了以下文章,感兴趣请前往查看原文
// 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: {}
})

components/region-picker-view2/city.wxs

var provinces = [
	{
	  "name": "北京市",
	  "id": "110000"
	}, 
	......
]

var citys = {
  "110000": [
    {
      "province": "北京市",
      "name": "市辖区",
      "id": "110100"
    }
  ],
  "120000": [
    {
      "province": "天津市",
      "name": "市辖区",
      "id": "120100"
    }
  ],
  ......
}

var areas = {
  "110100": [
    {
      "city": "市辖区",
      "name": "东城区",
      "id": "110101"
    },
    {
      "city": "市辖区",
      "name": "西城区",
      "id": "110102"
    },
    ......
  ],
   ......
}


module.exports = {
  citys:citys,
  provinces:provinces,
  areas:areas
}

components/region-picker-view2/region.wxs

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"
// 我们不能使用我们在WXS里面声明定义的函数,去监听自定义的事件
// bindready="{{region.onComponentReady}} 所以说这种方式是行不通的,怎么解决


region.onComponentReady = function (e, owner) {
  console.log(e, "onComponentReady");
  // onSelectedValueChanged(owner)
}



// ownerInstance不一定是页面对象,它是触发事件的组件的父组件ComponentDescriptor的实例
// 只有当触发事件的父组件是页面时,它才是页面对象
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

总结:
在小程序的自定义组件开发中,我们要以第二种方式为主
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值