绩效考核手机版
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.

438 lines
14 KiB

3 years ago
<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>