Browse Source

feat: 全站 UI 重构与设计系统(响应式)

- 扩展 store-theme 为统一设计令牌(页面容器、卡片、筛选表单、表格滚动)
- 新增 AppPageShell 布局组件;layout 支持 lg 断点侧栏折叠与移动端全宽内容区
- 将旧 .container 页面迁移为 .store-page,并修复批量样式清理导致的构建问题
- 趣选书/下载页等补充移动端布局;修正 viewport 为 device-width

Co-authored-by: Cursor <cursoragent@cursor.com>
Pchen. 3 weeks ago
parent
commit
b13301b7c4
34 changed files with 464 additions and 281 deletions
  1. 1 1
      index.html
  2. 2 0
      src/components/index.js
  3. 65 0
      src/components/layout/AppPageShell.vue
  4. 31 15
      src/layout/default-layout.vue
  5. 2 6
      src/pages/User/info/index.vue
  6. 2 6
      src/pages/User/setting/index.vue
  7. 2 6
      src/pages/admin/goods/addGoods.vue
  8. 2 6
      src/pages/admin/goods/sendCountRequestList.vue
  9. 2 6
      src/pages/admin/lepaoAccount/accountList.vue
  10. 2 5
      src/pages/admin/lepaoBindAudit/index.vue
  11. 3 7
      src/pages/admin/lepaoCountLedger/index.vue
  12. 2 6
      src/pages/admin/lepaoRecords/lepaoRecords.vue
  13. 2 5
      src/pages/admin/lepaoRecords/recordDetail.vue
  14. 2 6
      src/pages/admin/mqQueue/index.vue
  15. 2 5
      src/pages/admin/notice/index.vue
  16. 2 5
      src/pages/admin/popup/index.vue
  17. 2 6
      src/pages/admin/reqLog/index.vue
  18. 2 6
      src/pages/admin/user/userList.vue
  19. 64 26
      src/pages/admin/workOrder/orderDetail.vue
  20. 2 6
      src/pages/admin/workOrder/orderList.vue
  21. 33 31
      src/pages/download/index.vue
  22. 2 9
      src/pages/face/components/faceReco.vue
  23. 3 7
      src/pages/lepao/accountList/index.vue
  24. 3 7
      src/pages/lepao/countLedger/index.vue
  25. 2 6
      src/pages/lepao/lepaoRecords/index.vue
  26. 2 5
      src/pages/lepao/lepaoRecords/recordDetail.vue
  27. 2 6
      src/pages/openProxy/index.vue
  28. 8 11
      src/pages/path/pathDetail.vue
  29. 2 6
      src/pages/path/pathList.vue
  30. 19 8
      src/pages/qxs/getBookList.vue
  31. 1 1
      src/pages/store/goodsDetail/index.vue
  32. 1 1
      src/pages/store/orders/orderDetail/index.vue
  33. 2 6
      src/pages/store/sendCountRecords/index.vue
  34. 190 48
      src/styles/store-theme.less

+ 1 - 1
index.html

@@ -3,7 +3,7 @@
   <head>
   <head>
     <meta charset="UTF-8" />
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/logo.svg" />
     <link rel="icon" type="image/svg+xml" href="/logo.svg" />
-    <meta name="viewport" content="width=1280">
+    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
     <title>RunForge - 智能校园乐跑平台</title>
     <title>RunForge - 智能校园乐跑平台</title>
   </head>
   </head>
   <body>
   <body>

+ 2 - 0
src/components/index.js

@@ -16,6 +16,7 @@ import Chart from './Chart/index.vue'
 
 
 import Breadcrumb from './Breadcrumb/index.vue'
 import Breadcrumb from './Breadcrumb/index.vue'
 import EmailAutoComplete from './EmailAutoComplete/index.vue'
 import EmailAutoComplete from './EmailAutoComplete/index.vue'
