| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- <template>
- <Teleport to="body">
- <transition name="fade">
- <div
- v-if="visible"
- class="mask"
- @click="handleClose"
- ></div>
- </transition>
- <transition name="slide-up">
- <div v-if="visible" class="popup-container">
- <div class="drag-handle-area">
- <div class="drag-handle"></div>
- </div>
- <div class="popup-content">
- <h3 class="title">切换倍数</h3>
- <div class="stepper-box">
- <button class="step-btn minus" @click="decrement" :disabled="currentIndex <= 0">
- <span class="icon">−</span>
- </button>
- <div class="value-display">{{ currentValue }}x</div>
- <button class="step-btn plus" @click="increment" :disabled="currentIndex >= marks.length - 1">
- <span class="icon">+</span>
- </button>
- </div>
- <div class="slider-container">
- <div class="slider-track-bg"></div>
- <div
- class="slider-track-active"
- :style="{ width: getProgressPercent + '%' }"
- ></div>
- <div
- class="slider-thumb"
- :style="{ left: getProgressPercent + '%' }"
- @touchstart="startDrag"
- >
- <div class="inner-circle"></div>
- </div>
- <input
- type="range"
- min="0"
- :max="marks.length - 1"
- step="1"
- v-model="currentIndex"
- class="native-input"
- />
- </div>
- <div class="slider-labels">
- <span
- v-for="(mark, index) in marks"
- :key="mark"
- :class="{ active: index === currentIndex }"
- >
- {{ mark }}x
- </span>
- </div>
- <button class="confirm-btn" @click="confirmSelection">
- 确认
- </button>
- </div>
- </div>
- </transition>
- </Teleport>
- </template>
- <script setup>
- import { ref, computed, watch } from 'vue';
- const props = defineProps({
- visible: {
- type: Boolean,
- default: false
- },
- initialValue: {
- type: Number,
- default: 50
- }
- });
- const emit = defineEmits(['update:visible', 'confirm']);
- // 定义倍数阶梯 (非线性)
- const marks = [10, 50, 100, 500, 1000];
- // 当前选中的索引 (对应 marks 数组)
- const currentIndex = ref(1);
- // 监听弹窗打开,初始化数值
- watch(() => props.visible, (newVal) => {
- if (newVal) {
- const idx = marks.indexOf(props.initialValue);
- currentIndex.value = idx !== -1 ? idx : 1; // 默认选中50x或传入值
- }
- });
- // 计算当前显示的具体倍数值
- const currentValue = computed(() => marks[currentIndex.value]);
- // 计算进度条百分比
- const getProgressPercent = computed(() => {
- return (currentIndex.value / (marks.length - 1)) * 100;
- });
- // 步进器逻辑
- const increment = () => {
- if (currentIndex.value < marks.length - 1) currentIndex.value++;
- };
- const decrement = () => {
- if (currentIndex.value > 0) currentIndex.value--;
- };
- // 关闭弹窗
- const handleClose = () => {
- emit('update:visible', false);
- };
- // 确认选择
- const confirmSelection = () => {
- emit('confirm', currentValue.value);
- handleClose();
- };
- </script>
- <style scoped>
- /* 动画效果 */
- .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.25, 0.8, 0.5, 1); }
- .slide-up-enter-from, .slide-up-leave-to { transform: translateY(100%); }
- /* 遮罩层 */
- .mask {
- position: fixed;
- top: 0; left: 0; right: 0; bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- z-index: 998;
- }
- /* 弹窗容器 */
- .popup-container {
- position: fixed;
- bottom: 0; left: 0; right: 0;
- background: #ffffff;
- border-top-left-radius: 16px;
- border-top-right-radius: 16px;
- z-index: 999;
- padding-bottom: env(safe-area-inset-bottom);
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
- }
- /* 拖拽条 */
- .drag-handle-area {
- padding: 10px 0;
- display: flex;
- justify-content: center;
- }
- .drag-handle {
- width: 40px;
- height: 4px;
- background: #E0E0E0;
- border-radius: 2px;
- }
- .popup-content {
- padding: 0 20px 20px 20px;
- }
- .title {
- font-size: 18px;
- font-weight: 600;
- color: #333;
- margin: 0 0 20px 0;
- }
- /* 步进器样式 */
- .stepper-box {
- display: flex;
- align-items: center;
- justify-content: space-between;
- background: #F7F8FA;
- border-radius: 8px;
- padding: 5px;
- margin-bottom: 30px;
- height: 48px;
- }
- .step-btn {
- border: none;
- background: transparent;
- width: 48px;
- height: 100%;
- font-size: 24px;
- color: #999;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .step-btn:disabled { opacity: 0.3; }
- .step-btn:active { opacity: 0.6; }
- .value-display {
- font-size: 18px;
- font-weight: 500;
- color: #333;
- }
- /* 滑块样式核心 */
- .slider-container {
- position: relative;
- height: 30px; /* 增加触控区域 */
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
- .slider-track-bg {
- position: absolute;
- left: 0; right: 0;
- height: 6px;
- background: #F2F3F5;
- border-radius: 3px;
- }
- .slider-track-active {
- position: absolute;
- left: 0;
- height: 6px;
- background: #E54755; /* 主红色 */
- border-radius: 3px;
- pointer-events: none;
- }
- .slider-thumb {
- position: absolute;
- width: 24px;
- height: 24px;
- background: #fff;
- border: 2px solid #E54755;
- border-radius: 50%;
- transform: translateX(-50%); /* 居中对齐 */
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- pointer-events: none; /* 让点击事件穿透到 input */
- z-index: 2;
- }
- .inner-circle {
- width: 0px; /* 图片中看起来是实心的或者空心的,这里做个空心白底红圈 */
- height: 0px;
- }
- /* 原生 Input 覆盖在上面负责交互,但完全透明 */
- .native-input {
- position: absolute;
- width: 100%;
- height: 100%;
- opacity: 0;
- cursor: pointer;
- z-index: 3;
- margin: 0;
- }
- /* 刻度标签 */
- .slider-labels {
- display: flex;
- justify-content: space-between;
- color: #9FA2A8;
- font-size: 12px;
- margin-bottom: 30px;
- padding: 0 2px; /* 微微修正对齐 */
- }
- /* 确认按钮 */
- .confirm-btn {
- width: 100%;
- height: 48px;
- background: #E54755;
- color: white;
- border: none;
- border-radius: 24px;
- font-size: 16px;
- font-weight: 600;
- cursor: pointer;
- }
- .confirm-btn:active {
- background: #D13E4A;
- }
- </style>
|