Browse Source

feat: 优化多端响应式布局与关键页面体验

统一列表表格横向滚动与查询表单响应式,修复首页首屏动效与订单详情步骤展示;时间选择器使用 body 弹层;改进登录、个人中心、乐跑账号页及后台布局的移动端交互。

Co-authored-by: Cursor <cursoragent@cursor.com>
Pchen. 3 weeks ago
parent
commit
eb73d3adb5
39 changed files with 1949 additions and 644 deletions
  1. 23 0
      src/components/BindBot/bindBot.vue
  2. 143 173
      src/components/CanvasBackend/index.vue
  3. 6 8
      src/components/Footer/index.vue
  4. 17 4
      src/components/Header/index.vue
  5. 53 2
      src/components/LepaoAccountCard/accountDetailCard.vue
  6. 57 4
      src/components/Navbar/index.vue
  7. 130 3
      src/layout/default-layout.vue
  8. 24 29
      src/pages/Login/Login.vue
  9. 3 48
      src/pages/Login/components/container.vue
  10. 7 47
      src/pages/Login/components/login.vue
  11. 3 42
      src/pages/Login/components/register.vue
  12. 5 23
      src/pages/Login/uniLogin/uniLogin.vue
  13. 110 95
      src/pages/Main/Main.vue
  14. 78 17
      src/pages/Main/components/center.vue
  15. 60 21
      src/pages/Main/components/section2.vue
  16. 24 1
      src/pages/User/info/components/user-info-header.vue
  17. 9 11
      src/pages/User/info/index.vue
  18. 37 2
      src/pages/User/setting/components/basic-information.vue
  19. 30 1
      src/pages/User/setting/components/security-settings.vue
  20. 22 1
      src/pages/User/setting/components/social-bindings.vue
  21. 52 5
      src/pages/User/setting/components/user-panel.vue
  22. 27 5
      src/pages/User/setting/index.vue
  23. 61 8
      src/pages/admin/goods/orderDetail.vue
  24. 41 9
      src/pages/admin/lepaoAccount/accountList.vue
  25. 40 7
      src/pages/admin/lepaoBindAudit/index.vue
  26. 2 1
      src/pages/admin/lepaoCountLedger/index.vue
  27. 40 8
      src/pages/admin/lepaoRecords/lepaoRecords.vue
  28. 34 3
      src/pages/admin/lepaoRecords/recordDetail.vue
  29. 40 7
      src/pages/admin/user/userList.vue
  30. 57 11
      src/pages/admin/workOrder/orderList.vue
  31. 97 13
      src/pages/lepao/accountList/index.vue
  32. 2 1
      src/pages/lepao/countLedger/index.vue
  33. 39 7
      src/pages/lepao/lepaoRecords/index.vue
  34. 34 3
      src/pages/lepao/lepaoRecords/recordDetail.vue
  35. 25 0
      src/pages/store/goodsDetail/index.vue
  36. 69 5
      src/pages/store/orders/orderDetail/index.vue
  37. 165 3
      src/style.css
  38. 202 0
      src/styles/auth-marketing.less
  39. 81 16
      src/styles/store-theme.less

+ 23 - 0
src/components/BindBot/bindBot.vue

@@ -89,4 +89,27 @@ defineExpose({ openModal })
         }
     }
 }
+
+@media (max-width: 768px) {
+    .container {
+        flex-direction: column;
+        align-items: stretch;
+        gap: 12px;
+    }
+
+    .container .left {
+        display: flex;
+        justify-content: center;
+    }
+
+    .container .line {
+        width: 100%;
+        height: 1px;
+    }
+
+    .container .right .faceCode .value {
+        font-size: 1.75em;
+        word-break: break-all;
+    }
+}
 </style>

+ 143 - 173
src/components/CanvasBackend/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="canvas-backend" aria-hidden="true">
     <canvas ref="webgl" class="webgl"></canvas>
   </div>
 </template>
@@ -8,226 +8,196 @@
 import * as THREE from 'three'
 
 export default {
-  name: "WebGLComponent",
+  name: 'WebGLComponent',
   data() {
     return {
-      mouseX: 0,  // 鼠标的X坐标,用于追踪鼠标移动
-      mouseY: 0   // 鼠标的Y坐标
-    };
+      mouseX: 0,
+      mouseY: 0
+    }
   },
   mounted() {
-    this.initWebGL();  // 组件加载完成后,初始化WebGL
+    this.initWebGL()
+  },
+  beforeUnmount() {
+    document.removeEventListener('mousemove', this.onDocumentMouseMove, false)
+    document.removeEventListener('touchstart', this.onDocumentTouchStart, false)
+    document.removeEventListener('touchmove', this.onDocumentTouchMove, false)
   },
   methods: {
-    // 初始化WebGL的方法
     initWebGL() {
-      const canvas = this.$refs.webgl;  // 获取canvas元素
-      canvas.width = window.innerWidth;  // 设置canvas的宽度为窗口宽度
-      canvas.height = window.innerHeight;  // 设置canvas的高度为窗口高度
+      const canvas = this.$refs.webgl
+      if (!canvas) return
+
+      canvas.width = window.innerWidth
+      canvas.height = window.innerHeight
 
-      const gl = canvas.getContext("webgl");  // 获取WebGL上下文
+      const gl = canvas.getContext('webgl')
+      if (!gl) return
 
-      // 顶点着色器源代码
       const vertexShaderSource = `
-        attribute vec4 position;  // 顶点位置
-        attribute float scale;  // 顶点大小
-        uniform mat4 modelViewMatrix;  // 视图矩阵
-        uniform mat4 projectionMatrix;  // 投影矩阵
+        attribute vec4 position;
+        attribute float scale;
+        uniform mat4 modelViewMatrix;
+        uniform mat4 projectionMatrix;
         void main() {
-          vec4 mvPosition = modelViewMatrix * position;  // 计算模型视图变换后的顶点位置
-          gl_PointSize = scale * 1.0 * (200.0 / - mvPosition.z);  // 根据深度调整点的大小
-          gl_Position = projectionMatrix * mvPosition;  // 最终的顶点位置
+          vec4 mvPosition = modelViewMatrix * position;
+          gl_PointSize = scale * 1.0 * (200.0 / - mvPosition.z);
+          gl_Position = projectionMatrix * mvPosition;
         }
-      `;
+      `
 
-      // 片段着色器源代码
       const fragShaderSource = `
         void main() {
-          if (length(gl_PointCoord - vec2(0.5, 0.5)) > 0.49) discard;  // 如果超出圆形范围,则丢弃
-          gl_FragColor = vec4(1.0, 0.51, 0.65, 1.0);  // 设置点的颜色
+          if (length(gl_PointCoord - vec2(0.5, 0.5)) > 0.49) discard;
+          gl_FragColor = vec4(0.32, 0.72, 0.47, 0.35);
         }
-      `;
+      `
 
-      // 初始化着色器程序
-      const program = this.initShader(gl, vertexShaderSource, fragShaderSource);
+      const program = this.initShader(gl, vertexShaderSource, fragShaderSource)
 
-      // 获取着色器中属性和uniform的位置
-      const aposLocation = gl.getAttribLocation(program, 'position');
-      const scale = gl.getAttribLocation(program, 'scale');
-      const modelViewMatrixLoc = gl.getUniformLocation(program, 'modelViewMatrix');
-      const projectionMatrixLoc = gl.getUniformLocation(program, 'projectionMatrix');
+      const aposLocation = gl.getAttribLocation(program, 'position')
+      const scale = gl.getAttribLocation(program, 'scale')
+      const modelViewMatrixLoc = gl.getUniformLocation(program, 'modelViewMatrix')
+      const projectionMatrixLoc = gl.getUniformLocation(program, 'projectionMatrix')
 
-      // 粒子系统的参数
-      const SEPARATION = 100, AMOUNTX = 50, AMOUNTY = 50;
-      const numParticles = AMOUNTX * AMOUNTY;
+      const SEPARATION = 100
+      const AMOUNTX = 50
+      const AMOUNTY = 50
 
-      // 存储粒子的位置和大小
-      const positions = new Float32Array(numParticles * 3);
-      const scales = new Float32Array(numParticles);
+      const positions = new Float32Array(AMOUNTX * AMOUNTY * 3)
+      const scales = new Float32Array(AMOUNTX * AMOUNTY)
 
-      let i = 0, j = 0;
-      // 初始化粒子的位置和大小
+      let i = 0
+      let j = 0
       for (let ix = 0; ix < AMOUNTX; ix++) {
         for (let iy = 0; iy < AMOUNTY; iy++) {
-          positions[i] = ix * SEPARATION - ((AMOUNTX * SEPARATION) / 2); // x
-          positions[i + 1] = 0; // y
-          positions[i + 2] = iy * SEPARATION - ((AMOUNTY * SEPARATION) / 2); // z
-          scales[j] = 1;  // 默认大小
-          i += 3;
-          j++;
+          positions[i] = ix * SEPARATION - (AMOUNTX * SEPARATION) / 2
+          positions[i + 1] = 0
+          positions[i + 2] = iy * SEPARATION - (AMOUNTY * SEPARATION) / 2
+          scales[j] = 1
+          i += 3
+          j++
         }
       }
 
-      // 创建颜色缓冲区
-      const colorBuffer = gl.createBuffer();
-      gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
-      gl.bufferData(gl.ARRAY_BUFFER, scales, gl.STATIC_DRAW);
-      gl.vertexAttribPointer(scale, 1, gl.FLOAT, false, 0, 0);
-      gl.enableVertexAttribArray(scale);
-
-      // 创建位置缓冲区
-      const buffer = gl.createBuffer();
-      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
-      gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
-      gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0);
-      gl.enableVertexAttribArray(aposLocation);
-
-      // 开启深度测试
-      gl.enable(gl.DEPTH_TEST);
-
-      // 初始化相机
-      const width = window.innerWidth;
-      const height = window.innerHeight;
-      const camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000);
-      camera.position.set(944, 206, -262);  // 设置相机位置
-      camera.lookAt(new THREE.Vector3(0, 0, 0));  // 设置相机朝向
-      camera.updateProjectionMatrix();  // 更新相机的投影矩阵
-      camera.updateMatrixWorld(true);  // 更新相机的世界矩阵
-
-      // 将相机的投影矩阵传递给WebGL
-      const mat4 = new THREE.Matrix4();
-      mat4.copy(camera.projectionMatrix);
-      const mxArr = new Float32Array(mat4.elements);
-      gl.uniformMatrix4fv(projectionMatrixLoc, false, mxArr);
-
-      // 将相机的反向矩阵传递给WebGL
-      const mat4y = new THREE.Matrix4();
-      mat4y.copy(camera.matrixWorldInverse);
-      const myArr = new Float32Array(mat4y.elements);
-      gl.uniformMatrix4fv(modelViewMatrixLoc, false, myArr);
-
-      let count = 0;
-
-      // 动画绘制函数
+      const colorBuffer = gl.createBuffer()
+      gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
+      gl.bufferData(gl.ARRAY_BUFFER, scales, gl.STATIC_DRAW)
+      gl.vertexAttribPointer(scale, 1, gl.FLOAT, false, 0, 0)
+      gl.enableVertexAttribArray(scale)
+
+      const buffer = gl.createBuffer()
+      gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
+      gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)
+      gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
+      gl.enableVertexAttribArray(aposLocation)
+
+      gl.enable(gl.DEPTH_TEST)
+
+      const width = window.innerWidth
+      const height = window.innerHeight
+      const camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000)
+      camera.position.set(944, 206, -262)
+      camera.lookAt(new THREE.Vector3(0, 0, 0))
+      camera.updateProjectionMatrix()
+      camera.updateMatrixWorld(true)
+
+      const mat4 = new THREE.Matrix4()
+      mat4.copy(camera.projectionMatrix)
+      gl.uniformMatrix4fv(projectionMatrixLoc, false, new Float32Array(mat4.elements))
+
+      const mat4y = new THREE.Matrix4()
+      mat4y.copy(camera.matrixWorldInverse)
+      gl.uniformMatrix4fv(modelViewMatrixLoc, false, new Float32Array(mat4y.elements))
+
+      let count = 0
+      let rafId = null
+
       const draw = () => {
-        // 根据鼠标位置更新相机的位置
-        camera.position.x += (this.mouseX - camera.position.x) * 0.01;
-        camera.updateMatrixWorld(true);  // 更新相机世界矩阵
-        mat4y.copy(camera.matrixWorldInverse);  // 获取更新后的相机反向矩阵
-        const myArr = new Float32Array(mat4y.elements);
-        gl.uniformMatrix4fv(modelViewMatrixLoc, false, myArr);  // 传递新的矩阵给WebGL
-
-        // 更新粒子的位置和大小
-        let i = 0, j = 0;
+        camera.position.x += (this.mouseX - camera.position.x) * 0.01
+        camera.updateMatrixWorld(true)
+        mat4y.copy(camera.matrixWorldInverse)
+        gl.uniformMatrix4fv(modelViewMatrixLoc, false, new Float32Array(mat4y.elements))
+
+        let pi = 0
+        let pj = 0
         for (let ix = 0; ix < AMOUNTX; ix++) {
           for (let iy = 0; iy < AMOUNTY; iy++) {
-            positions[i + 1] = (Math.sin((ix + count) * 0.3) * 50) + (Math.sin((iy + count) * 0.5) * 50);
-            scales[j] = (Math.sin((ix + count) * 0.3) + 1.3) * 8 + (Math.sin((iy + count) * 0.5) + 1.3) * 8;
-            i += 3;
-            j++;
+            positions[pi + 1] =
+              Math.sin((ix + count) * 0.3) * 50 + Math.sin((iy + count) * 0.5) * 50
+            scales[pj] =
+              (Math.sin((ix + count) * 0.3) + 1.3) * 8 + (Math.sin((iy + count) * 0.5) + 1.3) * 8
+            pi += 3
+            pj++
           }
         }
-        count += 0.1;
-
-        // 更新缓冲区的数据
-        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
-        gl.bufferData(gl.ARRAY_BUFFER, scales, gl.STATIC_DRAW);
-        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
-        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
-
-        requestAnimationFrame(draw);  // 请求下一帧绘制
-        gl.drawArrays(gl.POINTS, 0, 2500);  // 绘制粒子
-      };
-
-      draw();  // 启动绘制
-
-      // 监听鼠标和触摸事件
-      document.addEventListener('mousemove', this.onDocumentMouseMove, false);
-      document.addEventListener('touchstart', this.onDocumentTouchStart, false);
-      document.addEventListener('touchmove', this.onDocumentTouchMove, false);
-
-      // 监听窗口大小变化,更新画布和相机的投影矩阵
-      window.onresize = () => {
-        canvas.width = window.innerWidth;
-        canvas.height = window.innerHeight;
-        gl.viewport(0, 0, window.innerWidth, window.innerHeight);
-        camera.aspect = window.innerWidth / window.innerHeight;
-        camera.updateProjectionMatrix();
-        mat4.copy(camera.projectionMatrix);
-        const mxArr = new Float32Array(mat4.elements);
-        gl.uniformMatrix4fv(projectionMatrixLoc, false, mxArr);
-      };
+        count += 0.1
+
+        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
+        gl.bufferData(gl.ARRAY_BUFFER, scales, gl.STATIC_DRAW)
+        gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
+        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)
+
+        rafId = requestAnimationFrame(draw)
+        gl.drawArrays(gl.POINTS, 0, AMOUNTX * AMOUNTY)
+      }
+
+      draw()
+
+      this._onResize = () => {
+        canvas.width = window.innerWidth
+        canvas.height = window.innerHeight
+        gl.viewport(0, 0, window.innerWidth, window.innerHeight)
+        camera.aspect = window.innerWidth / window.innerHeight
+        camera.updateProjectionMatrix()
+        mat4.copy(camera.projectionMatrix)
+        gl.uniformMatrix4fv(projectionMatrixLoc, false, new Float32Array(mat4.elements))
+      }
+
+      window.addEventListener('resize', this._onResize)
+      document.addEventListener('mousemove', this.onDocumentMouseMove, false)
     },
 
