blob: 1285845e06547a161067c61b542a85a79425ed8d [file] [log] [blame]
guangchao.xu070005a2020-12-07 09:56:40 +08001<template>
2 <view class="u-select">
3 <!-- <view class="u-select__action" :class="{
4 'u-select--border': border
5 }" @tap.stop="selectHandler">
6 <view class="u-select__action__icon" :class="{
7 'u-select__action__icon--reverse': value == true
8 }">
9 <u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
10 </view>
11 </view> -->
12 <u-popup :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex">
13 <view class="u-select">
14 <view class="u-select__header" @touchmove.stop.prevent="">
15 <view
16 class="u-select__header__cancel u-select__header__btn"
17 :style="{ color: cancelColor }"
18 hover-class="u-hover-class"
19 :hover-stay-time="150"
20 @tap="getResult('cancel')"
21 >
22 {{cancelText}}
23 </view>
24 <view class="u-select__header__title">
25 {{title}}
26 </view>
27 <view
28 class="u-select__header__confirm u-select__header__btn"
29 :style="{ color: moving ? cancelColor : confirmColor }"
30 hover-class="u-hover-class"
31 :hover-stay-time="150"
32 @touchmove.stop=""
33 @tap.stop="getResult('confirm')"
34 >
35 {{confirmText}}
36 </view>
37 </view>
38 <view class="u-select__body">
39 <picker-view @change="columnChange" class="u-select__body__picker-view" :value="defaultSelector" @pickstart="pickstart" @pickend="pickend">
40 <picker-view-column v-for="(item, index) in columnData" :key="index">
41 <view class="u-select__body__picker-view__item" v-for="(item1, index1) in item" :key="index1">
42 <view class="u-line-1">{{ item1[labelName] }}</view>
43 </view>
44 </picker-view-column>
45 </picker-view>
46 </view>
47 </view>
48 </u-popup>
49 </view>
50</template>
51
52<script>
53 /**
54 * select 列选择器
55 * @description 此选择器用于单列,多列,多列联动的选择场景。(从1.3.0版本起,不建议使用Picker组件的单列和多列模式,Select组件是专门为列选择而构造的组件,更简单易用。)
56 * @tutorial http://uviewui.com/components/select.html
57 * @property {String} mode 模式选择,"single-column"-单列模式,"mutil-column"-多列模式,"mutil-column-auto"-多列联动模式
58 * @property {Array} list 列数据,数组形式,见官网说明
59 * @property {Boolean} v-model 布尔值变量,用于控制选择器的弹出与收起
60 * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
61 * @property {String} cancel-color 取消按钮的颜色(默认#606266)
62 * @property {String} confirm-color 确认按钮的颜色(默认#2979ff)
63 * @property {String} confirm-text 确认按钮的文字
64 * @property {String} cancel-text 取消按钮的文字
65 * @property {String} default-value 提供的默认选中的下标,见官网说明
66 * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
67 * @property {String Number} z-index 弹出时的z-index值(默认10075)
68 * @property {String} value-name 自定义list数据的value属性名 1.3.6
69 * @property {String} label-name 自定义list数据的label属性名 1.3.6
70 * @property {String} child-name 自定义list数据的children属性名,只对多列联动模式有效 1.3.7
71 * @event {Function} confirm 点击确定按钮,返回当前选择的值
72 * @example <u-select v-model="show" :list="list"></u-select>
73 */
74
75export default {
76 props: {
77 // 列数据
78 list: {
79 type: Array,
80 default() {
81 return [];
82 }
83 },
84 // 是否显示边框
85 border: {
86 type: Boolean,
87 default: true
88 },
89 // 通过双向绑定控制组件的弹出与收起
90 value: {
91 type: Boolean,
92 default: false
93 },
94 // "取消"按钮的颜色
95 cancelColor: {
96 type: String,
97 default: '#606266'
98 },
99 // "确定"按钮的颜色
100 confirmColor: {
101 type: String,
102 default: '#2979ff'
103 },
104 // 弹出的z-index值
105 zIndex: {
106 type: [String, Number],
107 default: 0
108 },
109 safeAreaInsetBottom: {
110 type: Boolean,
111 default: false
112 },
113 // 是否允许通过点击遮罩关闭Picker
114 maskCloseAble: {
115 type: Boolean,
116 default: true
117 },
118 // 提供的默认选中的下标
119 defaultValue: {
120 type: Array,
121 default() {
122 return [0];
123 }
124 },
125 // 模式选择,single-column-单列,mutil-column-多列,mutil-column-auto-多列联动
126 mode: {
127 type: String,
128 default: 'single-column'
129 },
130 // 自定义value属性名
131 valueName: {
132 type: String,
133 default: 'value'
134 },
135 // 自定义label属性名
136 labelName: {
137 type: String,
138 default: 'label'
139 },
140 // 自定义多列联动模式的children属性名
141 childName: {
142 type: String,
143 default: 'children'
144 },
145 // 顶部标题
146 title: {
147 type: String,
148 default: ''
149 },
150 // 取消按钮的文字
151 cancelText: {
152 type: String,
153 default: '取消'
154 },
155 // 确认按钮的文字
156 confirmText: {
157 type: String,
158 default: '确认'
159 }
160 },
161 data() {
162 return {
163 // 用于列改变时,保存当前的索引,下一次变化时比较得出是哪一列发生了变化
164 defaultSelector: [0],
165 // picker-view的数据
166 columnData: [],
167 // 每次队列发生变化时,保存选择的结果
168 selectValue: [],
169 // 上一次列变化时的index
170 lastSelectIndex: [],
171 // 列数
172 columnNum: 0,
173 // 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
174 moving: false
175 };
176 },
177 watch: {
178 // 在select弹起的时候,重新初始化所有数据
179 value: {
180 immediate: true,
181 handler(val) {
182 if(val) setTimeout(() => this.init(), 10);
183 }
184 },
185 },
186 computed: {
187 uZIndex() {
188 // 如果用户有传递z-index值,优先使用
189 return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
190 },
191 },
192 methods: {
193 // 标识滑动开始,只有微信小程序才有这样的事件
194 pickstart() {
195 // #ifdef MP-WEIXIN
196 this.moving = true;
197 // #endif
198 },
199 // 标识滑动结束
200 pickend() {
201 // #ifdef MP-WEIXIN
202 this.moving = false;
203 // #endif
204 },
205 init() {
206 this.setColumnNum();
207 this.setDefaultSelector();
208 this.setColumnData();
209 this.setSelectValue();
210 },
211 // 获取默认选中列下标
212 setDefaultSelector() {
213 // 如果没有传入默认选中的值,生成长度为columnNum,用0填充的数组
214 this.defaultSelector = this.defaultValue.length == this.columnNum ? this.defaultValue : Array(this.columnNum).fill(0);
215 this.lastSelectIndex = this.$u.deepClone(this.defaultSelector);
216 },
217 // 计算列数
218 setColumnNum() {
219 // 单列的列数为1
220 if(this.mode == 'single-column') this.columnNum = 1;
221 // 多列时,this.list数组长度就是列数
222 else if(this.mode == 'mutil-column') this.columnNum = this.list.length;
223 // 多列联动时,通过历遍this.list的第一个元素,得出有多少列
224 else if(this.mode == 'mutil-column-auto') {
225 let num = 1;
226 let column = this.list;
227 // 只要有元素并且第一个元素有children属性,继续历遍
228 while(column[0][this.childName]) {
229 column = column[0] ? column[0][this.childName] : {};
230 num ++;
231 }
232 this.columnNum = num;
233 }
234 },
235 // 获取需要展示在picker中的列数据
236 setColumnData() {
237 let data = [];
238 this.selectValue = [];
239 if(this.mode == 'mutil-column-auto') {
240 // 获得所有数据中的第一个元素
241 let column = this.list[this.defaultSelector.length ? this.defaultSelector[0] : 0];
242 // 通过循环所有的列数,再根据设定列的数组,得出当前需要渲染的整个列数组
243 for (let i = 0; i < this.columnNum; i++) {
244 // 第一列默认为整个list数组
245 if (i == 0) {
246 data[i] = this.list;
247 column = column[this.childName];
248 } else {
249 // 大于第一列时,判断是否有默认选中的,如果没有就用该列的第一项
250 data[i] = column;
251 column = column[this.defaultSelector[i]][this.childName];
252 }
253 }
254 } else if(this.mode == 'single-column') {
255 data[0] = this.list;
256 } else {
257 data = this.list;
258 }
259 this.columnData = data;
260 },
261 // 获取默认选中的值,如果没有设置defaultValue,就默认选中每列的第一个
262 setSelectValue() {
263 let tmp = null;
264 for(let i = 0; i < this.columnNum; i++) {
265 tmp = this.columnData[i][this.defaultSelector[i]];
266 let data = {
267 value: tmp ? tmp[this.valueName] : null,
268 label: tmp ? tmp[this.labelName] : null
269 };
270 // 判断是否存在额外的参数,如果存在,就返回
271 if(tmp && tmp.extra) data.extra = tmp.extra;
272 this.selectValue.push(data)
273 }
274 },
275 // 列选项
276 columnChange(e) {
277 let index = null;
278 let columnIndex = e.detail.value;
279 // 由于后面是需要push进数组的,所以需要先清空数组
280 this.selectValue = [];
281 if(this.mode == 'mutil-column-auto') {
282 // 对比前后两个数组,寻找变更的是哪一列,如果某一个元素不同,即可判定该列发生了变化
283 this.lastSelectIndex.map((val, idx) => {
284 if (val != columnIndex[idx]) index = idx;
285 });
286 this.defaultSelector = columnIndex;
287 for (let i = index + 1; i < this.columnNum; i++) {
288 // 当前变化列的下一列的数据,需要获取上一列的数据,同时需要指定是上一列的第几个的children,再往后的
289 // 默认是队列的第一个为默认选项
290 this.columnData[i] = this.columnData[i - 1][i - 1 == index ? columnIndex[index] : 0][this.childName];
291 // 改变的列之后的所有列,默认选中第一个
292 this.defaultSelector[i] = 0;
293 }
294 // 在历遍的过程中,可能由于上一步修改this.columnData,导致产生连锁反应,程序触发columnChange,会有多次调用
295 // 只有在最后一次数据稳定后的结果是正确的,此前的历遍中,可能会产生undefined,故需要判断
296 columnIndex.map((item, index) => {
297 let data = this.columnData[index][columnIndex[index]];
298 let tmp = {
299 value: data ? data[this.valueName] : null,
300 label: data ? data[this.labelName] : null,
301 };
302 // 判断是否有需要额外携带的参数
303 if(data && data.extra !== undefined) tmp.extra = data.extra;
304 this.selectValue.push(tmp);
305
306 })
307 // 保存这一次的结果,用于下次列发生变化时作比较
308 this.lastSelectIndex = columnIndex;
309 } else if(this.mode == 'single-column') {
310 let data = this.columnData[0][columnIndex[0]];
311 // 初始默认选中值
312 let tmp = {
313 value: data ? data[this.valueName] : null,
314 label: data ? data[this.labelName] : null,
315 };
316 // 判断是否有需要额外携带的参数
317 if(data && data.extra !== undefined) tmp.extra = data.extra;
318 this.selectValue.push(tmp);
319 } else if(this.mode == 'mutil-column') {
320 // 初始默认选中值
321 columnIndex.map((item, index) => {
322 let data = this.columnData[index][columnIndex[index]];
323 // 初始默认选中值
324 let tmp = {
325 value: data ? data[this.valueName] : null,
326 label: data ? data[this.labelName] : null,
327 };
328 // 判断是否有需要额外携带的参数
329 if(data && data.extra !== undefined) tmp.extra = data.extra;
330 this.selectValue.push(tmp);
331 })
332 }
333 },
334 close() {
335 this.$emit('input', false);
336 },
337 // 点击确定或者取消
338 getResult(event = null) {
339 // #ifdef MP-WEIXIN
340 if (this.moving) return;
341 // #endif
342 if (event) this.$emit(event, this.selectValue);
343 this.close();
344 },
345 selectHandler() {
346 this.$emit('click');
347 }
348 }
349};
350</script>
351
352<style scoped lang="scss">
353@import "../../libs/css/style.components.scss";
354
355.u-select {
356
357 &__action {
358 position: relative;
359 line-height: $u-form-item-height;
360 height: $u-form-item-height;
361
362 &__icon {
363 position: absolute;
364 right: 20rpx;
365 top: 50%;
366 transition: transform .4s;
367 transform: translateY(-50%);
368 z-index: 1;
369
370 &--reverse {
371 transform: rotate(-180deg) translateY(50%);
372 }
373 }
374 }
375
376 &__hader {
377 &__title {
378 color: $u-content-color;
379 }
380 }
381
382 &--border {
383 border-radius: 6rpx;
384 border-radius: 4px;
385 border: 1px solid $u-form-item-border-color;
386 }
387
388 &__header {
389 @include vue-flex;
390 align-items: center;
391 justify-content: space-between;
392 height: 80rpx;
393 padding: 0 40rpx;
394 }
395
396 &__body {
397 width: 100%;
398 height: 500rpx;
399 overflow: hidden;
400 background-color: #fff;
401
402 &__picker-view {
403 height: 100%;
404 box-sizing: border-box;
405
406 &__item {
407 @include vue-flex;
408 align-items: center;
409 justify-content: center;
410 font-size: 32rpx;
411 color: $u-main-color;
412 padding: 0 8rpx;
413 }
414 }
415 }
416}
417</style>