467 lines
13 KiB
Markdown
467 lines
13 KiB
Markdown
# 前端移动端适配指南
|
||
|
||
## 一、现状评估
|
||
|
||
### 1.1 项目技术栈
|
||
|
||
| 项目 | 当前状态 |
|
||
|------|----------|
|
||
| UI 框架 | Element Plus(自带 `el-row`/`el-col` 响应式栅格) |
|
||
| CSS 框架 | **无**(无 Tailwind、无 Bootstrap) |
|
||
| 响应式工具 | 手写媒体查询,仅 4/22 个 Vue 文件包含 `@media` |
|
||
| 移动端检测 | `App.vue` 和 `RecommendForm.vue` 各自实现了 `window.innerWidth < 768` 逻辑,未复用 |
|
||
| 断点体系 | **仅一个断点**:`max-width: 767px`(手机),仅 `App.vue` 有平板断点(768–1024px) |
|
||
|
||
### 1.2 适配完成度
|
||
|
||
| 等级 | 文件数 | 占比 |
|
||
|------|:------:|:----:|
|
||
| ✅ 已适配 | 4 | 18% |
|
||
| ⚠️ 部分适配 | 3 | 14% |
|
||
| ❌ 未适配 | 15 | 68% |
|
||
|
||
**已适配**:`App.vue`、`RecommendForm.vue`、`ChatCopyButton.vue`、`ChatSuggestions.vue`
|
||
**部分适配**:`ChatPage.vue`、`ChatSidebar.vue`、`ChatEmbed.vue`
|
||
**未适配**:其余 15 个页面/组件
|
||
|
||
---
|
||
|
||
## 二、问题清单(按严重程度排序)
|
||
|
||
### 🔴 严重 — 移动端完全不可用
|
||
|
||
| 文件 | 问题 | 影响 |
|
||
|------|------|------|
|
||
| `RegisterPage.vue` | `.register-card` 硬编码 `width: 400px`,无媒体查询 | <420px 屏幕水平溢出 |
|
||
| `RecommendDetailView.vue` | `el-descriptions` 强制 3 列;4 列 plan 表格;标题栏 3 个按钮 | 布局严重错乱 |
|
||
| `RecommendHistory.vue` | 筛选栏最小宽度 ~560px;表格总宽 ~920px | 完全溢出 |
|
||
| `DashboardPage.vue` | KPI 卡片 `el-col :span="6"` 无 `:xs` 断点;快捷卡片 `:span="8"` | 4 列/3 列在手机上窄至 ~80px |
|
||
| `PermissionsPage.vue` | `el-col :span="8"` 和 `:span="16"` 无响应式断点 | 角色列表/权限面板在手机上不可用 |
|
||
| `DataSourcePage.vue` | 同步日志弹窗 `width="700px"` | 所有手机屏幕溢出 |
|
||
|
||
### 🟠 高 — 移动端严重降级
|
||
|
||
| 文件 | 问题 |
|
||
|------|------|
|
||
| `ChatFilters.vue` | flex 无 wrap,筛选栏溢出;`.filter-label` 设了 `white-space: nowrap` |
|
||
| `RecommendResult.vue`(组件) | 7 列表格无横向滚动;替换弹窗 `width="600px"` |
|
||
| `UsersPage.vue` | 筛选栏溢出(固定 160px × 2 + 按钮);表格 ~870px;弹窗 480px |
|
||
| `StatsPage.vue` | `el-col :span="12"`/`:span="8"` 无响应式;筛选器固定宽度 |
|
||
| `TemplatePage.vue` | 编辑弹窗 `width="50%"`(手机 ~180px);placeholder 行总宽 420px |
|
||
| `KBManagePage.vue` | 筛选栏总宽 ~580px;状态弹窗 500px |
|
||
|
||
### 🟡 中 — 移动端可感知问题
|
||
|
||
| 文件 | 问题 |
|
||
|------|------|
|
||
| `ChatPage.vue` | 侧边栏固定 240px,无平板适配 |
|
||
| `ChatEmbed.vue` | 纠错弹窗 `width="500px"`;消息列表/输入区 padding 不缩 |
|
||
| `PromptPage.vue` | 创建弹窗 `width="70%"`(手机 ~252px,过窄) |
|
||
| `NotificationPage.vue` | 弹窗 `width="50%"`(手机 ~180px,不可用) |
|
||
| `DepartmentPage.vue` | 弹窗 `width="500px"` |
|
||
| `LoginLogsPage.vue` | 筛选项固定宽度(150px/120px/280px) |
|
||
| `ChatLogsPage.vue` | 筛选项固定宽度(200px/150px/280px) |
|
||
|
||
### 🟢 低 — 细节问题
|
||
|
||
| 文件 | 问题 |
|
||
|------|------|
|
||
| `LoginPage.vue` | 背景装饰 `.bg-decoration` 可能在极窄屏幕造成溢出 |
|
||
| `ChatSidebar.vue` | 移动端 `max-height: 200px` 过小(仅 ~3 个会话);删除按钮仅 hover 可见,触屏不可发现 |
|
||
|
||
---
|
||
|
||
## 三、根因分析
|
||
|
||
### 3.1 六大共性问题
|
||
|
||
1. **无响应式框架** — 全靠手写 media query,覆盖率极低
|
||
2. **inline style 主导** — 大量页面用 `style="width: 160px"` 等内联样式,无法通过媒体查询覆盖
|
||
3. **弹窗宽度硬编码** — `el-dialog` 统一用固定像素(480–700px)或固定百分比(50%–70%),无移动端 override
|
||
4. **表格无响应式策略** — 多列表格总宽 870–920px,无横向滚动包裹、无列隐藏、无卡片视图
|
||
5. **断点体系缺失** — 仅 `max-width: 767px` 一个断点,无平板/大屏手机/横屏适配
|
||
6. **移动端检测重复** — `App.vue` 和 `RecommendForm.vue` 各自实现 `isMobile` 逻辑
|
||
|
||
---
|
||
|
||
## 四、适配方案
|
||
|
||
### 4.1 断点体系(统一标准)
|
||
|
||
```css
|
||
/* 手机竖屏 */
|
||
@media (max-width: 575px) { /* ... */ }
|
||
|
||
/* 手机横屏 / 大屏手机 */
|
||
@media (min-width: 576px) and (max-width: 767px) { /* ... */ }
|
||
|
||
/* 平板竖屏 */
|
||
@media (min-width: 768px) and (max-width: 1023px) { /* ... */ }
|
||
|
||
/* 桌面 */
|
||
@media (min-width: 1024px) { /* ... */ }
|
||
```
|
||
|
||
与 Element Plus 断点保持一致:`xs`(<768px)、`sm`(≥768px)、`md`(≥992px)、`lg`(≥1200px)。
|
||
|
||
### 4.2 全局 CSS 变量(建议新建 `src/styles/responsive.css`)
|
||
|
||
```css
|
||
:root {
|
||
/* 间距 */
|
||
--space-xs: 4px;
|
||
--space-sm: 8px;
|
||
--space-md: 16px;
|
||
--space-lg: 24px;
|
||
--space-xl: 32px;
|
||
|
||
/* 弹窗宽度 */
|
||
--dialog-width-sm: 90vw;
|
||
--dialog-width-md: 80vw;
|
||
--dialog-width-lg: 600px;
|
||
|
||
/* 表格 */
|
||
--table-scroll-max-width: calc(100vw - 48px);
|
||
}
|
||
|
||
@media (min-width: 768px) {
|
||
:root {
|
||
--dialog-width-sm: 500px;
|
||
--dialog-width-md: 600px;
|
||
--dialog-width-lg: 700px;
|
||
--table-scroll-max-width: 100%;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.3 共享 Composable(新建 `src/composables/useMobile.ts`)
|
||
|
||
```typescript
|
||
import { ref, onMounted, onUnmounted } from 'vue'
|
||
|
||
export function useMobile(breakpoint = 768) {
|
||
const isMobile = ref(window.innerWidth < breakpoint)
|
||
const isTablet = ref(window.innerWidth >= 768 && window.innerWidth < 1024)
|
||
|
||
const update = () => {
|
||
isMobile.value = window.innerWidth < breakpoint
|
||
isTablet.value = window.innerWidth >= 768 && window.innerWidth < 1024
|
||
}
|
||
|
||
onMounted(() => window.addEventListener('resize', update))
|
||
onUnmounted(() => window.removeEventListener('resize', update))
|
||
|
||
return { isMobile, isTablet }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、逐页面修复方案
|
||
|
||
### 5.1 注册页 `RegisterPage.vue`
|
||
|
||
**问题**:卡片固定 400px 宽度
|
||
**修复**:
|
||
|
||
```css
|
||
.register-card {
|
||
width: 400px;
|
||
}
|
||
@media (max-width: 575px) {
|
||
.register-card {
|
||
width: 92vw;
|
||
max-width: 400px;
|
||
}
|
||
}
|
||
```
|
||
|
||
`el-form` 的 `label-width` 改为响应式:`:label-width="isMobile ? '0px' : '80px'"`(手机用 top label)。
|
||
|
||
### 5.2 推荐详情页 `RecommendDetailView.vue`
|
||
|
||
**问题**:3 列描述、4 列表格、标题栏 3 按钮
|
||
**修复**:
|
||
|
||
- `el-descriptions`: `:column="isMobile ? 1 : 3"`
|
||
- 标题栏按钮:`flex-wrap: wrap` 或手机端折叠为下拉菜单
|
||
- plan 表格:外层加 `<div style="overflow-x: auto">`;手机端隐藏"推荐理由"列
|
||
|
||
### 5.3 推荐历史页 `RecommendHistory.vue`
|
||
|
||
**问题**:筛选栏溢出、表格过宽
|
||
**修复**:
|
||
|
||
- 筛选栏:`flex-wrap: wrap`;筛选项用 `min-width: 120px; flex: 1` 替代固定宽度
|
||
- 表格:外层 `<div style="overflow-x: auto">`;手机端隐藏低优先级列(如"保单号"、"创建时间")
|
||
- 或使用 Element Plus 的 `el-table` `scrollbar-always-on` + 设置列 `min-width`
|
||
|
||
### 5.4 仪表盘 `DashboardPage.vue`
|
||
|
||
**问题**:KPI 4 列、快捷 3 列无响应式
|
||
**修复**:
|
||
|
||
```html
|
||
<!-- KPI 卡片 -->
|
||
<el-col :xs="12" :sm="12" :md="6" v-for="item in kpiCards">
|
||
|
||
<!-- 快捷卡片 -->
|
||
<el-col :xs="12" :sm="8" :md="8" v-for="item in shortcutCards">
|
||
```
|
||
|
||
### 5.5 权限页 `PermissionsPage.vue`
|
||
|
||
**问题**:角色列表 8/权限 16 分割无响应式
|
||
**修复**:
|
||
|
||
```html
|
||
<el-col :xs="24" :sm="8"> <!-- 角色列表 -->
|
||
<el-col :xs="24" :sm="16"> <!-- 权限面板 -->
|
||
```
|
||
|
||
手机端改为上下堆叠布局。
|
||
|
||
### 5.6 数据源页 `DataSourcePage.vue`
|
||
|
||
**问题**:同步日志弹窗 700px
|
||
**修复**:改为 `width="var(--dialog-width-lg)"` 或 `width="min(700px, 90vw)"`
|
||
|
||
### 5.7 聊天筛选 `ChatFilters.vue`
|
||
|
||
**问题**:flex 无 wrap,标签 nowrap
|
||
**修复**:
|
||
|
||
```css
|
||
.chat-filters {
|
||
flex-wrap: wrap;
|
||
}
|
||
.filter-label {
|
||
white-space: normal; /* 允许换行 */
|
||
}
|
||
```
|
||
|
||
### 5.8 推荐结果组件 `RecommendResult.vue`
|
||
|
||
**问题**:7 列表格无滚动
|
||
**修复**:表格包裹 `<div class="table-responsive">`
|
||
|
||
```css
|
||
.table-responsive {
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
```
|
||
|
||
替换弹窗改为 `width="min(600px, 90vw)"`。
|
||
|
||
### 5.9 用户管理 `UsersPage.vue`
|
||
|
||
**问题**:筛选栏溢出、表格过宽、弹窗固定
|
||
**修复**:
|
||
|
||
- 筛选栏:`flex-wrap: wrap`;筛选项 `flex: 1; min-width: 140px`
|
||
- 表格:`overflow-x: auto` 包裹;手机端隐藏部分列
|
||
- 弹窗:`width="min(480px, 90vw)"`
|
||
|
||
### 5.10 统计页 `StatsPage.vue`
|
||
|
||
**问题**:`el-col` 无响应式、筛选器固定宽度
|
||
**修复**:
|
||
|
||
```html
|
||
<el-col :xs="24" :sm="12" :md="8">
|
||
```
|
||
|
||
筛选器宽度改为 `width: 100%`(手机端)或 `flex: 1; min-width: 120px`。
|
||
|
||
### 5.11 模板页 `TemplatePage.vue`
|
||
|
||
**问题**:弹窗 50% 太窄;placeholder 行溢出
|
||
**修复**:
|
||
|
||
- 弹窗:改为 `width="min(500px, 90vw)"`
|
||
- placeholder 行:`flex-wrap: wrap`,每项 `min-width: 120px`
|
||
|
||
### 5.12 提示词页 `PromptPage.vue`
|
||
|
||
**问题**:弹窗 70% 手机端过窄
|
||
**修复**:改为 `width="min(600px, 90vw)"`
|
||
|
||
### 5.13 通知页 `NotificationPage.vue`
|
||
|
||
**问题**:弹窗 50% 不可用
|
||
**修复**:改为 `width="min(500px, 90vw)"`
|
||
|
||
### 5.14 部门页 `DepartmentPage.vue`
|
||
|
||
**问题**:弹窗 500px
|
||
**修复**:改为 `width="min(500px, 90vw)"`
|
||
|
||
### 5.15 知识库管理 `KBManagePage.vue`
|
||
|
||
**问题**:筛选栏溢出、弹窗固定
|
||
**修复**:
|
||
|
||
- 筛选栏:`flex-wrap: wrap`;筛选项 `flex: 1; min-width: 130px`
|
||
- 弹窗:`width="min(500px, 90vw)"`
|
||
|
||
### 5.16 登录日志 `LoginLogsPage.vue` / 聊天日志 `ChatLogsPage.vue`
|
||
|
||
**问题**:筛选项固定宽度
|
||
**修复**:筛选项改为 `width: 100%`(手机端)或 `flex: 1; min-width: 140px`
|
||
|
||
### 5.17 聊天嵌入 `ChatEmbed.vue`
|
||
|
||
**问题**:纠错弹窗 500px、padding 不适配
|
||
**修复**:
|
||
|
||
- 弹窗:`width="min(500px, 90vw)"`
|
||
- `.message-list`:手机端 padding 缩小为 `8px`
|
||
- `.input-area`:手机端 padding 缩小为 `8px 12px`
|
||
|
||
### 5.18 聊天侧边栏 `ChatSidebar.vue`
|
||
|
||
**问题**:手机端 max-height 过小;删除按钮 hover 不可发现
|
||
**修复**:
|
||
|
||
- `max-height` 从 `200px` 调整为 `240px` 或 `40vh`
|
||
- 删除按钮:手机端改为 `opacity: 1`(始终可见),或增加长按触发
|
||
|
||
### 5.19 登录页 `LoginPage.vue`
|
||
|
||
**问题**:背景装饰可能溢出
|
||
**修复**:`.bg-decoration` 加 `display: none` 在手机端
|
||
|
||
```css
|
||
@media (max-width: 575px) {
|
||
.bg-decoration {
|
||
display: none;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 六、弹窗适配规则(全局)
|
||
|
||
| 原写法 | 推荐写法 | 说明 |
|
||
|--------|----------|------|
|
||
| `width="700px"` | `width="min(700px, 90vw)"` | 大弹窗 |
|
||
| `width="600px"` | `width="min(600px, 90vw)"` | 中弹窗 |
|
||
| `width="500px"` | `width="min(500px, 90vw)"` | 小弹窗 |
|
||
| `width="480px"` | `width="min(480px, 90vw)"` | 表单弹窗 |
|
||
| `width="70%"` | `width="min(600px, 90vw)"` | 百分比在手机上过窄 |
|
||
| `width="50%"` | `width="min(500px, 90vw)"` | 百分比在手机上不可用 |
|
||
| `width="90%"` | ✅ 可以保留 | 已是响应式写法 |
|
||
|
||
> **规则**:所有 `el-dialog` 的 `width` 使用 `min(固定值, 90vw)` 模式,确保手机端不超过 90% 视口宽度。
|
||
|
||
---
|
||
|
||
## 七、表格适配规则(全局)
|
||
|
||
### 7.1 横向滚动(最简单)
|
||
|
||
所有 `el-table` 外层包裹:
|
||
|
||
```html
|
||
<div class="table-scroll-wrapper">
|
||
<el-table ...>
|
||
<!-- columns -->
|
||
</el-table>
|
||
</div>
|
||
```
|
||
|
||
```css
|
||
.table-scroll-wrapper {
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
```
|
||
|
||
### 7.2 列优先级隐藏
|
||
|
||
```css
|
||
@media (max-width: 767px) {
|
||
/* 隐藏低优先级列 — 通过 el-table 的 v-if 或 class 控制 */
|
||
.hide-on-mobile {
|
||
display: none;
|
||
}
|
||
}
|
||
```
|
||
|
||
或使用 Element Plus 的 `el-table-column` 的 `show-overflow-tooltip` + `min-width` 替代 `width`。
|
||
|
||
### 7.3 推荐的列宽策略
|
||
|
||
| 场景 | 策略 |
|
||
|------|------|
|
||
| 2–3 列表格 | 无需特殊处理 |
|
||
| 4–5 列表格 | `overflow-x: auto` 包裹 |
|
||
| 6+ 列表格 | `overflow-x: auto` + 手机端隐藏低优先级列 |
|
||
| 筛选栏 | `flex-wrap: wrap` + 筛选项 `flex: 1; min-width: 120px` |
|
||
|
||
---
|
||
|
||
## 八、筛选栏适配模板
|
||
|
||
所有筛选栏统一模式:
|
||
|
||
```css
|
||
.filter-bar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-item {
|
||
flex: 1;
|
||
min-width: 120px;
|
||
}
|
||
|
||
@media (max-width: 575px) {
|
||
.filter-bar {
|
||
gap: 8px;
|
||
}
|
||
.filter-item {
|
||
min-width: 100%;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 九、实施优先级
|
||
|
||
### Phase 1 — 核心页面(用户直接使用)
|
||
1. `ChatPage.vue` + `ChatEmbed.vue` + `ChatSidebar.vue` + `ChatFilters.vue`
|
||
2. `LoginPage.vue` + `RegisterPage.vue`
|
||
3. `RecommendForm.vue` + `RecommendResult.vue` + `RecommendDetailView.vue` + `RecommendHistory.vue`
|
||
|
||
### Phase 2 — 管理后台
|
||
4. `DashboardPage.vue`
|
||
5. `UsersPage.vue` + `PermissionsPage.vue`
|
||
6. 其余 admin 页面(`PromptPage`、`TemplatePage`、`StatsPage` 等)
|
||
|
||
### Phase 3 — 全局优化
|
||
7. 创建 `src/composables/useMobile.ts`,替换所有 `isMobile` 重复逻辑
|
||
8. 创建 `src/styles/responsive.css`,统一 CSS 变量
|
||
9. 创建 `src/styles/table.css`,统一表格滚动样式
|
||
10. 创建 `src/styles/dialog.css`,统一弹窗响应式
|
||
|
||
---
|
||
|
||
## 十、验证清单
|
||
|
||
每个页面适配后,用以下方式验证:
|
||
|
||
- [ ] Chrome DevTools → 切换到 iPhone SE (375px) 查看
|
||
- [ ] Chrome DevTools → 切换到 iPhone 14 Pro (393px) 查看
|
||
- [ ] Chrome DevTools → 切换到 iPad (768px) 查看
|
||
- [ ] 检查无水平滚动条(除非是表格横向滚动)
|
||
- [ ] 弹窗在手机端可正常显示和操作
|
||
- [ ] 筛选栏在手机端可正常换行
|
||
- [ ] 表格在手机端可横向滚动
|
||
- [ ] 触屏设备上无 hover-only 的交互死角
|