Explorar o código

feat(cfc-web): 管理后台手机自适应 — 响应式侧边栏折叠 + 布局优化

Layout.vue:
- 768px 以下侧边栏折叠,汉堡按钮控制展开/收起
- 侧边栏以 fixed overlay 方式滑入/滑出,配合遮罩层
- 路由切换自动关闭移动端侧边栏
- window.resize 监听自动适配桌面/移动端

Login.vue:
- 480px 以下表单卡片全屏展示

全局 CSS:
- 移动端 padding 缩小,表格字号调小

docs:
- PROJECT-OVERVIEW v1.4 -> v1.6 冲突解决(取远程版本)
- 废弃设计文档同步移至 backup/
Sisyphus hai 1 mes
pai
achega
3ee7289e

+ 124 - 2
cfc-web/src/views/Layout.vue

@@ -1,6 +1,8 @@
 <template>
   <el-container class="layout-container">
-    <el-aside width="200px">
+    <!-- Mobile overlay -->
+    <div v-if="isMobile && sidebarVisible" class="sidebar-overlay" @click="sidebarVisible = false"></div>
+    <el-aside :class="['el-aside', { 'sidebar-mobile': isMobile, 'sidebar-open': isMobile && sidebarVisible, 'sidebar-closed': isMobile && !sidebarVisible }]" :width="isMobile ? '200px' : '200px'">
       <div class="logo">浠艾福</div>
       <el-scrollbar wrap-class="sidebar-scrollbar-wrap">
         <el-menu
@@ -50,6 +52,11 @@
     </el-aside>
     <el-container>
       <el-header>
+        <div class="header-left">
+          <el-button v-if="isMobile" type="text" class="hamburger-btn" @click="toggleSidebar">
+            <i :class="sidebarVisible ? 'el-icon-s-unfold' : 'el-icon-s-fold'"></i>
+          </el-button>
+        </div>
         <div class="header-right">
           <el-badge :value="unreadMessageCount" :hidden="unreadMessageCount === 0" class="msg-badge">
             <el-button type="text" class="msg-btn" @click="handleMessageClick" icon="el-icon-bell"></el-button>
