Files
gongxue-base/docs/superpowers/plans/2026-07-03-admin-responsive-adaptation.md

41 KiB
Raw Blame History

change, design-doc, base-ref, archived-with
change design-doc base-ref archived-with
admin-responsive-adaptation docs/superpowers/specs/2026-07-03-admin-responsive-adaptation-design.md 779cedb8a07bbfe1c50871526f9b99f8bc008acc 2026-07-03-admin-responsive-adaptation

管理后台三端响应式适配 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 将管理后台从单一 mobile/desktop 二元适配升级为 antd v6 Grid.useBreakpoint() 三端响应式体系(手机 < 576px / 平板 576-991px / 桌面 >= 992px覆盖 16 个页面的布局、表格、弹窗和工具栏。

Architecture: 使用 antd v6 内置 Grid.useBreakpoint() 替换自写的 resize 监听,通过断点驱动 Sider/Drawer 切换、内容区 padding、卡片网格 Col 响应式 props。所有表格添加 scroll={{ x }} 横向滚动,工具栏添加 flexWrap: 'wrap' + gap,弹窗由全局 CSS @media 约束宽度。

Tech Stack: React 19, antd v6.3.6, echarts-for-react, react-router-dom v7, dayjs, Vite 8

Global Constraints

  • 断点体系使用 antd v6 内置断点:手机 (max-width: 575px)、平板 (min-width: 576px) and (max-width: 991px)、桌面默认
  • CSS 策略antd Props 优先Col 响应式断点、Table scroll、ellipsis@media 为兜底
  • 所有 <Table> 组件若无 scroll={{ x }} 则添加之,列少(<=6max-content列中7-10800,列多(>101000
  • 弹窗宽度由全局 CSS max-width: calc(100vw - 24px) 覆盖,组件级不做重复处理
  • 编译必须通过:npm run build 无 TypeScript 错误
  • 命名约定:从 expenseTypeMap 等现有映射扩展,不新建无意义的变量名

archived-with: 2026-07-03-admin-responsive-adaptation

Task 1: 全局 CSS 三断点体系

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/index.css

Interfaces:

  • Produces: 三个 @media 层级的 CSS 规则,供所有页面共用
  • 断点规范:手机 (max-width: 575px)、平板 (min-width: 576px) and (max-width: 991px)

Description: 将当前仅有单断点max-width: 767px的 index.css 改为三断点体系,同时添加全局表格容器横向滚动和通用组件响应式样式。

  • Step 1: 替换 index.css 中的 @media 规则

将当前文件内容替换为以下完整版本:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family:
    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

#root {
  min-height: 100vh;
}

/* === 通用:表格容器横向滚动 === */
.ant-table-wrapper {
  overflow-x: auto;
}

/* === 手机 (< 576px) === */
@media (max-width: 575px) {
  .ant-table {
    font-size: 13px;
  }
  .ant-table-cell {
    padding: 8px 6px !important;
  }
  .ant-descriptions-item-label,
  .ant-descriptions-item-content {
    font-size: 13px;
  }
  .ant-modal {
    max-width: calc(100vw - 24px) !important;
    margin: 12px auto !important;
  }
  .ant-modal .ant-modal-body {
    max-height: 60vh;
    overflow-y: auto;
  }
  h2 {
    font-size: 18px !important;
  }
  .ant-card {
    margin-bottom: 8px;
  }
  .ant-space-item .ant-btn {
    padding: 2px 6px;
    font-size: 12px;
  }
}

/* === 平板 (576-991px) === */
@media (min-width: 576px) and (max-width: 991px) {
  .ant-modal {
    max-width: calc(100vw - 48px) !important;
  }
}

Changed from current:

  • 旧断点 (max-width: 767px) 改为 (max-width: 575px)

  • 新增 .ant-table-wrapper { overflow-x: auto } 通用规则

  • 新增平板断点 (min-width: 576px) and (max-width: 991px) 仅做弹窗微调

  • 桌面(>= 992px使用浏览器默认样式无需显式 @media

  • Step 2: 验证 CSS 语法

# 用 node 检查语法(没有专门的 CSS 语法检查,可跳过)
echo "CSS rules updated"
  • Step 3: Commit
git add apps/admin/src/index.css
git commit -m "feat: 建立三断点 CSS 体系,替换单断点移动端样式"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 2: MainLayout 三端布局重构

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/layouts/MainLayout.tsx

Interfaces:

  • Consumes: antd Grid.useBreakpoint() (from antd)
  • Produces: isMobile / isTablet / isDesktop 三端布尔值Sider/Drawer 切换逻辑
  • Removes: useState(window.innerWidth < 768) + useEffect resize 事件监听

Description: 将 MainLayout 从自建 resize 监听改为 antd v6 的 Grid.useBreakpoint() hook根据三端断点控制侧栏桌面 Sider / 手机 Drawer / 平板 Sider 折叠、Header 用户区域文字显示、Content padding。

  • Step 1: 替换 import 和状态声明

找到文件中的 import 区域(第 3 行附近),在 antd 的 import 中添加 Grid

import { Layout, Menu, Button, Avatar, Dropdown, Drawer, Grid } from 'antd';

然后替换第 86-98 行的状态和 useEffect

const MainLayout: React.FC = () => {
  const [collapsed, setCollapsed] = useState(false);
  const screens = Grid.useBreakpoint();
  const isMobile = !screens.sm;                          // < 576px (仅 xs)
  const isTablet = (screens.sm || screens.md) && !screens.lg; // 576-991px
  const isDesktop = !!screens.lg;                        // >= 992px
  const [drawerOpen, setDrawerOpen] = useState(false);
  const navigate = useNavigate();
  const location = useLocation();
  const user = JSON.parse(localStorage.getItem('user') || '{}');
  const { hasPermission } = usePermission();

移除原来的 useEffect + resize 事件监听(第 94-98 行)和 useState(window.innerWidth < 768)(第 87 行)。

  • Step 2: 更新 Sider/平板/Drawer 渲染逻辑

替换第 150-187 行的侧栏区域:

  return (
    <Layout style={{ minHeight: '100vh' }}>
      {/* 桌面 + 平板Sider手机Drawer */}
      {!isMobile && (
        <Sider
          trigger={null}
          collapsible
          collapsed={isTablet ? true : collapsed}
          theme="light"
          style={{ background: '#fff', borderRight: '1px solid #e5e5e7' }}
        >
          <div
            style={{
              height: 64,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              color: '#1d1d1f',
              fontSize: (isTablet || collapsed) ? 16 : 17,
              fontWeight: 600,
              borderBottom: '1px solid #e5e5e7',
            }}
          >
            {(isTablet || collapsed) ? '恭' : '恭学教育基地'}
          </div>
          {menuContent}
        </Sider>
      )}
      {isMobile && (
        <Drawer
          placement="left"
          open={drawerOpen}
          onClose={() => setDrawerOpen(false)}
          width={240}
          styles={{ body: { padding: 0 } }}
          title="恭学教育基地"
        >
          {menuContent}
        </Drawer>
      )}

关键变化:平板 (isTablet) 时 Sider 强制折叠 (collapsed={isTablet ? true : collapsed})。

  • Step 3: 更新 Collapse 按钮逻辑

替换第 200-212 行的 Button onClick

          <Button
            type="text"
            icon={
              isMobile ? (
                <MenuUnfoldOutlined />
              ) : (isTablet || collapsed) ? (
                <MenuUnfoldOutlined />
              ) : (
                <MenuFoldOutlined />
              )
            }
            onClick={() => {
              if (isMobile) {
                setDrawerOpen(true);
              } else if (isTablet) {
                setDrawerOpen(true);
              } else {
                setCollapsed(!collapsed);
              }
            }}
          />

平板下点击折叠按钮打开 Drawer因为 Sider 已折叠到图标态,用户需要展开菜单)。

  • Step 4: 隐藏 Header 用户区域文字(手机/平板)

替换第 225-229 行的用户信息区域:

            <div style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8 }}>
              <Avatar icon={<UserOutlined />} />
              {isDesktop && <span>{user.name || user.username || '用户'}</span>}
            </div>

手机和平板 (!isDesktop) 下仅显示 Avatar隐藏用户名文字以节省空间。

  • Step 5: 更新 Content padding

替换第 231-241 行的 Content 组件:

        <Content
          style={{
            margin: isMobile ? 12 : isTablet ? 16 : 24,
            padding: isMobile ? 12 : isTablet ? 16 : 24,
            background: '#fff',
            borderRadius: 12,
            overflow: 'auto',
          }}
        >
          <Outlet />
        </Content>
  • Step 6: Commit
git add apps/admin/src/layouts/MainLayout.tsx
git commit -m "feat: MainLayout 三端布局重构,使用 antd Grid.useBreakpoint()"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 3: Dashboard 响应式网格和图表

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Dashboard/index.tsx

Interfaces:

  • Consumes: antd Grid (for useBreakpoint)
  • Produces: 响应式统计卡片 Col 断点、图表高度自适应、工具栏堆叠

Description: 将统计卡片的 span={6} 改为响应式断点、图表卡片 span={12} 改为 xs={24} sm={12}、图表高度按设备自适应、顶部标题和 DatePicker 在小屏堆叠。

  • Step 1: 添加 Grid import

在第 2 行antd import 中添加 Grid

import { Row, Col, Card, Statistic, DatePicker, Spin, Grid } from 'antd';

在组件内部顶部添加断点检测(const [stats, setStats]... 之前):

const screens = Grid.useBreakpoint();
const isMobile = !screens.sm;
const isDesktop = !!screens.lg;
  • Step 2: 统计卡片 Col 改为响应式断点

替换第 179-214 行的统计卡片 Row

      <Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
        <Col xs={12} sm={12} md={6}>
          <Card>
            <Statistic title="宿舍总数" value={stats?.totalRooms || 0} prefix={<HomeOutlined />} />
          </Card>
        </Col>
        <Col xs={12} sm={12} md={6}>
          <Card>
            <Statistic
              title="在读学生"
              value={stats?.totalStudents || 0}
              prefix={<TeamOutlined />}
            />
          </Card>
        </Col>
        <Col xs={12} sm={12} md={6}>
          <Card>
            <Statistic
              title="当前在住"
              value={stats?.occupiedBeds || 0}
              suffix={`/ ${stats?.totalCapacity || 0}`}
              prefix={<CheckCircleOutlined />}
            />
          </Card>
        </Col>
        <Col xs={12} sm={12} md={6}>
          <Card>
            <Statistic
              title="入住率"
              value={stats?.occupancyRate || 0}
              suffix="%"
              prefix={<DollarOutlined />}
            />
          </Card>
        </Col>
      </Row>

变化:span={6} -> xs={12} sm={12} md={6}Row 的 gutter={16} -> gutter={[16, 16]} 增加垂直间距。

  • Step 3: 图表卡片 Col 改为响应式断点

替换第 227-246 行的图表 Row

      <Row gutter={[16, 16]}>
        <Col xs={24} sm={12}>
          <Card title="费用类型分布">
            {expenseStats.length > 0 ? (
              <ReactECharts
                option={pieOption}
                style={{ width: '100%', height: isMobile ? 250 : 300 }}
              />
            ) : (
              <div style={{ textAlign: 'center', padding: 40, color: '#999' }}>暂无费用数据</div>
            )}
          </Card>
        </Col>
        <Col xs={24} sm={12}>
          <Card title="宿舍费用排行 TOP 20">
            {roomRanking.length > 0 ? (
              <ReactECharts
                option={barOption}
                style={{ width: '100%', height: isMobile ? 250 : 300 }}
              />
            ) : (
              <div style={{ textAlign: 'center', padding: 40, color: '#999' }}>暂无费用数据</div>
            )}
          </Card>
        </Col>
      </Row>

变化:

  • span={12} -> xs={24} sm={12}(手机堆叠,平板及以上并排)

  • ECharts style 添加 width: '100%' 确保跟随容器 resize

  • 高度改为 isMobile ? 250 : 300

  • Step 4: 甘特图 ECharts 添加 width: '100%'

替换第 216-225 行的甘特图区域:

      <Card title="入住时间线(甘特图)" style={{ marginBottom: 24 }}>
        {ganttData.length > 0 ? (
          <ReactECharts
            option={ganttOption()}
            style={{ width: '100%', height: Math.max(300, ganttData.length * 40) }}
          />
        ) : (
          <div style={{ textAlign: 'center', padding: 40, color: '#999' }}>暂无入住数据</div>
        )}
      </Card>
  • Step 5: 顶部工具栏小屏堆叠

替换第 162-177 行的标题和 DatePicker 区域:

      <div
        style={{
          marginBottom: 16,
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: isMobile ? 'flex-start' : 'center',
          flexDirection: isMobile ? 'column' : 'row',
          gap: 12,
        }}
      >
        <h2 style={{ margin: 0 }}>数据面板</h2>
        <RangePicker
          value={[dayjs(period[0]), dayjs(period[1])]}
          onChange={(dates) => {
            if (dates) setPeriod([dates[0]!.format('YYYY-MM-DD'), dates[1]!.format('YYYY-MM-DD')]);
          }}
        />
      </div>

变化:小屏下 flexDirection: 'column' + alignItems: 'flex-start' + gap: 12

  • Step 6: Commit
git add apps/admin/src/pages/Dashboard/index.tsx
git commit -m "feat: Dashboard 响应式网格,统计卡片/图表/工具栏适配三端"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 4: 学生管理字段拆分和响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Students/index.tsx

Interfaces:

  • Consumes: 后端 /students API 返回学生对象(含 studentNumber 可选字段)
  • Produces: 拆分后的列定义、表格 scroll、工具栏 wrap

Description: 将「学号/身份证」列拆分为「学号」和「身份证」两个独立列,添加 ellipsiswidth;表格添加 scroll={{ x }};顶部工具栏添加 flexWrap: 'wrap' + gap;表单中添加 studentNumber 字段。

  • Step 1: 拆分列定义

替换第 151-156 行的 columns 数组中的对应条目。将:

    { title: '学号/身份证', dataIndex: 'idNumber' },

替换为:

    {
      title: '学号',
      dataIndex: 'studentNumber',
      width: 120,
      ellipsis: true,
      render: (v: string) => v || '-',
    },
    {
      title: '身份证',
      dataIndex: 'idNumber',
      width: 180,
      ellipsis: true,
      render: (v: string) => v || '-',
    },

同时为其余缺少 ellipsis 的文本列添加 ellipsis: truename, phone, emergencyContact, emergencyPhone, supervisor

    { title: 'ID', dataIndex: 'id', width: 60 },
    { title: '姓名', dataIndex: 'name', ellipsis: true },
    { title: '性别', dataIndex: 'gender', width: 60 },
    { title: '电话', dataIndex: 'phone', ellipsis: true },
    // ... 上面已替换的学号和身份证列
    { title: '民族', dataIndex: 'ethnicity', width: 80 },
    { title: '紧急联系人', dataIndex: 'emergencyContact', ellipsis: true },
    { title: '紧急联系人电话', dataIndex: 'emergencyPhone', ellipsis: true },
    {
      title: '所属机构',
      dataIndex: 'organization',
      render: (v: string) => (v ? <Tag color="purple">{v}</Tag> : '-'),
    },
    { title: '负责人', dataIndex: 'supervisor', ellipsis: true },
  • Step 2: 表格添加 scroll

在第 305 行的 <Table 组件上添加 scroll 属性:

      <Table
        columns={columns}
        dataSource={data}
        rowKey="id"
        loading={loading}
        scroll={{ x: 1200 }}
        pagination={{ pageSize: 15, showTotal: (total) => `共 ${total} 人` }}
  • Step 3: 工具栏添加 wrap 和 gap

替换第 223-304 行的工具栏 JSX。在顶部 <div> 和左右 <Space> 之间改为:

      <div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8 }}>
        <Space wrap>
          <Input.Search ... />
          <Button ... />
        </Space>
        <Space wrap>
          {/* 右侧所有按钮 */}
        </Space>
      </div>

