|
|
@@ -0,0 +1,227 @@
|
|
|
+<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" @click.stop>
|
|
|
+ <div class="drag-handle"></div>
|
|
|
+ <h3 class="modal-title">调整保证金</h3>
|
|
|
+
|
|
|
+ <!-- 步进器 -->
|
|
|
+ <div class="stepper-box">
|
|
|
+ <button class="step-btn" @click="adjustIndex(-1)">—</button>
|
|
|
+ <div class="step-value">{{ leverageMarks[sliderIndex] }}x</div>
|
|
|
+ <button class="step-btn" @click="adjustIndex(1)">+</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 滑块容器 -->
|
|
|
+ <div class="slider-container">
|
|
|
+ <!-- 静态起始圆点 (位于最左侧) -->
|
|
|
+ <div class="start-marker"></div>
|
|
|
+
|
|
|
+ <!-- 滑块 Input -->
|
|
|
+ <input
|
|
|
+ type="range"
|
|
|
+ min="0"
|
|
|
+ :max="leverageMarks.length - 1"
|
|
|
+ step="1"
|
|
|
+ v-model="sliderIndex"
|
|
|
+ class="custom-range"
|
|
|
+ :style="sliderStyle"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 刻度文字 -->
|
|
|
+ <div class="slider-marks">
|
|
|
+ <span
|
|
|
+ v-for="(mark, index) in leverageMarks"
|
|
|
+ :key="mark"
|
|
|
+ :class="{ active: index === Number(sliderIndex) }"
|
|
|
+ >
|
|
|
+ {{ mark }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button class="confirm-btn" @click="confirmLeverage">确认</button>
|
|
|
+ </div>
|
|
|
+ </transition>
|
|
|
+ </Teleport>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed, watch } from 'vue';
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ visible: Boolean,
|
|
|
+ initialValue: { type: Number, default: 20 }
|
|
|
+});
|
|
|
+
|
|
|
+const emit = defineEmits(['update:visible', 'confirm']);
|
|
|
+
|
|
|
+const leverageMarks = [20, 50, 100, 500, 1000];
|
|
|
+const sliderIndex = ref(0);
|
|
|
+
|
|
|
+watch(() => props.visible, (val) => {
|
|
|
+ if (val) {
|
|
|
+ const idx = leverageMarks.indexOf(props.initialValue);
|
|
|
+ sliderIndex.value = idx !== -1 ? idx : 0;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const close = () => {
|
|
|
+ emit('update:visible', false);
|
|
|
+};
|
|
|
+
|
|
|
+const adjustIndex = (delta) => {
|
|
|
+ let newIndex = Number(sliderIndex.value) + delta;
|
|
|
+ if (newIndex < 0) newIndex = 0;
|
|
|
+ if (newIndex > leverageMarks.length - 1) newIndex = leverageMarks.length - 1;
|
|
|
+ sliderIndex.value = newIndex;
|
|
|
+};
|
|
|
+
|
|
|
+const confirmLeverage = () => {
|
|
|
+ emit('confirm', leverageMarks[sliderIndex.value]);
|
|
|
+ close();
|
|
|
+};
|
|
|
+
|
|
|
+const sliderStyle = computed(() => {
|
|
|
+ const maxIndex = leverageMarks.length - 1;
|
|
|
+ const val = Number(sliderIndex.value);
|
|
|
+ const percentage = (val / maxIndex) * 100;
|
|
|
+ return {
|
|
|
+ background: `linear-gradient(to right, #F6465D 0%, #F6465D ${percentage}%, #F2F4F6 ${percentage}%, #F2F4F6 100%)`
|
|
|
+ };
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 遮罩和面板基础样式 */
|
|
|
+.modal-mask {
|
|
|
+ position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
|
|
|
+ background-color: rgba(0, 0, 0, 0.6); z-index: 1009;
|
|
|
+}
|
|
|
+.modal-panel {
|
|
|
+ position: fixed; bottom: 0; left: 0; width: 100%;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 16px 16px 0 0;
|
|
|
+ padding: 10px 20px 30px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ z-index: 1010;
|
|
|
+}
|
|
|
+.drag-handle {
|
|
|
+ width: 40px; height: 4px; background-color: #E0E0E0;
|
|
|
+ border-radius: 2px; margin: 0 auto 15px;
|
|
|
+}
|
|
|
+.modal-title { font-size: 16px; font-weight: 600; margin-bottom: 20px; color: #333; }
|
|
|
+
|
|
|
+/* 步进器 */
|
|
|
+.stepper-box {
|
|
|
+ display: flex; align-items: center;
|
|
|
+ background-color: #F7F8FA; border-radius: 8px; height: 48px; margin-bottom: 30px;
|
|
|
+}
|
|
|
+.step-btn {
|
|
|
+ width: 60px; height: 100%; border: none; background: transparent;
|
|
|
+ font-size: 24px; color: #999; cursor: pointer;
|
|
|
+}
|
|
|
+.step-value { flex: 1; text-align: center; font-size: 18px; font-weight: 600; color: #333; }
|
|
|
+
|
|
|
+/* 🔥🔥 Slider 容器 (高度统一为28px) 🔥🔥 */
|
|
|
+.slider-container {
|
|
|
+ position: relative;
|
|
|
+ margin-bottom: 50px;
|
|
|
+ padding: 0 12px;
|
|
|
+ height: 28px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center; /* 垂直居中 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 🔥🔥 静态起始圆点 (Start Marker) 🔥🔥 */
|
|
|
+.start-marker {
|
|
|
+ position: absolute;
|
|
|
+ left: 12px; /* 对应容器 padding */
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ /* 红色边框,无阴影,复刻截图 */
|
|
|
+ border: 2px solid #F6465D;
|
|
|
+ box-sizing: border-box;
|
|
|
+ z-index: 10;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+
|
|
|
+/* 🔥🔥 Input 滑块核心样式 🔥🔥 */
|
|
|
+.custom-range {
|
|
|
+ -webkit-appearance: none;
|
|
|
+ appearance: none;
|
|
|
+ width: 100%;
|
|
|
+ /* 高度占满容器,确保点击区域足够大,同时方便对齐 */
|
|
|
+ height: 10px;
|
|
|
+ outline: none;
|
|
|
+ position: relative;
|
|
|
+ z-index: 2;
|
|
|
+ margin: 0;
|
|
|
+ touch-action: none; /* 禁止页面滚动,丝滑拖动 */
|
|
|
+
|
|
|
+ background-color: transparent;
|
|
|
+ /* 轨道厚度 12px */
|
|
|
+ background-size: 100% 10px;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: center;
|
|
|
+ border-radius: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 轨道样式 */
|
|
|
+.custom-range::-webkit-slider-runnable-track {
|
|
|
+ width: 100%;
|
|
|
+ /*height: 10px;*/ /* 轨道填满 Input 高度(28px) */
|
|
|
+ background: transparent;
|
|
|
+ border: none;
|
|
|
+ border-radius: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 滑块圆钮 (Thumb) */
|
|
|
+.custom-range::-webkit-slider-thumb {
|
|
|
+ -webkit-appearance: none;
|
|
|
+ /* 尺寸与 Start Marker 完全一致 */
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ /* 红色边框,无阴影 */
|
|
|
+ border: 2px solid #F6465D;
|
|
|
+ box-sizing: border-box; /* 确保边框算在尺寸内 */
|
|
|
+
|
|
|
+ /* 因为轨道高28px,滑块高28px,自动居中,无需 margin */
|
|
|
+ margin-top: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 刻度文字 */
|
|
|
+.slider-marks {
|
|
|
+ position: absolute;
|
|
|
+ top: 38px; /* 位于滑块下方 */
|
|
|
+ left: 0; width: 100%;
|
|
|
+ padding: 0 12px; box-sizing: border-box;
|
|
|
+ display: flex; justify-content: space-between;
|
|
|
+ color: #999; font-size: 12px;
|
|
|
+}
|
|
|
+.slider-marks span.active { color: #333; font-weight: 600; }
|
|
|
+
|
|
|
+.confirm-btn {
|
|
|
+ width: 100%; height: 48px;
|
|
|
+ background-color: #F6465D; color: #fff;
|
|
|
+ border: none; border-radius: 24px;
|
|
|
+ font-size: 16px; font-weight: 600; cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+/* 动画 */
|
|
|
+.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 ease-out; }
|
|
|
+.slide-up-enter-from, .slide-up-leave-to { transform: translateY(100%); }
|
|
|
+</style>
|