Răsfoiți Sursa

今日工作:
1.【优化】 完成了 H5 项目的系统级性能优化:
解决了打包体积过大导致的首屏白屏问题(做了代码分割)。
解决了手机端点击有延迟、滑动不流畅的问题。
2.【开发】 完成了“调整保证金”弹窗组件,UI 已 1:1 还原设计稿,滑块交互已调优 ,去点杠杆新增币币交易页面。
3.【修复】 修复了页面刷新报错 404 的问题。

Hexinkui 1 lună în urmă
părinte
comite
28ce8a276e

+ 24 - 4
package-lock.json

@@ -21,9 +21,11 @@
         "@vue/cli-plugin-router": "~5.0.0",
         "@vue/cli-plugin-vuex": "~5.0.0",
         "@vue/cli-service": "~5.0.0",
+        "amfe-flexible": "^2.2.1",
         "babel-plugin-import": "^1.13.8",
         "less": "^4.0.0",
-        "less-loader": "^8.0.0"
+        "less-loader": "^8.0.0",
+        "postcss-pxtorem": "^6.1.0"
       }
     },
     "node_modules/@achrinza/node-ipc": {
@@ -1382,6 +1384,13 @@
         "ajv": "^6.9.1"
       }
     },
+    "node_modules/amfe-flexible": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/amfe-flexible/-/amfe-flexible-2.2.1.tgz",
+      "integrity": "sha512-L2VfvDzoETBjhRptg5u/IUuzHSuxm22JpSRb404p/TBGeRfwWmmNEbB+TFPIP/sS/+pbM18bCFH9QnMojLuPNw==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/ansi-escapes": {
       "version": "3.2.0",
       "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
@@ -5202,10 +5211,11 @@
       }
     },
     "node_modules/node-forge": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-1.3.1.tgz",
-      "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
+      "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
       "dev": true,
+      "license": "(BSD-3-Clause OR GPL-2.0)",
       "engines": {
         "node": ">= 6.13.0"
       }
@@ -6152,6 +6162,16 @@
         "postcss": "^8.2.15"
       }
     },
+    "node_modules/postcss-pxtorem": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-pxtorem/-/postcss-pxtorem-6.1.0.tgz",
+      "integrity": "sha512-ROODSNci9ADal3zUcPHOF/K83TiCgNSPXQFSbwyPHNV8ioHIE4SaC+FPOufd8jsr5jV2uIz29v1Uqy1c4ov42g==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "postcss": "^8.0.0"
+      }
+    },
     "node_modules/postcss-reduce-initial": {
       "version": "5.1.2",
       "resolved": "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz",

+ 8 - 1
package.json

@@ -16,13 +16,20 @@
     "vue-router": "^4.0.3",
     "vuex": "^4.0.0"
   },