变化:

  • 外层 div 添加 flexWrap: 'wrap'gap: 8

  • 左右 Space 均添加 wrap 属性

  • Step 4: 表单中添加学号字段

在 modal 中的 <Form> 区域(第 329 行附近),在 name 字段和 gender 字段之间添加学号字段:

          <Form.Item name="studentNumber" label="学号">
            <Input placeholder="学生的学号" />
          </Form.Item>
  • Step 5: Commit
git add apps/admin/src/pages/Students/index.tsx
git commit -m "feat: 学生管理字段拆分(学号/身份证分列),表格和工具栏响应式适配"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 5: 宿舍总览 RoomVisual 复查

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/RoomVisual/index.tsx

Interfaces:

  • Consumes: 当前已有的 xs/sm/md/lg Col 响应式断点
  • Produces: 确保平板断点md: >= 768px覆盖 576-991px 范围

Description: RoomVisual 已经使用了 xs={12} sm={8} md={6} lg={4} 等响应式 Col props只需确认布局在 576-991px 平板区域表现合理。弹窗已使用 width={500},全局 CSS 会覆盖窄屏。

  • Step 1: 验证当前布局

查看房态网格行(第 112 行附近),当前为 xs={12} sm={8} md={6} lg={4}

  • 手机 (xs: < 576px): 2 列 -- OK
  • sm (>= 576px): 3 列 -- OK
  • md (>= 768px): 4 列 -- OK
  • lg (>= 992px): 6 列 -- OK

