<template>
<!-- 蒙层 -->
<view v-if="visible" class="zy-popup-select-mask-layer" @click="handleMaskClick">
<view :class="{ 'zy-popup-select-mask-false': !mask, 'zy-popup-select-mask': mask }" style="height: 100%" />
</view>
<!-- 弹窗 -->
<view v-if="visible" class="zy-popup-select-popup-wrapper">
<view class="zy-popup-select-popup" :style="{ 'min-height': optionsHeight + optionsHeightPx }">
<!-- 滚动区域 -->
<scroll-view
class="zy-popup-select-options"
:class="{ 'zy-popup-select-options-scrollable': options.length > maxVisibleItems }"
scroll-y
:style="{ '--max-visible-height': maxVisibleHeight }"
@scroll="handleScroll"
>
<!-- 渲染选项 -->
<view
v-for="(item, index) in options"
:key="getValue(item)"
class="zy-popup-select-option-item"
:style="{ height: optionsHeight + optionsHeightPx }"
@click="toggleOption(item)"
>
<text class="zy-popup-select-option-text">
{{ getLabel(item) }}
</text>
<uni-icons v-if="isSelected(item)" class="zy-popup-select-checkmark" type="checkmarkempty" color="#0f56d5" />
</view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
name: 'ZyPopupSelect',
props: {
modelValue: { type: Boolean, default: false },
options: { type: Array, required: true },
labelKey: { type: String, default: 'label' },
valueKey: { type: String, default: 'value' },
maxSelected: { type: Number, default: 1 },
defaultSelected: { type: Array, default: () => [] },
mask: { type: Boolean, default: true },
optionsHeight: { type: Number, default: 57 },
optionsHeightPx: { type: String, default: 'px' },
maxVisibleItems: { type: Number, default: 3 },
isNeedSelected: { type: Boolean, default: true }
},
emits: ['update:modelValue', 'change', 'confirm'],
data() {
return {
selectedValues: [...this.defaultSelected]
}
},
computed: {
visible() {
return this.modelValue
},
maxVisibleHeight() {
return `${this.maxVisibleItems * this.optionsHeight}${this.optionsHeightPx}`
}
},
methods: {
// 清空所有选择
clearSelection() {
this.selectedValues = []
this.$emit('change', { item: null, selected: false })
},
// 全选功能(受 maxSelected 限制)
selectAll() {
if (this.maxSelected === 1) return
const restCount = this.maxSelected - this.selectedValues.length
if (restCount <= 0) {
uni.showToast({ title: `已达上限${this.maxSelected}项`, icon: 'none' })
return
}
const newValues = this.options
.map(this.getValue)
.filter(val => !this.selectedValues.includes(val))
.slice(0, restCount)
this.selectedValues.push(...newValues)
this.$emit('change', { item: null, selected: true })
},
getLabel(item) {
return item[this.labelKey]
},
getValue(item) {
return item[this.valueKey]
},
isSelected(item) {
return this.selectedValues.includes(this.getValue(item)) && this.isNeedSelected
},
toggleOption(item) {
const value = this.getValue(item)
const index = this.selectedValues.indexOf(value)
if (index > -1 || !this.isNeedSelected) {
this.selectedValues.splice(index, 1)
this.$emit('change', { item, selected: false })
} else {
if (this.selectedValues.length >= this.maxSelected) {
uni.showToast({
title: `最多选中 ${this.maxSelected} 项`,
icon: 'none',
duration: 1500
})
return
}
this.selectedValues.push(value)
this.$emit('change', { item, selected: true })
}
},
handleMaskClick() {
this.close(false) // 点击蒙层视为非取消关闭
},
close(fromCancel) {
this.$emit('update:modelValue', false)
this.$emit('confirm', {
selected: this.selectedValues.map(val => this.options.find(opt => this.getValue(opt) === val)).filter(Boolean),
fromCancel
})
}
}
}
</script>
<style lang="scss">
/* 蒙层外层 */
.zy-popup-select-mask-layer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 998;
pointer-events: auto;
}
.zy-popup-select-mask {
width: 100%;
background-color: #000000;
opacity: 0.5;
}
.zy-popup-select-mask-false {
opacity: 0;
width: 100%;
}
/* 弹窗外层 */
.zy-popup-select-popup-wrapper {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
z-index: 999;
background-color: #fff;
padding-bottom: env(safe-area-inset-bottom);
border-radius: 8px 8px 0 0;
overflow: hidden;
.zy-popup-select-popup {
position: relative;
}
}
/* 弹窗整体 */
.zy-popup-select-popup {
background-color: #ffffff;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
/* 滚动区域 */
.zy-popup-select-options {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
max-height: var(--max-visible-height);
}
.zy-popup-select-options-scrollable {
overflow-y: auto;
}
/* 单个选项 */
.zy-popup-select-option-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 0 60rpx;
box-sizing: border-box;
}
.zy-popup-select-option-item::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 1px;
background-color: #eeeeee;
transform: scaleY(0.5);
transform-origin: bottom;
pointer-events: none;
}
.zy-popup-select-option-item:last-child::after {
display: none;
}
/* 文本样式 */
.zy-popup-select-option-text {
font-size: $uni-font-size-18;
color: $uni-text-color;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc(100% - 100rpx);
text-align: center;
z-index: 1;
}
/* 对号图标 */
.zy-popup-select-checkmark {
transform: translate(22px, 0px);
color: $uni-primary;
font-weight: bold;
font-size: $uni-font-size-18;
width: auto;
height: auto;
z-index: 2;
}
/* 头部样式 */
.zy-popup-select-slot-header {
width: 100%;
padding: 12px 0;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f9f9f9;
position: relative;
}
.zy-popup-select-slot-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background-color: #eee;
transform: scaleY(0.5);
transform-origin: top;
}
.zy-popup-select-header-text {
font-size: $uni-font-size-14;
color: #666;
}
.zy-popup-select-header-actions {
display: flex;
gap: 16px;
}
.zy-popup-select-action {
font-size: $uni-font-size-14;
color: $uni-primary;
padding: 4px 8px;
border: 1px solid $uni-primary;
border-radius: 4px;
}
</style>
根据当前组件修改成一个新的组件,要求组件传入options,options中每项左右排列并且超出可视区域的时候可以左右滑动,每项可通过options中不同的value展示不同的图片,并在图片下方展示options每项的label,点击每项可调用方法,传入当前点击项的value和每项中的options
最新发布