@@ -323,6 +330,8 @@ export default {
       inviteCodeLoading: false,
       inviteCodeData: null,
       unreadMessageCount: 0,
+      isMobile: window.innerWidth <= 768,
+      sidebarVisible: window.innerWidth > 768,
       passwordRules: {
         oldPassword: [
           { required: true, message: '请输入原密码', trigger: 'blur' }
@@ -375,14 +384,27 @@ export default {
       return this.displayRoles
     }
   },
+  watch: {
+    '$route'() {
+      if (this.isMobile) {
+        this.sidebarVisible = false
+      }
+    }
+  },
   mounted() {
     // 清除可能的 beforeunload 处理,防止页面刷新弹出"离开网站?"提示
     window.onbeforeunload = null
+    // 响应式监听
+    this.handleResize = this.handleResize.bind(this)
+    window.addEventListener('resize', this.handleResize)
     // 有消息权限(老师/营养师角色)才加载未读数,管理员不调用
     if (hasPermission(this.effectivePerms, 'messages') && this.currentRole !== 'admin') {
       this.fetchUnreadCount()
     }
   },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.handleResize)
+  },
   methods: {
     hasPerm(required) {
       return hasPermission(this.effectivePerms, required)
@@ -396,6 +418,23 @@ export default {
       if (index && index.startsWith('/')) {
         this.$router.push(index)
       }
+      if (this.isMobile) {
+        this.sidebarVisible = false
+      }
+    },
+    toggleSidebar() {
+      this.sidebarVisible = !this.sidebarVisible
+    },
+    handleResize() {
+      const mobile = window.innerWidth <= 768
+      if (mobile !== this.isMobile) {
+        this.isMobile = mobile
+        if (!mobile) {
+          this.sidebarVisible = true
+        } else {
+          this.sidebarVisible = false
+        }
+      }
     },
     handleProfileCommand(command) {
       if (command === 'profile') {
@@ -592,7 +631,7 @@ export default {
   background-color: #fff;
   box-shadow: 0 1px 4px rgba(0,21,41,.08);
   display: flex;
-  justify-content: flex-end;
+  justify-content: space-between;
   align-items: center;
   padding: 0 20px;
 }
@@ -738,4 +777,87 @@ export default {
   min-height: 0;
   padding: 20px;
 }
+
+/* ========== 响应式:侧边栏折叠 ========== */
+@media (max-width: 768px) {
+  .sidebar-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.4);
+    z-index: 1001;
+  }
+
+  .sidebar-mobile {
+    position: fixed !important;
+    top: 0;
+    left: 0;
+    height: 100vh !important;
+    z-index: 1002;
+    transition: transform 0.25s ease;
+    transform: translateX(-100%);
+  }
+
+  .sidebar-mobile.sidebar-open {
+    transform: translateX(0);
+  }
+
+  .sidebar-mobile.sidebar-closed {
+    transform: translateX(-100%);
+  }
+
+  .header-left {
+    display: flex;
+    align-items: center;
+  }
+
+  .hamburger-btn {
+    font-size: 20px;
+    color: #606266;
+    padding: 8px;
+  }
+  .hamburger-btn:hover {
+    color: #6366F1;
+  }
+
+  .el-header {
+    padding: 0 10px !important;
+  }
+
+  .el-main {
+    padding: 12px !important;
+  }
+
+  .admin-page > .el-card .el-card__body {
+    padding: 12px !important;
+  }
+
+  .admin-page > div:only-child > .el-card .el-card__body {
+    padding: 12px !important;
+  }
+
+  .username {
+    margin-right: 10px !important;
+    font-size: 13px;
+  }
+
+  /* el-table 小屏字号 */
+  .el-table {
+    font-size: 12px;
+  }
+
+  .el-table .cell {
+    padding-left: 4px !important;
+    padding-right: 4px !important;
+  }
+}
+
+/* 桌面端隐藏 header-left(无汉堡按钮) */
+@media (min-width: 769px) {
+  .header-left {
+    display: none;
+  }
+}
 </style>

+ 23 - 0
cfc-web/src/views/Login.vue

@@ -828,4 +828,27 @@ export default {
     box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
   }
 }
+
+@media screen and (max-width: 480px) {
+  .form-panel {
+    padding: 0;
+    align-items: stretch;
+  }
+  .form-card {
+    max-width: none;
+    border-radius: 0;
+    padding: 36px 24px 24px;
+    box-shadow: none;
+    min-height: 100vh;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+  .form-card:hover {
+    box-shadow: none;
+  }
+  .form-header {
+    margin-bottom: 28px;
+  }
+}
 </style>

+ 54 - 0
docs/superpowers/specs/2026-07-24-cfc-web-responsive-design.md

@@ -0,0 +1,54 @@
+# cfc-web 管理后台手机自适应改造
+
+**日期:** 2026-07-24
+**状态:** 已批准实施
+**范围:** cfc-web (Vue 2 + Element UI)
+
+---
+
+## 1. 改造策略
+
+**原则:只改布局层,不改页面内部逻辑。**
+
+所有列表页、表单页的 `el-table` 在小屏下依赖 Element UI 自带的横向滚动,不做每个页面的单独调整。
+
+## 2. Layout.vue 改造
+
+### 2.1 侧边栏响应式折叠
+
+- 768px 以下:侧边栏默认折叠,显示汉堡菜单按钮
+- 折叠时 sidebar 宽度为 0 / 隐藏,用 `position: fixed` 遮罩弹出
+- 768px 以上:保持现有 200px 固定侧边栏
+- 汉堡按钮放在 header 左侧
+
+### 2.2 Header 调整
+
+- 左侧加汉堡菜单按钮(移动端可见)
+- 右侧按钮间距缩小
+
+### 2.3 Main 内容区
+
+- 移动端 padding 从 20px → 12px
+- el-card__body padding 缩小
+
+## 3. 全局 CSS
+
+- body 背景保持 `#f0f2f5`
+- 768px 断点:`el-table` 字号 13px → 12px
+- 搜索表单区域:el-form-item 在小屏下换行
+
+## 4. Login.vue 调整
+
+- 已有 900px 断点,768px 以下进一步优化
+- 表单卡片去除圆角、边距,全屏展示
+
+## 5. 不做的范围
+
+- 不改造每个页面的表格列(靠横向滚动)
+- 不改造统计卡片布局(Dashboard 4列在移动端变2列,由 el-col 响应式属性处理)
+- 不改造复杂表单的布局
+
+## 6. 风险
+
+- `.v-modal { display: none !important }` 全局遮罩隐藏 — 侧边栏弹出遮罩需用自定义遮罩,不能用 Element UI 的遮罩层
+- 路由跳转关闭侧边栏的逻辑需正确处理