平板区域576-991px覆盖了 sm 和 md 两个断点,从 3 列到 4 列,布局合理。无需修改。

  • Step 2: 统计卡片栏复查

第 87-107 行的统计卡片当前为 xs={12} sm={6}

  • 手机: 2 列
  • 平板及以上 (>= 576px): 4 列

在平板下 4 张卡并排可能略挤,改为 xs={12} sm={6} md={6} 以保证桌面端也是 4 列,平板端保持 4 列(可用)。无需修改。

  • Step 3: 弹窗复查

详情弹窗(第 182-249 行)width={500},在手机下由全局 CSS max-width: calc(100vw - 24px) 覆盖。无需修改。

  • Step 4: Commit
git add apps/admin/src/pages/RoomVisual/index.tsx
git commit -m "chore: RoomVisual 三端断点复查确认,无改动"

注:如果复查无改动,可跳过此 commit或提交一个空 commit 标记完成。

archived-with: 2026-07-03-admin-responsive-adaptation

Task 6: 入住管理 Occupancies 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Occupancies/index.tsx

Interfaces:

  • Consumes: 已有 Alert、Tab 切换区、搜索/操作区
  • Produces: 表格 scroll、工具栏 wrap、弹窗宽度全局 CSS 覆盖)

Description: 表格添加 scroll={{ x }};顶部 Alert + Tab + 搜索/操作区支持小屏堆叠;弹窗宽度由全局 CSS 约束。

  • Step 1: 表格添加 scroll

