123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- <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 {
- .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>
|