|
|
@@ -0,0 +1,146 @@
|
|
|
+<template>
|
|
|
+ <Teleport to="body">
|
|
|
+ <!-- 遮罩层 -->
|
|
|
+ <transition name="fade">
|
|
|
+ <div class="modal-mask" v-if="visible" @click="close"></div>
|
|
|
+ </transition>
|
|
|
+
|
|
|
+ <!-- 底部弹窗面板 -->
|
|
|
+ <transition name="slide-up">
|
|
|
+ <div class="modal-panel" v-if="visible">
|
|
|
+ <!-- 顶部小灰条 -->
|
|
|
+ <div class="panel-handle"></div>
|
|
|
+
|
|
|
+ <!-- 标题 -->
|
|
|
+ <div class="panel-header">
|
|
|
+ <h3 class="panel-title">设置自动锁定时间</h3>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 选项列表 -->
|
|
|
+ <div class="option-list">
|
|
|
+ <div
|
|
|
+ class="option-item"
|
|
|
+ v-for="item in options"
|
|
|
+ :key="item"
|
|
|
+ @click="selectItem(item)"
|
|
|
+ >
|
|
|
+ <span class="option-text">{{ item }}</span>
|
|
|
+
|
|
|
+ <!-- 选中状态显示的红色对勾图标 -->
|
|
|
+ <div class="check-icon" v-if="localSelected === item">
|
|
|
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
|
+ <circle cx="12" cy="12" r="10" fill="#E02F44"/> <!-- 红色圆底 -->
|
|
|
+ <path d="M7 12L10 15L17 8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部按钮 -->
|
|
|
+ <div class="footer-action">
|
|
|
+ <button class="confirm-btn" @click="handleConfirm">确认</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部安全区 -->
|
|
|
+ <div class="safe-area-bottom"></div>
|
|
|
+ </div>
|
|
|
+ </transition>
|
|
|
+ </Teleport>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, watch } from 'vue';
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ visible: Boolean,
|
|
|
+ currentValue: String // 接收父组件当前的值
|
|
|
+});
|
|
|
+
|
|
|
+const emit = defineEmits(['update:visible', 'confirm']);
|
|
|
+
|
|
|
+// 选项数据
|
|
|
+const options = ['立即', '5分钟', '10分钟'];
|
|
|
+
|
|
|
+// 内部维护一个选中状态,用于显示对勾
|
|
|
+const localSelected = ref('');
|
|
|
+
|
|
|
+// 每次打开弹窗时,把父组件传来的值赋给内部状态
|
|
|
+watch(() => props.visible, (val) => {
|
|
|
+ if (val) {
|
|
|
+ localSelected.value = props.currentValue;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const close = () => {
|
|
|
+ emit('update:visible', false);
|
|
|
+};
|
|
|
+
|
|
|
+// 点击列表项(只更新 UI,不提交)
|
|
|
+const selectItem = (item) => {
|
|
|
+ localSelected.value = item;
|
|
|
+};
|
|
|
+
|
|
|
+// 点击确认按钮(提交数据并关闭)
|
|
|
+const handleConfirm = () => {
|
|
|
+ emit('confirm', localSelected.value);
|
|
|
+ close();
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.modal-mask {
|
|
|
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
|
+ background: rgba(0,0,0,0.5); z-index: 998;
|
|
|
+}
|
|
|
+
|
|
|
+.modal-panel {
|
|
|
+ position: fixed; left: 0; right: 0; bottom: 0;
|
|
|
+ background: #fff;
|
|
|
+ z-index: 999;
|
|
|
+ border-radius: 16px 16px 0 0;
|
|
|
+ padding-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-handle {
|
|
|
+ width: 36px; height: 4px; background: #E0E0E0;
|
|
|
+ border-radius: 2px; margin: 10px auto;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-header {
|
|
|
+ padding: 10px 20px 20px;
|
|
|
+}
|
|
|
+.panel-title {
|
|
|
+ font-size: 18px; font-weight: 600; color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.option-list {
|
|
|
+ padding: 0 20px 20px;
|
|
|
+}
|
|
|
+.option-item {
|
|
|
+ display: flex; justify-content: space-between; align-items: center;
|
|
|
+ height: 50px;
|
|
|
+ font-size: 16px; color: #333;
|
|
|
+ /* border-bottom: 1px solid #f5f5f5; 可选分割线 */
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+/* 底部按钮区 */
|
|
|
+.footer-action {
|
|
|
+ padding: 10px 20px;
|
|
|
+}
|
|
|
+.confirm-btn {
|
|
|
+ width: 100%; height: 48px;
|
|
|
+ background: #E02F44; /* 截图中的红色 */
|
|
|
+ color: #fff; border: none; border-radius: 24px;
|
|
|
+ font-size: 16px; font-weight: 600;
|
|
|
+}
|
|
|
+.confirm-btn:active { opacity: 0.9; }
|
|
|
+
|
|
|
+.safe-area-bottom { 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; }
|
|
|
+.slide-up-enter-from, .slide-up-leave-to { transform: translateY(100%); }
|
|
|
+</style>
|