| <template> | 
|     <view class="u-select"> | 
|         <!-- <view class="u-select__action" :class="{ | 
|             'u-select--border': border | 
|         }" @tap.stop="selectHandler"> | 
|             <view class="u-select__action__icon" :class="{ | 
|                 'u-select__action__icon--reverse': value == true | 
|             }"> | 
|                 <u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon> | 
|             </view> | 
|         </view> --> | 
|         <u-popup :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex"> | 
|             <view class="u-select"> | 
|                 <view class="u-select__header" @touchmove.stop.prevent=""> | 
|                     <view | 
|                         class="u-select__header__cancel u-select__header__btn" | 
|                         :style="{ color: cancelColor }" | 
|                         hover-class="u-hover-class" | 
|                         :hover-stay-time="150" | 
|                         @tap="getResult('cancel')" | 
|                     > | 
|                         {{cancelText}} | 
|                     </view> | 
|                     <view class="u-select__header__title"> | 
|                         {{title}} | 
|                     </view> | 
|                     <view | 
|                         class="u-select__header__confirm u-select__header__btn" | 
|                         :style="{ color: moving ? cancelColor : confirmColor }" | 
|                         hover-class="u-hover-class" | 
|                         :hover-stay-time="150" | 
|                         @touchmove.stop="" | 
|                         @tap.stop="getResult('confirm')" | 
|                     > | 
|                         {{confirmText}} | 
|                     </view> | 
|                 </view> | 
|                 <view class="u-select__body"> | 
|                     <picker-view @change="columnChange" class="u-select__body__picker-view" :value="defaultSelector" @pickstart="pickstart" @pickend="pickend"> | 
|                         <picker-view-column v-for="(item, index) in columnData" :key="index"> | 
|                             <view class="u-select__body__picker-view__item" v-for="(item1, index1) in item" :key="index1"> | 
|                                 <view class="u-line-1">{{ item1[labelName] }}</view> | 
|                             </view> | 
|                         </picker-view-column> | 
|                     </picker-view> | 
|                 </view> | 
|             </view> | 
|         </u-popup> | 
|     </view> | 
| </template> | 
|   | 
| <script> | 
|     /** | 
|      * select 列选择器 | 
|      * @description 此选择器用于单列,多列,多列联动的选择场景。(从1.3.0版本起,不建议使用Picker组件的单列和多列模式,Select组件是专门为列选择而构造的组件,更简单易用。) | 
|      * @tutorial http://uviewui.com/components/select.html | 
|      * @property {String} mode 模式选择,"single-column"-单列模式,"mutil-column"-多列模式,"mutil-column-auto"-多列联动模式 | 
|      * @property {Array} list 列数据,数组形式,见官网说明 | 
|      * @property {Boolean} v-model 布尔值变量,用于控制选择器的弹出与收起 | 
|      * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false) | 
|      * @property {String} cancel-color 取消按钮的颜色(默认#606266) | 
|      * @property {String} confirm-color 确认按钮的颜色(默认#2979ff) | 
|      * @property {String} confirm-text 确认按钮的文字 | 
|      * @property {String} cancel-text 取消按钮的文字 | 
|      * @property {String} default-value 提供的默认选中的下标,见官网说明 | 
|      * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true) | 
|      * @property {String Number} z-index 弹出时的z-index值(默认10075) | 
|      * @property {String} value-name 自定义list数据的value属性名 1.3.6 | 
|      * @property {String} label-name 自定义list数据的label属性名 1.3.6 | 
|      * @property {String} child-name 自定义list数据的children属性名,只对多列联动模式有效 1.3.7 | 
|      * @event {Function} confirm 点击确定按钮,返回当前选择的值 | 
|      * @example <u-select v-model="show" :list="list"></u-select> | 
|      */ | 
|   | 
| export default { | 
|     props: { | 
|         // 列数据 | 
|         list: { | 
|             type: Array, | 
|             default() { | 
|                 return []; | 
|             } | 
|         }, | 
|         // 是否显示边框 | 
|         border: { | 
|             type: Boolean, | 
|             default: true | 
|         }, | 
|         // 通过双向绑定控制组件的弹出与收起 | 
|         value: { | 
|             type: Boolean, | 
|             default: false | 
|         }, | 
|         // "取消"按钮的颜色 | 
|         cancelColor: { | 
|             type: String, | 
|             default: '#606266' | 
|         }, | 
|         // "确定"按钮的颜色 | 
|         confirmColor: { | 
|             type: String, | 
|             default: '#2979ff' | 
|         }, | 
|         // 弹出的z-index值 | 
|         zIndex: { | 
|             type: [String, Number], | 
|             default: 0 | 
|         }, | 
|         safeAreaInsetBottom: { | 
|             type: Boolean, | 
|             default: false | 
|         }, | 
|         // 是否允许通过点击遮罩关闭Picker | 
|         maskCloseAble: { | 
|             type: Boolean, | 
|             default: true | 
|         }, | 
|         // 提供的默认选中的下标 | 
|         defaultValue: { | 
|             type: Array, | 
|             default() { | 
|                 return [0]; | 
|             } | 
|         }, | 
|         // 模式选择,single-column-单列,mutil-column-多列,mutil-column-auto-多列联动 | 
|         mode: { | 
|             type: String, | 
|             default: 'single-column' | 
|         }, | 
|         // 自定义value属性名 | 
|         valueName: { | 
|             type: String, | 
|             default: 'value' | 
|         }, | 
|         // 自定义label属性名 | 
|         labelName: { | 
|             type: String, | 
|             default: 'label' | 
|         }, | 
|         // 自定义多列联动模式的children属性名 | 
|         childName: { | 
|             type: String, | 
|             default: 'children' | 
|         }, | 
|         // 顶部标题 | 
|         title: { | 
|             type: String, | 
|             default: '' | 
|         }, | 
|         // 取消按钮的文字 | 
|         cancelText: { | 
|             type: String, | 
|             default: '取消' | 
|         }, | 
|         // 确认按钮的文字 | 
|         confirmText: { | 
|             type: String, | 
|             default: '确认' | 
|         } | 
|     }, | 
|     data() { | 
|         return { | 
|             // 用于列改变时,保存当前的索引,下一次变化时比较得出是哪一列发生了变化 | 
|             defaultSelector: [0], | 
|             // picker-view的数据 | 
|             columnData: [], | 
|             // 每次队列发生变化时,保存选择的结果 | 
|             selectValue: [], | 
|             // 上一次列变化时的index | 
|             lastSelectIndex: [], | 
|             // 列数 | 
|             columnNum: 0, | 
|             // 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确 | 
|             moving: false | 
|         }; | 
|     }, | 
|     watch: { | 
|         // 在select弹起的时候,重新初始化所有数据 | 
|         value: { | 
|             immediate: true, | 
|             handler(val) { | 
|                 if(val) setTimeout(() => this.init(), 10); | 
|             } | 
|         }, | 
|     }, | 
|     computed: { | 
|         uZIndex() { | 
|             // 如果用户有传递z-index值,优先使用 | 
|             return this.zIndex ? this.zIndex : this.$u.zIndex.popup; | 
|         }, | 
|     }, | 
|     methods: { | 
|         // 标识滑动开始,只有微信小程序才有这样的事件 | 
|         pickstart() { | 
|             // #ifdef MP-WEIXIN | 
|             this.moving = true; | 
|             // #endif | 
|         }, | 
|         // 标识滑动结束 | 
|         pickend() { | 
|             // #ifdef MP-WEIXIN | 
|             this.moving = false; | 
|             // #endif | 
|         }, | 
|         init() { | 
|             this.setColumnNum(); | 
|             this.setDefaultSelector(); | 
|             this.setColumnData(); | 
|             this.setSelectValue(); | 
|         }, | 
|         // 获取默认选中列下标 | 
|         setDefaultSelector() { | 
|             // 如果没有传入默认选中的值,生成长度为columnNum,用0填充的数组 | 
|             this.defaultSelector = this.defaultValue.length == this.columnNum ? this.defaultValue : Array(this.columnNum).fill(0); | 
|             this.lastSelectIndex = this.$u.deepClone(this.defaultSelector); | 
|         }, | 
|         // 计算列数 | 
|         setColumnNum() { | 
|             // 单列的列数为1 | 
|             if(this.mode == 'single-column') this.columnNum = 1; | 
|             // 多列时,this.list数组长度就是列数 | 
|             else if(this.mode == 'mutil-column') this.columnNum = this.list.length; | 
|             // 多列联动时,通过历遍this.list的第一个元素,得出有多少列 | 
|             else if(this.mode == 'mutil-column-auto') { | 
|                 let num = 1; | 
|                 let column = this.list; | 
|                 // 只要有元素并且第一个元素有children属性,继续历遍 | 
|                 while(column[0][this.childName]) { | 
|                     column = column[0] ? column[0][this.childName] : {}; | 
|                     num ++; | 
|                 } | 
|                 this.columnNum = num; | 
|             } | 
|         }, | 
|         // 获取需要展示在picker中的列数据 | 
|         setColumnData() { | 
|             let data = []; | 
|             this.selectValue = []; | 
|             if(this.mode == 'mutil-column-auto') { | 
|                 // 获得所有数据中的第一个元素 | 
|                 let column = this.list[this.defaultSelector.length ? this.defaultSelector[0] : 0]; | 
|                 // 通过循环所有的列数,再根据设定列的数组,得出当前需要渲染的整个列数组 | 
|                 for (let i = 0; i < this.columnNum; i++) { | 
|                     // 第一列默认为整个list数组 | 
|                     if (i == 0) { | 
|                         data[i] = this.list; | 
|                         column = column[this.childName]; | 
|                     } else { | 
|                         // 大于第一列时,判断是否有默认选中的,如果没有就用该列的第一项 | 
|                         data[i] = column; | 
|                         column = column[this.defaultSelector[i]][this.childName]; | 
|                     } | 
|                 } | 
|             } else if(this.mode == 'single-column') { | 
|                 data[0] = this.list; | 
|             } else { | 
|                 data = this.list; | 
|             } | 
|             this.columnData = data; | 
|         }, | 
|         // 获取默认选中的值,如果没有设置defaultValue,就默认选中每列的第一个 | 
|         setSelectValue() { | 
|             let tmp = null; | 
|             for(let i = 0; i < this.columnNum; i++) { | 
|                 tmp = this.columnData[i][this.defaultSelector[i]]; | 
|                 let data = { | 
|                     value: tmp ? tmp[this.valueName] : null, | 
|                     label: tmp ? tmp[this.labelName] : null | 
|                 }; | 
|                 // 判断是否存在额外的参数,如果存在,就返回 | 
|                 if(tmp && tmp.extra) data.extra = tmp.extra; | 
|                 this.selectValue.push(data) | 
|             } | 
|         }, | 
|         // 列选项 | 
|         columnChange(e) { | 
|             let index = null; | 
|             let columnIndex = e.detail.value; | 
|             // 由于后面是需要push进数组的,所以需要先清空数组 | 
|             this.selectValue = []; | 
|             if(this.mode == 'mutil-column-auto') { | 
|                 // 对比前后两个数组,寻找变更的是哪一列,如果某一个元素不同,即可判定该列发生了变化 | 
|                 this.lastSelectIndex.map((val, idx) => { | 
|                     if (val != columnIndex[idx]) index = idx; | 
|                 }); | 
|                 this.defaultSelector = columnIndex; | 
|                 for (let i = index + 1; i < this.columnNum; i++) { | 
|                     // 当前变化列的下一列的数据,需要获取上一列的数据,同时需要指定是上一列的第几个的children,再往后的 | 
|                     // 默认是队列的第一个为默认选项 | 
|                     this.columnData[i] = this.columnData[i - 1][i - 1 == index ? columnIndex[index] : 0][this.childName]; | 
|                     // 改变的列之后的所有列,默认选中第一个 | 
|                     this.defaultSelector[i] = 0; | 
|                 } | 
|                 // 在历遍的过程中,可能由于上一步修改this.columnData,导致产生连锁反应,程序触发columnChange,会有多次调用 | 
|                 // 只有在最后一次数据稳定后的结果是正确的,此前的历遍中,可能会产生undefined,故需要判断 | 
|                 columnIndex.map((item, index) => { | 
|                     let data = this.columnData[index][columnIndex[index]]; | 
|                     let tmp = { | 
|                         value: data ? data[this.valueName] : null, | 
|                         label: data ? data[this.labelName] : null, | 
|                     }; | 
|                     // 判断是否有需要额外携带的参数 | 
|                     if(data && data.extra !== undefined) tmp.extra = data.extra; | 
|                     this.selectValue.push(tmp); | 
|   | 
|                 }) | 
|                 // 保存这一次的结果,用于下次列发生变化时作比较 | 
|                 this.lastSelectIndex = columnIndex; | 
|             } else if(this.mode == 'single-column') { | 
|                 let data = this.columnData[0][columnIndex[0]]; | 
|                 // 初始默认选中值 | 
|                 let tmp = { | 
|                     value: data ? data[this.valueName] : null, | 
|                     label: data ? data[this.labelName] : null, | 
|                 }; | 
|                 // 判断是否有需要额外携带的参数 | 
|                 if(data && data.extra !== undefined) tmp.extra = data.extra; | 
|                 this.selectValue.push(tmp); | 
|             } else if(this.mode == 'mutil-column') { | 
|                 // 初始默认选中值 | 
|                 columnIndex.map((item, index) => { | 
|                     let data = this.columnData[index][columnIndex[index]]; | 
|                     // 初始默认选中值 | 
|                     let tmp = { | 
|                         value: data ? data[this.valueName] : null, | 
|                         label: data ? data[this.labelName] : null, | 
|                     }; | 
|                     // 判断是否有需要额外携带的参数 | 
|                     if(data && data.extra !== undefined) tmp.extra = data.extra; | 
|                     this.selectValue.push(tmp); | 
|                 }) | 
|             } | 
|         }, | 
|         close() { | 
|             this.$emit('input', false); | 
|         }, | 
|         // 点击确定或者取消 | 
|         getResult(event = null) { | 
|             // #ifdef MP-WEIXIN | 
|             if (this.moving) return; | 
|             // #endif | 
|             if (event) this.$emit(event, this.selectValue); | 
|             this.close(); | 
|         }, | 
|         selectHandler() { | 
|             this.$emit('click'); | 
|         } | 
|     } | 
| }; | 
| </script> | 
|   | 
| <style scoped lang="scss"> | 
| @import "../../libs/css/style.components.scss"; | 
|   | 
| .u-select { | 
|   | 
|     &__action { | 
|         position: relative; | 
|         line-height: $u-form-item-height; | 
|         height: $u-form-item-height; | 
|   | 
|         &__icon { | 
|             position: absolute; | 
|             right: 20rpx; | 
|             top: 50%; | 
|             transition: transform .4s; | 
|             transform: translateY(-50%); | 
|             z-index: 1; | 
|   | 
|             &--reverse { | 
|                 transform: rotate(-180deg) translateY(50%); | 
|             } | 
|         } | 
|     } | 
|   | 
|     &__hader { | 
|         &__title { | 
|             color: $u-content-color; | 
|         } | 
|     } | 
|   | 
|     &--border { | 
|         border-radius: 6rpx; | 
|         border-radius: 4px; | 
|         border: 1px solid $u-form-item-border-color; | 
|     } | 
|   | 
|     &__header { | 
|         @include vue-flex; | 
|         align-items: center; | 
|         justify-content: space-between; | 
|         height: 80rpx; | 
|         padding: 0 40rpx; | 
|     } | 
|   | 
|     &__body { | 
|         width: 100%; | 
|         height: 500rpx; | 
|         overflow: hidden; | 
|         background-color: #fff; | 
|   | 
|         &__picker-view { | 
|             height: 100%; | 
|             box-sizing: border-box; | 
|   | 
|             &__item { | 
|                 @include vue-flex; | 
|                 align-items: center; | 
|                 justify-content: center; | 
|                 font-size: 32rpx; | 
|                 color: $u-main-color; | 
|                 padding: 0 8rpx; | 
|             } | 
|         } | 
|     } | 
| } | 
| </style> |