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.
324 lines
7.8 KiB
324 lines
7.8 KiB
3 years ago
|
<template>
|
||
|
<view class="select-container" v-show="show" @touchmove.stop.prevent>
|
||
|
<view class="mask" :class="activeClass ? 'mask-show' : ''" @tap="onCancel(true)"></view>
|
||
|
<view class="select-box" :class="activeClass ? 'select-box-show' : ''">
|
||
|
<view class="header">
|
||
|
<text class="cancel" @tap="onCancel">{{cancelText}}</text>
|
||
|
<view class="all" @tap="onAllToggle" v-if="allShow">
|
||
|
<text :class="isAll ? 'all-active' : ''">全选</text>
|
||
|
</view>
|
||
|
<text class="confirm" @tap="onConfirm">{{confirmText}}</text>
|
||
|
</view>
|
||
|
<view class="body-warp">
|
||
|
<scroll-view class="body" scroll-y="true">
|
||
|
<slot v-if="!data.length" name="tips">
|
||
|
<view class="empty-tips">暂无数据~</view>
|
||
|
</slot>
|
||
|
<view
|
||
|
class="select-item"
|
||
|
:class="[item.disabled ? 'disabled' : '',selectedArr[index] ? 'selected' : '']"
|
||
|
v-for="(item,index) in data"
|
||
|
:key="item[valueName]"
|
||
|
@tap="onSelected(index)"
|
||
|
>
|
||
|
<view class="label">{{item[labelName]}}</view>
|
||
|
<text v-show="selectedArr[index]" class="selected-icon">✔</text>
|
||
|
</view>
|
||
|
</scroll-view>
|
||
|
</view>
|
||
|
</view>
|
||
|
</view>
|
||
|
</template>
|
||
|
<!-- 多选组件 -->
|
||
|
<script>
|
||
|
export default {
|
||
|
model: {
|
||
|
prop: "value",
|
||
|
event: ["input"]
|
||
|
},
|
||
|
data() {
|
||
|
return {
|
||
|
show: false, //是否显示
|
||
|
activeClass: false, //激活样式状态
|
||
|
selectedArr: [], //选择对照列表
|
||
|
selectedArrOld: [] //选择对照列表上一次的数据
|
||
|
};
|
||
|
},
|
||
|
onShow() {
|
||
|
this.show = this.value;
|
||
|
},
|
||
|
computed: {
|
||
|
// 返回是否全选
|
||
|
isAll() {
|
||
|
let wipeDisabledList = this.returnWipeDisabledList();
|
||
|
if (!wipeDisabledList.length) return false;
|
||
|
return !wipeDisabledList.includes(false);
|
||
|
}
|
||
|
},
|
||
|
props: {
|
||
|
// 双向绑定--是否调起底部
|
||
|
value: {
|
||
|
type: Boolean,
|
||
|
default: false
|
||
|
},
|
||
|
// 取消按钮文字
|
||
|
cancelText: {
|
||
|
type: String,
|
||
|
default: "取消"
|
||
|
},
|
||
|
// 确认按钮文字
|
||
|
confirmText: {
|
||
|
type: String,
|
||
|
default: "确认"
|
||
|
},
|
||
|
// label对应的key名称
|
||
|
labelName: {
|
||
|
type: String,
|
||
|
default: "label"
|
||
|
},
|
||
|
// value对应的key名称
|
||
|
valueName: {
|
||
|
type: String,
|
||
|
default: "value"
|
||
|
},
|
||
|
// 是否允许点击遮罩层关闭
|
||
|
maskCloseAble: {
|
||
|
type: Boolean,
|
||
|
default: true
|
||
|
},
|
||
|
// 是否显示全选
|
||
|
allShow: {
|
||
|
type: Boolean,
|
||
|
default: true
|
||
|
},
|
||
|
// 模式
|
||
|
mode: {
|
||
|
type: String,
|
||
|
default: "multiple"
|
||
|
},
|
||
|
// 默认选中值
|
||
|
defaultSelected: {
|
||
|
type: Array,
|
||
|
default: function() {
|
||
|
return [];
|
||
|
}
|
||
|
},
|
||
|
// 数据源
|
||
|
data: {
|
||
|
type: Array,
|
||
|
required: true,
|
||
|
default: () => {
|
||
|
return [];
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
watch: {
|
||
|
async value(newVal) {
|
||
|
this.show = newVal;
|
||
|
await this.$nextTick();
|
||
|
this.activeClass = newVal;
|
||
|
if (newVal) {
|
||
|
this.selectedArrOld = JSON.parse(JSON.stringify(this.selectedArr));
|
||
|
}
|
||
|
},
|
||
|
show(newVal) {
|
||
|
this.$emit("input", newVal);
|
||
|
this.$emit("change", newVal);
|
||
|
},
|
||
|
data: {
|
||
|
// 设置初始选择对照列表
|
||
|
handler(list) {
|
||
|
this.selectedArr = list.map(el => false);
|
||
|
this.setItemActiveState();
|
||
|
},
|
||
|
deep: true,
|
||
|
immediate: true
|
||
|
},
|
||
|
defaultSelected: {
|
||
|
handler() {
|
||
|
this.setItemActiveState();
|
||
|
},
|
||
|
deep: true,
|
||
|
immediate: true
|
||
|
}
|
||
|
},
|
||
|
methods: {
|
||
|
// 设置默认选中通用办法
|
||
|
setItemActiveState() {
|
||
|
if (this.data.length && this.defaultSelected.length) {
|
||
|
this.data.forEach((item, i) => {
|
||
|
for (let n = 0; n < this.defaultSelected.length; n++) {
|
||
|
if (
|
||
|
!item.disabled &&
|
||
|
item[this.valueName] === this.defaultSelected[n]
|
||
|
) {
|
||
|
this.selectedArr.splice(i, 1, true);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
/**
|
||
|
* 选择事件
|
||
|
* @index {Number} 点击下标
|
||
|
*/
|
||
|
onSelected(index) {
|
||
|
if (this.data[index].disabled) return;
|
||
|
let index2Active = this.selectedArr[index];
|
||
|
this.selectedArr.splice(index, 1, !index2Active);
|
||
|
},
|
||
|
// 取消事件
|
||
|
onCancel(isMask) {
|
||
|
if (!isMask || this.maskCloseAble) {
|
||
|
this.show = false;
|
||
|
this.selectedArr = JSON.parse(JSON.stringify(this.selectedArrOld));
|
||
|
} else {
|
||
|
return;
|
||
|
}
|
||
|
this.$emit("cancel");
|
||
|
},
|
||
|
// 返回去除了disabled状态后的对照列表
|
||
|
returnWipeDisabledList() {
|
||
|
let arr = [];
|
||
|
this.selectedArr.forEach((el, index) => {
|
||
|
if (!this.data[index].disabled) arr.push(el);
|
||
|
});
|
||
|
return arr;
|
||
|
},
|
||
|
// 全选/非全选事件
|
||
|
onAllToggle() {
|
||
|
let wipeDisabledList = this.returnWipeDisabledList();
|
||
|
// 如果去除了disabled的对照列表有false的数据,代表未全选
|
||
|
if (wipeDisabledList.includes(false)) {
|
||
|
this.selectedArr.forEach((el, index) => {
|
||
|
if (!this.data[index].disabled)
|
||
|
this.selectedArr.splice(index, 1, true);
|
||
|
});
|
||
|
} else {
|
||
|
this.selectedArr.forEach((el, index) => {
|
||
|
if (!this.data[index].disabled)
|
||
|
el = this.selectedArr.splice(index, 1, false);
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
// 确定事件
|
||
|
onConfirm() {
|
||
|
this.show = false;
|
||
|
let selectedData = [];
|
||
|
this.selectedArr.forEach((el, index) => {
|
||
|
if (el) {
|
||
|
selectedData.push(this.data[index]);
|
||
|
}
|
||
|
});
|
||
|
if (this.mode === "multiple") {
|
||
|
this.$emit("confirm", selectedData);
|
||
|
} else {
|
||
|
let backData = selectedData[0] || {};
|
||
|
this.$emit("confirm", backData);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
</script>
|
||
|
<style lang="scss" scoped>
|
||
|
.select-container {
|
||
|
width: 100vw;
|
||
|
height: 100vh;
|
||
|
position: fixed;
|
||
|
left: 0;
|
||
|
top: 0;
|
||
|
z-index: 999;
|
||
|
$paddingLR: 18rpx;
|
||
|
.mask {
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
background-color: $uni-bg-color-mask;
|
||
|
opacity: 0;
|
||
|
transition: opacity 0.3s;
|
||
|
&.mask-show {
|
||
|
opacity: 1;
|
||
|
}
|
||
|
}
|
||
|
// 选择器内容区域
|
||
|
.select-box {
|
||
|
width: 100%;
|
||
|
position: absolute;
|
||
|
bottom: 0;
|
||
|
left: 0;
|
||
|
transform: translate3d(0px, 100%, 0px);
|
||
|
background-color: $uni-bg-color;
|
||
|
transition: all 0.3s;
|
||
|
&.select-box-show {
|
||
|
transform: translateZ(0);
|
||
|
}
|
||
|
.header {
|
||
|
display: flex;
|
||
|
box-sizing: border-box;
|
||
|
width: 100%;
|
||
|
justify-content: space-between;
|
||
|
border-bottom: 1px solid $uni-border-color;
|
||
|
line-height: 76rpx;
|
||
|
font-size: 30rpx;
|
||
|
padding: 0 $paddingLR;
|
||
|
.cancel {
|
||
|
color: $uni-text-color-grey;
|
||
|
}
|
||
|
.all {
|
||
|
color: $uni-color-success;
|
||
|
.all-active {
|
||
|
&::after {
|
||
|
display: inline-block;
|
||
|
content: "✔";
|
||
|
padding-left: 8rpx;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
.confirm {
|
||
|
color: $uni-color-primary;
|
||
|
}
|
||
|
}
|
||
|
.body-warp {
|
||
|
width: 100%;
|
||
|
height: 30vh;
|
||
|
box-sizing: border-box;
|
||
|
padding: 20rpx $paddingLR;
|
||
|
}
|
||
|
.body {
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
overflow-y: auto;
|
||
|
.empty-tips {
|
||
|
margin-top: 25%;
|
||
|
text-align: center;
|
||
|
font-size: 26rpx;
|
||
|
color: $uni-color-error;
|
||
|
}
|
||
|
.select-item {
|
||
|
display: flex;
|
||
|
font-size: 26rpx;
|
||
|
line-height: 58rpx;
|
||
|
color: #303133;
|
||
|
position: relative;
|
||
|
transition: all 0.3s;
|
||
|
&.selected {
|
||
|
color: $uni-color-primary;
|
||
|
}
|
||
|
&.disabled {
|
||
|
color: $uni-text-color-disable;
|
||
|
}
|
||
|
> .label {
|
||
|
flex: 1;
|
||
|
text-align: center;
|
||
|
}
|
||
|
> .selected-icon {
|
||
|
position: absolute;
|
||
|
right: 0;
|
||
|
top: 50%;
|
||
|
transform: translateY(-50%);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
</style>
|