-    // 初始化着色器的方法
     initShader(gl, vertexShaderSource, fragmentShaderSource) {
-      const vertexShader = gl.createShader(gl.VERTEX_SHADER);
-      const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
-      gl.shaderSource(vertexShader, vertexShaderSource);
-      gl.shaderSource(fragmentShader, fragmentShaderSource);
-      gl.compileShader(vertexShader);
-      gl.compileShader(fragmentShader);
-      const program = gl.createProgram();
-      gl.attachShader(program, vertexShader);
-      gl.attachShader(program, fragmentShader);
-      gl.linkProgram(program);
-      gl.useProgram(program);
-      return program;
+      const vertexShader = gl.createShader(gl.VERTEX_SHADER)
+      const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
+      gl.shaderSource(vertexShader, vertexShaderSource)
+      gl.shaderSource(fragmentShader, fragmentShaderSource)
+      gl.compileShader(vertexShader)
+      gl.compileShader(fragmentShader)
+      const program = gl.createProgram()
+      gl.attachShader(program, vertexShader)
+      gl.attachShader(program, fragmentShader)
+      gl.linkProgram(program)
+      gl.useProgram(program)
+      return program
     },
 
-    // 鼠标移动事件处理
     onDocumentMouseMove(event) {
-      this.mouseX = event.clientX - window.innerWidth / 2;
-      this.mouseY = event.clientY - window.innerHeight / 2;
-      
+      this.mouseX = event.clientX - window.innerWidth / 2
+      this.mouseY = event.clientY - window.innerHeight / 2
     },
 
-    // 触摸开始事件处理
-    onDocumentTouchStart(event) {
-      if (event.touches.length === 1) {
-        event.preventDefault();
-        this.mouseX = event.touches[0].pageX - window.innerWidth / 2;
-        this.mouseY = event.touches[0].pageY - window.innerHeight / 2;
-      }
-    },
+    onDocumentTouchStart() {},
 
-    // 触摸移动事件处理
-    onDocumentTouchMove(event) {
-      if (event.touches.length === 1) {
-        event.preventDefault();
-        this.mouseX = event.touches[0].pageX - window.innerWidth / 2;
-        this.mouseY = event.touches[0].pageY - window.innerHeight / 2;
-      }
-    }
+    onDocumentTouchMove() {}
   }
-};
+}
 </script>
 
 <style scoped>
-body {
-  padding: 0;
-  margin: 0;
-  background-color: transparent;
-  font-family: "微软雅黑";
+.canvas-backend {
+  position: fixed;
+  inset: 0;
+  z-index: 0;
+  pointer-events: none;
   overflow: hidden;
 }
 
 .webgl {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  /* height: 100%; */
-  background-color: transparent;
+  display: block;
   width: 100%;
+  height: 100%;
+  pointer-events: none;
 }
 </style>

+ 6 - 8
src/components/Footer/index.vue

@@ -6,20 +6,18 @@
 
 <style scoped>
 .footer {
-    cursor: pointer;
-    position: absolute;
-    bottom: 15px;
-    left: 50%;
-    transform: translateX(-50%);
-    color: #777;
+    width: 100%;
+    padding: 18px 16px;
+    box-sizing: border-box;
+    text-align: center;
+    color: rgba(27, 48, 34, 0.62);
     font-size: 12px;
     display: flex;
-    min-width: 300px;
     justify-content: center;
     gap: 10px;
 }
 
 a {
-    color: #777;
+    color: rgba(27, 48, 34, 0.62);
 }
 </style>

+ 17 - 4
src/components/Header/index.vue

@@ -73,14 +73,16 @@ const logout = () => {
 getuser()
 </script>
 
-<style scoped>
+<style scoped lang="less">
+@import '@/styles/store-theme.less';
+
 .site-header {
     position: absolute;
     top: 0;
     left: 0;
     right: 0;
     z-index: 100;
-    padding: 16px 24px;
+    padding: clamp(12px, 2vh, 16px) 0;
     box-sizing: border-box;
     user-select: none;
 }
@@ -89,10 +91,11 @@ getuser()
     display: flex;
     align-items: center;
     justify-content: space-between;
-    gap: 24px;
-    max-width: 1200px;
+    gap: clamp(12px, 2vw, 24px);
     width: 100%;
+    max-width: 100%;
     margin: 0 auto;
+    padding-inline: @store-page-padding-x;
     box-sizing: border-box;
 }
 
@@ -101,11 +104,14 @@ getuser()
     align-items: center;
     cursor: pointer;
     flex-shrink: 0;
+    min-width: 0;
 }
 
 .logo .title {
     font-size: 1.5em;
     margin: 5px 8px;
+    white-space: nowrap;
+    line-height: 1;
 }
 
 .nav-actions {
@@ -140,4 +146,11 @@ getuser()
     text-overflow: ellipsis;
     white-space: nowrap;
 }
+
+@media (max-width: @app-breakpoint-lg) {
+    .logo .title {
+        font-size: 1.25em;
+        margin: 4px 6px;
+    }
+}
 </style>

+ 53 - 2
src/components/LepaoAccountCard/accountDetailCard.vue

@@ -31,7 +31,7 @@
           <a-tag :color="stateTag.color">{{ stateTag.text }}</a-tag>
         </div>
 
-        <a-descriptions :column="3" bordered size="small" class="account-desc">
+        <a-descriptions :column="isMobile ? 1 : 3" bordered size="small" class="account-desc">
           <a-descriptions-item v-if="admin" label="创建用户">{{ account.create_user || '-' }}</a-descriptions-item>
           <a-descriptions-item v-if="admin" label="人脸状态">{{ faceStateLabel }}</a-descriptions-item>
           <a-descriptions-item v-if="admin" label="手机型号">{{ account.deviceModel || '-' }}</a-descriptions-item>