+import AppPageShell from './layout/AppPageShell.vue'
 
 
 use([
 use([
   CanvasRenderer,
   CanvasRenderer,
@@ -41,5 +42,6 @@ export default {
     Vue.component('Chart', Chart)
     Vue.component('Chart', Chart)
     Vue.component('Breadcrumb', Breadcrumb)
     Vue.component('Breadcrumb', Breadcrumb)
     Vue.component('EmailAutoComplete', EmailAutoComplete)
     Vue.component('EmailAutoComplete', EmailAutoComplete)
+    Vue.component('AppPageShell', AppPageShell)
   },
   },
 }
 }

+ 65 - 0
src/components/layout/AppPageShell.vue

@@ -0,0 +1,65 @@
+<template>
+  <div class="store-page app-page" :class="widthClass">
+    <div v-if="useInner" class="app-page__inner" :class="innerWidthClass">
+      <Breadcrumb v-if="showBreadcrumb" />
+      <header v-if="title || $slots.header" class="app-page-header">
+        <slot name="header">
+          <div class="app-page-header__text">
+            <h1 v-if="title" class="store-section-title">{{ title }}</h1>
+            <p v-if="description" class="store-section-desc">{{ description }}</p>
+          </div>
+          <div v-if="$slots.actions" class="app-page-header__actions">
+            <slot name="actions" />
+          </div>
+        </slot>
+      </header>
+      <a-alert v-if="notice" type="info" closable class="app-notice">{{ notice }}</a-alert>
+      <a-spin :loading="loading" class="store-spin">
+        <slot />
+      </a-spin>
+    </div>
+    <template v-else>
+      <Breadcrumb v-if="showBreadcrumb" />
+      <header v-if="title || $slots.header" class="app-page-header">
+        <slot name="header">
+          <div class="app-page-header__text">
+            <h1 v-if="title" class="store-section-title">{{ title }}</h1>
+            <p v-if="description" class="store-section-desc">{{ description }}</p>
+          </div>
+          <div v-if="$slots.actions" class="app-page-header__actions">
+            <slot name="actions" />
+          </div>
+        </slot>
+      </header>
+      <a-alert v-if="notice" type="info" closable class="app-notice">{{ notice }}</a-alert>
+      <a-spin :loading="loading" class="store-spin">
+        <slot />
+      </a-spin>
+    </template>
+  </div>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+const props = defineProps({
+  title: { type: String, default: '' },
+  description: { type: String, default: '' },
+  notice: { type: String, default: '' },
+  loading: { type: Boolean, default: false },
+  showBreadcrumb: { type: Boolean, default: true },
+  /** full | wide | narrow — 对应 1200 / 1200 inner / 720 inner */
+  width: { type: String, default: 'wide' },
+  useInner: { type: Boolean, default: true }
+})
+
+const widthClass = computed(() => {
+  if (props.width === 'full') return 'app-page--full'
+  return 'app-page--wide'
+})
+
+const innerWidthClass = computed(() => {
+  if (props.width === 'narrow') return 'app-page__inner--narrow'
+  return 'app-page__inner--wide'
+})
+</script>

+ 31 - 15
src/layout/default-layout.vue

@@ -3,12 +3,18 @@
     <a-layout-header class="layout-navbar">
     <a-layout-header class="layout-navbar">
       <Navbar />
       <Navbar />
     </a-layout-header>
     </a-layout-header>
-    <a-layout>
-      <a-layout-sider class="layout-sider">
+    <a-layout class="layout-body">
+      <a-layout-sider
+        class="layout-sider"
+        :width="200"
+        :collapsed-width="0"
+        collapsible
+        breakpoint="lg"
+      >
         <Menu class="menu-wrapper" />
         <Menu class="menu-wrapper" />
       </a-layout-sider>
       </a-layout-sider>
       <a-layout-content class="layout-content">
       <a-layout-content class="layout-content">
-        <PageLayout style="margin-bottom: 30px;" />
+        <PageLayout class="page-layout-main" />
         <Footer />
         <Footer />
       </a-layout-content>
       </a-layout-content>
     </a-layout>
     </a-layout>
@@ -127,12 +133,24 @@ const handlePopupClose = async () => {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
+@import '@/styles/store-theme.less';
+
 @nav-size-height: 60px;
 @nav-size-height: 60px;
 
 
+.page-layout-main {
+  margin-bottom: 24px;
+  min-height: calc(100vh - @nav-size-height - 80px);
+}
+
 .layout {
 .layout {
-  min-width: 1280px;
+  min-width: 0;
   width: 100%;
   width: 100%;
-  height: 100%;
+  min-height: 100vh;
+}
+
+.layout-body {
+  margin-top: @nav-size-height;
+  min-height: calc(100vh - @nav-size-height);
 }
 }
 
 
 .layout-navbar {
 .layout-navbar {
@@ -145,11 +163,10 @@ const handlePopupClose = async () => {
 }
 }
 
 
 .layout-sider {
 .layout-sider {
-  position: fixed;
-  top: 0;
-  left: 0;
+  position: sticky;
+  top: @nav-size-height;
   z-index: 99;
   z-index: 99;
-  height: 100%;
+  height: calc(100vh - @nav-size-height);
   transition: all 0.2s cubic-bezier(0.34, 0.69, 0.1, 1);
   transition: all 0.2s cubic-bezier(0.34, 0.69, 0.1, 1);
 
 
   &::after {
   &::after {
@@ -190,13 +207,12 @@ const handlePopupClose = async () => {
 }
 }
 
 
 .layout-content {
 .layout-content {
-  position: absolute;
-  top: @nav-size-height;
-  left: 200px;
-  width: calc(100% - 200px);
-  min-height: calc(100% - @nav-size-height);
+  flex: 1;
+  min-width: 0;
+  min-height: calc(100vh - @nav-size-height);
+  overflow-x: hidden;
   overflow-y: auto;
   overflow-y: auto;
-  background-color: var(--color-fill-2);
+  background-color: @store-bg;
   transition: padding 0.2s cubic-bezier(0.34, 0.69, 0.1, 1);
   transition: padding 0.2s cubic-bezier(0.34, 0.69, 0.1, 1);
 
 
   &::-webkit-scrollbar {
   &::-webkit-scrollbar {

+ 2 - 6
src/pages/User/info/index.vue

@@ -1,6 +1,6 @@
-<template>
+<template>
   
   
-  <div class="container">
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
     <UserInfoHeader />
     <UserInfoHeader />
     <div class="content">
     <div class="content">
@@ -23,10 +23,6 @@ import MyProject from './components/my-project.vue'
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .content {
 .content {
   display: flex;
   display: flex;
   margin-top: 12px;
   margin-top: 12px;

+ 2 - 6
src/pages/User/setting/index.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
     <a-row style="margin-bottom: 16px">
     <a-row style="margin-bottom: 16px">
       <a-col :span="24">
       <a-col :span="24">
@@ -32,10 +32,6 @@ import SocialBindings from './components/social-bindings.vue'
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .wrapper {
 .wrapper {
   padding: 20px 0 0 20px;
   padding: 20px 0 0 20px;
   min-height: 580px;
   min-height: 580px;

+ 2 - 6
src/pages/admin/goods/addGoods.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
 
 
     <a-card :bordered="false" class="page-card" :title="isEdit ? '编辑商品' : '新增商品'">
     <a-card :bordered="false" class="page-card" :title="isEdit ? '编辑商品' : '新增商品'">
@@ -254,10 +254,6 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-  padding: 0 20px 20px;
-}
-
 .page-card {
 .page-card {
   border-radius: 12px;
   border-radius: 12px;
 }
 }

+ 2 - 6
src/pages/admin/goods/sendCountRequestList.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
 
 
     <a-card title="赠送审核列表">
     <a-card title="赠送审核列表">
@@ -272,10 +272,6 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .table {
 .table {
   margin-top: 15px;
   margin-top: 15px;
 }
 }

+ 2 - 6
src/pages/admin/lepaoAccount/accountList.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
 
 
         <a-card title="乐跑账号管理">
         <a-card title="乐跑账号管理">
@@ -847,10 +847,6 @@ const autoTimeLabel = (record) => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-    padding: 0 20px 20px 20px;
-}
-
 .table-clickable {
 .table-clickable {
     :deep(.arco-table-tr) {
     :deep(.arco-table-tr) {
         cursor: pointer;
         cursor: pointer;

+ 2 - 5
src/pages/admin/lepaoBindAudit/index.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
     <a-card title="绑定解绑审计">
     <a-card title="绑定解绑审计">
       <a-row>
       <a-row>
@@ -255,9 +255,6 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-  padding: 0 20px 20px 20px;
-}
 .table {
 .table {
   margin-top: 16px;
   margin-top: 16px;
 }
 }

+ 3 - 7
src/pages/admin/lepaoCountLedger/index.vue

@@ -1,9 +1,9 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
 
 
     <a-card title="乐跑次数明细">
     <a-card title="乐跑次数明细">
-      <a-row class="queryForm">
+      <a-row class="queryForm app-query-form">
         <a-col :flex="1">
         <a-col :flex="1">
           <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }" label-align="left">
           <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }" label-align="left">
             <a-row :gutter="16">
             <a-row :gutter="16">
@@ -260,10 +260,6 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .queryForm {
 .queryForm {
   margin-bottom: 16px;
   margin-bottom: 16px;
 }
 }

+ 2 - 6
src/pages/admin/lepaoRecords/lepaoRecords.vue

@@ -1,6 +1,6 @@
-<template>
+<template>
 
 
-  <div class="container">
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
     <a-card title="乐跑记录">
     <a-card title="乐跑记录">
       <a-row>
       <a-row>
@@ -278,10 +278,6 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .table {
 .table {
   font-family: -apple-system, BlinkMacSystemFont;
   font-family: -apple-system, BlinkMacSystemFont;
   margin-top: 15px;
   margin-top: 15px;

+ 2 - 5
src/pages/admin/lepaoRecords/recordDetail.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
         <a-card title="记录详情">
         <a-card title="记录详情">
             <a-skeleton animation :loading="loading">
             <a-skeleton animation :loading="loading">
@@ -98,7 +98,4 @@ function formatSecondsToMinSec(totalSeconds) {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-    padding: 0 20px 20px 20px;
-}
 </style>
 </style>

+ 2 - 6
src/pages/admin/mqQueue/index.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
 
 
     <a-card title="队列概览" class="card-block">
     <a-card title="队列概览" class="card-block">
@@ -303,10 +303,6 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .card-block {
 .card-block {
   margin-bottom: 16px;
   margin-bottom: 16px;
 }
 }

+ 2 - 5
src/pages/admin/notice/index.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
     <a-card title="横幅公告管理">
     <a-card title="横幅公告管理">
       <a-space style="margin-bottom: 12px;">
       <a-space style="margin-bottom: 12px;">
@@ -168,7 +168,4 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-  padding: 0 20px 20px 20px;
-}
 </style>
 </style>

+ 2 - 5
src/pages/admin/popup/index.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
     <a-card title="首页弹窗公告">
     <a-card title="首页弹窗公告">
       <a-space style="margin-bottom: 12px;">
       <a-space style="margin-bottom: 12px;">
@@ -279,7 +279,4 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-  padding: 0 20px 20px 20px;
-}
 </style>
 </style>

+ 2 - 6
src/pages/admin/reqLog/index.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
 
 
         <a-card title="请求日志">
         <a-card title="请求日志">
@@ -284,10 +284,6 @@ const stramptoTime = (time) => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-    padding: 0 20px 20px 20px;
-}
-
 .table {
 .table {
     margin-top: 15px;
     margin-top: 15px;
 }
 }

+ 2 - 6
src/pages/admin/user/userList.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
 
 
         <a-card title="用户管理">
         <a-card title="用户管理">
@@ -636,10 +636,6 @@ const formatLastLoginType = (type) => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-    padding: 0 20px 20px 20px;
-}
-
 .table {
 .table {
     margin-top: 15px;
     margin-top: 15px;
 }
 }

+ 64 - 26
src/pages/admin/workOrder/orderDetail.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
         <a-card title="工单详情" :loading="loading">
         <a-card title="工单详情" :loading="loading">
             <a-descriptions :data="info" :column="2" />
             <a-descriptions :data="info" :column="2" />
@@ -14,10 +14,12 @@
             <div class="message" v-for="(msg, index) in data.msg">
             <div class="message" v-for="(msg, index) in data.msg">
                 <a-divider v-if="index !== 0" />
                 <a-divider v-if="index !== 0" />
                 <div class="head">
                 <div class="head">
-                    <a-avatar>
-                        <IconUser v-if="!data.userInfo[msg.uuid]?.avatar" />
-                        <img alt="" :src="data.userInfo[msg.uuid]?.avatar" v-else />
-                    </a-avatar>
+                    <div class="head__avatar-wrap">
+                        <a-avatar :size="36">
+                            <IconUser v-if="!data.userInfo[msg.uuid]?.avatar" />
+                            <img alt="" :src="data.userInfo[msg.uuid]?.avatar" v-else />
+                        </a-avatar>
+                    </div>
 
 
                     <div class="right">
                     <div class="right">
                         <div class="username">
                         <div class="username">
@@ -65,7 +67,7 @@
         </a-card>
         </a-card>
 
 
         <a-card title="回复工单" style="margin-top: 15px;" v-if="data.state !== 2">
         <a-card title="回复工单" style="margin-top: 15px;" v-if="data.state !== 2">
-            <a-form :model="form" :rules="rules" layout="vertical" :style="{ width: '600px' }"
+            <a-form :model="form" :rules="rules" layout="vertical" class="reply-form"
                 @submit-success="handleSubmit">
                 @submit-success="handleSubmit">
                 <a-form-item field="content" label="内容">
                 <a-form-item field="content" label="内容">
                     <a-textarea v-model="form.content" placeholder="请详细说明您遇到的问题,详细的阐述有助于我们快速为您解决问题..." :max-length="200"
                     <a-textarea v-model="form.content" placeholder="请详细说明您遇到的问题,详细的阐述有助于我们快速为您解决问题..." :max-length="200"
@@ -257,50 +259,86 @@ const stramptoTime = (time) => {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-    padding: 0 20px 20px 20px;
-
-    .buttonGroup {
-        display: flex;
-        justify-content: center;
-        gap: 20px;
-        margin: 15px;
-    }
-}
+@import '@/styles/store-theme.less';
 
 
 .message {
 .message {
     display: flex;
     display: flex;
     flex-direction: column;
     flex-direction: column;
-    margin-bottom: 5px;
+    margin-bottom: 12px;
 
 
     .head {
     .head {
         display: flex;
         display: flex;
+        align-items: flex-start;
+        gap: 12px;
+
+        .head__avatar-wrap {
+            flex: 0 0 36px;
+            width: 36px;
+            min-width: 36px;
+            height: 36px;
+            overflow: hidden;
+            border-radius: 50%;
+
+            :deep(.arco-avatar) {
+                display: block;
+                width: 36px;
+                height: 36px;
+            }
+
+            :deep(img) {
+                width: 36px;
+                height: 36px;
+                object-fit: cover;
+            }
+        }
 
 
         .right {
         .right {
+            flex: 1;
+            min-width: 0;
             display: flex;
             display: flex;
             flex-direction: column;
             flex-direction: column;
-            margin-left: 10px;
+            gap: 4px;
 
 
             .username {
             .username {
-                font-size: 1.2em;
+                font-size: 1rem;
+                font-weight: 600;
                 display: flex;
                 display: flex;
-                gap: 10px
+                flex-wrap: wrap;
+                align-items: center;
+                gap: 8px;
+                color: @store-primary;
             }
             }
 
 
             .time {
             .time {
-                font-size: 0.9em;
-                color: #777;
+                font-size: 0.8rem;
+                color: @store-text-muted;
             }
             }
         }
         }
     }
     }
 
 
     .content {
     .content {
-        margin-top: 10px;
+        margin-top: 8px;
+        margin-left: 48px;
+        display: inline-block;
+        width: fit-content;
+        max-width: calc(100% - 48px);
+        padding: 12px 14px;
+        border-radius: @store-radius-sm;
+        background: @store-bg;
+        line-height: 1.65;
+        word-break: break-word;
+        color: @store-text-muted;
     }
     }
 
 
     .filebox {
     .filebox {
-        margin-top: -10px;
-        max-width: 500px;
+        margin-top: 8px;
+        margin-left: 48px;
+        max-width: calc(100% - 48px);
     }
     }
 }
 }
+
+.reply-form {
+    max-width: 640px;
+    width: 100%;
+}
 </style>
 </style>

+ 2 - 6
src/pages/admin/workOrder/orderList.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
 
 
         <a-card title="工单列表">
         <a-card title="工单列表">
@@ -216,10 +216,6 @@ const stramptoTime = (time) => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-    padding: 0 20px 20px 20px;
-}
-
 .table {
 .table {
     margin-top: 15px;
     margin-top: 15px;
 }
 }

+ 33 - 31
src/pages/download/index.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
 
 
         <a-card title="客户端下载">
         <a-card title="客户端下载">
@@ -81,40 +81,42 @@ const html = decodeURI(atob('JTNDaDIlM0UlRTYlQjUlOEYlRTglQTclODglRTUlOTklQTglRTY
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
-.container {
-    padding: 0 20px 20px 20px;
+@import '@/styles/store-theme.less';
 
 
-    .cardcontainer {
-        display: flex;
-        gap: 20px;
-        padding: 20px;
+.cardcontainer {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+  padding: 8px 0;
 
 
-        .downcard {
-            width: 50%;
-            min-height: 250px;
+  .downcard {
+    flex: 1 1 280px;
+    min-width: 0;
+    min-height: 250px;
 
 
-            .content {
-                display: flex;
-                flex-direction: column;
-                font-size: 1.1em;
-                gap: 10px;
+    .content {
+      display: flex;
+      flex-direction: column;
+      font-size: 1rem;
+      gap: 10px;
 
 
-                .key {
-                    display: block;
-                    font-weight: bold;
-                    min-width: 100px;
-                }
-            }
-        }
+      .key {
+        display: block;
+        font-weight: 600;
+        color: @store-primary;
+        margin-bottom: 4px;
+      }
 
 
-        .two-demo {
-            margin-left: 24px;
-            transition-property: all;
-        }
-
-        .downcard:hover {
-            transform: translateY(-1px);
-        }
+      .value {
+        color: @store-text-muted;
+        word-break: break-word;
+      }
     }
     }
+  }
+
+  .downcard:hover {
+    transform: translateY(-2px);
+    box-shadow: @store-shadow-hover;
+  }
 }
 }
 </style>
 </style>

+ 2 - 9
src/pages/face/components/faceReco.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <div v-if="step === 1">
     <div v-if="step === 1">
       <div class="faceWindowWrapper">
       <div class="faceWindowWrapper">
         <div class="faceWindow" :style="{ borderColor: tagColor }">
         <div class="faceWindow" :style="{ borderColor: tagColor }">
@@ -419,13 +419,6 @@ function retry() {
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
-.container {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  align-items: center;
-}
-
 .faceWindowWrapper {
 .faceWindowWrapper {
   position: relative;
   position: relative;
   width: 270px;
   width: 270px;

+ 3 - 7
src/pages/lepao/accountList/index.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
 
 
     <userCard />
     <userCard />
@@ -42,7 +42,7 @@
         </a-button>
         </a-button>
       </div>
       </div>
 
 
-      <a-row class="queryForm">
+      <a-row class="queryForm app-query-form">
         <a-col :flex="1">
         <a-col :flex="1">
           <a-form :model="queryDataForm" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
           <a-form :model="queryDataForm" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
             label-align="left">
             label-align="left">
@@ -858,10 +858,6 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .queryForm {
 .queryForm {
   margin-top: 20px;
   margin-top: 20px;
   padding: 0 15px
   padding: 0 15px

+ 3 - 7
src/pages/lepao/countLedger/index.vue

@@ -1,11 +1,11 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
 
 
     <userCard />
     <userCard />
 
 
     <a-card title="乐跑次数明细" style="margin-top: 15px;">
     <a-card title="乐跑次数明细" style="margin-top: 15px;">
-      <a-row class="queryForm">
+      <a-row class="queryForm app-query-form">
         <a-col :flex="'1000px'">
         <a-col :flex="'1000px'">
           <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }" label-align="left">
           <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }" label-align="left">
             <a-row :gutter="16">
             <a-row :gutter="16">
@@ -215,10 +215,6 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .queryForm {
 .queryForm {
   margin-bottom: 16px;
   margin-bottom: 16px;
 }
 }

+ 2 - 6
src/pages/lepao/lepaoRecords/index.vue

@@ -1,6 +1,6 @@
-<template>
+<template>
 
 
-  <div class="container">
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
     <a-card title="乐跑记录">
     <a-card title="乐跑记录">
       <a-row>
       <a-row>
@@ -288,10 +288,6 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .table {
 .table {
   margin-top: 15px;
   margin-top: 15px;
   font-family: -apple-system, BlinkMacSystemFont;
   font-family: -apple-system, BlinkMacSystemFont;

+ 2 - 5
src/pages/lepao/lepaoRecords/recordDetail.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
         <a-card title="记录详情">
         <a-card title="记录详情">
             <a-skeleton animation :loading="loading">
             <a-skeleton animation :loading="loading">
@@ -97,7 +97,4 @@ function formatSecondsToMinSec(totalSeconds) {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-    padding: 0 20px 20px 20px;
-}
 </style>
 </style>

+ 2 - 6
src/pages/openProxy/index.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
         <a-card title="启动乐跑登录器">
         <a-card title="启动乐跑登录器">
             <a-result title="乐跑登录器未开启" subtitle="点击下方按钮开启登录器" v-if="!ready">
             <a-result title="乐跑登录器未开启" subtitle="点击下方按钮开启登录器" v-if="!ready">
@@ -97,10 +97,6 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-    padding: 0 20px 20px 20px;
-}
-
 .output {
 .output {
     margin: 10px;
     margin: 10px;
     padding: 10px;
     padding: 10px;

+ 8 - 11
src/pages/path/pathDetail.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
         <a-card title="路径详情">
         <a-card title="路径详情">
             <a-descriptions :data="info" :column="2" />
             <a-descriptions :data="info" :column="2" />
@@ -119,14 +119,11 @@ function formatSecondsToMinSec(totalSeconds) {
 </script>
 </script>
 
 
 <style scoped lang="less">
 <style scoped lang="less">
-.container {
-    padding: 0 20px 20px 20px;
-
-    .buttonGroup {
-        display: flex;
-        justify-content: center;
-        gap: 20px;
-        margin: 15px;
-    }
+.buttonGroup {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: center;
+  gap: 12px;
+  margin: 16px 0;
 }
 }
 </style>
 </style>

+ 2 - 6
src/pages/path/pathList.vue

@@ -1,5 +1,5 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
 
 
         <a-card title="路径列表">
         <a-card title="路径列表">
@@ -210,10 +210,6 @@ function formatSecondsToMinSec(totalSeconds) {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-    padding: 0 20px 20px 20px;
-}
-
 .table {
 .table {
     margin-top: 15px;
     margin-top: 15px;
 }
 }

+ 19 - 8
src/pages/qxs/getBookList.vue

@@ -1,10 +1,10 @@
-<template>
-    <div class="container">
+<template>
+    <div class="store-page">
         <Breadcrumb />
         <Breadcrumb />
 
 
         <a-card title="趣选书 · 书单查询" class="card">
         <a-card title="趣选书 · 书单查询" class="card">
             <div class="userLogin">
             <div class="userLogin">
-                <a-form :model="form" direction="vertical" size="large" :style="{ width: '50%' }">
+                <a-form :model="form" direction="vertical" size="large" class="login-form">
                     <a-form-item field="username" label="用户名">
                     <a-form-item field="username" label="用户名">
                         <a-input v-model="form.username" :style="{ width: '320px' }" placeholder="请输入趣选书账户" allow-clear>
                         <a-input v-model="form.username" :style="{ width: '320px' }" placeholder="请输入趣选书账户" allow-clear>
                             <template #prefix>
                             <template #prefix>
@@ -195,10 +195,6 @@ const getBookList = async () => {
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
-.container {
-    padding: 0 20px 20px 20px;
-}
-
 .table {
 .table {
     margin-top: 15px;
     margin-top: 15px;
 
 
@@ -211,15 +207,30 @@ const getBookList = async () => {
 .card {
 .card {
     .userLogin {
     .userLogin {
         display: flex;
         display: flex;
+        flex-wrap: wrap;
+        gap: 24px;
         width: 100%;
         width: 100%;
         margin: 0 auto;
         margin: 0 auto;
 
 
+        .login-form {
+            flex: 1 1 280px;
+            min-width: 0;
+            max-width: 100%;
+
+            :deep(.arco-input),
+            :deep(.arco-input-wrapper) {
+                width: 100% !important;
+                max-width: 360px;
+            }
+        }
+
         .userInfo {
         .userInfo {
             display: flex;
             display: flex;
             flex-direction: column;
             flex-direction: column;
             justify-content: center;
             justify-content: center;
             align-items: center;
             align-items: center;
-            width: 50%;
+            flex: 1 1 200px;
+            min-width: 0;
 
 
             .username {
             .username {
                 margin-top: 10px;
                 margin-top: 10px;

+ 1 - 1
src/pages/store/goodsDetail/index.vue

@@ -1,5 +1,5 @@
 <template>
 <template>
-  <div class="store-page goods-detail-page">
+  <div class="store-page goods-detail-page app-page--wide">
     <Breadcrumb />
     <Breadcrumb />
 
 
     <a-spin :loading="loading" class="store-spin">
     <a-spin :loading="loading" class="store-spin">

+ 1 - 1
src/pages/store/orders/orderDetail/index.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
   <div class="store-page order-detail-page">
   <div class="store-page order-detail-page">
-    <div class="order-detail-page__inner">
+    <div class="order-detail-page__inner app-page__inner--wide">
       <Breadcrumb />
       <Breadcrumb />
 
 
       <a-spin :loading="loading" class="store-spin">
       <a-spin :loading="loading" class="store-spin">

+ 2 - 6
src/pages/store/sendCountRecords/index.vue

@@ -1,5 +1,5 @@
-<template>
-  <div class="container">
+<template>
+  <div class="store-page">
     <Breadcrumb />
     <Breadcrumb />
 
 
     <a-card title="赠送记录">
     <a-card title="赠送记录">
@@ -192,10 +192,6 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-  padding: 0 20px 20px 20px;
-}
-
 .table {
 .table {
   margin-top: 15px;
   margin-top: 15px;
 }
 }

+ 190 - 48
src/styles/store-theme.less

@@ -1,4 +1,4 @@
-// 云商城 — 与首页一致的绿色系设计令牌
+// RunForge 用户端 / 管理端统一设计令牌(绿色系)
 @store-primary: #1b3022;
 @store-primary: #1b3022;
 @store-primary-light: #2d4a38;
 @store-primary-light: #2d4a38;
 @store-accent: #52b878;
 @store-accent: #52b878;
@@ -13,72 +13,212 @@
 @store-shadow-hover: 0 12px 40px rgba(27, 48, 34, 0.12);
 @store-shadow-hover: 0 12px 40px rgba(27, 48, 34, 0.12);
 @store-price: #e85d04;
 @store-price: #e85d04;
 @store-layout-max-width: 1200px;
 @store-layout-max-width: 1200px;
+@store-layout-narrow: 720px;
 
 
-// 由 style.css 全局引入,避免 scoped 导致样式失效
-.store-page {
-  padding: 0 20px 32px;
+@app-breakpoint-sm: 576px;
+@app-breakpoint-md: 768px;
+@app-breakpoint-lg: 992px;
+@app-breakpoint-xl: 1280px;
+
+// —— 页面容器(兼容旧 .container)——
+.container,
+.store-page,
+.app-page {
+  padding: 0 12px 24px;
   width: 100%;
   width: 100%;
   max-width: @store-layout-max-width;
   max-width: @store-layout-max-width;
   margin: 0 auto;
   margin: 0 auto;
   box-sizing: border-box;
   box-sizing: border-box;
+
+  @media (min-width: @app-breakpoint-md) {
+    padding: 0 20px 32px;
+  }
 }
 }
 
 
-// 售后服务 — 窄栏在内容区居中(与 order-page 一致)
-.service-page {
+.app-page--full,
+.service-page,
+.order-page,
+.order-detail-page,
+.power-page {
   width: 100%;
   width: 100%;
   max-width: none;
   max-width: none;
 }
 }
 
 
-.service-page__inner {
+.app-page--wide {
+  max-width: none;
+}
+
+.app-page__inner,
+.service-page__inner,
+.power-page__inner,
+.order-page__inner,
+.order-detail-page__inner {
   width: 100%;
   width: 100%;
-  max-width: 720px;
   margin-left: auto;
   margin-left: auto;
   margin-right: auto;
   margin-right: auto;
+}
+
+.app-page__inner--wide,
+.service-page__inner--wide,
+.power-page__inner--wide,
+.order-page__inner,
+.order-detail-page__inner {
+  max-width: @store-layout-max-width;
+}
+
+.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;
+}
+
+// —— 页头 / 工具栏 ——
+.app-page-header,
+.page-header {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: flex-end;
+  justify-content: space-between;
+  gap: 12px;
+  margin-bottom: 16px;
+
+  @media (min-width: @app-breakpoint-md) {
+    gap: 16px;
+    margin-bottom: 20px;
+  }
+
+  &__text {
+    flex: 1 1 200px;
+    min-width: 0;
+  }
 
 
-  &--wide {
-    max-width: @store-layout-max-width;
+  &__actions {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    align-items: center;
+    justify-content: flex-end;
   }
   }
 }
 }
 
 
-// 宿舍电费
-.power-page {
-  width: 100%;
-  max-width: none;
+.store-section-title {
+  margin: 0 0 4px;
+  font-size: clamp(1.25rem, 4vw, 1.5rem);
+  font-weight: 600;
+  color: @store-primary;
+  line-height: 1.3;
 }
 }
 
 
-.power-page__inner {
-  width: 100%;
-  max-width: 720px;
-  margin-left: auto;
-  margin-right: auto;
+.store-section-desc {
+  margin: 0;
+  font-size: 0.9rem;
+  color: @store-text-muted;
+  line-height: 1.5;
+}
+
+.app-notice,
+.notice {
+  margin-bottom: 16px;
+  border-radius: @store-radius-sm;
+}
 
 
-  &--wide {
-    max-width: @store-layout-max-width;
+// —— 按钮 ——
+.app-btn-primary,
+.submit-btn,
+.primary-btn {
+  border-radius: 999px;
+  background: @store-primary !important;
+  border-color: @store-primary !important;
+
+  &:hover:not(:disabled) {
+    background: @store-primary-light !important;
+    border-color: @store-primary-light !important;
   }
   }
 }
 }
 
 
-// 云商城订单 — 列表与详情在内容区居中
-.order-page,
-.order-detail-page {
-  width: 100%;
-  max-width: none;
+.buttonGroup,
+.app-toolbar {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-bottom: 16px;
+
+  @media (min-width: @app-breakpoint-md) {
+    gap: 10px;
+  }
 }
 }
 
 
-.order-page__inner {
-  width: 100%;
-  max-width: @store-layout-max-width;
-  margin-left: auto;
-  margin-right: auto;
+// —— 卡片 ——
+.container .arco-card,
+.store-page .arco-card,
+.app-page .arco-card,
+.app-card {
+  border-radius: @store-radius;
+  border: 1px solid @store-card-border;
+  box-shadow: @store-shadow;
+  margin-bottom: 16px;
+
+  .arco-card-header {
+    border-bottom: 1px solid @store-card-border;
+  }
+
+  .arco-card-header-title {
+    font-weight: 600;
+    color: @store-primary;
+  }
+
+  .arco-card-body {
+    overflow-x: auto;
+  }
+
+  .arco-table {
+    min-width: 560px;
+  }
 }
 }
 
 
-.order-detail-page__inner {
+// —— 表格(移动端横向滚动)——
+.app-table-wrap,
+.table-wrap {
   width: 100%;
   width: 100%;
-  max-width: 900px;
-  margin-left: auto;
-  margin-right: auto;
+  overflow-x: auto;
+  -webkit-overflow-scrolling: touch;
+  margin-top: 12px;
+
+  .arco-table {
+    min-width: 640px;
+  }
+}
+
+// —— 筛选表单响应式 ——
+.app-query-form {
+  .arco-form-item {
+    margin-bottom: 12px;
+  }
 }
 }
 
 
-// Arco Spin 默认为 inline-block,会导致子内容宽度塌陷
+@media (max-width: @app-breakpoint-lg) {
+  .app-query-form .arco-row > .arco-col,
+  .queryForm .arco-row > .arco-col {
+    flex: 0 0 100% !important;
+    max-width: 100% !important;
+  }
+
+  .app-query-form .arco-divider-vertical,
+  .queryForm + .arco-divider,
+  .queryForm .arco-divider-vertical {
+    display: none;
+  }
+
+  .app-query-actions,
+  .queryForm ~ .arco-col[style*='text-align: right'] {
+    flex: 0 0 100% !important;
+    max-width: 100% !important;
+    text-align: left !important;
+    margin-top: 4px;
+  }
+}
+
+// —— Spin ——
 .store-spin {
 .store-spin {
   display: block !important;
   display: block !important;
   width: 100% !important;
   width: 100% !important;
@@ -88,20 +228,14 @@
   }
   }
 }
 }
 
 
-.store-section-title {
-  margin: 0 0 4px;
-  font-size: 1.5rem;
-  font-weight: 600;
-  color: @store-primary;
-}
-
-.store-section-desc {
-  margin: 0 0 20px;
-  font-size: 0.9rem;
-  color: @store-text-muted;
+// —— 商品网格 ——
+.goods-grid {
+  @media (max-width: @app-breakpoint-md) {
+    grid-template-columns: 1fr !important;
+  }
 }
 }
 
 
-// 云商城搜索框(class 落在 Arco Input 根节点,须全局样式且覆盖 mengtu 主题)
+// —— 搜索框 ——
 .store-search.arco-input-wrapper {
 .store-search.arco-input-wrapper {
   background-color: @store-card-bg !important;
   background-color: @store-card-bg !important;
   border: 1px solid #c5d9cc !important;
   border: 1px solid #c5d9cc !important;
@@ -132,3 +266,11 @@
     color: @store-accent;
     color: @store-accent;
   }
   }
 }
 }
+
+// —— 弹窗(移动端宽度)——
+@media (max-width: @app-breakpoint-md) {
+  .arco-modal {
+    max-width: calc(100vw - 24px) !important;
+    margin: 12px;
+  }
+}