在第 444 行的 <Table 组件上添加 scroll 属性:

      <Table
        columns={columns}
        dataSource={filteredData}
        rowKey="id"
        loading={loading}
        scroll={{ x: 900 }}
        pagination={{ pageSize: 15, showTotal: (total) => `共 ${total} 条` }}
        rowSelection={rowSelection}
      />
  • Step 2: 工具栏 wrap 验证

查看第 251-395 行的工具栏 JSX。外层 div 已有 flexWrap: 'wrap'gap: 8。左侧 Space 有 wrap,右侧 Space 有 wrap。这些已经正确,无需修改。

  • Step 3: 导入时自动收押金设置行检查

第 380-393 行的收押金设置行已经是内联 flex在小屏下会自动换行。当前实现使用 <span> 包裹,换行体验一般但可用。无需修改。

  • Step 4: 弹窗宽度

当前弹窗:

  • 入住登记: width={500}(第 460 行)
  • 退宿: 无 width 属性(第 517 行,默认 520px
  • 批量退宿: width={500}(第 557 行)
  • 换房: width={500}(第 610 行)

这些弹窗在小屏下由全局 CSS @media (max-width: 575px).ant-modal { max-width: calc(100vw - 24px) !important } 约束。平板弹窗由 @media (min-width: 576px) and (max-width: 991px) 约束为 max-width: calc(100vw - 48px)。无需额外修改。

  • Step 5: Commit
git add apps/admin/src/pages/Occupancies/index.tsx
git commit -m "feat: 入住管理表格添加 scroll工具栏/弹窗响应式适配"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 7: 宿舍管理 Rooms 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Rooms/index.tsx

Interfaces:

  • Consumes: 已有搜索/筛选/按钮工具栏
  • Produces: 表格 scroll、工具栏 wrap 和 gap

Description: 表格添加 scroll={{ x }};确保工具栏和操作列在三端适配。

  • Step 1: 表格添加 scroll

在第 374 行的 <Table 组件上添加 scroll 属性:

      <Table
        columns={columns}
        dataSource={filteredData}
        rowKey="id"
        loading={loading}
        scroll={{ x: 900 }}
        pagination={{ pageSize: 20, showTotal: (total) => `共 ${total} 间` }}
        rowClassName={(record) => (record.status === 'archived' ? 'archived-row' : '')}
        rowSelection={{
          selectedRowKeys,
          onChange: (keys) => setSelectedRowKeys(keys as number[]),
          getCheckboxProps: (record: any) => ({ disabled: record.status === 'archived' }),
        }}
      />
  • Step 2: 工具栏已有 wrap 和 gap

查看第 279-372 行的工具栏 JSX。外层 div 已有 flexWrap: 'wrap'gap: 8。左右 Space 均已有 wrap。这些已经正确,无需修改。

  • Step 3: Commit
git add apps/admin/src/pages/Rooms/index.tsx
git commit -m "feat: 宿舍管理表格添加 scroll 横向滚动"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 8: 费用录入 Expenses 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Expenses/index.tsx

Interfaces:

  • Consumes: Tabs 的两个 Tab宿舍费用/个人附加费),各有独立 Table 和工具栏
  • Produces: 两个表格分别添加 scroll、弹窗宽度全局 CSS