+  "overrides": {
+      "resolutions": {
+        "postcss": "^8.4.31"
+      }
+  },
   "devDependencies": {
     "@vue/cli-plugin-router": "~5.0.0",
     "@vue/cli-plugin-vuex": "~5.0.0",
     "@vue/cli-service": "~5.0.0",
+    "amfe-flexible": "^2.2.1",
     "babel-plugin-import": "^1.13.8",
     "less": "^4.0.0",
-    "less-loader": "^8.0.0"
+    "less-loader": "^8.0.0",
+    "postcss-pxtorem": "^6.1.0"
   },
   "browserslist": [
     "> 1%",

+ 16 - 0
postcss.config.js

@@ -0,0 +1,16 @@
+
+
+module.exports = {
+  plugins: {
+    'postcss-pxtorem': {
+      // 关键点:因为你的设计稿是 375px
+      // 这里的数值必须是 37.5 (即 1rem = 37.5px)
+      // 如果你这里写的是 75,页面元素会缩小一半;如果写的是 16,页面会变得巨大。
+      rootValue: 37.5,
+
+      propList: ['*'], // 所有属性都转 rem
+      selectorBlackList: ['.norem'], // 过滤掉 .norem- 开头的 class,不进行 rem 转换
+      exclude: /node_modules/i // 可选:忽略 node_modules 里的库(如果你发现组件库变小了就加上这行)
+    },
+  },
+};

+ 2 - 2
public/index.html

@@ -3,8 +3,8 @@
   <head>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=375">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">
+    <link rel="preload" as="image" href="src/assets/img/index/Rectangle 1.png">
     <title><%= htmlWebpackPlugin.options.title %></title>
   </head>
   <body>

+ 7 - 0
src/App.vue

@@ -9,4 +9,11 @@ const { locale } = useI18n();*/
 </script>
 
 <style>
+* {
+  -webkit-overflow-scrolling: touch;
+}
+
+* {
+  -webkit-tap-highlight-color: transparent;
+}
 </style>

+ 62 - 0
src/assets/h5-reset.css

@@ -0,0 +1,62 @@
+/* H5 性能优化专用重置样式
+  请在 main.js 第一行引入: import '@/styles/h5-reset.css'
+*/
+
+:root {
+  /* 预设变量,方便统一调整 */
+  --safe-area-bottom: env(safe-area-inset-bottom);
+}
+
+html, body {
+  width: 100%;
+  height: 100%;
+  /* 1. 开启 iOS 原生惯性滚动 (解决滑动生涩) */
+  -webkit-overflow-scrolling: touch;
+
+  /* 2. 禁止双击缩放 (解决 300ms 点击延迟) */
+  touch-action: manipulation;
+
+  /* 3. 字体抗锯齿 (让文字更清晰) */
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+
+  /* 4. 防止 iOS 下拉回弹导致页面整体拖动 (可选) */
+  overscroll-behavior-y: none;
+}
+
+/* 全局去除点击高亮 (解决点击闪烁) */
+* {
+  -webkit-tap-highlight-color: transparent;
+  box-sizing: border-box;
+}
+
+/* 图片渲染优化 */
+img {
+  /* 防止图片加载时布局跳动 */
+  display: block;
+  max-width: 100%;
+  /* 只有当图片进入视口才渲染 (提升长列表性能) */
+  content-visibility: auto;
+}
+
+/* 强制开启 GPU 加速 (解决弹窗/动画掉帧) */
+.modal-mask,
+.modal-panel,
+.slide-up-enter-active,
+.slide-up-leave-active {
+  transform: translateZ(0);
+  will-change: transform, opacity;
+  backface-visibility: hidden;
+  perspective: 1000px;
+}
+
+/* 统一滚动容器体验 */
+.scroll-container {
+  overflow-y: auto;
+  -webkit-overflow-scrolling: touch;
+  /* 隐藏滚动条但保留功能 */
+  scrollbar-width: none;
+}
+.scroll-container::-webkit-scrollbar {
+  display: none;
+}

+ 4 - 2
src/main.js

@@ -1,9 +1,11 @@
 import { createApp } from "vue";
 import App from "./App.vue";
+import 'amfe-flexible'
 import router from "./router";
-import store from "./store";
+// import store from "./store";
 import 'vant/lib/index.css';
 import "./assets/less/index.less"; // 你的全局样式
+import "./assets/h5-reset.css";
 
 // 引入 i18n 和 加载函数
 import i18n, { loadLanguageAsync } from './locales/index'
@@ -28,7 +30,7 @@ loadLanguageAsync(savedLang).then(() => {
   const app = createApp(App);
 
   app.use(Button).use(Popup).use(Form).use(Field).use(NavBar).use(Picker).use(Icon).use(Toast);
-  app.use(store);
+  // app.use(store);
   app.use(router);
   app.use(i18n);
 

+ 8 - 2
src/router/index.js

@@ -1,4 +1,4 @@
-import { createRouter, createWebHistory } from "vue-router";
+import { createRouter, createWebHashHistory } from "vue-router";
 import HomeIndex from "../views/HomeIndex.vue";
 import AppIndex from "../views/index/Index.vue";
 import MarketIndex from "../views/market/Index.vue";
@@ -87,6 +87,12 @@ const routes = [
             component: TradeOptions,
             meta: { title: '期权' },
           },
+          {
+            path: 'CryptocurrencyTrading',
+            name: 'Cryptocurrency',
+            component: () =>import('@/views/bitcoin/lever/CryptocurrencyTrading.vue'),
+            meta: { title: '币币交易' },
+          },
 
             {
                 path: "margin",
@@ -435,7 +441,7 @@ const routes = [
 ];
 
 const router = createRouter({
-  history: createWebHistory(process.env.BASE_URL),
+    history: createWebHashHistory(),
   routes,
 });
 

+ 7 - 7
src/views/HomeIndex.vue

@@ -60,13 +60,13 @@
       selectedImage: require("@/assets/icon/tabbar/wallet-clicked.svg"),
       text: "资产",
     },
-    {
-      key: "user",
-      path: "/userIndex",
-      image: require("@/assets/icon/tabbar/user-circle.svg"),
-      selectedImage: require("@/assets/icon/tabbar/user-circle-clicked.svg"),
-      text: "我的",
-    },
+    // {
+    //   key: "user",
+    //   path: "/userIndex",
+    //   image: require("@/assets/icon/tabbar/user-circle.svg"),
+    //   selectedImage: require("@/assets/icon/tabbar/user-circle-clicked.svg"),
+    //   text: "我的",
+    // },
   ];
 </script>
 <style lang="less" scoped>

+ 1 - 0
src/views/bitcoin/TradeFutures.vue

@@ -421,6 +421,7 @@ const onOrderConfirmed = () => {
   }
 
   .menu-right {
+    display: flex;
     img {
       margin-left: 14px;
     }

+ 4 - 4
src/views/bitcoin/TradeLayout.vue

@@ -31,11 +31,11 @@
 
         <div
           class="nav-item pf600 sys-notifi"
-          :class="isCurrent('TradeMargin') ? 'fs18 fc121212' : 'fs14 fcA8A8A8'"
-          @click="switchTab('TradeMargin')"
+          :class="isCurrent('Cryptocurrency') ? 'fs18 fc121212' : 'fs14 fcA8A8A8'"
+          @click="switchTab('Cryptocurrency')"
         >
-          杠杆
-          <div v-if="isCurrent('TradeMargin')" class="active-line"></div>
+          币币交易
+          <div v-if="isCurrent('Cryptocurrency')" class="active-line"></div>
         </div>
       </div>
     </div>

+ 6 - 3
src/views/bitcoin/components/sellOrder.vue

@@ -4,6 +4,9 @@
     const assetlessState = defineAsyncComponent(() => import("./assetlessState.vue"));
     //持有仓位组件
     const position = defineAsyncComponent(() => import("./position.vue"));
+    import { useRouter } from 'vue-router'; // 【新增】 引入路由
+
+    const router = useRouter(); // 【新增】 实例化路由
 
 
 
@@ -43,7 +46,7 @@
     const containerRef = ref(null)
     // 当前选中的 Tab,默认选中 index 1 (当前委托)
     const currentTab = ref(0);
-    const tabs = ['持有仓位(0)', '当前委托(2)'];
+    const tabs = ['持有仓位(2)', '当前委托(2)'];
     const TabActive = (index) => {
         currentTab.value = index
     }
@@ -77,11 +80,11 @@
       </div>
 
       <div class="tabs-right">
-        <span class="history-icon" style="align-content: center; display: flex; gap: 5px; align-items: center">
+        <span @click="router.push('/historyIndex')" class="history-icon" style="align-content: center; display: flex; gap: 5px; align-items: center">
           <img src="../../../assets/icon/bitcoin/shizhong.svg" alt=""> 全部</span>
       </div>
     </div>
-    <div v-if="currentTab===8">
+    <div v-if="currentTab===0">
       <position ></position>
     </div>
 

+ 12 - 0
src/views/bitcoin/lever/CryptocurrencyTrading.vue

@@ -0,0 +1,12 @@
+<script setup>
+
+</script>
+
+<template>
+  <div style="margin-top: 60px">开发中</div>
+
+</template>
+
+<style scoped lang="less">
+
+</style>

+ 1 - 1
src/views/bitcoin/lever/TradeSeconds.vue

@@ -65,7 +65,7 @@
       <div id="k-line-chart" class="kline-container"></div>
     </div>
     <ChooseThisCycle></ChooseThisCycle>
-    <sellOrder  ></sellOrder>
+    <sellOrder></sellOrder>
 <!--    <MarketPriceAndPlan></MarketPriceAndPlan>-->
   </div>
 </template>

+ 78 - 59
src/views/bitcoin/lever/components/MarketPriceAndPlan.vue

@@ -60,7 +60,7 @@
       >
         {{ amt }}
       </div>
-      <div class="tag-btn light">更多</div>
+      <div class="tag-btn light" @click="showModal=true">更多</div>
     </div>
 
     <div class="balance-row">
@@ -78,55 +78,55 @@
       </button>
     </div>
 
-    <sellOrder v-bind:tradeType="'plan'" ></sellOrder>
-
     <div >
       <assetlessState v-if="isassetlessState"></assetlessState>
     </div>
 
-    <div v-if="showModal" class="modal-mask" @click="showModal = false">
-      <div class="modal-content" @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
-            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>
-    </div>
+<!--    <div v-if="showModal" class="modal-mask" @click="showModal = false">-->
+<!--      <div class="modal-content" @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-->
+<!--            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>-->
+<!--    </div>-->
+    <tangb v-model:visible="showModal"></tangb>
   </div>
 </template>
 
 <script setup>
 import {ref, computed, defineAsyncComponent} from 'vue'
+const tangb = defineAsyncComponent(() => import("@/views/bitcoin/lever/components/tangb.vue"));
 
 const assetlessState = defineAsyncComponent(() => import("@/views/bitcoin/components/assetlessState.vue"));
 
@@ -345,7 +345,7 @@ const sliderStyle = computed(() => {
 .sub-text { font-size: 10px; opacity: 0.9; font-weight: 400; }
 
 /* Modal 弹窗 */
-.modal-mask {
+/*.modal-mask {
   position: fixed;
   top: 0;
   left: 0;
@@ -366,12 +366,12 @@ const sliderStyle = computed(() => {
   padding: 10px 20px 30px;
   box-sizing: border-box;
   animation: slideUp 0.3s ease-out;
-}
+}*/
 @keyframes slideUp {
   from { transform: translateY(100%); }
   to { transform: translateY(0); }
 }
-.drag-handle {
+/*.drag-handle {
   width: 40px;
   height: 4px;
   background-color: #E0E0E0;
@@ -384,7 +384,7 @@ const sliderStyle = computed(() => {
   margin-bottom: 20px;
 }
 
-/* 步进器 */
+!* 步进器 *!
 .stepper-box {
   display: flex;
   align-items: center;
@@ -410,19 +410,27 @@ const sliderStyle = computed(() => {
   color: #333;
 }
 
-/* Slider 容器与样式 */
+!* Slider 容器与样式 *!
 .slider-container {
   position: relative;
-  margin-bottom: 40px;
+  !* 这里的 margin-bottom 要足够大,给绝对定位的文字留出空间 *!
+  margin-bottom: 50px;
   padding: 0 12px;
-}
+  height: 24px; !* 固定高度,等于滑块高度 *!
+  display: flex;
+  align-items: center; !* 保持滑块和圆圈垂直居中 *!
+}*/
 
 /* 起始点的固定圆圈 */
+/*
 .start-marker {
-  position: absolute;
-  left: 12px;
-  top: 29%;
+ position: absolute;
+  left: 12px; !* 这个值要和 slider-container 的 padding-left 一致,或者根据 input 的 margin 调整 *!
+
+  !* 🔥🔥 修改点:垂直居中 🔥🔥 *!
+  top: 50%;
   transform: translateY(-50%);
+
   width: 24px;
   height: 24px;
   background: #fff;
@@ -430,10 +438,12 @@ const sliderStyle = computed(() => {
   border-radius: 50%;
   z-index: 10;
   box-sizing: border-box;
+  pointer-events: none; !* 防止它挡住 input 的点击 *!
 }
+*/
 
-.custom-range {
-  -webkit-appearance: none;
+/*.custom-range {
+   -webkit-appearance: none;
   width: 100%;
   height: 6px;
   border-radius: 3px;
@@ -441,6 +451,7 @@ const sliderStyle = computed(() => {
   position: relative;
   z-index: 2;
   background: transparent;
+  margin: 0; !* 去掉默认 margin *!
 }
 
 .custom-range::-webkit-slider-thumb {
@@ -454,22 +465,30 @@ const sliderStyle = computed(() => {
   box-shadow: 0 2px 6px rgba(0,0,0,0.15);
   margin-top: -1px;
   box-sizing: border-box;
-}
+}*/
 
 /* 刻度文字 */
-.slider-marks {
+/*.slider-marks {
+  position: absolute;
+  !* 让文字跑到容器底部下方 *!
+  top: 30px;
+  left: 0;
+  width: 100%;
+  !* 这里的 padding 必须和 container 一致,保证文字和滑块两端对齐 *!
+  padding: 0 12px;
+  box-sizing: border-box;
+
   display: flex;
   justify-content: space-between;
-  margin-top: 10px;
   color: #999;
   font-size: 12px;
-}
+}*/
 .slider-marks span.active {
   color: #333;
   font-weight: 600;
 }
 
-.confirm-btn {
+/*.confirm-btn {
   width: 100%;
   height: 48px;
   background-color: #F6465D;
@@ -479,5 +498,5 @@ const sliderStyle = computed(() => {
   font-size: 16px;
   font-weight: 600;
   cursor: pointer;
-}
+}*/
 </style>

+ 227 - 0
src/views/bitcoin/lever/components/tangb.vue

@@ -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>

+ 3 - 3
src/views/user/Index.vue

@@ -348,7 +348,7 @@ const progressSegments = computed(() => {
       //margin-top: 20px;
       width: 345px ;
       height: 68px;
-      padding-left: 15px;
+      //padding-left: 15px;
     }
 
     .user-func {
@@ -360,7 +360,7 @@ const progressSegments = computed(() => {
         justify-content: space-between;
         align-items: center;
         margin-top: 26.5px;
-        width: 355px;
+        width: 345px;
         height: 24px;
 
         &:first-child {
@@ -422,7 +422,7 @@ const progressSegments = computed(() => {
 .content { width:100%;padding: 15px; }
 
 .user-card {
-  background: #fff; border-radius: 16px; padding: 0 0px 0 15px;
+  background: #fff; border-radius: 16px; padding: 0 15px;
   display: flex; justify-content: space-between; align-items: center;
   //box-shadow: 0 4px 12px rgba(0,0,0,0.03);
 }

+ 48 - 4
vue.config.js

@@ -3,10 +3,54 @@ const { defineConfig } = require('@vue/cli-service')
 
 module.exports = defineConfig({
   transpileDependencies: true,
-  // 这里不要留 configureWebpack 了,删干净
-    publicPath: './',
 
-  // 生产环境是否生成 sourceMap 文件
-  // 设置为 false 可以加速打包,且避免源码泄露
+  // 1. 基础路径 (解决白屏问题)
+  publicPath: './',
+
+  // 2. 关闭 SourceMap (最简单粗暴的减体积)
+  // 生产环境不生成 .map 文件,体积能减少 50% 以上,且防源码泄露
   productionSourceMap: false,
+
+  // 3. Webpack 核心优化 (代码分割)
+  configureWebpack: config => {
+    // 只有在打包生产环境 (npm run build) 时才运行优化
+    if (process.env.NODE_ENV === 'production') {
+
+      // 开启代码分割 (SplitChunks)
+      config.optimization.splitChunks = {
+        chunks: 'all', // 对所有代码进行分割 (无论是异步还是同步)
+
+        // 缓存组配置:决定什么代码打包到什么文件里
+        cacheGroups: {
+          // 组1: 第三方库 (node_modules)
+          // 作用:把所有 node_modules 里的东西拆出来
+          libs: {
+            name: 'chunk-libs',
+            test: /[\\/]node_modules[\\/]/,
+            priority: 10,
+            chunks: 'initial' // 只打包初始依赖
+          },
+
+          // 组2: Vant UI 组件库 (单独拆分)
+          // 作用:Vant 体积较大,单独打包有利于缓存
+          vant: {
+            name: 'chunk-vant',
+            priority: 20, // 优先级更高,会先被提取
+            test: /[\\/]node_modules[\\/]_?vant(.*)/
+          },
+
+          // 组3: 公共代码
+          // 作用:如果你的多个页面都引用了同一个组件,提取出来公用
+          commons: {
+            name: 'chunk-commons',
+            test: /[\\/]src[\\/]components[\\/]/, // 匹配 src/components 下的组件
+            minChunks: 2, // 只要被引用2次及以上就提取
+            priority: 5,
+            reuseExistingChunk: true
+          }
+        }
+      };
+    }
+  },
+
 })