218 lines
4.5 KiB
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>
|