Procházet zdrojové kódy

Merge branch 'web3_transection'

Hexinkui před 1 měsícem
rodič
revize
89440154b8

+ 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": {
@@ -1395,6 +1397,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",
@@ -5215,10 +5224,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"
       }
@@ -6165,6 +6175,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);
 

+ 18 - 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";
@@ -107,6 +107,12 @@ const routes = [
             component: TradeOptions,
             meta: { title: "期权" },
           },
+          {
+            path: 'CryptocurrencyTrading',
+            name: 'Cryptocurrency',
+            component: () =>import('@/views/bitcoin/lever/CryptocurrencyTrading.vue'),
+            meta: { title: '币币交易' },
+          },
 
           {
             path: "margin",
@@ -241,6 +247,16 @@ const routes = [
     name: "SecuritySettings",
     component: () => import("@/views/user/SecuritySettings.vue"), // 核心页面
   },
+  {
+    path: '/set-password',
+    name: 'SetPassword',
+    component: () => import('@/views/user/SetPassword.vue') // 新页面
+  },
+  {
+    path: '/delete-account',
+    name: 'DeleteAccount',
+    component: () => import('@/views/user/DeleteAccount.vue') // 新页面
+  },
   {
     // 地址认证:复用同一个组件,通过 query 参数 type 来区分是 "home" 还是 "work"
     path: "/auth/address",
@@ -554,7 +570,7 @@ const routes = [
 ];
 
 const router = createRouter({
-  history: createWebHistory(process.env.BASE_URL),
+    history: createWebHashHistory(),
   routes,
 });
 

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

+ 132 - 31
src/views/user/DeleteAccount.vue

@@ -1,37 +1,138 @@
 <template>
-  <!-- 注销账号 -->
-  <HeaderNav headerText="注销账号"></HeaderNav>
-  <div class="delete-account pf400 fs14 fc666666">
-    注销账户后,您可能需要考虑以下几点:<br />
-    数据无法恢复:一旦注销账户,您在该账户中的所有数据可能会永久丢失。因此,请务必在注销前做好数据备份。<br />
-    影响其他服务:有些账户可能与其他服务相关联,例如通过社交媒体账户登录的应用。如果您注销了这些账户,可能会影响您使用的其他服务。<br />
-    等待期:一些平台在您申请注销后,可能会设置一个等待期。在此期间,您仍然可以取消注销请求,恢复账户。<br />
-    隐私设置:即使您注销了某些账户,您在其他平台上的信息仍然可能被保留。定期检查您的隐私设置,确保您的信息安全。<br />
-    注销账户是保护个人隐私和安全的重要步骤。在决定注销之前,确保备份所有重要的数据,了解注销的具体步骤和后果。每个平台的注销流程可能有所不同,因此在操作时请务必仔细阅读相关说明。<br />
+  <div class="page-container">
+    <!-- 导航栏 -->
+    <div class="nav-bar">
+      <div class="nav-left" @click="$router.back()">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
+          <path d="M15 18L9 12L15 6" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
+      </div>
+      <div class="nav-title">注销账号</div>
+      <div class="nav-right"></div>
+    </div>
+
+    <div class="content">
+      <p class="intro-text">注销账户后,您可能需要考虑以下几点:</p>
+
+      <div class="text-block">
+        <span class="block-title">数据无法恢复:</span>
+        一旦注销账户,您在该账户中的所有数据可能会永久丢失。因此,请务必在注销前做好数据备份。
+      </div>
+
+      <div class="text-block">
+        <span class="block-title">影响其他服务:</span>
+        有些账户可能与其他服务相关联,例如通过社交媒体账户登录的应用。如果您注销了这些账户,可能会影响您使用的其他服务。
+      </div>
+
+      <div class="text-block">
+        <span class="block-title">等待期:</span>
+        一些平台在您申请注销后,可能会设置一个等待期。在此期间,您仍然可以取消注销请求,恢复账户。
+      </div>
+
+      <div class="text-block">
+        <span class="block-title">隐私设置:</span>
+        即使您注销了某些账户,您在其他平台上的信息仍然可能被保留。定期检查您的隐私设置,确保您的信息安全。
+      </div>
+
+      <div class="text-block summary-text">
+        注销账户是保护个人隐私和安全的重要步骤。在决定注销之前,确保备份所有重要的数据,了解注销的具体步骤和后果。每个平台的注销流程可能有所不同,因此在操作时请务必仔细阅读相关说明。
+      </div>
+    </div>
+
+    <!-- 底部按钮 -->
+    <div class="footer-action">
+      <button class="delete-btn" @click="handleDelete">注销</button>
+    </div>
   </div>
-  <div class="submit pf600 fs14 fcFFFFFF">注销</div>
 </template>
+
 <script setup>
-  import HeaderNav from "../index/components/HeaderNav.vue";
-</script>
-<style lang="less" scoped>
-  .delete-account {
-    margin: 0 auto;
-    margin-top: 48px;
-    width: 345px;
-    line-height: 24px;
-    letter-spacing: 0.2px;
-  }
+import { useRouter } from 'vue-router';
+// import { showConfirmDialog, showToast } from 'vant'; // 如果有 Vant
+
+const router = useRouter();
 
-  .submit {
-    margin: 0 auto;
-    margin-top: 235px;
-    width: 311px;
-    height: 40px;
-    line-height: 40px;
-    text-align: center;
-    border-radius: 100px;
-    background: #df384c;
-    letter-spacing: 0.2px;
+const handleDelete = () => {
+  // 模拟确认弹窗
+  const isConfirmed = confirm('确定要注销账号吗?此操作无法撤销。');
+  if (isConfirmed) {
+    console.log('执行注销逻辑...');
+    alert('申请已提交');
+    router.push('/');
   }
-</style>
+};
+</script>
+
+<style scoped>
+.page-container {
+  min-height: 100vh;
+  background-color: #fff;
+  display: flex; flex-direction: column;
+}
+
+/* 导航栏 */
+.nav-bar {
+  display: flex; justify-content: space-between; align-items: center;
+  height: 44px; padding: 0 16px; background: #fff;
+  position: sticky; top: 0; z-index: 10;
+}
+.nav-title { font-size: 18px; font-weight: 600; color: #333; }
+.nav-left, .nav-right { width: 40px; display: flex; align-items: center; }
+
+/* 内容区域 */
+.content {
+  padding: 16px 20px 100px; /* 底部留出按钮空间 */
+  flex: 1;
+  overflow-y: auto;
+}
+
+.intro-text {
+  font-size: 14px; color: #666;
+  margin-bottom: 20px;
+  line-height: 1.6;
+}
+
+.text-block {
+  font-size: 14px;
+  color: #666;
+  line-height: 1.8; /* 较大的行高,增加可读性 */
+  margin-bottom: 20px;
+  text-align: justify; /* 两端对齐,使大段文字边缘整齐 */
+}
+
+/* 重点文字加深颜色 */
+.block-title {
+  color: #333;
+  font-weight: 500;
+}
+
+/* 底部总结段落 */
+.summary-text {
+  margin-top: 30px;
+}
+
+/* 底部固定按钮区 */
+.footer-action {
+  position: fixed;
+  bottom: 0; left: 0; right: 0;
+  padding: 10px 20px 30px; /* 适配 iPhone 底部安全区 */
+  background: #fff;
+  /* 可选:加个上边框或阴影让层次更分明 */
+  /* border-top: 1px solid #eee; */
+}
+
+.delete-btn {
+  width: 100%;
+  height: 48px;
+  background: #E02F44; /* 警示红 */
+  color: #fff;
+  border: none;
+  border-radius: 24px;
+  font-size: 16px;
+  font-weight: 600;
+  cursor: pointer;
+}
+
+.delete-btn:active {
+  opacity: 0.9;
+}
+</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);
 }

+ 146 - 0
src/views/user/LockTimeModal.vue

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

+ 29 - 4
src/views/user/SecuritySettings.vue

@@ -88,7 +88,7 @@
         <div class="list-item" @click="handleSetting('payment')">
           <span class="item-label">支付密码</span>
           <div class="item-right">
-             <span class="item-value">设置</span>
+             <span class="item-value" @click="router.push({name: 'SetPassword'})">设置</span>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M9 18L15 12L9 6" stroke="#999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
           </div>
         </div>
@@ -125,7 +125,7 @@
         <div class="list-item" @click="handleSetting('appLock')">
           <span class="item-label">APP锁定</span>
           <div class="item-right">
-             <span class="item-value">5分钟</span>
+             <span class="item-value">{{ lockTime }}</span>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M9 18L15 12L9 6" stroke="#999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
           </div>
         </div>
@@ -133,6 +133,14 @@
       </div>
 
     </div>
+     <!-- 引入弹窗组件 -->
+    <!-- currentValue: 传入当前值,保证弹窗打开时回显正确 -->
+    <!-- @confirm: 接收子组件选中的新值 -->
+    <LockTimeModal
+      v-model:visible="showModal"
+      :currentValue="lockTime"
+      @confirm="updateLockTime"
+    />
   </div>
 </template>
 
@@ -142,6 +150,21 @@ import { useRouter } from 'vue-router';
 
 const router = useRouter();
 
+import { ref } from 'vue';
+import LockTimeModal from '@/views/user/LockTimeModal.vue';
+//锁定APP
+const showModal = ref(false);
+
+// 核心状态:当前锁定的时间,默认 "5分钟"
+const lockTime = ref('5分钟');
+
+// 处理子组件传回来的值
+const updateLockTime = (newValue) => {
+  lockTime.value = newValue;
+  console.log('用户选择了:', newValue);
+  // 这里可以调用 API 保存设置
+};
+
 // 页面状态
 const settings = reactive({
   hideWallet: true,
@@ -155,8 +178,10 @@ const handleCardClick = (type) => {
 
 const handleSetting = (type) => {
   console.log('点击设置:', type);
-  if (type === 'payment') {
-    // router.push('/security/payment');
+  if (type === 'logout') {
+    router.push({name: 'DeleteAccount'});
+  }else if(type === 'appLock') {
+    showModal.value = true
   }
 };
 </script>

+ 164 - 0
src/views/user/SetPassword.vue

@@ -0,0 +1,164 @@
+<template>
+  <div class="page-container">
+    <!-- 导航栏 -->
+    <div class="nav-bar">
+      <div class="nav-left" @click="$router.back()">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M15 18L9 12L15 6" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
+      </div>
+      <div class="nav-title" >设置密码</div>
+      <div class="nav-right"></div>
+    </div>
+
+    <div class="content">
+
+      <!-- 表单区域 -->
+      <div class="form-group">
+        <label class="form-label">填写支付密码</label>
+        <div class="input-wrapper">
+          <input
+            type="password"
+            v-model="password"
+            placeholder="请输入"
+            class="custom-input"
+            maxlength="6"
+          />
+        </div>
+      </div>
+
+      <div class="form-group">
+        <label class="form-label">请确认支付密码</label>
+        <!-- 这里的 class 用于演示截图中的选中状态效果,实际使用中浏览器自带 focus 样式 -->
+        <div class="input-wrapper">
+          <input
+            type="password"
+            v-model="confirmPassword"
+            placeholder="请输入"
+            class="custom-input"
+            maxlength="6"
+          />
+        </div>
+      </div>
+
+      <!-- 提交按钮 -->
+      <div class="footer-action">
+        <button class="submit-btn" @click="handleSubmit">提交</button>
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+// 引入 Vant Toast (如果你的项目有安装 Vant,没有则用 alert)
+// import { showToast } from 'vant';
+
+const router = useRouter();
+
+const password = ref('');
+const confirmPassword = ref('');
+
+const handleSubmit = () => {
+  if (!password.value || !confirmPassword.value) {
+    alert('请输入密码');
+    return;
+  }
+  if (password.value !== confirmPassword.value) {
+    alert('两次输入的密码不一致');
+    return;
+  }
+  if (password.value.length < 6) {
+    alert('密码长度不能少于6位');
+    return;
+  }
+
+  // 提交逻辑...
+  console.log('提交密码:', password.value);
+  alert('设置成功');
+  router.back();
+};
+</script>
+
+<style scoped>
+.page-container {
+  min-height: 100vh;
+  background-color: #fff;
+  display: flex; flex-direction: column;
+}
+
+/* 导航栏 */
+.nav-bar {
+  display: flex; justify-content: space-between; align-items: center;
+  height: 44px; padding: 0 16px; background: #fff;
+  position: sticky; top: 0; z-index: 10;
+}
+.nav-title { font-size: 18px; font-weight: 600; color: #333; }
+.nav-left, .nav-right { width: 40px; display: flex; align-items: center; }
+
+/* 内容区 */
+.content {
+  padding: 20px;
+  flex: 1;
+  display: flex; flex-direction: column;
+}
+
+.form-group {
+  margin-bottom: 24px;
+}
+
+.form-label {
+  display: block;
+  font-size: 16px;
+  color: #333;
+  font-weight: 500;
+  margin-bottom: 12px;
+}
+
+/* 输入框样式 */
+.custom-input {
+  width: 100%;
+  height: 50px;
+  background: #F7F8FA; /* 截图中的浅灰背景 */
+  border: 1px solid transparent; /* 默认无边框 */
+  border-radius: 8px;
+  padding: 0 16px;
+  font-size: 16px;
+  color: #333;
+  box-sizing: border-box;
+  outline: none; /* 去掉浏览器默认的黑框 */
+  transition: all 0.2s;
+}
+
+/* 占位符颜色 */
+.custom-input::placeholder {
+  color: #C2C4CC;
+}
+
+/* 核心细节:输入框聚焦时变蓝 */
+.custom-input:focus {
+  border-color: #2B6BFF; /* 截图中的亮蓝色边框 */
+  background: #fff;      /* 聚焦时背景可能变白,视设计而定,这里保留灰底或变白均可 */
+}
+
+/* 底部按钮区域 */
+.footer-action {
+  margin-top: auto; /* 把按钮推到最底部 */
+  padding-bottom: 30px;
+}
+
+.submit-btn {
+  width: 100%;
+  height: 48px;
+  background: #E02F44; /* 截图中的红色 */
+  color: #fff;
+  border: none;
+  border-radius: 24px;
+  font-size: 16px;
+  font-weight: 600;
+  cursor: pointer;
+}
+.submit-btn:active {
+  opacity: 0.9;
+}
+</style>

+ 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
+          }
+        }
+      };
+    }
+  },
+
 })