| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- <script setup>
- // --- 接收父组件参数 ---
- const props = defineProps({
- // 1. 控制显示 (对应 v-model:show)
- show: {
- type: Boolean,
- default: false
- },
- // 2. 当前选中的值 (对应 v-model)
- modelValue: {
- type: String,
- default: ''
- }
- });
- // --- 定义事件 ---
- const emit = defineEmits(['update:show', 'update:modelValue']);
- const options = [
- { label: '深度1', value: 'depth1' },
- { label: '深度2', value: 'depth2' },
- { label: '深度3', value: 'depth3' },
- ];
- // --- 交互逻辑 ---
- const close = () => {
- emit('update:show', false); // 通知父组件:把 show 改成 false
- };
- const selectItem = (item) => {
- emit('update:modelValue', item.value); // 通知父组件:更新选中的值
- close(); // 选完直接关闭
- };
- </script>
- <template>
- <teleport to="body">
- <transition name="fade">
- <div v-if="show" class="overlay" @click="close"></div>
- </transition>
- <transition name="slide-up">
- <div v-if="show" class="popup-wrapper" @click.stop>
- <div class="popup-header">
- <div class="handle-bar"></div>
- <div class="title">选择深度</div>
- </div>
- <div class="popup-content">
- <div
- v-for="item in options"
- :key="item.value"
- class="option-item"
- @click="selectItem(item)"
- >
- <span :class="['label', { 'active': modelValue === item.value }]">
- {{ item.label }}
- </span>
- <svg v-if="modelValue === item.value" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#DC4653" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
- <polyline points="20 6 9 17 4 12"></polyline>
- </svg>
- </div>
- </div>
- <div class="safe-area"></div>
- </div>
- </transition>
- </teleport>
- </template>
- <style scoped>
- /* --- 仅包含弹窗相关的样式 --- */
- .overlay {
- position: fixed; top: 0; left: 0; right: 0; bottom: 0;
- background-color: rgba(0, 0, 0, 0.6);
- backdrop-filter: blur(2px);
- z-index: 2000;
- }
- .popup-wrapper {
- position: fixed; bottom: 0; left: 0; right: 0;
- background-color: #FFFFFF;
- border-top-left-radius: 16px;
- border-top-right-radius: 16px;
- z-index: 2001;
- padding-bottom: 10px;
- }
- .popup-header { padding-top: 10px; margin-bottom: 10px; }
- .handle-bar {
- width: 36px; height: 4px; background-color: #E6E8EA;
- border-radius: 2px; margin: 0 auto 16px; }
- .title { font-size: 18px; font-weight: 600; color: #1E2329; padding: 0 20px; }
- .popup-content { max-height: 60vh; overflow-y: auto; }
- .option-item { display: flex; justify-content: space-between;
- align-items: center; height: 56px; padding: 0 20px; cursor: pointer; }
- .option-item:active { background-color: #F5F5F5; }
- .label { font-size: 16px; color: #848E9C; }
- .label.active { color: #1E2329; font-weight: 500; }
- .safe-area { height: env(safe-area-inset-bottom); }
- /* 动画 */
- .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }
- .fade-enter-from, .fade-leave-to { opacity: 0; }
- .slide-up-enter-active, .slide-up-leave-active { transition: transform 0.3s cubic-bezier(0.2, 0, 0.2, 1); }
- .slide-up-enter-from, .slide-up-leave-to { transform: translateY(100%); }
- </style>
|