ChooseThisDepth.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. <script setup>
  2. // --- 接收父组件参数 ---
  3. const props = defineProps({
  4. // 1. 控制显示 (对应 v-model:show)
  5. show: {
  6. type: Boolean,
  7. default: false
  8. },
  9. // 2. 当前选中的值 (对应 v-model)
  10. modelValue: {
  11. type: String,
  12. default: ''
  13. }
  14. });
  15. // --- 定义事件 ---
  16. const emit = defineEmits(['update:show', 'update:modelValue']);
  17. const options = [
  18. { label: '深度1', value: 'depth1' },
  19. { label: '深度2', value: 'depth2' },
  20. { label: '深度3', value: 'depth3' },
  21. ];
  22. // --- 交互逻辑 ---
  23. const close = () => {
  24. emit('update:show', false); // 通知父组件:把 show 改成 false
  25. };
  26. const selectItem = (item) => {
  27. emit('update:modelValue', item.value); // 通知父组件:更新选中的值
  28. close(); // 选完直接关闭
  29. };
  30. </script>
  31. <template>
  32. <teleport to="body">
  33. <transition name="fade">
  34. <div v-if="show" class="overlay" @click="close"></div>
  35. </transition>
  36. <transition name="slide-up">
  37. <div v-if="show" class="popup-wrapper" @click.stop>
  38. <div class="popup-header">
  39. <div class="handle-bar"></div>
  40. <div class="title">选择深度</div>
  41. </div>
  42. <div class="popup-content">
  43. <div
  44. v-for="item in options"
  45. :key="item.value"
  46. class="option-item"
  47. @click="selectItem(item)"
  48. >
  49. <span :class="['label', { 'active': modelValue === item.value }]">
  50. {{ item.label }}
  51. </span>
  52. <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">
  53. <polyline points="20 6 9 17 4 12"></polyline>
  54. </svg>
  55. </div>
  56. </div>
  57. <div class="safe-area"></div>
  58. </div>
  59. </transition>
  60. </teleport>
  61. </template>
  62. <style scoped>
  63. /* --- 仅包含弹窗相关的样式 --- */
  64. .overlay {
  65. position: fixed; top: 0; left: 0; right: 0; bottom: 0;
  66. background-color: rgba(0, 0, 0, 0.6);
  67. backdrop-filter: blur(2px);
  68. z-index: 2000;
  69. }
  70. .popup-wrapper {
  71. position: fixed; bottom: 0; left: 0; right: 0;
  72. background-color: #FFFFFF;
  73. border-top-left-radius: 16px;
  74. border-top-right-radius: 16px;
  75. z-index: 2001;
  76. padding-bottom: 10px;
  77. }
  78. .popup-header { padding-top: 10px; margin-bottom: 10px; }
  79. .handle-bar {
  80. width: 36px; height: 4px; background-color: #E6E8EA;
  81. border-radius: 2px; margin: 0 auto 16px; }
  82. .title { font-size: 18px; font-weight: 600; color: #1E2329; padding: 0 20px; }
  83. .popup-content { max-height: 60vh; overflow-y: auto; }
  84. .option-item { display: flex; justify-content: space-between;
  85. align-items: center; height: 56px; padding: 0 20px; cursor: pointer; }
  86. .option-item:active { background-color: #F5F5F5; }
  87. .label { font-size: 16px; color: #848E9C; }
  88. .label.active { color: #1E2329; font-weight: 500; }
  89. .safe-area { height: env(safe-area-inset-bottom); }
  90. /* 动画 */
  91. .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }
  92. .fade-enter-from, .fade-leave-to { opacity: 0; }
  93. .slide-up-enter-active, .slide-up-leave-active { transition: transform 0.3s cubic-bezier(0.2, 0, 0.2, 1); }
  94. .slide-up-enter-from, .slide-up-leave-to { transform: translateY(100%); }
  95. </style>