Files
vibe_coding/.cursor/rules/references/018-responsive-deep.md
2026-03-05 21:27:11 +08:00

388 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 018-responsive.mdc (Deep Reference)
> 该文件为原始详细规范归档,供 Tier 3 按需读取。
---
# 📱 Responsive Design & Multi-Device Standards
## 断点体系(与 Tailwind 统一)
> **⚠️ 双前端区分**:本文件中的 Element Plus 移动端适配内容**仅适用于管理端** (`Case-Database-Frontend-admin/`)。
> 用户端 (`Case-Database-Frontend-user/`) 使用 Headless UI + Tailwind CSS**禁止引入 Element Plus**。
| 断点 | Tailwind 前缀 | 最小宽度 | 目标设备 |
|------|--------------|---------|---------|
| 默认 | (无前缀) | 0px | 手机竖屏 (< 640px) |
| sm | `sm:` | 640px | 手机横屏 / 小平板 |
| md | `md:` | 768px | 平板竖屏 (iPad) |
| lg | `lg:` | 1024px | 平板横屏 / 小屏笔记本 |
| xl | `xl:` | 1280px | 桌面 |
| 2xl | `2xl:` | 1536px | 大屏桌面 / 4K |
> **原则**移动优先Mobile-First— 先写手机样式,用断点向上覆盖。
```html
<!-- ✅ 移动优先:默认手机,逐步增强 -->
<div class="p-4 md:p-6 xl:p-8 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<!-- 手机 1列 → 平板 2列 → 桌面 3列 -->
</div>
<!-- ❌ 桌面优先禁用max-* 限制往下适配 -->
<div class="grid grid-cols-3 max-md:grid-cols-1">...</div>
```
---
## 布局组件响应式模式
### 侧边栏布局(后台管理系统标准模式)
```vue
<!-- src/layouts/AppLayout.vue -->
<template>
<div class="flex h-screen overflow-hidden bg-gray-50 dark:bg-gray-900">
<!-- 遮罩层移动端打开侧边栏时显示 -->
<Transition name="fade">
<div
v-if="isMobile && sidebarOpen"
class="fixed inset-0 z-20 bg-black/50"
@click="sidebarOpen = false"
/>
</Transition>
<!-- 侧边栏桌面固定移动端抽屉式 -->
<aside
:class="[
'fixed lg:relative z-30 h-full transition-transform duration-300',
'w-64 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700',
isMobile && !sidebarOpen ? '-translate-x-full' : 'translate-x-0',
]"
>
<ArtSidebarMenu />
</aside>
<!-- 主内容区 -->
<div class="flex-1 flex flex-col min-w-0 overflow-hidden">
<!-- 顶部导航 -->
<header class="h-14 md:h-16 flex items-center px-4 md:px-6 border-b bg-white dark:bg-gray-800">
<!-- 移动端汉堡菜单按钮 -->
<button
class="lg:hidden mr-3 p-2 rounded-lg text-gray-500 hover:bg-gray-100"
aria-label="打开菜单"
@click="sidebarOpen = !sidebarOpen"
>
<el-icon :size="20"><Menu /></el-icon>
</button>
<ArtHeader />
</header>
<!-- 页面内容 -->
<main class="flex-1 overflow-auto">
<div class="p-4 md:p-6 xl:p-8 max-w-screen-2xl mx-auto">
<router-view />
</div>
</main>
</div>
</div>
</template>
<script setup>
const { isMobile } = useDevice()
const sidebarOpen = ref(false)
// 路由变化时关闭移动端侧边栏
const route = useRoute()
watch(() => route.path, () => {
if (isMobile.value) sidebarOpen.value = false
})
</script>
```
### `useDevice` Composable
```typescript
// src/composables/useDevice.ts
export function useDevice() {
const width = ref(window.innerWidth)
const handleResize = useDebounceFn(() => {
width.value = window.innerWidth
}, 100)
onMounted(() => window.addEventListener('resize', handleResize))
onUnmounted(() => window.removeEventListener('resize', handleResize))
return {
width: readonly(width),
isMobile: computed(() => width.value < 768),
isTablet: computed(() => width.value >= 768 && width.value < 1024),
isDesktop: computed(() => width.value >= 1024),
isTouch: computed(() => 'ontouchstart' in window),
}
}
```
---
## Element Plus 移动端适配(仅管理端)
### 组件尺寸策略
```vue
<script setup>
const { isMobile } = useDevice()
const elSize = computed(() => (isMobile.value ? 'small' : 'default'))
</script>
<template>
<!-- 根据设备自适应 size -->
<el-form :size="elSize">
<el-input :size="elSize" />
<el-button :size="elSize" type="primary">提交</el-button>
</el-form>
<!-- 表格移动端简化列 -->
<el-table :data="tableData">
<el-table-column prop="name" label="名称" min-width="120" />
<el-table-column v-if="!isMobile" prop="createdAt" label="创建时间" width="160" />
<el-table-column v-if="!isMobile" prop="status" label="状态" width="100" />
<!-- 移动端合并展示 -->
<el-table-column v-if="isMobile" label="详情" min-width="200">
<template #default="{ row }">
<div class="text-sm">{{ row.status }} · {{ row.createdAt }}</div>
</template>
</el-table-column>
</el-table>
</template>
```
### 对话框适配
```vue
<!-- 移动端全屏对话框 -->
<el-dialog
v-model="dialogVisible"
:fullscreen="isMobile"
:width="isMobile ? '100%' : '600px'"
:class="{ 'rounded-t-2xl': isMobile }"
>
```
### 分页适配
```vue
<!-- 移动端简化分页 -->
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:layout="isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
:pager-count="isMobile ? 3 : 7"
:page-sizes="isMobile ? [10, 20] : [10, 20, 50, 100]"
:total="total"
/>
```
### 表单布局
```vue
<!-- 移动端垂直布局桌面水平布局 -->
<el-form
:label-position="isMobile ? 'top' : 'right'"
:label-width="isMobile ? 'auto' : '100px'"
>
```
---
## 触摸与手势优化
### 最小触摸目标尺寸
```scss
// src/assets/styles/touch.scss
// 根据 WCAG 2.5.5,触摸目标最小 44×44px
.touch-target {
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
@media (hover: none) and (pointer: coarse) {
// 触屏设备:增大可点击区域
.el-button {
min-height: 44px;
padding-left: 16px;
padding-right: 16px;
}
.el-input__wrapper {
min-height: 44px;
}
}
```
### 滑动手势(移动端列表操作)
```typescript
// src/composables/useSwipeAction.ts
export function useSwipeAction(onDelete: () => void, onEdit: () => void) {
const startX = ref(0)
const offsetX = ref(0)
const THRESHOLD = 80
function onTouchStart(e: TouchEvent) {
startX.value = e.touches[0].clientX
}
function onTouchMove(e: TouchEvent) {
const diff = e.touches[0].clientX - startX.value
offsetX.value = Math.max(-160, Math.min(0, diff)) // 最多滑动 160px
}
function onTouchEnd() {
if (offsetX.value < -THRESHOLD) {
// 滑过阈值:显示操作按钮
} else {
offsetX.value = 0 // 弹回
}
}
return { offsetX, onTouchStart, onTouchMove, onTouchEnd }
}
```
---
## 图片与媒体响应式
```vue
<template>
<!-- 响应式图片不同分辨率加载不同尺寸 -->
<picture>
<source media="(min-width: 1280px)" srcset="/img/banner-xl.webp" />
<source media="(min-width: 768px)" srcset="/img/banner-md.webp" />
<img src="/img/banner-sm.webp" alt="Banner" class="w-full h-auto object-cover" loading="lazy" />
</picture>
<!-- 使用 Intersection Observer 懒加载 -->
<img
v-lazy="imageUrl"
alt="产品图片"
class="w-full aspect-square object-cover rounded-lg"
:class="{ 'animate-pulse bg-gray-200': !isLoaded }"
/>
</template>
```
---
## 字体与文字排版
```scss
// 流式字体大小:随屏幕宽度线性变化
:root {
--font-base: clamp(14px, 1vw + 12px, 16px);
--font-heading: clamp(20px, 3vw + 10px, 36px);
}
body { font-size: var(--font-base); }
h1 { font-size: var(--font-heading); }
// 行高:移动端更宽松(小屏阅读舒适度)
p {
line-height: 1.6;
@media (max-width: 767px) {
line-height: 1.8;
}
}
```
---
## 安全区域(刘海屏 / 全面屏)
```css
/* 底部安全区域iOS 全面屏 Home Bar*/
.bottom-nav {
padding-bottom: env(safe-area-inset-bottom, 16px);
}
/* 顶部安全区域(刘海屏)*/
.top-bar {
padding-top: env(safe-area-inset-top, 0px);
}
```
```typescript
// tailwind.config.ts — 添加安全区域工具类
theme: {
extend: {
padding: {
'safe-bottom': 'env(safe-area-inset-bottom, 16px)',
'safe-top': 'env(safe-area-inset-top, 0px)',
},
},
},
```
---
## 响应式测试矩阵
| 设备 | 分辨率 | 断点 | 关键功能检查 |
|------|--------|------|------------|
| iPhone SE (3代) | 375×667 | xs | 侧边栏抽屉、表单垂直布局 |
| iPhone 14 Pro | 393×852 | xs | 安全区域、全面屏底部导航 |
| iPhone 14 Pro Max | 430×932 | sm | 横屏布局、键盘遮挡处理 |
| iPad (10代) | 820×1180 | md | 平板双栏、对话框宽度 |
| iPad Pro 12.9" | 1024×1366 | lg | 分页组件、表格完整列 |
| MacBook Air 13" | 1280×800 | xl | 完整侧边栏、桌面表格 |
| 4K 显示器 | 1920×1080 | 2xl | 最大宽度限制、留白 |
### 测试脚本Playwright
```typescript
// tests/responsive.spec.ts
import { test, expect } from '@playwright/test'
const viewports = [
{ name: 'mobile', width: 375, height: 812 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1280, height: 800 },
]
for (const vp of viewports) {
test(`dashboard layout on ${vp.name}`, async ({ page }) => {
await page.setViewportSize({ width: vp.width, height: vp.height })
await page.goto('/dashboard')
if (vp.name === 'mobile') {
// 移动端:侧边栏默认隐藏
await expect(page.locator('aside')).toHaveCSS('transform', 'matrix(1, 0, 0, 1, -256, 0)')
// 汉堡按钮可见
await expect(page.locator('[aria-label="打开菜单"]')).toBeVisible()
} else {
// 平板/桌面:侧边栏默认显示
await expect(page.locator('aside')).toBeVisible()
}
})
}
```
---
## 规则
- 所有新页面必须在 375px / 768px / 1280px 三个断点下验证
- 禁止使用 `px` 设置字体大小(用 `rem` 或 Tailwind 文本类)
- 触摸目标最小 44×44px
- 禁止使用 `:hover` 作为唯一交互反馈(移动端无 hover
- 管理端:所有 `el-dialog` 必须处理移动端 `fullscreen` 属性(用户端使用 Headless UI Dialog
- 图片必须设置 `loading="lazy"``aspect-ratio`(防止布局抖动 CLS
- 横屏landscape模式下的布局需专门测试