@@ -166,7 +166,7 @@
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
+import { ref, computed, onMounted, onUnmounted } from 'vue'
 import { useRouter } from 'vue-router'
 import {
   lepaoRecords,
@@ -214,8 +214,23 @@ const officialSummary = ref({})
 const officialPagination = ref({ current: 1, pageSize: 10, total: 0 })
 const recordTab = ref('platform')
 const calendarYear = ref(new Date().getFullYear())
+const isMobile = ref(false)
 const router = useRouter()
 
+let mobileResizeHandler = null
+
+onMounted(() => {
+  mobileResizeHandler = () => {
+    isMobile.value = window.innerWidth <= 768
+  }
+  mobileResizeHandler()
+  window.addEventListener('resize', mobileResizeHandler)
+})
+
+onUnmounted(() => {
+  if (mobileResizeHandler) window.removeEventListener('resize', mobileResizeHandler)
+})
+
 const modalTitle = computed(() => {
   if (!account.value) return '账号详情'
   const name = account.value.name || account.value.student_num
@@ -628,4 +643,40 @@ defineExpose({ openModal })
     background-color: rgb(var(--red-6));
   }
 }
+
+@media (max-width: 768px) {
+  .account-header {
+    flex-wrap: wrap;
+    align-items: flex-start;
+    gap: 10px;
+  }
+
+  .section-title {
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+
+  .record-count {
+    width: 100%;
+  }
+
+  .account-detail-modal {
+    :deep(.arco-modal) {
+      width: calc(100vw - 16px) !important;
+      max-width: calc(100vw - 16px) !important;
+      margin: 8px auto !important;
+    }
+
+    :deep(.arco-descriptions-bordered .arco-descriptions-item) {
+      width: 100%;
+    }
+
+    :deep(.arco-descriptions-bordered .arco-descriptions-item-label),
+    :deep(.arco-descriptions-bordered .arco-descriptions-item-value) {
+      padding: 8px 10px;
+      font-size: 12px;
+      line-height: 1.45;
+    }
+  }
+}
 </style>

+ 57 - 4
src/components/Navbar/index.vue

@@ -3,14 +3,14 @@
     <div class="left-side">
       <a-space style="cursor: pointer; -webkit-app-region: no-drag;" @click="$router.push('/')">
         <img alt="RunForge" src="/logo.svg" height="35">
-        <a-typography-title :style="{ margin: 0, fontSize: '18px' }" :heading="5">
+        <a-typography-title class="brand-title" :style="{ margin: 0, fontSize: '18px' }" :heading="5">
           RunForge
         </a-typography-title>
       </a-space>
     </div>
 
     <ul class="right-side">
-      <li v-if="!isElectron()">
+      <li v-if="!isElectron()" class="desktop-shortcut">
         <a-tooltip content="首页">
           <a-button class="nav-btn" type="outline" :shape="'circle'" @click="$router.push('/')">
             <template #icon>
@@ -20,7 +20,7 @@
         </a-tooltip>
       </li>
 
-      <li v-if="!isElectron()">
+      <li v-if="!isElectron()" class="desktop-shortcut">
         <a-tooltip content="售后服务">
           <a-button class="nav-btn" type="outline" :shape="'circle'" @click="$router.push('/service/createOrder')">
             <template #icon>
@@ -30,7 +30,7 @@
         </a-tooltip>
       </li>
 
-      <li v-if="!isElectron()">
+      <li v-if="!isElectron()" class="desktop-shortcut">
         <a-tooltip content="下载专区">
           <a-button class="nav-btn" type="outline" :shape="'circle'" @click="goDownload()">
             <template #icon>
@@ -40,6 +40,27 @@
         </a-tooltip>
       </li>
 
+      <li v-if="!isElectron()" class="mobile-shortcut">
+        <a-dropdown trigger="click" position="br">
+          <a-button class="nav-btn" type="outline" :shape="'circle'">
+            <template #icon>
+              <icon-more />
+            </template>
+          </a-button>
+          <template #content>
+            <a-doption @click="$router.push('/')">
+              <icon-home /> 首页
+            </a-doption>
+            <a-doption @click="$router.push('/service/createOrder')">
+              <icon-customer-service /> 售后服务
+            </a-doption>
+            <a-doption @click="goDownload()">
+              <icon-download /> 下载专区
+            </a-doption>
+          </template>
+        </a-dropdown>
+      </li>
+
       <li v-if="avatar">
         <a-dropdown trigger="hover">
           <a-avatar class="nav-btn" :size="32" :style="{ marginRight: '8px', cursor: 'pointer' }">
@@ -147,6 +168,14 @@ function maximizeWindow() {
   display: flex;
   align-items: center;
   padding-left: 20px;
+  min-width: 0;
+}
+
+.brand-title {
+  margin: 0;
+  white-space: nowrap;
+  line-height: 1;
+  flex-shrink: 0;
 }
 
 .center-side {
@@ -200,4 +229,28 @@ function maximizeWindow() {
   background-color: rgb(var(--red-5));
   color: white;
 }
+
+@media (max-width: 992px) {
+  .brand-title {
+    font-size: 16px !important;
+  }
+
+  .right-side {
+    padding-right: 12px;
+  }
+
+  .right-side li {
+    padding: 0 4px;
+  }
+
+  .desktop-shortcut {
+    display: none !important;
+  }
+}
+
+@media (min-width: 993px) {
+  .mobile-shortcut {
+    display: none !important;
+  }
+}
 </style>

+ 130 - 3
src/layout/default-layout.vue

@@ -1,15 +1,35 @@
 <template>
   <a-layout class="layout">
     <a-layout-header class="layout-navbar">
+      <a-button
+        class="mobile-menu-trigger"
+        type="text"
+        shape="circle"
+        @click="toggleSider"
+      >
+        <template #icon>
+          <icon-menu-unfold v-if="siderCollapsed" />
+          <icon-menu-fold v-else />
+        </template>
+      </a-button>
       <Navbar />
     </a-layout-header>
-    <a-layout class="layout-body">
+    <a-layout
+      class="layout-body"
+      :class="{
+        'layout-body--mobile': isMobile,
+        'layout-body--menu-open': isMobile && !siderCollapsed
+      }"
+    >
       <a-layout-sider
         class="layout-sider"
         :width="200"
         :collapsed-width="0"
+        :collapsed="siderCollapsed"
+        :hide-trigger="true"
         collapsible
         breakpoint="lg"
+        @collapse="(val) => (siderCollapsed = val)"
       >
         <Menu class="menu-wrapper" />
       </a-layout-sider>
@@ -17,6 +37,11 @@
         <PageLayout class="page-layout-main" />
         <Footer />
       </a-layout-content>
+      <div
+        v-if="isMobile && !siderCollapsed"
+        class="mobile-menu-mask"
+        @click="siderCollapsed = true"
+      />
     </a-layout>
 
     <!-- <a-avatar class="aiButton" @click="chat = !chat">
@@ -53,6 +78,8 @@ import Footer from '@/components/Footer/index.vue'
 const chat = ref(false)
 const popupVisible = ref(false)
 const popupData = ref({})
+const siderCollapsed = ref(false)
+const isMobile = ref(false)
 
 // 按钮拖动
 let isDragging = false
@@ -93,6 +120,8 @@ onMounted(() => {
     button.addEventListener('mousedown', handleMouseDown)
   }
   eventBus.on('closeAI', () => { chat.value = false })
+  syncSiderCollapsed()
+  window.addEventListener('resize', syncSiderCollapsed)
   getPopup()
 })
 
@@ -100,6 +129,7 @@ onBeforeUnmount(() => {
   if (button) {
     button.removeEventListener('mousedown', handleMouseDown)
   }
+  window.removeEventListener('resize', syncSiderCollapsed)
 })
 
 onUnmounted(() => {
@@ -130,6 +160,20 @@ const handlePopupClose = async () => {
   } catch (_) {}
 }
 
+const syncSiderCollapsed = () => {
+  const mobile = window.innerWidth <= 992
+  isMobile.value = mobile
+  if (mobile) {
+    siderCollapsed.value = true
+  } else {
+    siderCollapsed.value = false
+  }
+}
+
+const toggleSider = () => {
+  siderCollapsed.value = !siderCollapsed.value
+}
+
 </script>
 
 <style scoped lang="less">
@@ -151,6 +195,9 @@ const handlePopupClose = async () => {
 .layout-body {
   margin-top: @nav-size-height;
   min-height: calc(100vh - @nav-size-height);
+  height: calc(100vh - @nav-size-height);
+  position: relative;
+  overflow: hidden;
 }
 
 .layout-navbar {
@@ -162,6 +209,10 @@ const handlePopupClose = async () => {
   height: @nav-size-height;
 }
 
+.mobile-menu-trigger {
+  display: none;
+}
+
 .layout-sider {
   position: sticky;
   top: @nav-size-height;
@@ -183,7 +234,8 @@ const handlePopupClose = async () => {
 
 .menu-wrapper {
   user-select: none;
-  margin-top: 60px;
+  margin-top: 0;
+  height: 100%;
   overflow: auto;
   overflow-x: hidden;
 
@@ -207,13 +259,15 @@ const handlePopupClose = async () => {
 }
 
 .layout-content {
+  display: flex;
+  flex-direction: column;
   flex: 1;
   min-width: 0;
   min-height: calc(100vh - @nav-size-height);
   overflow-x: hidden;
   overflow-y: auto;
   background-color: @store-bg;
-  transition: padding 0.2s cubic-bezier(0.34, 0.69, 0.1, 1);
+  transition: transform 0.22s ease;
 
   &::-webkit-scrollbar {
     width: 10px;
@@ -237,6 +291,35 @@ const handlePopupClose = async () => {
   }
 }
 
+@media (min-width: 993px) {
+  .layout-body {
+    display: block;
+  }
+
+  .layout-sider {
+    position: fixed;
+    left: 0;
+    top: @nav-size-height;
+    width: 200px !important;
+    height: calc(100vh - @nav-size-height);
+  }
+
+  .layout-content {
+    margin-left: 200px;
+    height: calc(100vh - @nav-size-height);
+  }
+}
+
+.mobile-menu-mask {
+  position: fixed;
+  top: @nav-size-height;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 1000;
+  background: rgba(0, 0, 0, 0.28);
+}
+
 .aiButton {
   position: fixed;
   bottom: 100px;
@@ -322,4 +405,48 @@ const handlePopupClose = async () => {
     background-color: rgba(229, 233, 237, 1);
   }
 }
+
+@media (max-width: 992px) {
+  .mobile-menu-trigger {
+    display: inline-flex;
+    position: absolute;
+    left: 8px;
+    top: 50%;
+    transform: translateY(-50%);
+    z-index: 1001;
+  }
+
+  :deep(.navbar .left-side) {
+    padding-left: 48px;
+  }
+
+  .layout-body--mobile {
+    display: block;
+  }
+
+  .layout-body--mobile .layout-sider {
+    position: fixed;
+    left: 0;
+    top: @nav-size-height;
+    z-index: 1001;
+    height: calc(100vh - @nav-size-height);
+    width: 200px !important;
+    transform: translateX(-100%);
+    transition: transform 0.22s ease;
+    box-shadow: 2px 0 16px rgba(0, 0, 0, 0.14);
+  }
+
+  .layout-body--menu-open .layout-sider {
+    transform: translateX(0);
+  }
+
+  .layout-body--mobile .layout-content {
+    min-height: calc(100vh - @nav-size-height);
+    transform: translateX(0);
+  }
+
+  .layout-body--menu-open .layout-content {
+    transform: translateX(200px);
+  }
+}
 </style>

+ 24 - 29
src/pages/Login/Login.vue

@@ -1,41 +1,36 @@
 <template>
-    <div class="root">
-        <Header v-if="!isElectron()"/>
-        <Navbar v-else style="position: relative; z-index: 999;"/>
-        <Container />
-        <CanvasBackend />
-        <Footer />
-    </div>
+  <div class="auth-page">
+    <header class="auth-topbar">
+      <router-link to="/" class="auth-topbar__brand">
+        <img src="/logo.svg" height="32" width="32" alt="RunForge" />
+        <span>RunForge</span>
+      </router-link>
+      <router-link v-if="!isElectron()" to="/" class="auth-topbar__link">返回首页</router-link>
+    </header>
+
+    <Navbar v-if="isElectron()" class="auth-page__nav" />
+
+    <main class="auth-page__main">
+      <Container />
+    </main>
+
+    <p class="auth-page__legal">© {{ year }} RunForge · 智能校园乐跑平台</p>
+  </div>
 </template>
 
 <script setup>
-import CanvasBackend from '@/components/CanvasBackend/index.vue'
-import Header from '@/components/Header/index.vue'
 import Navbar from '@/components/Navbar/index.vue'
 import Container from './components/container.vue'
-import Footer from '@/components/Footer/index.vue'
 import { isElectron } from '@/utils/electron'
-</script>
 
-<style scoped>
-.root {
-    text-align: center;
-}
+const year = new Date().getFullYear()
+</script>
 
-.center {
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    width: 800px;
-    height: 300px;
-    z-index: 2;
-    transform: translate(-50%, -50%);
-    pointer-events: auto;
-    /* center 可以接收鼠标事件 */
-}
+<style scoped lang="less">
+@import '@/styles/auth-marketing.less';
 
-.button {
-    position: absolute;
-    z-index: 101;
+.auth-page__nav {
+  position: relative;
+  z-index: 20;
 }
 </style>

+ 3 - 48
src/pages/Login/components/container.vue

@@ -1,5 +1,5 @@
 <template>
-    <div class='center'>
+    <div class="auth-card" :class="current">
         <login v-if="current === 'login'" @changeMode="changeMode" />
         <register v-else-if="current === 'register'" @changeMode="changeMode" />
         <uni-login v-else-if="current === 'uniLogin'" @changeMode="changeMode"></uni-login>
@@ -18,7 +18,6 @@ const route = useRoute()
 let current = ref('login')
 
 const changeMode = (mode) => {
-    document.querySelector('.center').className = `center ${mode}`
     current.value = mode
 }
 
@@ -32,50 +31,6 @@ onMounted(() => {
 
 </script>
 
-<style scoped>
-.center {
-    background-image: linear-gradient(to bottom,
-            rgba(235, 242, 255, 0.7),
-            rgba(210, 230, 255, 0.5),
-            rgba(200, 225, 255, 0.3),
-            rgba(245, 255, 255, 0.1));
-
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    /* background-color: rgba(190, 218, 255, 0.2); */
-    display: flex;
-    justify-content: center;
-    border-radius: 10px;
-    padding: 110px 10px;
-}
-
-@keyframes registAnimation {
-    0% {
-        padding: 140px 10px;
-    }
-
-    100% {
-        padding: 180px 10px;
-    }
-}
-
-@keyframes loginAnimation {
-    0% {
-        padding: 180px 10px;
-    }
-
-    100% {
-        padding: 140px 10px;
-    }
-}
-
-.register {
-    animation: registAnimation 0.3s ease-in-out forwards;
-}
-
-.login {
-    animation: loginAnimation 0.5s ease-in-out forwards;
-}
+<style scoped lang="less">
+@import '@/styles/auth-marketing.less';
 </style>

+ 7 - 47
src/pages/Login/components/login.vue

@@ -1,5 +1,5 @@
 <template>
-    <div class="root">
+    <div class="auth-form-panel">
         <div class="logo">
             <img alt="RunForge" src="/logo.svg" height="40">
             <span class="title">RunForge | 用户登录</span>
@@ -128,53 +128,13 @@ const requestFailed = (msg) => {
 }
 </script>
 
-<style scoped>
-.root {
-    display: flex;
-    gap: 40px;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-}
-
-.form {
-    width: 100%;
-}
-
-.formitem {
-    width: 350px;
-    min-height: 35px
-}
-
-.logo {
-    display: flex
-}
-
-.logo .title {
-    color: #FE82A5;
-    font-size: 1.8em;
-    font-weight: bold;
-    margin-left: 15px;
-    font-family: Alimama ShuHeiTi, -apple-system, BlinkMacSystemFont;
-}
+<style scoped lang="less">
+@import '@/styles/auth-marketing.less';
 
 .tip {
-    color: #777;
-    font-size: 0.9em;
-    text-align: left;
-    margin-bottom: 20px;
-    margin-left: -8px;
-}
-
-.forgetpass {
-    width: 100%;
-    margin-top: -10px;
-    display: flex;
-    justify-content: space-between;
-}
-
-.captcha {
-    max-height: 35px;
-    margin-right: -10px;
+  color: #777;
+  font-size: 0.9em;
+  text-align: left;
+  margin-bottom: 12px;
 }
 </style>

+ 3 - 42
src/pages/Login/components/register.vue

@@ -1,5 +1,5 @@
 <template>
-    <div class="root">
+    <div class="auth-form-panel">
         <div class="logo">
             <img alt="RunForge" src="/logo.svg" height="40">
             <span class="title">RunForge | 用户注册</span>
@@ -192,45 +192,6 @@ const requestFailed = (msg) => {
 }
 </script>
 
-<style scoped>
-.root {
-    display: flex;
-    gap: 40px;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    margin: 20px
-}
-
-.form {
-    width: 100%;
-}
-
-.formitem {
-    width: 350px;
-    min-height: 35px
-}
-
-.logo {
-    display: flex
-}
-
-.logo .title {
-    color: #FE82A5;
-    font-size: 1.8em;
-    font-weight: bold;
-    margin-left: 15px;
-    font-family: Alimama ShuHeiTi, -apple-system, BlinkMacSystemFont;
-}
-
-.forgetpass {
-    width: 130px;
-    margin-top: -10px;
-}
-
-.captcha {
-    max-height: 35px;
-    margin-right: -10px;
-
-}
+<style scoped lang="less">
+@import '@/styles/auth-marketing.less';
 </style>

+ 5 - 23
src/pages/Login/uniLogin/uniLogin.vue

@@ -1,5 +1,5 @@
 <template>
-    <div class="root">
+    <div class="auth-form-panel uni-login-panel">
         <div class="logo">
             <img alt="RunForge" src="/logo.svg" height="40">
             <span class="title">RunForge | 快捷登录</span>
@@ -142,6 +142,8 @@ onMounted(() => {
 </script>
 
 <style lang="less" scoped>
+@import '@/styles/auth-marketing.less';
+
 .loading {
     position: fixed;
     top: 0;
@@ -166,28 +168,8 @@ onMounted(() => {
     }
 }
 
-.root {
-    display: flex;
-    gap: 40px;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-}
-
-.form {
-    width: 100%;
-}
-
-.logo {
-    display: flex;
-
-    .title {
-        color: #FE82A5;
-        font-size: 1.8em;
-        font-weight: bold;
-        margin-left: 15px;
-        font-family: Alimama ShuHeiTi, -apple-system, BlinkMacSystemFont;
-    }
+.uni-login-panel {
+  position: relative;
 }
 
 .tip {

+ 110 - 95
src/pages/Main/Main.vue

@@ -54,13 +54,13 @@ const scrollToSection = (sectionId) => {
 
 <style lang="less" scoped>
 @import './theme.less';
+@import '@/styles/store-theme.less';
 
 .section {
     padding: 0;
     margin: 0;
     width: 100%;
-    min-height: 100vh;
-    scroll-snap-align: start;
+    min-height: 100dvh;
     position: relative;
     box-sizing: border-box;
 }
@@ -68,7 +68,7 @@ const scrollToSection = (sectionId) => {
 .hero-section {
     display: flex;
     flex-direction: column;
-    background: @home-bg-hero;
+    background: linear-gradient(165deg, #e6f8ec 0%, #d8f3e2 45%, #c5ecd5 100%);
     overflow: hidden;
 }
 
@@ -82,58 +82,50 @@ const scrollToSection = (sectionId) => {
 
 .hero-bg-gradient {
     position: absolute;
-    inset: -50%;
-    width: 200%;
-    height: 200%;
+    inset: 0;
     background: linear-gradient(
-        -45deg,
-        @home-gradient-pale 0%,
-        @home-gradient-mint 18%,
-        @home-gradient-green 38%,
-        @home-gradient-teal 55%,
-        @home-gradient-sage 72%,
-        @home-gradient-deep 88%,
-        @home-gradient-pale 100%
+        145deg,
+        fade(@home-gradient-pale, 85%) 0%,
+        fade(@home-gradient-mint, 75%) 40%,
+        fade(@home-gradient-sage, 68%) 100%
     );
-    background-size: 500% 500%;
-    animation: homeGradientFlow 10s ease-in-out infinite;
+    background-size: 180% 180%;
+    animation: heroGradientFlow 14s ease-in-out infinite alternate;
 }
 
 .hero-bg-orb {
     position: absolute;
     border-radius: 50%;
-    filter: blur(72px);
-    opacity: 0.72;
-    animation: homeOrbFloat 14s ease-in-out infinite;
+    filter: blur(56px);
+    opacity: 0.42;
+    will-change: transform, opacity;
 }
 
 .hero-bg-orb--1 {
-    width: 480px;
-    height: 480px;
-    top: -10%;
-    right: 0;
-    background: radial-gradient(circle, fade(@home-gradient-deep, 85%) 0%, fade(@home-gradient-green, 45%) 45%, transparent 72%);
-    animation-duration: 12s;
+    width: clamp(220px, 34vw, 460px);
+    height: clamp(220px, 34vw, 460px);
+    top: -12%;
+    right: -4%;
+    background: radial-gradient(circle, fade(@home-gradient-deep, 58%) 0%, fade(@home-gradient-green, 24%) 46%, transparent 76%);
+    animation: orbFloatOne 12s ease-in-out infinite;
 }
 
 .hero-bg-orb--2 {
-    width: 400px;
-    height: 400px;
+    width: clamp(220px, 30vw, 380px);
+    height: clamp(220px, 30vw, 380px);
     bottom: 5%;
-    left: -8%;
-    background: radial-gradient(circle, fade(@home-gradient-teal, 80%) 0%, fade(@home-gradient-sage, 40%) 50%, transparent 72%);
-    animation-duration: 16s;
-    animation-delay: -3s;
+    left: -10%;
+    background: radial-gradient(circle, fade(@home-gradient-teal, 54%) 0%, fade(@home-gradient-sage, 22%) 52%, transparent 76%);
+    animation: orbFloatTwo 15s ease-in-out infinite;
 }
 
 .hero-bg-orb--3 {
-    width: 320px;
-    height: 320px;
-    top: 38%;
-    left: 32%;
-    background: radial-gradient(circle, fade(@home-gradient-pale, 95%) 0%, fade(@home-gradient-mint, 50%) 40%, transparent 70%);
-    animation-duration: 18s;
-    animation-delay: -6s;
+    width: clamp(180px, 22vw, 300px);
+    height: clamp(180px, 22vw, 300px);
+    top: 34%;
+    left: 30%;
+    background: radial-gradient(circle, fade(#fff, 80%) 0%, fade(@home-gradient-mint, 22%) 45%, transparent 76%);
+    animation: orbFloatThree 11s ease-in-out infinite;
 }
 
 .hero-inner {
@@ -143,39 +135,41 @@ const scrollToSection = (sectionId) => {
     display: flex;
     align-items: center;
     justify-content: center;
-    gap: 48px;
-    max-width: 1200px;
-    width: 90%;
+    gap: clamp(18px, 4vw, 56px);
+    max-width: 1180px;
+    width: 100%;
     margin: 0 auto;
-    padding: 120px 24px 100px;
+    padding: clamp(88px, 11vh, 124px) @store-page-padding-x clamp(74px, 10vh, 104px);
     box-sizing: border-box;
 }
 
 .hero-visual {
     flex: 0 0 auto;
-    max-width: 420px;
+    max-width: min(44vw, 460px);
 }
 
 .hero-illustration {
     width: 100%;
-    max-width: 400px;
+    max-width: 420px;
     height: auto;
     object-fit: contain;
-    filter: drop-shadow(0 16px 40px rgba(27, 48, 34, 0.12));
+    filter: drop-shadow(0 16px 32px rgba(27, 48, 34, 0.12));
 }
 
 .floating-button {
     z-index: 2;
-    background-color: fade(@home-primary, 12%);
+    background-color: fade(#fff, 58%);
     color: @home-primary;
     position: absolute;
-    bottom: 50px;
+    bottom: clamp(20px, 5vh, 44px);
     left: 50%;
     transform: translateX(-50%);
-    border: 1px solid fade(@home-primary, 20%);
-    transition: background-color 0.3s, color 0.3s;
+    border: 1px solid fade(@home-primary, 16%);
+    backdrop-filter: blur(8px);
+    transition: transform 0.22s ease, background-color 0.22s ease, color 0.22s ease;
 
     &:hover {
+        transform: translateX(-50%) translateY(-2px);
         background-color: @home-primary;
         color: #fff;
     }
@@ -183,7 +177,7 @@ const scrollToSection = (sectionId) => {
 
 .features-wrap {
     min-height: auto;
-    background: @home-bg-page;
+    background: linear-gradient(180deg, #f2fbf5 0%, #edf8f1 100%);
     overflow: hidden;
 }
 
@@ -197,19 +191,13 @@ const scrollToSection = (sectionId) => {
 
 .features-bg-gradient {
     position: absolute;
-    inset: -40%;
-    width: 180%;
-    height: 180%;
+    inset: 0;
     background: linear-gradient(
-        120deg,
-        @home-bg-page 0%,
-        fade(@home-gradient-mint, 85%) 25%,
-        fade(@home-gradient-green, 70%) 50%,
-        fade(@home-gradient-teal, 75%) 75%,
-        @home-bg-page 100%
+        160deg,
+        fade(@home-bg-page, 98%) 0%,
+        fade(@home-gradient-mint, 32%) 48%,
+        fade(@home-gradient-teal, 22%) 100%
     );
-    background-size: 400% 400%;
-    animation: homeGradientFlow 12s ease-in-out infinite reverse;
 }
 
 .features-wrap > :not(.features-bg) {
@@ -217,58 +205,85 @@ const scrollToSection = (sectionId) => {
     z-index: 1;
 }
 
-@keyframes homeGradientFlow {
+.site-footer {
+    background: linear-gradient(120deg, @home-primary 0%, fade(@home-primary, 88%) 100%);
+    color: fade(#fff, 82%);
+    font-size: 13px;
+    text-align: center;
+    padding: 18px 16px;
+    width: 100%;
+    box-sizing: border-box;
+}
+
+@media (max-width: @app-breakpoint-lg) {
+    .hero-inner {
+        flex-direction: column;
+        text-align: center;
+        justify-content: stretch;
+        align-items: stretch;
+        min-height: calc(100dvh - 72px);
+        padding-top: clamp(34px, 6vh, 58px);
+        padding-bottom: clamp(24px, 4vh, 40px);
+        gap: 8px;
+    }
+
+    .hero-visual {
+        display: none;
+    }
+
+    .floating-button {
+        bottom: 20px;
+    }
+}
+
+@keyframes heroGradientFlow {
     0% {
         background-position: 0% 50%;
+        filter: saturate(1);
     }
-
     50% {
-        background-position: 100% 50%;
+        background-position: 50% 45%;
+        filter: saturate(1.08);
     }
-
     100% {
-        background-position: 0% 50%;
+        background-position: 100% 50%;
+        filter: saturate(1.16);
     }
 }
 
-@keyframes homeOrbFloat {
+@keyframes orbFloatOne {
     0%,
     100% {
-        transform: translate(0, 0) scale(1);
+        transform: translate3d(0, 0, 0) scale(1);
+        opacity: 0.46;
     }
-
-    33% {
-        transform: translate(48px, -36px) scale(1.1);
-    }
-
-    66% {
-        transform: translate(-40px, 32px) scale(0.9);
+    50% {
+        transform: translate3d(-18px, 22px, 0) scale(1.08);
+        opacity: 0.62;
     }
 }
 
-@media (prefers-reduced-motion: reduce) {
-    .hero-bg-gradient,
-    .hero-bg-orb,
-    .features-bg-gradient {
-        animation: none;
+@keyframes orbFloatTwo {
+    0%,
+    100% {
+        transform: translate3d(0, 0, 0) scale(1);
+        opacity: 0.4;
     }
-
-    .hero-bg-gradient {
-        inset: 0;
-        width: 100%;
-        height: 100%;
-        background: linear-gradient(135deg, @home-gradient-pale 0%, @home-bg-hero 35%, @home-bg-hero-end 65%, @home-gradient-deep 100%);
-        background-size: 100% 100%;
+    50% {
+        transform: translate3d(22px, -16px, 0) scale(1.1);
+        opacity: 0.56;
     }
 }
 
-.site-footer {
-    background: @home-primary;
-    color: fade(#fff, 75%);
-    font-size: 13px;
-    text-align: center;
-    padding: 20px 16px;
-    width: 100%;
-    box-sizing: border-box;
+@keyframes orbFloatThree {
+    0%,
+    100% {
+        transform: translate3d(0, 0, 0) scale(1);
+        opacity: 0.36;
+    }
+    50% {
+        transform: translate3d(-12px, -14px, 0) scale(1.14);
+        opacity: 0.5;
+    }
 }
 </style>

+ 78 - 17
src/pages/Main/components/center.vue

@@ -1,35 +1,49 @@
 <template>
     <div class="center">
-        <p class="eyebrow">智能校园乐跑平台</p>
-        <h1>RunForge</h1>
-        <h2>轻松完成乐跑任务,成绩更省心</h2>
-        <button type="button" class="cta-button" @click="$router.push('/lepao')">
-            立即体验
-        </button>
+        <div class="hero-copy">
+            <p class="eyebrow">智能校园乐跑平台</p>
+            <h1>RunForge</h1>
+            <h2>轻松完成乐跑任务,成绩更省心</h2>
+        </div>
+        <div class="action-row">
+            <button type="button" class="cta-button" @click="$router.push('/lepao')">
+                立即体验
+            </button>
+        </div>
     </div>
 </template>
 
 <style lang="less" scoped>
 @import '../theme.less';
+@import '@/styles/store-theme.less';
 
 .center {
     flex: 1;
-    max-width: 560px;
+    max-width: 600px;
     text-align: left;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+}
+
+.hero-copy {
+    display: flex;
+    flex-direction: column;
 }
 
 .eyebrow {
     margin: 0 0 12px;
-    font-size: 20px;
+    font-size: clamp(0.92rem, 2.4vw, 1.08rem);
     font-weight: 600;
-    letter-spacing: 0.08em;
+    letter-spacing: 0.14em;
     text-transform: uppercase;
-    color: @home-text-muted;
+    color: @store-accent;
 }
 
 .center h1 {
     margin: 0;
-    font-size: clamp(4rem, 9vw, 6rem);
+    font-size: clamp(3.2rem, 10vw, 6rem);
     line-height: 1.1;
     font-weight: 700;
     color: @home-primary;
@@ -38,15 +52,16 @@
 
 .center h2 {
     margin: 16px 0 0;
-    font-size: clamp(1.5rem, 3vw, 2rem);
-    line-height: 1.6;
+    font-size: clamp(1.15rem, 3.2vw, 1.8rem);
+    line-height: 1.7;
     font-weight: 400;
     color: @home-text-muted;
     font-family: Alimama ShuHeiTi, -apple-system, BlinkMacSystemFont, sans-serif;
+    max-width: 24em;
 }
 
 .cta-button {
-    margin-top: 32px;
+    margin-top: 28px;
     cursor: pointer;
     border: none;
     border-radius: @home-radius-btn;
@@ -54,13 +69,59 @@
     font-size: 16px;
     font-weight: 600;
     color: #fff;
-    background: @home-primary;
+    background: linear-gradient(135deg, @home-primary 0%, @store-primary-light 100%);
     font-family: Alimama ShuHeiTi, -apple-system, BlinkMacSystemFont, sans-serif;
-    transition: background-color 0.2s, transform 0.2s;
+    box-shadow: 0 10px 24px fade(@home-primary, 24%);
+    transition: transform 0.2s, box-shadow 0.2s, filter 0.2s;
 
     &:hover {
-        background: @home-primary-hover;
         transform: translateY(-1px);
+        box-shadow: 0 14px 28px fade(@home-primary, 28%);
+        filter: brightness(1.05);
+    }
+}
+
+.action-row {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    flex-wrap: wrap;
+}
+
+
+@media (max-width: 992px) {
+    .center {
+        text-align: center;
+        align-items: center;
+        width: 100%;
+        min-height: clamp(430px, 62dvh, 620px);
+    }
+
+    .hero-copy {
+        margin-top: auto;
+        margin-bottom: auto;
+        transform: translateY(-4vh);
+    }
+
+    .center h2 {
+        margin-left: auto;
+        margin-right: auto;
+        max-width: 18em;
+    }
+
+    .action-row {
+        justify-content: center;
+        width: 100%;
+        margin-top: auto;
+        padding-bottom: clamp(74px, 12vh, 112px);
+    }
+
+    .cta-button {
+        margin-top: 0;
+        min-width: min(150px, 84vw);
+        padding: 13px 24px;
+        font-size: 17px;
     }
 }
+
 </style>

+ 60 - 21
src/pages/Main/components/section2.vue

@@ -78,43 +78,48 @@ onUnmounted(() => {
 
 <style lang="less" scoped>
 @import '../theme.less';
+@import '@/styles/store-theme.less';
 
 .features-section {
     position: relative;
-    padding: 80px 24px 100px;
+    padding: clamp(52px, 9vh, 88px) @store-page-padding-x clamp(64px, 9vh, 108px);
     display: flex;
-    gap: 32px;
+    gap: clamp(18px, 3.6vh, 34px);
     flex-direction: column;
     align-items: center;
+    box-sizing: border-box;
 }
 
 .section-title {
     margin: 0;
-    font-size: 2.25rem;
+    font-size: clamp(1.8rem, 4vw, 2.4rem);
     color: @home-primary;
     font-family: Alimama ShuHeiTi, -apple-system, BlinkMacSystemFont, sans-serif;
+    text-align: center;
 }
 
 .section-subtitle {
-    margin: -16px 0 24px;
-    font-size: 1.1rem;
+    margin: -4px 0 20px;
+    font-size: clamp(0.95rem, 2.3vw, 1.08rem);
     color: @home-text-muted;
+    text-align: center;
+    max-width: 38rem;
 }
 
 .card {
-    width: min(1200px, 100%);
+    width: min(1160px, 100%);
     display: flex;
-    gap: 48px;
+    gap: clamp(20px, 3vw, 44px);
     align-items: center;
     justify-content: center;
-    background: @home-card-bg;
-    border: 1px solid @home-card-border;
-    border-radius: @home-radius-card;
-    box-shadow: @home-shadow-card;
-    padding: 40px 48px;
+    background: linear-gradient(145deg, fade(#fff, 95%) 0%, fade(#f2fbf6, 88%) 100%);
+    border: 1px solid fade(@home-card-border, 92%);
+    border-radius: 22px;
+    box-shadow: 0 16px 40px fade(@home-primary, 9%);
+    padding: clamp(18px, 2.4vw, 34px);
     box-sizing: border-box;
     opacity: 0;
-    transform: translateY(48px);
+    transform: translateY(36px);
     transition: opacity 0.8s ease-out, transform 0.8s ease-out;
 
     &.in-view {
@@ -129,33 +134,67 @@ onUnmounted(() => {
     .content {
         flex: 1;
         max-width: 500px;
+        min-width: 0;
 
         .title {
             font-weight: 700;
-            font-size: 1.75rem;
+            font-size: clamp(1.24rem, 3.2vw, 1.7rem);
             color: @home-primary;
             font-family: Alimama ShuHeiTi, -apple-system, BlinkMacSystemFont, sans-serif;
             display: flex;
             align-items: center;
-            gap: 12px;
+            gap: 10px;
+            line-height: 1.35;
 
             img {
-                width: 36px;
-                height: 36px;
+                width: 34px;
+                height: 34px;
             }
         }
 
         .dec {
             margin-top: 16px;
-            font-size: 1.05rem;
+            font-size: clamp(0.94rem, 2.2vw, 1.02rem);
             line-height: 1.75;
             color: @home-text-muted;
+            max-width: 34ch;
+        }
+    }
+
+    .img {
+        padding: clamp(10px, 1.4vw, 16px);
+        border-radius: 18px;
+        background: fade(#fff, 85%);
+        border: 1px solid fade(@home-card-border, 86%);
+
+        img {
+            max-width: min(420px, 38vw);
+            max-height: 280px;
+            user-select: none;
+            object-fit: contain;
         }
     }
+}
+
+@media (max-width: @app-breakpoint-lg) {
+    .card,
+    .card.reverse {
+        flex-direction: column;
+        text-align: center;
+    }
+
+    .card .content .title {
+        justify-content: center;
+    }
+
+    .card .content .dec {
+        margin-left: auto;
+        margin-right: auto;
+        max-width: 32ch;
+    }
 
-    .img img {
-        max-width: min(420px, 40vw);
-        user-select: none;
+    .card .img img {
+        max-width: min(100%, 340px);
     }
 }
 </style>

+ 24 - 1
src/pages/User/info/components/user-info-header.vue

@@ -48,6 +48,20 @@ getuser()
     border-radius: 4px;
 
     .user-msg {
+      max-width: 100%;
+
+      :deep(.arco-space) {
+        width: 100%;
+        justify-content: center;
+      }
+
+      :deep(.arco-typography) {
+        max-width: 70vw;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+
       .arco-icon {
         color: rgb(var(--gray-10));
       }
@@ -68,5 +82,14 @@ getuser()
     position: absolute;
   }
 
-  
+  @media (max-width: 768px) {
+    .header,
+    .header::after {
+      height: 176px;
+    }
+
+    .center {
+      width: calc(100% - 24px);
+    }
+  }
 </style>

+ 9 - 11
src/pages/User/info/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
   
   <div class="store-page">
     <Breadcrumb />
@@ -46,21 +46,19 @@ import MyProject from './components/my-project.vue'
     padding: 0 16px 16px 16px;
   }
 }
-</style>
 
-<style lang="less" scoped>
-.mobile {
+@media (max-width: 768px) {
   .content {
     display: block;
+  }
 
-    &-left {
-      margin-right: 0;
-      margin-bottom: 16px;
-    }
+  .content-left {
+    margin-right: 0;
+    margin-bottom: 12px;
+  }
 
-    &-right {
-      width: 100%;
-    }
+  .content-right {
+    width: 100%;
   }
 }
 </style>

+ 37 - 2
src/pages/User/setting/components/basic-information.vue

@@ -18,7 +18,7 @@
     <a-divider />
 
     <div class="email">
-      <a-form size="large" ref="emailFormRef" :model="emailForm" class="form" @submit="changeEmail">
+      <a-form size="large" ref="emailFormRef" :model="emailForm" class="form" @submit="changeEmail" >
         <a-form-item field="email" label="邮箱" :rules="[{ type: 'email', required: true, message: '请填写正确的邮箱地址' }]"
           :validate-trigger="['change']">
           <EmailAutoComplete placeholder="请输入邮箱" allow-clear v-model="emailForm.email">
@@ -209,7 +209,7 @@ const changeEmail = async () => {
 
 <style scoped lang="less">
 .form {
-  width: 540px;
+  width: min(100%, 540px);
   margin: 0 auto;
   display: flex;
   flex-direction: column;
@@ -220,4 +220,39 @@ const changeEmail = async () => {
 .button {
   width: 150px;
 }
+
+:deep(.arco-form-item-label-col > label) {
+  white-space: nowrap;
+}
+
+@media (max-width: 768px) {
+  .form {
+    width: 100%;
+    align-items: stretch;
+  }
+
+  .button {
+    width: 100%;
+  }
+
+  :deep(.arco-form-item) {
+    margin-bottom: 12px;
+  }
+
+  :deep(.arco-form-item-label-col),
+  :deep(.arco-form-item-wrapper-col) {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  :deep(.arco-form-item-label-col) {
+    margin-bottom: 4px;
+  }
+
+  :deep(.arco-input-group-append .arco-btn) {
+    width: 86px !important;
+    padding-left: 0;
+    padding-right: 0;
+  }
+}
 </style>

+ 30 - 1
src/pages/User/setting/components/security-settings.vue

@@ -89,7 +89,7 @@ const changePassword = async () => {
 
 <style scoped lang="less">
 .form {
-  width: 560px;
+  width: min(100%, 560px);
   margin: 0 auto;
   display: flex;
   flex-direction: column;
@@ -100,4 +100,33 @@ const changePassword = async () => {
 .button {
   width: 150px;
 }
+
+:deep(.arco-form-item-label-col > label) {
+  white-space: nowrap;
+}
+
+@media (max-width: 768px) {
+  .form {
+    width: 100%;
+    align-items: stretch;
+  }
+
+  .button {
+    width: 100%;
+  }
+
+  :deep(.arco-form-item) {
+    margin-bottom: 12px;
+  }
+
+  :deep(.arco-form-item-label-col),
+  :deep(.arco-form-item-wrapper-col) {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  :deep(.arco-form-item-label-col) {
+    margin-bottom: 4px;
+  }
+}
 </style>

+ 22 - 1
src/pages/User/setting/components/social-bindings.vue

@@ -129,7 +129,7 @@ onBeforeMount(refreshUser)
 
 <style scoped lang="less">
 .social-bindings {
-  width: 560px;
+  width: min(100%, 560px);
   margin: 0 auto;
 }
 
@@ -170,4 +170,25 @@ onBeforeMount(refreshUser)
   color: var(--color-text-3);
   font-size: 12px;
 }
+
+@media (max-width: 768px) {
+  .social-bindings {
+    width: 100%;
+  }
+
+  .account-card {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 12px;
+    padding: 14px;
+  }
+
+  .account-info {
+    align-items: flex-start;
+  }
+
+  .account-card :deep(.arco-btn) {
+    width: 100%;
+  }
+}
 </style>

+ 52 - 5
src/pages/User/setting/components/user-panel.vue

@@ -1,6 +1,6 @@
 <template>
   <a-card :bordered="false">
-    <a-space :size="54">
+    <div class="panel-inner">
       <a-upload :custom-request="customRequest" :file-list="fileList" :show-upload-button="true" :show-file-list="false"
         :on-before-upload="beforeUpload" @change="uploadChange" accept="image/png, image/jpeg, image/jpg">
         <template #upload-button>
@@ -23,12 +23,9 @@
         <template #label="{ label }">{{ label }} :</template>
         <template #value="{ value, data }">
           <span>{{ value }}</span>
-          <a-tag v-if="data.label === '邮箱' && value !== '未设置'" color="green" size="small" style="margin-left: 5px">
-            已验证
-          </a-tag>
         </template>
       </a-descriptions>
-    </a-space>
+    </div>
   </a-card>
 </template>
 
@@ -138,6 +135,17 @@ const customRequest = async ({ onSuccess, onError, fileItem }) => {
   border-radius: 4px;
 }
 
+.panel-inner {
+  display: flex;
+  align-items: center;
+  gap: 54px;
+}
+
+:deep(.arco-descriptions-layout-inline-horizontal .arco-descriptions-item-label),
+:deep(.arco-descriptions-layout-inline-horizontal .arco-descriptions-item-value) {
+  white-space: nowrap;
+}
+
 :deep(.arco-avatar-trigger-icon-button) {
   width: 32px;
   height: 32px;
@@ -150,4 +158,43 @@ const customRequest = async ({ onSuccess, onError, fileItem }) => {
     font-size: 14px;
   }
 }
+
+@media (max-width: 768px) {
+  .arco-card {
+    padding: 10px 0 2px;
+  }
+
+  .panel-inner {
+    flex-direction: column;
+    align-items: center;
+    gap: 16px;
+  }
+
+  :deep(.arco-descriptions) {
+    width: 100%;
+  }
+
+  :deep(.arco-descriptions-view) {
+    width: 100%;
+  }
+
+  :deep(.arco-descriptions-layout-inline-horizontal .arco-descriptions-item) {
+    width: 100%;
+  }
+
+  :deep(.arco-descriptions-layout-inline-horizontal .arco-descriptions-item-label) {
+    min-width: 72px;
+    padding-right: 8px;
+  }
+
+  :deep(.arco-descriptions-layout-inline-horizontal .arco-descriptions-item-value) {
+    flex: 1;
+    min-width: 0;
+  }
+
+  :deep(.arco-descriptions-item-value > span) {
+    word-break: break-word;
+    overflow-wrap: anywhere;
+  }
+}
 </style>

+ 27 - 5
src/pages/User/setting/index.vue

@@ -1,14 +1,14 @@
-<template>
+<template>
   <div class="store-page">
     <Breadcrumb />
-    <a-row style="margin-bottom: 16px">
+    <a-row class="user-setting-head">
       <a-col :span="24">
         <UserPanel :user="user" />
       </a-col>
     </a-row>
     <a-row class="wrapper">
       <a-col :span="24">
-        <a-tabs default-active-key="1" type="rounded">
+        <a-tabs default-active-key="1" type="rounded" class="settings-tabs">
           <a-tab-pane key="1" title="基础信息">
             <BasicInformation />
           </a-tab-pane>
@@ -33,10 +33,10 @@ import SocialBindings from './components/social-bindings.vue'
 
 <style scoped lang="less">
 .wrapper {
-  padding: 20px 0 0 20px;
+  padding: 20px 20px 0;
   min-height: 580px;
   background-color: var(--color-bg-2);
-  border-radius: 4px;
+  border-radius: 8px;
 }
 
 :deep(.section-title) {
@@ -44,4 +44,26 @@ import SocialBindings from './components/social-bindings.vue'
   margin-bottom: 16px;
   font-size: 14px;
 }
+
+.user-setting-head {
+  margin-bottom: 16px;
+}
+
+@media (max-width: 768px) {
+  .wrapper {
+    min-height: auto;
+    padding: 12px 12px 0;
+    border-radius: 6px;
+  }
+
+  .user-setting-head {
+    margin-bottom: 12px;
+  }
+
+  .settings-tabs {
+    :deep(.arco-tabs-content) {
+      padding-top: 4px;
+    }
+  }
+}
 </style>

+ 61 - 8
src/pages/admin/goods/orderDetail.vue

@@ -5,10 +5,10 @@
       <div v-if="!loading && data.orderId" class="detail-grid">
         <a-card :bordered="false" class="detail-card detail-card--status">
           <template #title>订单进度</template>
-          <a-steps :current="stepCurrent" :status="stepStatus" label-placement="vertical">
-            <a-step description="用户提交订单">待支付</a-step>
-            <a-step description="支付成功,系统处理">待处理</a-step>
-            <a-step description="权益已发放">已完成</a-step>
+          <a-steps :current="stepCurrent" :status="stepStatus" label-placement="vertical" class="order-steps">
+            <a-step :description="isMobile ? '' : '用户提交订单'">待支付</a-step>
+            <a-step :description="isMobile ? '' : '支付成功,系统处理'">待处理</a-step>
+            <a-step :description="isMobile ? '' : '权益已发放'">已完成</a-step>
           </a-steps>
           <div class="state-center">
             <a-tag :color="stateMeta.color" size="large">{{ stateMeta.label }}</a-tag>
@@ -35,7 +35,7 @@
             <a-descriptions-item label="支付方式">{{ getPayTypeLabel(data.pay_type) }}</a-descriptions-item>
             <a-descriptions-item label="下单时间">{{ formatStoreTimeFull(data.create_time) }}</a-descriptions-item>
             <a-descriptions-item label="支付时间">{{ formatStoreTimeFull(data.pay_time) }}</a-descriptions-item>
-            <a-descriptions-item v-if="data.pay_id" label="支付平台单号" :span="2">
+            <a-descriptions-item v-if="data.pay_id" label="支付平台单号" :span="isMobile ? 1 : 2">
               {{ data.pay_id }}
             </a-descriptions-item>
             <a-descriptions-item v-if="data.lepao_count != null" label="乐跑次数">
@@ -52,7 +52,7 @@
           <a-descriptions :column="{ xs: 1, sm: 2 }" bordered>
             <a-descriptions-item label="用户名">{{ data.username || '-' }}</a-descriptions-item>
             <a-descriptions-item label="邮箱">{{ data.user_email || '-' }}</a-descriptions-item>
-            <a-descriptions-item label="用户 UUID" :span="2">{{ data.create_user || '-' }}</a-descriptions-item>
+            <a-descriptions-item label="用户 UUID" :span="isMobile ? 1 : 2">{{ data.create_user || '-' }}</a-descriptions-item>
           </a-descriptions>
         </a-card>
 
@@ -66,7 +66,7 @@
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue'
+import { ref, computed, onMounted, onUnmounted } from 'vue'
 import { useRoute } from 'vue-router'
 import { adminOrderDetail } from '@/api/order'
 import { Notification } from '@arco-design/web-vue'
@@ -81,6 +81,8 @@ const route = useRoute()
 const loading = ref(true)
 const data = ref({})
 const goodsContent = ref('')
+const isMobile = ref(false)
+let resizeHandler = null
 
 const stateMeta = computed(() => getOrderStateMeta(data.value?.state ?? -1))
 
@@ -121,7 +123,19 @@ const fetchDetail = async () => {
   }
 }
 
-onMounted(fetchDetail)
+onMounted(async () => {
+  const syncMobile = () => {
+    isMobile.value = window.innerWidth <= 768
+  }
+  resizeHandler = syncMobile
+  syncMobile()
+  window.addEventListener('resize', resizeHandler)
+  await fetchDetail()
+})
+
+onUnmounted(() => {
+  if (resizeHandler) window.removeEventListener('resize', resizeHandler)
+})
 </script>
 
 <style scoped lang="less">
@@ -159,6 +173,10 @@ onMounted(fetchDetail)
   color: #e85d04;
 }
 
+.order-steps {
+  margin-top: 4px;
+}
+
 .goods-content {
   line-height: 1.75;
 
@@ -166,4 +184,39 @@ onMounted(fetchDetail)
     max-width: 100%;
   }
 }
+
+@media (max-width: 768px) {
+  .admin-order-detail {
+    padding: 0 12px 16px;
+  }
+
+  .order-steps {
+    overflow: visible;
+  }
+
+  .order-steps :deep(.arco-steps-item) {
+    flex: 1 1 0;
+    min-width: 0;
+  }
+
+  .order-steps :deep(.arco-steps-item-title) {
+    font-size: 11px;
+    white-space: nowrap;
+  }
+
+  .order-steps :deep(.arco-steps-item-description) {
+    display: none;
+  }
+
+  .order-steps :deep(.arco-steps-item-content) {
+    width: 100%;
+    min-width: 0;
+  }
+
+  :deep(.arco-descriptions-item-value) {
+    white-space: normal;
+    word-break: break-all;
+    overflow-wrap: anywhere;
+  }
+}
 </style>

+ 41 - 9
src/pages/admin/lepaoAccount/accountList.vue

@@ -1,11 +1,11 @@
-<template>
+<template>
     <div class="store-page">
         <Breadcrumb />
 
         <a-card title="乐跑账号管理">
-            <a-row>
-                <a-col :flex="1">
-                    <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
+            <a-row class="query-row">
+                <a-col :flex="'1000px'" class="query-main">
+                    <a-form class="queryForm app-query-form" :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
                         label-align="left">
                         <a-row :gutter="16">
                             <a-col :span="6">
@@ -78,9 +78,9 @@
                         </a-row>
                     </a-form>
                 </a-col>
-                <a-divider style="height: 84px" direction="vertical" />
-                <a-col :flex="'86px'" style="text-align: right">
-                    <a-space direction="vertical" :size="18">
+                <a-divider class="query-divider" style="height: 84px" direction="vertical" />
+                <a-col :flex="1" class="app-query-actions">
+                    <a-space class="query-actions-space" direction="vertical" :size="18">
                         <a-button type="primary" @click="search">
                             <template #icon>
                                 <icon-search />
@@ -97,7 +97,7 @@
                 </a-col>
             </a-row>
 
-            <a-table :data="data" :bordered="false" hoverable class="table table-clickable" :loading="loading" :columns="columns" @row-click="onRowClick" :pagination="{
+            <a-table :data="data" :bordered="false" hoverable class="table table-clickable" :loading="loading" :columns="columns" :scroll="{ x: 2850 }" @row-click="onRowClick" :pagination="{
                 showPageSize: true,
                 showJumper: true,
                 showTotal: true,
@@ -253,7 +253,7 @@
 
     <a-modal v-model:visible="bindAuditVisible" title="绑定解绑记录" :footer="false" width="980px" draggable>
         <a-table :bordered="false" :data="bindAuditData" :columns="bindAuditColumns" :loading="bindAuditLoading" :pagination="false"
-            :scroll="{ y: 420 }">
+            :scroll="{ x: 1450, y: 420 }">
             <template #lepao_user="{ record }">
                 <a-space>
                     <a-avatar :size="26">
@@ -847,6 +847,18 @@ const autoTimeLabel = (record) => {
 </script>
 
 <style scoped>
+.query-row {
+    margin-bottom: 10px;
+}
+
+.query-main {
+    min-width: 0;
+}
+
+.query-actions-space {
+    width: 100%;
+}
+
 .table-clickable {
     :deep(.arco-table-tr) {
         cursor: pointer;
@@ -883,4 +895,24 @@ const autoTimeLabel = (record) => {
         }
     }
 }
+
+@media (max-width: 768px) {
+    .query-row {
+        display: block;
+    }
+
+    .query-divider {
+        display: none;
+    }
+
+    .query-actions-space {
+        margin-top: 4px;
+        flex-direction: row !important;
+        gap: 10px !important;
+    }
+
+    .query-actions-space :deep(.arco-btn) {
+        flex: 1;
+    }
+}
 </style>

+ 40 - 7
src/pages/admin/lepaoBindAudit/index.vue

@@ -1,10 +1,10 @@
-<template>
+<template>
   <div class="store-page">
     <Breadcrumb />
     <a-card title="绑定解绑审计">
-      <a-row>
-        <a-col :flex="'1000px'">
-          <a-form :model="query" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }" label-align="left">
+      <a-row class="query-row">
+        <a-col :flex="'1000px'" class="query-main">
+          <a-form class="queryForm app-query-form" :model="query" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }" label-align="left">
             <a-row :gutter="16">
               <a-col :span="12">
                 <a-form-item field="student_num" label="学号">
@@ -52,9 +52,9 @@
             </a-row>
           </a-form>
         </a-col>
-        <a-divider style="height: 84px" direction="vertical" />
-        <a-col :flex="1">
-          <a-space direction="vertical" :size="18">
+        <a-divider class="query-divider" style="height: 84px" direction="vertical" />
+        <a-col :flex="1" class="app-query-actions">
+          <a-space class="query-actions-space" direction="vertical" :size="18">
             <a-button type="primary" @click="search">搜索</a-button>
             <a-button @click="reset">重置</a-button>
           </a-space>
@@ -65,6 +65,7 @@
         class="table"
         :data="data"
         :loading="loading"
+        :scroll="{ x: 1450 }"
         :pagination="{
           showPageSize: true,
           showJumper: true,
@@ -255,7 +256,39 @@ onMounted(() => {
 </script>
 
 <style scoped>
+.query-row {
+  margin-bottom: 10px;
+}
+
+.query-main {
+  min-width: 0;
+}
+
+.query-actions-space {
+  width: 100%;
+}
+
 .table {
   margin-top: 16px;
 }
+
+@media (max-width: 768px) {
+  .query-row {
+    display: block;
+  }
+
+  .query-divider {
+    display: none;
+  }
+
+  .query-actions-space {
+    margin-top: 4px;
+    flex-direction: row !important;
+    gap: 10px !important;
+  }
+
+  .query-actions-space :deep(.arco-btn) {
+    flex: 1;
+  }
+}
 </style>

+ 2 - 1
src/pages/admin/lepaoCountLedger/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
   <div class="store-page">
     <Breadcrumb />
 
@@ -67,6 +67,7 @@
         :data="data"
         :loading="loading"
         :bordered="false"
+        :scroll="{ x: 1280 }"
         :summary="tableSummary"
         :pagination="{
           showPageSize: true,

+ 40 - 8
src/pages/admin/lepaoRecords/lepaoRecords.vue

@@ -1,11 +1,11 @@
-<template>
+<template>
 
   <div class="store-page">
     <Breadcrumb />
     <a-card title="乐跑记录">
-      <a-row>
-        <a-col :flex="'1000px'">
-          <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
+      <a-row class="query-row">
+        <a-col :flex="'1000px'" class="query-main">
+          <a-form class="queryForm app-query-form" :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
             label-align="left">
             <a-row :gutter="16">
               <a-col :span="12">
@@ -38,9 +38,9 @@
             </a-row>
           </a-form>
         </a-col>
-        <a-divider style="height: 84px" direction="vertical" />
-        <a-col :flex="1">
-          <a-space direction="vertical" :size="18">
+        <a-divider class="query-divider" style="height: 84px" direction="vertical" />
+        <a-col :flex="1" class="app-query-actions">
+          <a-space class="query-actions-space" direction="vertical" :size="18">
             <a-button type="primary" @click="search">
               <template #icon>
                 <icon-search />
@@ -57,7 +57,7 @@
         </a-col>
       </a-row>
 
-      <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" :pagination="{
+      <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" :scroll="{ x: 1650 }" :pagination="{
         showPageSize: true,
         showJumper: true,
         showTotal: true,
@@ -278,6 +278,18 @@ onMounted(() => {
 </script>
 
 <style scoped lang="less">
+.query-row {
+  margin-bottom: 10px;
+}
+
+.query-main {
+  min-width: 0;
+}
+
+.query-actions-space {
+  width: 100%;
+}
+
 .table {
   font-family: -apple-system, BlinkMacSystemFont;
   margin-top: 15px;
@@ -321,4 +333,24 @@ onMounted(() => {
   display: flex;
   justify-content: space-between;
 }
+
+@media (max-width: 768px) {
+  .query-row {
+    display: block;
+  }
+
+  .query-divider {
+    display: none;
+  }
+
+  .query-actions-space {
+    margin-top: 4px;
+    flex-direction: row !important;
+    gap: 10px !important;
+  }
+
+  .query-actions-space :deep(.arco-btn) {
+    flex: 1;
+  }
+}
 </style>

+ 34 - 3
src/pages/admin/lepaoRecords/recordDetail.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
     <div class="store-page">
         <Breadcrumb />
         <a-card title="记录详情">
@@ -8,7 +8,11 @@
                     <a-skeleton-line :rows="5" />
                 </a-space>
             </a-skeleton>
-            <a-descriptions :data="info" :column="2" />
+            <a-descriptions
+                :data="info"
+                :column="isMobile ? 1 : 2"
+                class="record-descriptions"
+            />
             <MapContainer v-if="showMap" :point_list="data.result.point_list" :log_list="data.point_data" :pathData="data.data" threeD
                 style="margin-top: 10px;" />
         </a-card>
@@ -16,7 +20,7 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, onUnmounted } from 'vue'
 import { adminGetRecordDetail } from '@/api/lepao'
 import { Notification } from '@arco-design/web-vue'
 import { useRoute } from 'vue-router'
@@ -29,6 +33,8 @@ const loading = ref(false)
 const showMap = ref(false)
 const data = ref({})
 const info = ref([])
+const isMobile = ref(false)
+let mobileResizeHandler = null
 
 const GetRecordDetail = async (routeId) => {
     try {
@@ -73,9 +79,18 @@ const GetRecordDetail = async (routeId) => {
 }
 
 onMounted(() => {
+    mobileResizeHandler = () => {
+        isMobile.value = window.innerWidth <= 768
+    }
+    mobileResizeHandler()
+    window.addEventListener('resize', mobileResizeHandler)
     GetRecordDetail(route.params.id)
 })
 
+onUnmounted(() => {
+    if (mobileResizeHandler) window.removeEventListener('resize', mobileResizeHandler)
+})
+
 const stramptoTime = (time) => {
     return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
 }
@@ -98,4 +113,20 @@ function formatSecondsToMinSec(totalSeconds) {
 </script>
 
 <style scoped lang="less">
+.record-descriptions {
+    margin-top: 8px;
+}
+
+@media (max-width: 768px) {
+    .record-descriptions :deep(.arco-descriptions-item-label) {
+        white-space: nowrap;
+        width: 88px !important;
+    }
+
+    .record-descriptions :deep(.arco-descriptions-item-value) {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+}
 </style>

+ 40 - 7
src/pages/admin/user/userList.vue

@@ -1,11 +1,11 @@
-<template>
+<template>
     <div class="store-page">
         <Breadcrumb />
 
         <a-card title="用户管理">
-            <a-row>
-                <a-col :flex="1">
-                    <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
+            <a-row class="query-row">
+                <a-col :flex="'1000px'" class="query-main">
+                    <a-form class="queryForm app-query-form" :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
                         label-align="left">
                         <a-row :gutter="16">
                             <a-col :span="8">
@@ -31,9 +31,9 @@
                         </a-row>
                     </a-form>
                 </a-col>
-                <a-divider style="height: 84px" direction="vertical" />
-                <a-col :flex="'86px'" style="text-align: right">
-                    <a-space direction="vertical" :size="18">
+                <a-divider class="query-divider" style="height: 84px" direction="vertical" />
+                <a-col :flex="1" class="app-query-actions">
+                    <a-space class="query-actions-space" direction="vertical" :size="18">
                         <a-button type="primary" @click="search">
                             <template #icon>
                                 <icon-search />
@@ -51,6 +51,7 @@
             </a-row>
 
             <a-table :data="data" :bordered="false" hoverable column-resizable class="table" :loading="loading"
+                :scroll="{ x: 1550 }"
                 :columns="columns" :pagination="{
                     showPageSize: true,
                     showJumper: true,
@@ -636,6 +637,18 @@ const formatLastLoginType = (type) => {
 </script>
 
 <style scoped>
+.query-row {
+    margin-bottom: 10px;
+}
+
+.query-main {
+    min-width: 0;
+}
+
+.query-actions-space {
+    width: 100%;
+}
+
 .table {
     margin-top: 15px;
 }
@@ -679,4 +692,24 @@ const formatLastLoginType = (type) => {
 .permission-resource-card + .permission-resource-card {
     margin-top: 12px;
 }
+
+@media (max-width: 768px) {
+    .query-row {
+        display: block;
+    }
+
+    .query-divider {
+        display: none;
+    }
+
+    .query-actions-space {
+        margin-top: 4px;
+        flex-direction: row !important;
+        gap: 10px !important;
+    }
+
+    .query-actions-space :deep(.arco-btn) {
+        flex: 1;
+    }
+}
 </style>

+ 57 - 11
src/pages/admin/workOrder/orderList.vue

@@ -1,11 +1,11 @@
-<template>
+<template>
     <div class="store-page">
         <Breadcrumb />
 
         <a-card title="工单列表">
-            <a-row>
-                <a-col :flex="1">
-                    <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
+            <a-row class="query-row">
+                <a-col :flex="'1000px'" class="query-main">
+                    <a-form class="queryForm app-query-form" :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
                         label-align="left">
                         <a-row :gutter="16">
                             <a-col :span="8">
@@ -24,9 +24,9 @@
                         </a-row>
                     </a-form>
                 </a-col>
-                <a-divider style="height: 84px" direction="vertical" />
-                <a-col :flex="'86px'" style="text-align: right">
-                    <a-space direction="vertical" :size="18">
+                <a-divider class="query-divider" style="height: 84px" direction="vertical" />
+                <a-col :flex="1" class="app-query-actions">
+                    <a-space class="query-actions-space" direction="vertical" :size="18">
                         <a-button type="primary" @click="search">
                             <template #icon>
                                 <icon-search />
@@ -45,6 +45,7 @@
 
 
             <a-table :bordered="false" :data="data" stripe hoverable column-resizable class="table" :loading="loading" :columns="columns"
+                :scroll="{ x: 980 }"
                 :pagination="{
                     showPageSize: true,
                     showJumper: true,
@@ -135,29 +136,42 @@ const columns = [
     {
         title: '工单ID',
         dataIndex: 'id',
+        width: 90
     },
     {
         title: '工单标题',
         dataIndex: 'title',
+        width: 180,
+        ellipsis: true,
+        tooltip: true
     }, {
         title: '发起人',
         slotName: 'username',
+        width: 140
     },
     {
         title: '提醒邮箱',
         dataIndex: 'email',
+        width: 180,
+        ellipsis: true,
+        tooltip: true
     }, {
         title: '创建时间',
-        slotName: 'create_time'
+        slotName: 'create_time',
+        width: 170
     }, {
         title: '最后更新时间',
-        slotName: 'update_time'
+        slotName: 'update_time',
+        width: 170
     }, {
         title: '状态',
-        slotName: 'state'
+        slotName: 'state',
+        width: 110
     }, {
         title: '操作',
-        slotName: 'optional'
+        slotName: 'optional',
+        width: 120,
+        fixed: 'right'
     }
 ]
 
@@ -216,7 +230,39 @@ const stramptoTime = (time) => {
 </script>
 
 <style scoped>
+.query-row {
+    margin-bottom: 10px;
+}
+
+.query-main {
+    min-width: 0;
+}
+
+.query-actions-space {
+    width: 100%;
+}
+
 .table {
     margin-top: 15px;
 }
+
+@media (max-width: 768px) {
+    .query-row {
+        display: block;
+    }
+
+    .query-divider {
+        display: none;
+    }
+
+    .query-actions-space {
+        margin-top: 4px;
+        flex-direction: row !important;
+        gap: 10px !important;
+    }
+
+    .query-actions-space :deep(.arco-btn) {
+        flex: 1;
+    }
+}
 </style>

+ 97 - 13
src/pages/lepao/accountList/index.vue

@@ -1,11 +1,11 @@
-<template>
+<template>
   <div class="store-page">
     <Breadcrumb />
 
     <userCard />
 
     <a-card title="账号列表" style="margin-top: 15px;">
-      <div class="buttonGroup">
+      <div class="buttonGroup buttonGroup-desktop">
         <a-button v-if="hasPermission('action.lepao.addAccount')" type="primary" size="large" @click="editAccount()">
           <template #icon>
             <icon-plus />
@@ -13,28 +13,28 @@
           添加账号
         </a-button>
 
-        <a-button size="large" @click="download('windows')" style="margin-left: 10px;">
+        <a-button size="large" @click="download('windows')">
           <template #icon>
             <icon-desktop />
           </template>
           Windows操作说明
         </a-button>
 
-        <a-button size="large" @click="download('android')" style="margin-left: 10px;">
+        <a-button size="large" @click="download('android')">
           <template #icon>
             <icon-mobile />
           </template>
           安卓手机操作说明
         </a-button>
 
-        <a-button size="large" @click="download('iphone')" style="margin-left: 10px;">
+        <a-button size="large" @click="download('iphone')">
           <template #icon>
             <icon-mobile />
           </template>
           iPhone操作说明
         </a-button>
 
-        <a-button size="large" @click="download('page')" style="margin-left: 10px;" v-if="!isElectron()">
+        <a-button size="large" @click="download('page')" v-if="!isElectron()">
           <template #icon>
             <icon-download />
           </template>
@@ -42,8 +42,41 @@
         </a-button>
       </div>
 
-      <a-row class="queryForm app-query-form">
-        <a-col :flex="1">
+      <div class="buttonGroup buttonGroup-mobile">
+        <a-button v-if="hasPermission('action.lepao.addAccount')" type="primary" size="large" @click="editAccount()">
+          <template #icon>
+            <icon-plus />
+          </template>
+          添加账号
+        </a-button>
+        <a-dropdown trigger="click" position="br">
+          <a-button size="large" class="more-actions-btn">
+            更多操作
+            <icon-down />
+          </a-button>
+          <template #content>
+            <a-doption @click="download('windows')">
+              <icon-desktop />
+              Windows操作说明
+            </a-doption>
+            <a-doption @click="download('android')">
+              <icon-mobile />
+              安卓手机操作说明
+            </a-doption>
+            <a-doption @click="download('iphone')">
+              <icon-mobile />
+              iPhone操作说明
+            </a-doption>
+            <a-doption v-if="!isElectron()" @click="download('page')">
+              <icon-download />
+              客户端/登录器下载
+            </a-doption>
+          </template>
+        </a-dropdown>
+      </div>
+
+      <a-row class="queryForm app-query-form query-row">
+        <a-col :flex="'1000px'" class="query-main">
           <a-form :model="queryDataForm" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
             label-align="left">
             <a-row :gutter="16">
@@ -95,15 +128,16 @@
               <a-col :span="6">
                 <a-form-item field="queryTime" label="更新时间">
                   <a-range-picker v-model="queryDataForm.queryTime" show-time format="YY-MM-DD HH:mm" value-format="x"
+                    popup-container="body"
                     @ok="search()" />
                 </a-form-item>
               </a-col>
             </a-row>
           </a-form>
         </a-col>
-        <a-divider style="height: 84px" direction="vertical" />
-        <a-col :flex="'86px'" style="text-align: right">
-          <a-space direction="vertical" :size="18">
+        <a-divider class="query-divider" style="height: 84px" direction="vertical" />
+        <a-col :flex="1" class="app-query-actions">
+          <a-space class="query-actions-space" direction="vertical" :size="18">
             <a-button type="primary" @click="search">
               <template #icon>
                 <icon-search />
@@ -123,7 +157,7 @@
       <a-alert v-if="notice" style="margin-bottom: 15px;">{{ notice }}</a-alert>
 
       <a-table :data="data" :bordered="false" hoverable class="table table-clickable" :loading="loading" expandable :scroll="{
-        x: 1600
+        x: 2400
       }" @row-click="onRowClick" :pagination="{
         showPageSize: true,
         showJumper: true,
@@ -860,7 +894,28 @@ onUnmounted(() => {
 <style scoped lang="less">
 .queryForm {
   margin-top: 20px;
-  padding: 0 15px
+  padding: 0;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.buttonGroup {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  margin-bottom: 12px;
+}
+
+.buttonGroup-mobile {
+  display: none;
+}
+
+.query-main {
+  min-width: 0;
+}
+
+.query-actions-space {
+  width: 100%;
 }
 
 .table-clickable {
@@ -920,4 +975,33 @@ onUnmounted(() => {
 .auto-fill-tip {
   color: rgb(var(--gray-6));
 }
+
+@media (max-width: 768px) {
+  .buttonGroup-desktop {
+    display: none;
+  }
+
+  .buttonGroup-mobile {
+    display: flex;
+    align-items: center;
+  }
+
+  .query-row {
+    display: block;
+  }
+
+  .query-divider {
+    display: none;
+  }
+
+  .query-actions-space {
+    margin-top: 4px;
+    flex-direction: row !important;
+    gap: 10px !important;
+  }
+
+  .query-actions-space :deep(.arco-btn) {
+    flex: 1;
+  }
+}
 </style>

+ 2 - 1
src/pages/lepao/countLedger/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
   <div class="store-page">
     <Breadcrumb />
 
@@ -51,6 +51,7 @@
         :data="data"
         :loading="loading"
         :bordered="false"
+        :scroll="{ x: 980 }"
         :summary="tableSummary"
         :pagination="{
           showPageSize: true,

+ 39 - 7
src/pages/lepao/lepaoRecords/index.vue

@@ -3,9 +3,9 @@
   <div class="store-page">
     <Breadcrumb />
     <a-card title="乐跑记录">
-      <a-row>
-        <a-col :flex="'1000px'">
-          <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
+      <a-row class="query-row">
+        <a-col :flex="'1000px'" class="query-main">
+          <a-form class="queryForm app-query-form" :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
             label-align="left">
             <a-row :gutter="16">
               <a-col :span="12">
@@ -33,9 +33,9 @@
             </a-row>
           </a-form>
         </a-col>
-        <a-divider style="height: 84px" direction="vertical" />
-        <a-col :flex="1">
-          <a-space direction="vertical" :size="18">
+        <a-divider class="query-divider" style="height: 84px" direction="vertical" />
+        <a-col :flex="1" class="app-query-actions">
+          <a-space class="query-actions-space" direction="vertical" :size="18">
             <a-button type="primary" @click="search">
               <template #icon>
                 <icon-search />
@@ -54,7 +54,7 @@
 
       <a-alert v-if="notice" style="margin-bottom: 15px;">{{ notice }}</a-alert>
 
-      <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" :pagination="{
+      <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" :scroll="{ x: 1300 }" :pagination="{
         showPageSize: true,
         showJumper: true,
         showTotal: true,
@@ -288,6 +288,18 @@ onMounted(() => {
 </script>
 
 <style scoped lang="less">
+.query-row {
+  margin-bottom: 10px;
+}
+
+.query-main {
+  min-width: 0;
+}
+
+.query-actions-space {
+  width: 100%;
+}
+
 .table {
   margin-top: 15px;
   font-family: -apple-system, BlinkMacSystemFont;
@@ -331,4 +343,24 @@ onMounted(() => {
   display: flex;
   justify-content: space-between;
 }
+
+@media (max-width: 768px) {
+  .query-row {
+    display: block;
+  }
+
+  .query-divider {
+    display: none;
+  }
+
+  .query-actions-space {
+    margin-top: 4px;
+    flex-direction: row !important;
+    gap: 10px !important;
+  }
+
+  .query-actions-space :deep(.arco-btn) {
+    flex: 1;
+  }
+}
 </style>

+ 34 - 3
src/pages/lepao/lepaoRecords/recordDetail.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
     <div class="store-page">
         <Breadcrumb />
         <a-card title="记录详情">
@@ -8,7 +8,11 @@
                     <a-skeleton-line :rows="5" />
                 </a-space>
             </a-skeleton>
-            <a-descriptions :data="info" :column="2" />
+            <a-descriptions
+                :data="info"
+                :column="isMobile ? 1 : 2"
+                class="record-descriptions"
+            />
             <MapContainer v-if="showMap" :point_list="data.result.point_list" :log_list="data.point_data" :pathData="data.data" threeD
                 style="margin-top: 10px;" />
         </a-card>
@@ -16,7 +20,7 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, onUnmounted } from 'vue'
 import { GetRecordDetail } from '@/api/lepao'
 import { Notification } from '@arco-design/web-vue'
 import { useRoute } from 'vue-router'
@@ -29,6 +33,8 @@ const loading = ref(false)
 const showMap = ref(false)
 const data = ref({})
 const info = ref([])
+const isMobile = ref(false)
+let mobileResizeHandler = null
 
 const getRecordDetail = async (routeId) => {
     try {
@@ -72,9 +78,18 @@ const getRecordDetail = async (routeId) => {
 }
 
 onMounted(() => {
+    mobileResizeHandler = () => {
+        isMobile.value = window.innerWidth <= 768
+    }
+    mobileResizeHandler()
+    window.addEventListener('resize', mobileResizeHandler)
     getRecordDetail(route.params.id)
 })
 
+onUnmounted(() => {
+    if (mobileResizeHandler) window.removeEventListener('resize', mobileResizeHandler)
+})
+
 const stramptoTime = (time) => {
     return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
 }
@@ -97,4 +112,20 @@ function formatSecondsToMinSec(totalSeconds) {
 </script>
 
 <style scoped lang="less">
+.record-descriptions {
+    margin-top: 8px;
+}
+
+@media (max-width: 768px) {
+    .record-descriptions :deep(.arco-descriptions-item-label) {
+        white-space: nowrap;
+        width: 88px !important;
+    }
+
+    .record-descriptions :deep(.arco-descriptions-item-value) {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+}
 </style>

+ 25 - 0
src/pages/store/goodsDetail/index.vue

@@ -476,4 +476,29 @@ getGoodsDetail()
   background: @store-primary !important;
   border-color: @store-primary !important;
 }
+
+@media (max-width: 768px) {
+  .coupon-row {
+    flex-direction: column;
+  }
+
+  .coupon-row :deep(.arco-btn) {
+    width: 100%;
+  }
+
+  :deep(.checkout-drawer .arco-drawer) {
+    width: 100vw !important;
+    max-width: 100vw !important;
+  }
+
+  :deep(.checkout-drawer .arco-drawer-footer) {
+    display: grid;
+    grid-template-columns: 1fr;
+    gap: 10px;
+  }
+
+  :deep(.checkout-drawer .arco-drawer-footer .arco-btn) {
+    width: 100%;
+  }
+}
 </style>

+ 69 - 5
src/pages/store/orders/orderDetail/index.vue

@@ -20,10 +20,16 @@
         </div>
 
         <a-card :bordered="false" class="status-card">
-          <a-steps :current="stepCurrent" :status="stepStatus" label-placement="vertical" class="steps">
-            <a-step :description="stepDescriptions[0]">{{ stepLabels[0] }}</a-step>
-            <a-step :description="stepDescriptions[1]">{{ stepLabels[1] }}</a-step>
-            <a-step :description="stepDescriptions[2]">{{ stepLabels[2] }}</a-step>
+          <a-steps
+            :current="stepCurrent"
+            :status="stepStatus"
+            direction="horizontal"
+            label-placement="vertical"
+            class="steps"
+          >
+            <a-step :description="displayStepDescriptions[0]">{{ stepLabels[0] }}</a-step>
+            <a-step :description="displayStepDescriptions[1]">{{ stepLabels[1] }}</a-step>
+            <a-step :description="displayStepDescriptions[2]">{{ stepLabels[2] }}</a-step>
           </a-steps>
           <div class="status-badge-wrap">
             <OrderStateTag :state="data?.state ?? 0" />
@@ -94,6 +100,7 @@ const data = ref({})
 const payData = ref({})
 const content = ref('')
 const paymentCountdownText = ref('')
+const isMobile = ref(false)
 
 const hasPay = computed(() => hasPayData(payData.value))
 
@@ -101,9 +108,13 @@ const stepCurrent = ref(1)
 const stepStatus = ref('process')
 const stepLabels = ref(['待支付', '待处理', '已完成'])
 const stepDescriptions = ref(['', '', ''])
+const displayStepDescriptions = computed(() =>
+  isMobile.value ? ['', '', ''] : stepDescriptions.value
+)
 
 const PAY_TIMEOUT_SEC = 300
 let timer = null
+let resizeHandler = null
 
 const updateStepState = () => {
   const state = data.value?.state
@@ -199,13 +210,22 @@ const stopPolling = () => {
 }
 
 onMounted(async () => {
+  const syncMobile = () => {
+    isMobile.value = window.innerWidth <= 768
+  }
+  resizeHandler = syncMobile
+  syncMobile()
+  window.addEventListener('resize', resizeHandler)
   loading.value = true
   await getOrderDetail()
   loading.value = false
   if (data.value?.state === 0) startPolling()
 })
 
-onUnmounted(stopPolling)
+onUnmounted(() => {
+  stopPolling()
+  if (resizeHandler) window.removeEventListener('resize', resizeHandler)
+})
 
 const pay = () => {
   if (!hasPay.value) {
@@ -335,4 +355,48 @@ function openPaymentWindow(payUrl, formData) {
   flex-wrap: wrap;
   margin-top: 8px;
 }
+
+@media (max-width: 768px) {
+  .pay-banner {
+    padding: 14px 12px;
+  }
+
+  .steps {
+    margin: 4px 0 10px;
+    overflow: visible;
+  }
+
+  .steps :deep(.arco-steps-item) {
+    flex: 1 1 0;
+    min-width: 0;
+  }
+
+  .steps :deep(.arco-steps-item-content) {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    text-align: center;
+    width: 100%;
+    min-width: 0;
+  }
+
+  .steps :deep(.arco-steps-item-title) {
+    font-size: 11px;
+    white-space: nowrap;
+    text-align: center;
+  }
+
+  .steps :deep(.arco-steps-item-description) {
+    display: none;
+  }
+
+  .detail-actions {
+    display: grid;
+    grid-template-columns: 1fr;
+  }
+
+  .detail-actions :deep(.arco-btn) {
+    width: 100%;
+  }
+}
 </style>

+ 165 - 3
src/style.css

@@ -30,9 +30,13 @@ a:hover {
 
 body {
   margin: 0;
-  display: flex;
-  place-items: center;
-  min-height: 100%;
+  min-height: 100vh;
+  min-width: 0;
+  width: 100%;
+}
+
+html {
+  -webkit-text-size-adjust: 100%;
 }
 
 h1 {
@@ -42,6 +46,164 @@ h1 {
 
 #app {
   width: 100%;
+  min-height: 100vh;
   padding: 0;
   margin: 0;
 }
+
+.store-page,
+.store-page .arco-card,
+.store-page .arco-card .arco-card-body {
+  min-width: 0;
+  max-width: 100%;
+  box-sizing: border-box;
+}
+
+@media (min-width: 769px) {
+  .queryForm.app-query-form,
+  .query-row {
+    display: flex !important;
+    flex-wrap: nowrap !important;
+    align-items: flex-start;
+  }
+
+  .queryForm.app-query-form > .arco-col:first-child,
+  .query-row .query-main {
+    flex: 1 1 auto !important;
+    max-width: 100% !important;
+    min-width: 0;
+  }
+
+  .queryForm.app-query-form > .arco-col:last-child,
+  .query-row > .arco-col[style*='text-align: right'],
+  .query-row .app-query-actions {
+    flex: 0 0 86px !important;
+    max-width: 86px !important;
+    text-align: right;
+  }
+}
+
+@media (max-width: 768px) {
+  .arco-modal {
+    width: calc(100vw - 24px) !important;
+    max-width: calc(100vw - 24px) !important;
+    margin: 12px auto !important;
+  }
+
+  .arco-modal .arco-modal-header {
+    padding: 14px 16px 10px;
+  }
+
+  .arco-modal .arco-modal-body,
+  .arco-modal .arco-modal-footer {
+    padding-left: 16px;
+    padding-right: 16px;
+  }
+
+  .arco-modal .arco-modal-body {
+    max-height: 70vh;
+    overflow-y: auto;
+  }
+
+  .arco-modal-confirm .arco-modal-footer {
+    display: grid;
+    grid-template-columns: 1fr;
+    gap: 8px;
+  }
+
+  .arco-modal-confirm .arco-modal-footer .arco-btn {
+    width: 100%;
+  }
+
+  .arco-drawer {
+    width: 100vw !important;
+    max-width: 100vw !important;
+  }
+
+  .arco-drawer .arco-drawer-body {
+    padding: 14px 16px 16px;
+  }
+
+  .arco-drawer .arco-drawer-footer {
+    padding: 10px 16px 14px;
+  }
+
+  .arco-tabs-nav {
+    overflow-x: auto;
+    scrollbar-width: none;
+    -ms-overflow-style: none;
+  }
+
+  .arco-tabs-nav::-webkit-scrollbar {
+    display: none;
+  }
+
+  .queryForm .arco-row > .arco-col,
+  .app-query-form .arco-row > .arco-col {
+    flex: 0 0 100% !important;
+    max-width: 100% !important;
+  }
+
+  .queryForm .arco-divider-vertical,
+  .app-query-form .arco-divider-vertical {
+    display: none !important;
+  }
+
+  .queryForm ~ .arco-col[style*='text-align: right'],
+  .app-query-actions {
+    flex: 0 0 100% !important;
+    max-width: 100% !important;
+    text-align: left !important;
+    margin-top: 6px;
+  }
+
+  .queryForm.app-query-form {
+    display: block;
+  }
+
+  .queryForm.app-query-form > .arco-col {
+    flex: 0 0 100% !important;
+    max-width: 100% !important;
+  }
+
+  .queryForm.app-query-form > .arco-col:last-child {
+    margin-top: 6px;
+    text-align: left !important;
+  }
+
+  .queryForm.app-query-form > .arco-col:last-child .arco-space {
+    width: 100%;
+    flex-direction: row !important;
+    gap: 10px !important;
+  }
+
+  .queryForm.app-query-form > .arco-col:last-child .arco-space .arco-space-item {
+    flex: 1;
+  }
+
+  .queryForm.app-query-form > .arco-col:last-child .arco-btn {
+    width: 100%;
+  }
+
+  .store-page .arco-card .arco-table-th,
+  .store-page .arco-card .arco-table-td {
+    white-space: nowrap;
+  }
+
+  .store-page .arco-card .arco-form .arco-row > .arco-col {
+    flex: 0 0 100% !important;
+    max-width: 100% !important;
+  }
+
+  .store-page .arco-card .arco-form .arco-input-wrapper,
+  .store-page .arco-card .arco-form .arco-select-view,
+  .store-page .arco-card .arco-form .arco-picker,
+  .store-page .arco-card .arco-form .arco-input-number {
+    width: 100% !important;
+    max-width: 100%;
+  }
+
+  .store-page .arco-card .arco-form .arco-form-item-label-col > label {
+    white-space: nowrap;
+  }
+}

+ 202 - 0
src/styles/auth-marketing.less

@@ -0,0 +1,202 @@
+@import './store-theme.less';
+
+.auth-page {
+  position: relative;
+  z-index: 1;
+  min-height: 100vh;
+  min-height: 100dvh;
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  background: linear-gradient(165deg, @store-bg 0%, fade(@store-accent, 12%) 45%, @store-bg 100%);
+  box-sizing: border-box;
+  overflow-x: hidden;
+}
+
+.auth-topbar {
+  position: relative;
+  z-index: 20;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 14px @store-page-padding-x;
+  background: fade(@store-card-bg, 92%);
+  border-bottom: 1px solid @store-card-border;
+  backdrop-filter: blur(10px);
+}
+
+.auth-topbar__brand,
+a.auth-topbar__brand {
+  display: inline-flex;
+  align-items: center;
+  gap: 10px;
+  text-decoration: none;
+  color: @store-primary;
+  font-weight: 600;
+  font-size: 1.05rem;
+}
+
+.auth-topbar__link {
+  font-size: 0.9rem;
+  color: @store-accent;
+  text-decoration: none;
+  font-weight: 500;
+
+  &:hover {
+    color: @store-accent-hover;
+  }
+}
+
+.auth-page__main {
+  position: relative;
+  z-index: 10;
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 24px @store-page-padding-x 16px;
+  box-sizing: border-box;
+  pointer-events: auto;
+}
+
+.auth-card {
+  position: relative;
+  z-index: 10;
+  width: 100%;
+  max-width: min(100%, 24rem);
+  margin: 0 auto;
+  padding: 28px 24px 32px;
+  box-sizing: border-box;
+  border-radius: @store-radius;
+  background: @store-card-bg;
+  border: 1px solid @store-card-border;
+  box-shadow: @store-shadow-hover;
+  pointer-events: auto;
+}
+
+.auth-form-panel {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+  gap: 4px;
+  width: 100%;
+  pointer-events: auto;
+
+  .form {
+    width: 100%;
+    pointer-events: auto;
+  }
+
+  :deep(.arco-form-item) {
+    margin-bottom: 14px;
+    pointer-events: auto;
+  }
+
+  .formitem,
+  :deep(.arco-btn.formitem) {
+    width: 100%;
+    min-height: 44px;
+    margin-top: 8px;
+    border-radius: 999px;
+    background: @store-primary !important;
+    border-color: @store-primary !important;
+    pointer-events: auto;
+    cursor: pointer;
+
+    &:hover:not(:disabled) {
+      background: @store-primary-light !important;
+      border-color: @store-primary-light !important;
+    }
+  }
+
+  .logo {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 8px;
+    margin-bottom: 24px;
+    text-align: center;
+
+    img {
+      flex-shrink: 0;
+    }
+
+    .title {
+      color: @store-primary;
+      font-size: 1.15rem;
+      font-weight: 600;
+    }
+  }
+
+  .forgetpass {
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    align-items: center;
+    gap: 8px;
+
+    :deep(.arco-btn-text) {
+      color: @store-accent;
+      pointer-events: auto;
+      cursor: pointer;
+    }
+  }
+
+  .captcha {
+    max-height: 36px;
+    cursor: pointer;
+    pointer-events: auto;
+  }
+
+  :deep(.arco-input-wrapper) {
+    pointer-events: auto;
+    background: @store-card-bg;
+    border: 1px solid @store-card-border;
+    border-radius: @store-radius-sm;
+
+    &.arco-input-focus {
+      border-color: @store-accent;
+      box-shadow: 0 0 0 3px fade(@store-accent, 18%);
+    }
+  }
+
+  :deep(.arco-input),
+  :deep(.arco-input-password) {
+    pointer-events: auto;
+  }
+}
+
+.uni-login-panel {
+  .uniLoginButton {
+    display: flex;
+    justify-content: center;
+    gap: 32px;
+    padding: 12px 0;
+  }
+
+  .tip {
+    color: @store-text-muted;
+    font-size: 0.85rem;
+    text-align: center;
+  }
+
+  :deep(.arco-avatar) {
+    background: fade(@store-accent, 22%) !important;
+    color: @store-primary !important;
+    cursor: pointer;
+    pointer-events: auto;
+  }
+}
+
+.auth-page__legal {
+  position: relative;
+  z-index: 10;
+  margin: 0;
+  padding: 12px @store-page-padding-x 20px;
+  text-align: center;
+  font-size: 12px;
+  color: @store-text-muted;
+}

+ 81 - 16
src/styles/store-theme.less

@@ -12,8 +12,10 @@
 @store-shadow: 0 4px 24px rgba(27, 48, 34, 0.06);
 @store-shadow-hover: 0 12px 40px rgba(27, 48, 34, 0.12);
 @store-price: #e85d04;
-@store-layout-max-width: 1200px;
-@store-layout-narrow: 720px;
+// 内容区宽度:相对父级 100%,窄栏用 rem 上限(非固定 px)
+@store-content-narrow: min(100%, 42rem);
+@store-page-padding-x: clamp(12px, 2.5vw, 40px);
+@store-page-padding-bottom: clamp(20px, 3vh, 40px);
 
 @app-breakpoint-sm: 576px;
 @app-breakpoint-md: 768px;
@@ -24,15 +26,11 @@
 .container,
 .store-page,
 .app-page {
-  padding: 0 12px 24px;
   width: 100%;
-  max-width: @store-layout-max-width;
+  max-width: 100%;
   margin: 0 auto;
+  padding: 0 @store-page-padding-x @store-page-padding-bottom;
   box-sizing: border-box;
-
-  @media (min-width: @app-breakpoint-md) {
-    padding: 0 20px 32px;
-  }
 }
 
 .app-page--full,
@@ -63,13 +61,15 @@
 .power-page__inner--wide,
 .order-page__inner,
 .order-detail-page__inner {
-  max-width: @store-layout-max-width;
+  max-width: 100%;
+  width: 100%;
 }
 
 .app-page__inner--narrow,
 .service-page__inner:not(.service-page__inner--wide),
 .power-page__inner:not(.power-page__inner--wide) {
-  max-width: @store-layout-narrow;
+  max-width: @store-content-narrow;
+  width: 100%;
 }
 
 // —— 页头 / 工具栏 ——
@@ -153,6 +153,8 @@
 .store-page .arco-card,
 .app-page .arco-card,
 .app-card {
+  max-width: 100%;
+  overflow: visible;
   border-radius: @store-radius;
   border: 1px solid @store-card-border;
   box-shadow: @store-shadow;
@@ -168,11 +170,7 @@
   }
 
   .arco-card-body {
-    overflow-x: auto;
-  }
-
-  .arco-table {
-    min-width: 560px;
+    overflow: visible;
   }
 }
 
@@ -216,6 +214,14 @@
     text-align: left !important;
     margin-top: 4px;
   }
+
+  .arco-modal {
+    max-width: calc(100vw - 32px);
+  }
+
+  .arco-drawer {
+    max-width: 100vw;
+  }
 }
 
 // —— Spin ——
@@ -270,7 +276,66 @@
 // —— 弹窗(移动端宽度)——
 @media (max-width: @app-breakpoint-md) {
   .arco-modal {
+    width: calc(100vw - 24px) !important;
     max-width: calc(100vw - 24px) !important;
-    margin: 12px;
+    margin: 12px auto !important;
+  }
+
+  .arco-modal .arco-modal-body {
+    max-height: 70vh;
+    overflow-y: auto;
+  }
+
+  .arco-modal .arco-modal-header {
+    padding: 14px 16px 10px;
+  }
+
+  .arco-modal .arco-modal-body,
+  .arco-modal .arco-modal-footer {
+    padding-left: 16px;
+    padding-right: 16px;
+  }
+
+  .arco-drawer {
+    width: 100vw !important;
+    max-width: 100vw !important;
+  }
+
+  .arco-drawer .arco-drawer-body {
+    padding: 14px 16px 16px;
+  }
+
+  .arco-drawer .arco-drawer-footer {
+    padding: 10px 16px 14px;
+  }
+
+  .arco-drawer .arco-drawer-footer .arco-btn {
+    width: 100%;
+  }
+
+  .arco-tabs-nav {
+    overflow-x: auto;
+    overflow-y: hidden;
+    scrollbar-width: none;
+    -ms-overflow-style: none;
+  }
+
+  .arco-tabs-nav::-webkit-scrollbar {
+    display: none;
+  }
+
+  .arco-tabs-nav .arco-tabs-nav-type-line,
+  .arco-tabs-nav .arco-tabs-nav-type-rounded,
+  .arco-tabs-nav .arco-tabs-nav-type-card {
+    min-width: max-content;
+  }
+
+  .arco-descriptions-layout-inline-horizontal .arco-descriptions-item {
+    width: 100%;
+  }
+
+  .arco-descriptions-layout-inline-horizontal .arco-descriptions-item-label,
+  .arco-descriptions-layout-inline-horizontal .arco-descriptions-item-value {
+    width: auto !important;
   }
 }