volunteer-4/src/pages-sub/components/drop-menu/DropMenuItem.vue

218 lines
4.5 KiB
Vue

<template>
<view
class="drop-menu-item"
:class="[customClass, { 'drop-menu-item--show': isShow }]"
:style="{
'z-index': zIndex,
'transition-duration': `${duration}ms`,
}"
>
<view class="drop-menu-item__wrapper" :class="{ 'drop-menu-item__wrapper--show': isShow }">
<!-- 默认选项列表 -->
<scroll-view v-if="!$slots.default" scroll-y class="drop-menu-item__content">
<view class="drop-menu-item__option-list">
<view
v-for="(option, index) in options"
:key="index"
class="drop-menu-item__option"
:class="{ 'drop-menu-item__option--active': isOptionActive(option) }"
@click="handleOptionClick(option)"
>
<text class="drop-menu-item__text">{{ getOptionText(option) }}</text>
<text v-if="isOptionActive(option)" class="drop-menu-item__icon">✓</text>
</view>
</view>
</scroll-view>
<!-- 自定义内容插槽 -->
<view v-else class="drop-menu-item__custom-content">
<slot></slot>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, inject, onMounted, computed, watch } from 'vue'
const props = defineProps({
modelValue: {
type: [String, Number, Object],
default: '',
},
title: {
type: String,
required: true,
},
options: {
type: Array,
default: () => [],
},
labelKey: {
type: String,
default: 'text',
},
valueKey: {
type: String,
default: 'value',
},
customClass: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
activation: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue', 'change', 'open'])
// 注入父组件提供的数据和方法
const { activeIndex, addTitle, closeDropMenu, zIndex, duration, direction, titles } = inject(
'dropMenu',
) as any
// 当前组件的索引
const itemIndex = ref(-1)
// 是否显示下拉内容
const isShow = computed(() => activeIndex.value === itemIndex.value)
// 监听显示状态变化
watch(isShow, (newVal) => {
if (newVal) {
emit('open')
}
})
// 获取选项的显示文本
const getOptionText = (option: any) => {
if (typeof option === 'object' && props.labelKey) {
return option[props.labelKey]
}
return option
}
// 获取选项的值
const getOptionValue = (option: any) => {
if (typeof option === 'object' && props.valueKey) {
return option[props.valueKey]
}
return option
}
// 判断选项是否被选中
const isOptionActive = (option: any) => {
const optionValue = getOptionValue(option)
return props.modelValue === optionValue
}
// 处理选项点击
const handleOptionClick = (option: any) => {
const value = getOptionValue(option)
emit('update:modelValue', value)
emit('change', value)
closeDropMenu()
}
// 组件挂载时,向父组件注册标题并设置索引
onMounted(() => {
// 先获取当前索引
itemIndex.value = titles.value.length
// 再添加标题
addTitle({ title: props.title, disabled: props.disabled, activation: props.activation })
})
// 监听标题变化
watch(
() => props.title,
(newTitle) => {
// 更新对应索引位置的标题
if (titles.value[itemIndex.value].title !== newTitle) {
titles.value[itemIndex.value].title = newTitle
}
},
{ immediate: false },
)
watch(
() => props.activation,
(newVal) => {
titles.value[itemIndex.value].activation = newVal
},
)
</script>
<style scoped>
.drop-menu-item {
position: relative;
width: 100%;
}
.drop-menu-item__wrapper {
position: absolute;
left: 0;
right: 0;
top: 0;
background: #fff;
transform: translateY(-5px);
transition: all 0.25s ease;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
opacity: 0;
visibility: hidden;
z-index: 11;
}
.drop-menu-item__wrapper--show {
transform: translateY(0);
opacity: 1;
visibility: visible;
}
.drop-menu-item__content {
max-height: 400rpx;
}
.drop-menu-item__custom-content {
width: 100%;
background: #fff;
transform-origin: top;
}
.drop-menu-item__option-list {
padding: 12rpx 0;
}
.drop-menu-item__option {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
line-height: 1.2;
cursor: pointer;
}
.drop-menu-item__option:active {
background-color: #f2f2f2;
}
.drop-menu-item__option--active {
color: #0083ff;
}
.drop-menu-item__text {
font-size: 28rpx;
}
.drop-menu-item__icon {
font-size: 32rpx;
color: #0083ff;
}
</style>