Files
----/前端源码/uni-app/components/MysticSelect.vue

271 lines
5.9 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<transition name="mystic-select">
<view v-if="isOpen" class="mystic-select-overlay">
<!-- Backdrop -->
<view class="mystic-select-backdrop" @click="handleClose"></view>
<!-- Modal Content -->
<view class="mystic-select-modal">
<!-- Header -->
<view class="mystic-select-header">
<view class="mystic-select-close" @click="handleClose">
<CloseIcon class="mystic-select-icon" />
</view>
<text class="mystic-select-title">{{ title }}</text>
<view class="mystic-select-confirm" @click="handleConfirm">
<CheckIcon class="mystic-select-icon" />
</view>
</view>
<!-- H5 友好用可滚动列表保证能下滑能点击选中 -->
<scroll-view
scroll-y
class="mystic-select-view"
:scroll-into-view="`opt-${selectedIndex}`"
>
<view
v-for="(item, idx) in options"
:key="item.value"
:id="`opt-${idx}`"
class="mystic-select-item"
:class="{ 'mystic-select-item-active': idx === selectedIndex }"
@click="selectOption(idx)"
>
<view class="mystic-select-item-main">
<text class="mystic-select-item-label">{{ item.label }}</text>
<text v-if="item.desc" class="mystic-select-item-desc">{{ item.desc }}</text>
</view>
<view v-if="idx === selectedIndex" class="mystic-select-check">
<CheckIcon class="mystic-select-check-icon" />
</view>
</view>
</scroll-view>
<!-- Footer Tip -->
<view v-if="tip" class="mystic-select-footer">
<text class="mystic-select-tip">{{ tip }}</text>
</view>
</view>
</view>
</transition>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import CloseIcon from './icons/CloseIcon.vue';
import CheckIcon from './icons/CheckIcon.vue';
export interface SelectOption {
value: string;
label: string;
desc?: string;
}
interface Props {
isOpen: boolean;
title?: string;
tip?: string;
options: SelectOption[];
defaultValue?: string;
}
const props = withDefaults(defineProps<Props>(), {
title: '请选择',
tip: '',
defaultValue: ''
});
const emit = defineEmits<{
close: [];
confirm: [option: SelectOption];
}>();
const selectedIndex = ref(0);
// 当打开时设置默认值
watch(() => props.isOpen, (newVal: boolean) => {
if (newVal) {
// 延迟设置,确保列表渲染完成
setTimeout(() => {
const idx = props.options.findIndex((o: SelectOption) => o.value === props.defaultValue);
selectedIndex.value = idx >= 0 ? idx : 0;
}, 50);
}
}, { immediate: true });
const handleClose = () => {
emit('close');
};
const handleConfirm = () => {
const selectedOption = props.options[selectedIndex.value];
emit('confirm', selectedOption);
handleClose();
};
const selectOption = (idx: number) => {
selectedIndex.value = idx;
};
</script>
<style scoped>
.mystic-select-overlay {
position: fixed;
inset: 0;
z-index: 999;
display: flex;
align-items: flex-end;
justify-content: center;
}
.mystic-select-backdrop {
position: absolute;
inset: 0;
background: rgba(26, 26, 26, 0.6);
backdrop-filter: blur(8rpx);
z-index: 1;
}
.mystic-select-modal {
position: relative;
z-index: 2;
width: 100%;
background: #fcfaf5;
border-top-left-radius: 32rpx;
border-top-right-radius: 32rpx;
overflow: hidden;
box-shadow: 0 -16rpx 64rpx rgba(0, 0, 0, 0.25);
border-top: 8rpx solid #8b2323;
}
.mystic-select-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx 40rpx;
border-bottom: 2rpx solid #eaddcf;
background: #f9f7f2;
}
.mystic-select-close,
.mystic-select-confirm {
padding: 16rpx;
color: #5a5a5a;
cursor: pointer;
transition: opacity 0.3s;
}
.mystic-select-close:active,
.mystic-select-confirm:active {
opacity: 0.6;
}
.mystic-select-confirm {
color: #8b2323;
}
.mystic-select-icon {
width: 44rpx;
height: 44rpx;
display: block;
}
.mystic-select-title {
font-size: 36rpx;
font-weight: bold;
color: #2c2c2c;
letter-spacing: 0.3em;
font-family: SimSun, "Songti SC", serif;
}
.mystic-select-view {
height: 480rpx;
width: 100%;
}
.mystic-select-item {
height: 100rpx;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
font-family: SimSun, "Songti SC", serif;
padding: 0 32rpx;
box-sizing: border-box;
transition: background-color 0.2s ease, border-color 0.2s ease;
border-bottom: 1rpx solid rgba(234, 221, 207, 0.7);
cursor: pointer;
}
.mystic-select-item-main {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4rpx;
min-width: 0;
}
.mystic-select-item-active {
background: rgba(139, 35, 35, 0.06);
border-bottom-color: rgba(139, 35, 35, 0.25);
}
.mystic-select-item-label {
font-size: 32rpx;
font-weight: bold;
color: #2c2c2c;
}
.mystic-select-item-desc {
font-size: 20rpx;
color: #8a8a8a;
}
.mystic-select-check {
width: 56rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.mystic-select-check-icon {
width: 36rpx;
height: 36rpx;
}
.mystic-select-footer {
background: #f9f7f2;
padding: 24rpx;
text-align: center;
border-top: 2rpx solid #eaddcf;
}
.mystic-select-tip {
font-size: 22rpx;
color: #8a8a8a;
letter-spacing: 0.1em;
}
/* Transition */
.mystic-select-enter-active,
.mystic-select-leave-active {
transition: opacity 0.3s;
}
.mystic-select-enter-active .mystic-select-modal,
.mystic-select-leave-active .mystic-select-modal {
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.mystic-select-enter-from,
.mystic-select-leave-to {
opacity: 0;
}
.mystic-select-enter-from .mystic-select-modal,
.mystic-select-leave-to .mystic-select-modal {
transform: translateY(100%);
}
</style>