You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
14 KiB
437 lines
14 KiB
<template>
|
|
<view class="s-picker" :class="custom_class" :style="custom_style">
|
|
<s-popup
|
|
position="bottom"
|
|
:value="visible"
|
|
:z-index="zIndex"
|
|
:animate="animate"
|
|
:animate-duration="animateDuration"
|
|
:mask="mask"
|
|
:mask-opacity="maskOpacity"
|
|
:mask-close="maskClose"
|
|
:safe-area-inset-bottom="safeAreaInsetBottom"
|
|
@open="onOpen"
|
|
@opend="$emit('opend')"
|
|
@mask-close="triggerCancel"
|
|
@close="onClose"
|
|
@closed="onClosed"
|
|
>
|
|
<view class="s-picker__header s-hairline--bottom">
|
|
<view class="s-picker__header-left">
|
|
<s-button
|
|
v-if="showCancel"
|
|
type="text"
|
|
size="custom"
|
|
square
|
|
custom-class="s-picker__action s-picker__cancel-action"
|
|
:custom-style="{ color: cancelColor }"
|
|
@click="onCance"
|
|
>
|
|
{{ cancelText }}
|
|
</s-button>
|
|
</view>
|
|
<view class="s-picker__title" :style="{ color: titleColor }">{{ title }}</view>
|
|
<view class="s-picker__header-right">
|
|
<s-button
|
|
type="text"
|
|
size="custom"
|
|
square
|
|
:custom-class="confirm_class"
|
|
:custom-style="{
|
|
color: isDisabledConfirm ? isDisabledConfirm : confirmColor,
|
|
}"
|
|
@click="onConfirm"
|
|
>
|
|
{{ confirmText }}
|
|
</s-button>
|
|
</view>
|
|
</view>
|
|
<view class="s-picker__body">
|
|
<picker-view
|
|
v-if="pickerView"
|
|
:value="indexList"
|
|
class="s-picker__picker-view"
|
|
:indicator-class="indicator_class"
|
|
:indicator-style="indicator_style"
|
|
:mask-class="mask_class"
|
|
:mask-style="mask_Style"
|
|
@change="onPickerChange"
|
|
@pickstart="onPickerStart"
|
|
@pickend="onPickerEnd"
|
|
>
|
|
<picker-view-column
|
|
v-for="(column, columnIndex) of columnDataList"
|
|
:key="columnIndex"
|
|
>
|
|
<view v-for="(item, index) of column" :key="index" class="s-picker__option">
|
|
<slot
|
|
:scopeParams="scopeParams"
|
|
:columnIndex="columnIndex"
|
|
:item="item"
|
|
:index="index"
|
|
>
|
|
<text class="s-picker__option-text">{{ item[labelKey] }}</text>
|
|
</slot>
|
|
</view>
|
|
</picker-view-column>
|
|
</picker-view>
|
|
</view>
|
|
<view v-if="loading" class="s-picker__loading">
|
|
<s-loading />
|
|
</view>
|
|
</s-popup>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import componentMixin from '../../mixins/componentMixin';
|
|
import toValidListData from '../../utils/toValidListData';
|
|
|
|
/**
|
|
* s-picker 底部弹出滑动选择组件 picker
|
|
* @description 滑动选则,支持多列,级联选择
|
|
* @property {Array|Object} columns 选择数据列表 [{label:'中国',value:'1'}],multiple 为true时,必须是数组格式[[{label:'中国',value:'1'}]]
|
|
* @property {String} title 标题
|
|
* @property {Boolean} visible 弹框显示隐藏
|
|
* @property {Boolean} multiple 是否是多列选择
|
|
* @property {*} value 选中的value,多列选择时为数组
|
|
* @property {String} text 双向绑定用来同步确认后的选中label描述
|
|
* @property {String} valuelKey 取数据中的那个字段作为value关联
|
|
* @property {String} labelKey 取数据中的那个字段作为显示文字
|
|
* @property {String|Array} placeholder 给列数据的前面插入一项value为''的占位选择项数据
|
|
* @property {String} separator text在多列选择结果字符串的分割符号,默认 -
|
|
* @property {String} cancelText 取消按钮文字
|
|
* @property {String} confirmText 确认按钮文字
|
|
* @property {String} titleColor 标题颜色
|
|
* @property {String} cancelColor 取消按钮颜色
|
|
* @property {String} confirmColor 确认按钮颜色
|
|
* @property {String} disabledColor 确认按钮禁用时颜色
|
|
* @property {String|Object} indicatorClass 设置选择器中间选中框的类名
|
|
* @property {String|Object} indicatorStyle 设置选择器中间选中框的样式
|
|
* @property {String|Object} maskClass 设置蒙层的类名
|
|
* @property {String|Object} maskStyle 设置蒙层的样式
|
|
* @property {String|Number} zIndex 同popup组件zIndex属性
|
|
* @property {String|Boolean|Object} animate 同popup组件animate属性
|
|
* @property {Number|String|Object} animateDuration 同popup组件animateDuration属性
|
|
* @property {Boolean} mask 同popup组件mask属性
|
|
* @property {Boolean} maskClose 点击遮罩是否关闭
|
|
* @property {Number|String} maskOpacity 同popup组件maskOpacity属性
|
|
* @property {Boolean} showCancel 是否显示取消按钮
|
|
* @property {Boolean} safeAreaInsetBottom 同popup组件safeAreaInsetBottom属性,默认true
|
|
* @event {Function} change ({
|
|
i: 当前列下标,
|
|
index: 当前列选中的下标,
|
|
value: 当前列选中的value,
|
|
data: 当前列选中的数据对象,
|
|
indexList: 选中的下标数组,
|
|
valueList: 选中的value数组,
|
|
dataList: 选中的数据对象数组,
|
|
text: 选中的label拼接字符串,
|
|
showLoading: 拉取数据时可以开启loading,
|
|
hideLoading: 拉取数据后可以关闭loading,
|
|
setData: 级联选择时在change事件中设置列的数据,
|
|
}) 滑动改变时触发
|
|
* @event {Function} cancel 点击取消按钮或遮罩关闭取消时触发
|
|
* @event {Function} confirm ({
|
|
indexList: 选中的index数组,
|
|
valueList: 选中的value数组,
|
|
dataList: 选中的data数组,
|
|
value: 选中的value,只有在单列时会有
|
|
data: 选中的data,只有在单列时会有
|
|
text: 选中的label拼接字符串,
|
|
getText: 根据自定义间隔符返回选中的label拼接字符串,
|
|
}) 点击确认时触发
|
|
* @event {Function} open 打开弹出层时触发
|
|
* @event {Function} opened 打开弹出层且动画结束后触发
|
|
* @event {Function} close 关闭弹出层时触发
|
|
* @event {Function} closed 关闭弹出层且动画结束后触发
|
|
* @example <s-picker v-model="value" :columns="columns" :visible.sync="visible" @confirm="onConfirm" />
|
|
*/
|
|
export default {
|
|
name: 'SPicker',
|
|
mixins: [componentMixin],
|
|
props: {
|
|
visible: Boolean,
|
|
columns: {
|
|
type: [Array, Object],
|
|
default: () => [],
|
|
},
|
|
value: {
|
|
default: '',
|
|
},
|
|
multiple: Boolean,
|
|
valueKey: {
|
|
type: String,
|
|
default: 'value',
|
|
},
|
|
labelKey: {
|
|
type: String,
|
|
default: 'label',
|
|
},
|
|
placeholder: {
|
|
type: [Array, String],
|
|
default: () => [],
|
|
},
|
|
text: String,
|
|
separator: {
|
|
type: String,
|
|
default: '-',
|
|
},
|
|
title: String,
|
|
titleColor: String,
|
|
cancelText: {
|
|
type: String,
|
|
default: '取消',
|
|
},
|
|
cancelColor: String,
|
|
confirmText: {
|
|
type: String,
|
|
default: '确定',
|
|
},
|
|
confirmColor: String,
|
|
disabledColor: String,
|
|
indicatorClass: [String, Object],
|
|
indicatorStyle: [String, Object],
|
|
maskClass: [String, Object],
|
|
maskStyle: [String, Object],
|
|
zIndex: [Number, String],
|
|
mask: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
maskOpacity: [Number, String],
|
|
maskClose: Boolean,
|
|
showCancel: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
animate: {
|
|
type: [String, Boolean, Object],
|
|
default: 'auto',
|
|
},
|
|
animateDuration: {
|
|
type: [Number, String, Object],
|
|
default: 300,
|
|
},
|
|
safeAreaInsetBottom: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
columnDataList: [], // column 合法数据数组
|
|
indexList: [], // column 每列选中的索引
|
|
pickerView: false, // 初始化picker-view
|
|
isDisabledConfirm: false, // 滑动时禁用确认按钮
|
|
loading: false,
|
|
};
|
|
},
|
|
computed: {
|
|
initState() {
|
|
return [this.columns, this.multiple];
|
|
},
|
|
indicator_class() {
|
|
return this.$mergeClass('s-picker__indicator', this.indicatorClass);
|
|
},
|
|
indicator_style() {
|
|
return this.$mergeStyle(this.indicatorStyle);
|
|
},
|
|
mask_class() {
|
|
return this.$mergeClass('s-picker__mask', this.maskClass);
|
|
},
|
|
mask_Style() {
|
|
return this.$mergeStyle(this.maskStyle);
|
|
},
|
|
confirm_class() {
|
|
return this.$mergeClass('s-picker__action s-picker__confirm-action', {
|
|
's-picker__confirm-action--disabled': this.isDisabledConfirm,
|
|
});
|
|
},
|
|
},
|
|
watch: {
|
|
initState() {
|
|
this.init();
|
|
},
|
|
value(value) {
|
|
this.updateValue(value);
|
|
},
|
|
},
|
|
created() {
|
|
this.columnIndexList = [];
|
|
this.valueList = [];
|
|
this.dataList = [];
|
|
this.init();
|
|
},
|
|
methods: {
|
|
init() {
|
|
const columnIndexList = [];
|
|
const columnDataList = [];
|
|
const valueList = [];
|
|
const indexList = [];
|
|
const dataList = [];
|
|
const columns = this.multiple ? this.columns : [this.columns];
|
|
const values = this.$getType(this.value) === 'array' ? this.value : [this.value];
|
|
columns.forEach((data, i) => {
|
|
columnIndexList[i] = i;
|
|
columnDataList[i] = this.toColumnDataList(i, data);
|
|
indexList[i] = 0;
|
|
valueList[i] = this.getDefValue(values[i]);
|
|
dataList[i] = null;
|
|
});
|
|
this.columnIndexList = columnIndexList;
|
|
this.columnDataList = columnDataList;
|
|
this.valueList = valueList;
|
|
this.indexList = indexList;
|
|
this.dataList = dataList;
|
|
this.updateValue(this.value);
|
|
},
|
|
getDefValue(value, defaultValue = '') {
|
|
return this.$isDef(value) ? value : defaultValue;
|
|
},
|
|
onOpen() {
|
|
this.$emit('open');
|
|
this.$nextTick(() => {
|
|
this.updateValue(this.value);
|
|
this.pickerView = true;
|
|
this.isDisabledConfirm = false;
|
|
});
|
|
},
|
|
onClose() {
|
|
this.$emit('close');
|
|
this.close();
|
|
},
|
|
onClosed() {
|
|
this.$emit('closed');
|
|
this.hideLoading();
|
|
this.pickerView = false;
|
|
},
|
|
close() {
|
|
this.$emit('update:visible', false);
|
|
},
|
|
onPickerStart() {
|
|
this.isDisabledConfirm = true;
|
|
},
|
|
onPickerEnd() {
|
|
this.isDisabledConfirm = false;
|
|
},
|
|
onPickerChange(e) {
|
|
const prevIndexList = this.indexList;
|
|
this.indexList = [...e.detail.value];
|
|
this.indexList.some((index, i) => {
|
|
if (index !== prevIndexList[i]) {
|
|
this.onColumnChange(i);
|
|
return true;
|
|
}
|
|
});
|
|
},
|
|
toColumnDataList(i, data) {
|
|
const valueKey = this.valueKey;
|
|
const labelKey = this.labelKey;
|
|
const placeholderList = this.$getType(this.placeholder) === 'array' ? this.placeholder : [this.placeholder];
|
|
const placeholder = placeholderList[i];
|
|
return [
|
|
...(placeholder ? [{ [valueKey]: '', [labelKey]: placeholder }] : []),
|
|
...toValidListData(data, valueKey, labelKey),
|
|
];
|
|
},
|
|
showLoading() {
|
|
this.loading = true;
|
|
this.loadingVisible = true;
|
|
},
|
|
hideLoading() {
|
|
this.loadingVisible = false;
|
|
this.$nextTick(() => {
|
|
this.$nextTick(() => {
|
|
if (!this.loadingVisible) {
|
|
this.loading = false;
|
|
}
|
|
});
|
|
});
|
|
},
|
|
setData(i, data) {
|
|
const list = this.toColumnDataList(i, data);
|
|
if (JSON.stringify(this.columnDataList[i]) !== JSON.stringify(list)) {
|
|
this.$set(this.columnDataList, i, list);
|
|
this.$nextTick(() => {
|
|
this.updateColumnIndex(i);
|
|
});
|
|
}
|
|
},
|
|
updateColumnIndex(i) {
|
|
const { valueList, valueKey, columnDataList, indexList } = this;
|
|
const value = String(this.getDefValue(valueList[i]));
|
|
const valueIndex = columnDataList[i].findIndex(item => value === String(this.getDefValue(item[valueKey])));
|
|
let index = this.getColumnIndex(i);
|
|
index = valueIndex > -1 ? valueIndex : index;
|
|
this.$set(indexList, i, index);
|
|
this.onColumnChange(i);
|
|
},
|
|
onColumnChange(i) {
|
|
const index = this.getColumnIndex(i);
|
|
const prevData = this.dataList[i] || null;
|
|
const data = this.columnDataList[i][index] || null;
|
|
if (data !== prevData) {
|
|
this.dataList[i] = data;
|
|
this.valueList[i] = data ? data[this.valueKey] : '';
|
|
this.$emit('change', {
|
|
i,
|
|
index,
|
|
value: this.valueList[i],
|
|
data,
|
|
indexList: [...this.indexList],
|
|
valueList: [...this.valueList],
|
|
dataList: [...this.dataList],
|
|
setData: this.setData,
|
|
showLoading: this.showLoading,
|
|
hideLoading: this.hideLoading,
|
|
});
|
|
}
|
|
},
|
|
getColumnIndex(i) {
|
|
return Math.max(0, Math.min(this.indexList[i], this.columnDataList[i].length - 1));
|
|
},
|
|
getText(separator) {
|
|
separator = this.getDefValue(separator, this.separator);
|
|
return this.dataList.filter(item => item && this.$isDef(item[this.labelKey])).map(item => item[this.labelKey]).join(separator);
|
|
},
|
|
updateValue(value) {
|
|
const values = this.$getType(value) === 'array' ? value : [value];
|
|
this.columnIndexList.forEach(i => {
|
|
this.valueList[i] = this.getDefValue(values[i]);
|
|
this.updateColumnIndex(i);
|
|
});
|
|
},
|
|
triggerCancel() {
|
|
this.$emit('cancel');
|
|
},
|
|
onCance() {
|
|
this.close();
|
|
this.triggerCancel();
|
|
},
|
|
onConfirm() {
|
|
if (!this.isDisabledConfirm) {
|
|
const valueList = this.dataList.map(item => item ? item[this.valueKey] : '');
|
|
const text = this.getText();
|
|
const params = {
|
|
text,
|
|
indexList: [...this.indexList],
|
|
valueList: [...valueList],
|
|
dataList: [...this.dataList],
|
|
getText: this.getText,
|
|
};
|
|
if (!this.multiple) {
|
|
params.value = valueList[0];
|
|
params.data = this.dataList[0];
|
|
}
|
|
this.close();
|
|
this.$emit('input', this.multiple ? [...valueList] : valueList[0]);
|
|
this.$emit('update:text', text);
|
|
this.$emit('confirm', params);
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" src="./index.scss"></style>
|
|
|