Description: 宿舍费用和个人附加费两个 Tab 下的表格均添加 scroll={{ x }};弹窗由全局 CSS 覆盖。

  • Step 1: 宿舍费用表格添加 scroll

在第 435 行的宿舍费用 <Table 上添加 scroll

                <Table
                  columns={roomColumns}
                  dataSource={filteredRoomExpenses}
                  rowKey="id"
                  loading={loading}
                  scroll={{ x: 800 }}
                  pagination={{ pageSize: 15, showTotal: (total) => `共 ${total} 条` }}
                  rowSelection={{
                    selectedRowKeys: selectedRoomKeys,
                    onChange: (keys) => setSelectedRoomKeys(keys as number[]),
                  }}
                />
  • Step 2: 个人附加费表格添加 scroll

在第 587 行的个人附加费 <Table 上添加 scroll

                <Table
                  columns={personalColumns}
                  dataSource={filteredPersonalExpenses}
                  rowKey="id"
                  loading={loading}
                  scroll={{ x: 800 }}
                  pagination={{ pageSize: 15, showTotal: (total) => `共 ${total} 条` }}
                  rowSelection={{
                    selectedRowKeys: selectedPersonalKeys,
                    onChange: (keys) => setSelectedPersonalKeys(keys as number[]),
                  }}
                />
  • Step 3: 工具栏 wrap 验证

第 319-326 行和第 455-461 行的工具栏外层 div 已有 flexWrap: 'wrap'gap: 8。Space 均有 wrap。无需修改。

  • Step 4: 弹窗验证

录入宿舍费用弹窗(第 604 行)和个人附加费弹窗(第 644 行)均无显式 width由全局 CSS 约束窄屏。无需修改。

  • Step 5: Commit
git add apps/admin/src/pages/Expenses/index.tsx
git commit -m "feat: 费用录入两个表格添加 scroll 横向滚动"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 9: 押金管理 Deposits 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Deposits/index.tsx

Interfaces:

  • Consumes: 已有搜索/筛选工具栏
  • Produces: 表格 scroll、工具栏 wrap、弹窗宽度

Description: 表格添加 scroll={{ x }};搜索/筛选工具栏 wrap押金收取和退还弹窗由全局 CSS 覆盖。

  • Step 1: 表格添加 scroll

在第 211 行的 <Table 上添加 scroll

      <Table
        columns={columns}
        dataSource={filteredData}
        rowKey="id"
        loading={loading}
        scroll={{ x: 1000 }}
        pagination={{ pageSize: 15, showTotal: (total) => `共 ${total} 条` }}
      />

