| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- <template>
- <Teleport to="body">
- <transition name="fade">
- <div v-if="visible" class="modal-mask" @click="closeModal"></div>
- </transition>
- <transition name="slide-up">
- <div v-if="visible" class="modal-panel">
- <div class="panel-handle-wrap">
- <div class="panel-handle"></div>
- </div>
- <div class="panel-title">订单时效</div>
- <div class="option-list">
- <div
- v-for="item in options"
- :key="item.value"
- class="option-item"
- @click="selectOption(item.value)"
- >
- <span :class="{ 'active-text': modelValue === item.value }">
- {{ item.label }}
- </span>
- <div v-if="modelValue === item.value" class="check-icon">
- <svg viewBox="0 0 1024 1024" width="16" height="16">
- <path d="M512 0C229.23 0 0 229.23 0 512s229.23 512 512 512 512-229.23 512-512S794.77 0 512 0z" fill="#E03C3C"/>
- <path d="M426.67 725.33L213.33 512l60.33-60.33L426.67 604.67 750.33 281l60.34 60.33-384 384z" fill="#FFFFFF"/>
- </svg>
- </div>
- </div>
- </div>
- <div class="safe-area-bottom"></div>
- </div>
- </transition>
- </Teleport>
- </template>
- <script setup>
- import { defineProps, defineEmits } from 'vue';
- import { toRef } from 'vue' // 1. 引入 toRef
- import { useBodyScrollLock } from '@/composables/useBodyScrollLock' // 2. 引入 Hook
- // 定义接收的属性
- const props = defineProps({
- visible: {
- type: Boolean,
- default: false
- },
- modelValue: {
- type: String,
- default: ''
- }
- });
- useBodyScrollLock(toRef(props, 'visible'))
- // 定义抛出的事件
- const emit = defineEmits(['update:visible', 'update:modelValue']);
- // 选项数据 (模拟设计稿内容)
- const options = [
- { label: 'GTC(一直有效直到取消)', value: 'GTC' },
- { label: 'IOC(立即成交否则取消)', value: 'IOC' },
- { label: 'FOK(全部成交否则取消)', value: 'FOK' }
- ];
- // 选择逻辑
- const selectOption = (value) => {
- emit('update:modelValue', value); // 带回选中的值
- closeModal(); // 关闭弹窗
- };
- // 关闭逻辑
- const closeModal = () => {
- emit('update:visible', false);
- };
- </script>
- <style scoped>
- /* 遮罩层样式 */
- .modal-mask {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 1000;
- }
- /* 弹窗主体样式 */
- .modal-panel {
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100%;
- background: white;
- border-radius: 16px 16px 0 0;
- padding: 10px 0 0 0;
- z-index: 1001;
- box-sizing: border-box;
- /* 模拟手机最大宽度,防止在PC上太宽 */
- max-width: 600px;
- left: 50%;
- transform: translateX(-50%);
- }
- /* 顶部小灰条 */
- .panel-handle-wrap {
- display: flex;
- justify-content: center;
- padding-bottom: 20px;
- }
- .panel-handle {
- width: 36px;
- height: 4px;
- background: #E0E0E0;
- border-radius: 2px;
- }
- /* 标题 */
- .panel-title {
- font-size: 18px;
- font-weight: bold;
- color: #333;
- padding: 0 20px 20px 20px;
- }
- /* 列表项 */
- .option-list {
- padding: 0 20px;
- }
- .option-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px 0;
- font-size: 16px;
- color: #999; /* 未选中默认为灰色 */
- cursor: pointer;
- }
- /* 选中时的文字颜色 */
- .active-text {
- color: #333;
- font-weight: 500;
- }
- .check-icon {
- display: flex;
- align-items: center;
- }
- .safe-area-bottom {
- height: 30px; /* 简单的安全区预留 */
- }
- /* Vue 动画过渡类 */
- .fade-enter-active, .fade-leave-active {
- transition: opacity 0.3s ease;
- }
- .fade-enter-from, .fade-leave-to {
- opacity: 0;
- }
- .slide-up-enter-active, .slide-up-leave-active {
- transition: transform 0.3s ease;
- }
- .slide-up-enter-from, .slide-up-leave-to {
- transform: translate(-50%, 100%); /* 从底部滑出 */
- }
- </style>
|