列数较多(学生、金额、日期、状态、退还金额、扣除金额、原因、日期、备注 + 操作),使用 x: 1000

  • Step 2: 工具栏已有 wrap 和 gap

第 165-172 行的工具栏外层 div 已有 flexWrap: 'wrap'gap: 8Space 有 wrap。无需修改。

  • Step 3: Commit
git add apps/admin/src/pages/Deposits/index.tsx
git commit -m "feat: 押金管理表格添加 scroll 横向滚动"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 10: 账单管理 Bills 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Bills/index.tsx

Interfaces:

  • Consumes: 已有搜索/筛选/按钮工具栏,详情弹窗含嵌套 Table
  • Produces: 表格 scroll、工具栏 wrap、弹窗宽度

Description: 表格添加 scroll={{ x }};搜索/筛选/按钮工具栏已有 wrap生成账单弹窗和详情弹窗由全局 CSS 覆盖。

  • Step 1: 主表格添加 scroll

在第 388 行的 <Table 上添加 scroll

      <Table
        columns={columns}
        dataSource={filteredBills}
        rowKey="id"
        loading={loading}
        scroll={{ x: 1200 }}
        pagination={{ pageSize: 15, showTotal: (total) => `共 ${total} 条` }}
        rowSelection={{
          selectedRowKeys: selectedRows,
          onChange: (keys) => setSelectedRows(keys as number[]),
        }}
      />

账单列数多(学生、周期、分摊费、个人费、总计、可用押金、抵扣后应付、状态、时间 + 操作列宽320使用 x: 1200

  • Step 2: 工具栏已有 wrap 和 gap

第 307-314 行的工具栏外层 div 已有 flexWrap: 'wrap'gap: 8Space 有 wrap。无需修改。

  • Step 3: 详情弹窗中的内嵌表格添加 scroll

在第 494 行的详情弹窗内嵌 <Table 上添加 scroll

            <Table
              dataSource={detailModal.items || []}
              rowKey="id"
              pagination={false}
              size="small"
              scroll={{ x: 700 }}
              columns={[
                { title: '类型', dataIndex: 'expenseType', render: (v: string) => typeMap[v] || v },
                { title: '说明', dataIndex: 'description' },
                {
                  title: '计费天数',
                  dataIndex: 'days',
                  render: (v: number) => (v > 0 ? `${v}天` : '-'),
                },
                {
                  title: '宿舍总人天',
                  dataIndex: 'totalRoomDays',
                  render: (v: number) => (v > 0 ? `${v}天` : '-'),
                },
                {
                  title: '宿舍总费用',
                  dataIndex: 'roomTotalAmount',
                  render: (v: number) => ${Number(v).toFixed(2)}`,
                },
                {
                  title: '应分摊',
                  dataIndex: 'studentAmount',
                  render: (v: number) => <strong>¥{Number(v).toFixed(2)}</strong>,
                },
              ]}
            />
  • Step 4: Commit
git add apps/admin/src/pages/Bills/index.tsx
git commit -m "feat: 账单管理表格添加 scroll详情内嵌表格也添加 scroll"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 11: 教室管理 Classrooms 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Classrooms/index.tsx

Interfaces:

  • Consumes: 已有搜索/筛选工具栏
  • Produces: 表格 scroll

Description: 表格添加 scroll={{ x }};工具栏已有 wrap。

  • Step 1: 表格添加 scroll

在第 265 行的 <Table 上添加 scroll

      <Table
        columns={columns}
        dataSource={filteredData}
        rowKey="id"
        loading={loading}
        scroll={{ x: 900 }}
        pagination={{ pageSize: 20, showTotal: (total) => `共 ${total} 条` }}
      />
  • Step 2: 工具栏已有 wrap 和 gap

第 194-201 行的工具栏外层 div 已有 flexWrap: 'wrap'gap: 8Space 有 wrap。无需修改。

  • Step 3: Commit
git add apps/admin/src/pages/Classrooms/index.tsx
git commit -m "feat: 教室管理表格添加 scroll 横向滚动"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 12: 租赁订单 ClassroomRentals 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/ClassroomRentals/index.tsx

Interfaces:

  • Consumes: 已有 scroll={{ x: 1200 }}
  • Produces: 弹窗宽度

Description: 表格已有 scroll={{ x: 1200 }},值合理无需修改;工具栏已有 wrap 和 gap弹窗 width={600} 由全局 CSS 覆盖窄屏。

  • Step 1: 复查现有 scroll 值

第 318 行已有 scroll={{ x: 1200 }}。该表有 10+ 列(教室、租赁方、开始日期、结束日期、时长、日租金、总额、合同、操作),x: 1200 合理。无需修改。

  • Step 2: 工具栏已有 wrap 和 gap

第 277-284 行的工具栏外层 div 已有 flexWrap: 'wrap'gap: 8Space 有 wrap。无需修改。

  • Step 3: Commit
git add apps/admin/src/pages/ClassroomRentals/index.tsx
git commit -m "chore: ClassroomRentals 复查 scroll 和 wrap确认无需修改"

注:如果复查无改动,可跳过此 commit。

archived-with: 2026-07-03-admin-responsive-adaptation

Task 13: 租赁方 Tenants 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Tenants/index.tsx

Interfaces:

  • Consumes: 已有工具栏
  • Produces: 表格 scroll

Description: 表格添加 scroll={{ x }};工具栏已有 wrap 和 gap。

  • Step 1: 表格添加 scroll

在第 183 行的 <Table 上添加 scroll

      <Table
        columns={columns}
        dataSource={filteredData}
        rowKey="id"
        loading={loading}
        scroll={{ x: 700 }}
        pagination={{ pageSize: 20, showTotal: (total) => `共 ${total} 条` }}
      />

列较少(名称、联系人、电话、颜色、备注 + 操作),使用 x: 700

  • Step 2: 工具栏已有 wrap 和 gap

第 152-159 行的工具栏外层 div 已有 flexWrap: 'wrap'gap: 8。无需修改。

  • Step 3: Commit
git add apps/admin/src/pages/Tenants/index.tsx
git commit -m "feat: 租赁方表格添加 scroll 横向滚动"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 14: 操作日志 OperationLogs 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/OperationLogs/index.tsx

Interfaces:

  • Consumes: 已有 scroll={{ x: 1000 }} 和工具栏
  • Produces: 确认现有适配

Description: 表格已有 scroll={{ x: 1000 }} 且值合理8 列),无需修改;工具栏已有 wrap 和 gap。

  • Step 1: 复查现有 scroll 值

第 151 行已有 scroll={{ x: 1000 }}。该表有 8 列时间、操作人、模块、操作、状态、详情、IP、终端x: 1000 合理。无需修改。

  • Step 2: 工具栏已有 flexWrap 和 gap

第 113-121 行已有 flexWrap: 'wrap'gap: 12。无需修改。

  • Step 3: Commit
git add apps/admin/src/pages/OperationLogs/index.tsx
git commit -m "chore: OperationLogs 复查 scroll 和 wrap确认无需修改"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 15: 角色管理 Roles 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Roles/index.tsx

Interfaces:

  • Consumes: 已有 scroll={{ x: 800 }}、工具栏、权限分配弹窗
  • Produces: 工具栏 wrap、弹窗宽度

Description: 表格已有 scroll={{ x: 800 }};工具栏添加 wrap权限分配弹窗 width={700} 由全局 CSS 覆盖窄屏。

  • Step 1: 工具栏添加 flexWrap 和 gap

替换第 206-212 行的工具栏 div

      <div
        style={{
          marginBottom: 16,
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          flexWrap: 'wrap',
          gap: 8,
        }}
      >
        <h2 style={{ margin: 0 }}>角色管理</h2>
        <PermissionButton ...>新增角色</PermissionButton>
      </div>
  • Step 2: 复查表格 scroll

第 224 行已有 scroll={{ x: 800 }}。该表有 ID、名称、描述、权限标签、系统 + 操作列fixed: rightx: 800 合理。无需修改。

  • Step 3: 弹窗宽度

权限分配弹窗第 238 行 width={700},在窄屏下由全局 CSS 覆盖。无需修改。

  • Step 4: Commit
git add apps/admin/src/pages/Roles/index.tsx
git commit -m "feat: 角色管理工具栏添加 flexWrap 响应式适配"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 16: 权限一览 Permissions 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Permissions/index.tsx

Interfaces:

  • Consumes: 卡片列表(非 Table 页面)
  • Produces: 工具栏 wrap

Description: 权限一览是卡片列表(非表格),只需确保标题 + 搜索栏在小屏下堆叠。

  • Step 1: 工具栏添加 flexWrap 和 gap

替换第 58-64 行的工具栏 div

      <div
        style={{
          marginBottom: 16,
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          flexWrap: 'wrap',
          gap: 8,
        }}
      >
        <h2 style={{ margin: 0 }}>权限一览</h2>
        <Input.Search ... />
      </div>
  • Step 2: Commit
git add apps/admin/src/pages/Permissions/index.tsx
git commit -m "feat: 权限一览工具栏添加 flexWrap 响应式适配"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 17: 账号管理 Users 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Users/index.tsx

Interfaces:

  • Consumes: 已有 scroll={{ x: 1000 }}
  • Produces: 工具栏 wrap

Description: 表格已有 scroll={{ x: 1000 }};工具栏添加 wrap。

  • Step 1: 工具栏添加 flexWrap 和 gap

替换第 198-204 行的工具栏 div

      <div
        style={{
          marginBottom: 16,
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          flexWrap: 'wrap',
          gap: 8,
        }}
      >
        <h2 style={{ margin: 0 }}>账号管理</h2>
        <PermissionButton ...>新增账号</PermissionButton>
      </div>
  • Step 2: 复查表格 scroll

第 216 行已有 scroll={{ x: 1000 }}。该表有 ID、用户名、姓名、角色、状态、最后登录、创建时间 + 操作列fixed: rightx: 1000 合理。无需修改。

  • Step 3: Commit
git add apps/admin/src/pages/Users/index.tsx
git commit -m "feat: 账号管理工具栏添加 flexWrap 响应式适配"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 18: 教室排期 ClassroomSchedule 特殊适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/ClassroomSchedule/index.tsx

Interfaces:

  • Consumes: 已有 overflowX: 'auto' 容器、sticky 首列、minWidth 日期列
  • Produces: 确认日期列不被挤压sticky 列在小屏正常

Description: 教室排期是自定义 HTML <table>,含 31+ 日期列 + sticky 首列。当前外层 div 已有 overflowX: 'auto',日期列 minWidth: 26,首列 sticky。确认在平板/手机下正常工作。

  • Step 1: 日期列 minWidth 不变

第 234 行已设置 minWidth: 26,在窄屏下不会挤压。无需修改。

  • Step 2: 首列 sticky 左偏移确认

第 210 行首列已有 position: 'sticky', left: 0,在小屏下仍然生效。无需修改。

  • Step 3: 详情弹窗宽度

第 327 行弹窗 width={500},在窄屏下由全局 CSS 覆盖。无需修改。

  • Step 4: Commit
git add apps/admin/src/pages/ClassroomSchedule/index.tsx
git commit -m "chore: ClassroomSchedule 复查 sticky 列和日期列 minWidth确认无需修改"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 19: 登录页 Login 响应式适配

Files:

  • Modify: /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Login/index.tsx

Interfaces:

  • Consumes: 已有 Card 登录表单
  • Produces: 卡片宽度改为 maxWidth + calc

Description: 登录卡片当前 width: 400,改为 maxWidth: 400, width: 'calc(100vw - 48px)' 以在手机下自适应。

  • Step 1: 卡片宽度响应式

替换第 39-45 行的 Card style

      <Card
        style={{
          maxWidth: 400,
          width: 'calc(100vw - 48px)',
          borderRadius: 16,
          boxShadow: '0 4px 24px rgba(0,0,0,0.08)',
          border: 'none',
        }}
      >

变化:width: 400 -> maxWidth: 400, width: 'calc(100vw - 48px)'

  • Step 2: Commit
git add apps/admin/src/pages/Login/index.tsx
git commit -m "feat: 登录卡片改为 maxWidth + calc 响应式宽度"

archived-with: 2026-07-03-admin-responsive-adaptation

Task 20: 全局验证与编译检查

Files:

  • No specific file modifications; validation only

Interfaces:

  • Consumes: 所有已完成的页面改动
  • Produces: 验证报告(视觉 + 编译)

Description: 在 Chrome DevTools 响应式模式下手动检查所有 16 个页面在三端视口宽度下的表现,确认无溢出、弹窗不出屏、表格可横向滚动。最后运行 npm run build 确保 TypeScript 编译通过。

  • Step 1: Chrome DevTools 响应式模式视觉检查

打开浏览器访问 http://localhost:5173(或部署地址),使用 Chrome DevTools 的设备工具栏切换以下宽度:

宽度 端侧 检查要点
375px 手机 表格可横向滚动、按钮不溢出、弹窗不出屏、文字不截断
768px 平板 Sider 默认折叠、Content padding 为 16px、卡片网格正确
992px 桌面小屏 Sider 可展开、Content padding 为 24px
1440px 桌面大屏 全部正常,无奇怪空白

逐页检查16 个页面):

  1. /dashboard
  2. /room-visual
  3. /students
  4. /rooms
  5. /occupancies
  6. /expenses
  7. /deposits
  8. /bills
  9. /classrooms
  10. /classroom-rentals
  11. /tenants
  12. /classroom-schedule
  13. /operation-logs
  14. /roles
  15. /permissions
  16. /users
  17. /login

每个页面检查项:

  • 表格有无横向滚动条(在窄屏下出现,且不影响行选择/操作)

  • 按钮组是否错位/溢出容器

  • 弹窗(打开任意新增/编辑弹窗)是否超出屏幕边界

  • 统计卡片/网格是否按预期列数排列

  • ECharts 图表是否正确跟随容器宽度

  • 文字是否正常截断ellipsis 生效)

  • Step 2: TypeScript 编译检查

cd /Users/tiku1/code/gongxue-base
npm run build

预期:tsc -b && vite build 无错误输出,构建成功。

如果有 TypeScript 错误,修复后再执行:

cd /Users/tiku1/code/gongxue-base/apps/admin
npx tsc -b --noEmit
  • Step 3: Commit
git add -A
git commit -m "chore: 全局响应式适配验证完成"

如果验证中发现问题需要修复,则在对应页面进行修复后单独 commit。

archived-with: 2026-07-03-admin-responsive-adaptation

Critical Files for Implementation

  • /Users/tiku1/code/gongxue-base/apps/admin/src/index.css
  • /Users/tiku1/code/gongxue-base/apps/admin/src/layouts/MainLayout.tsx
  • /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Dashboard/index.tsx
  • /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Students/index.tsx
  • /Users/tiku1/code/gongxue-base/apps/admin/src/pages/Occupancies/index.tsx