- 文档版本: 1.0
- 更新日期: 2026-01-06
- 适用项目: React DocQA 应用
- 参考标准: ChatGPT Web、WeChat Web、Slack 等业界标准
┌─ Web 端 (≥768px) ┌─ H5 端 (<768px)
│ ├─ Header (固定, 56px) │ ├─ Header (固定, 56px)
│ ├─ Sidebar (可选) │ ├─ Content (内部或全局滚)
│ ├─ Content (滚动) │ └─ TabBar (固定, 64px, safe-area)
│ └─ Footer (可选) │
│ └─ 无 Sidebar
const HEIGHTS = {
HEADER: 56, // 顶部导航栏
MOBILE_TABBAR: 64, // H5 底部导航(含 safe-area)
DESKTOP_SIDEBAR: 256, // 桌面侧边栏宽度
INPUT_MIN: 44, // 输入框最小高度
};| 页面类型 | 场景 | Web 滚动 | H5 滚动 | 推荐布局 |
|---|---|---|---|---|
| 数据展示 | 文档列表、首页 | 全局滚 | 全局滚 | ScrollablePageLayout |
| 实时交互 | 聊天、客服 | 消息独占 | 消息独占 | InteractivePageLayout |
| 混合型 | 设置页面 | 分区滚 | 分区滚 | HybridPageLayout |
适用页面:DocumentsPage、HomePage、SettingsPage(列表模式)
结构:
┌─────────────────────────────┐
│ Header (flex-shrink-0) │ 56px
├─────────────────────────────┤
│ │
│ Main Content (flex-1) │ 全局 overflow-y-auto
│ - Can scroll globally │
│ │
├─────────────────────────────┤
│ TabBar (md:hidden) │ 64px (H5 only)
└─────────────────────────────┘
关键 CSS:
<div class="flex flex-col h-screen bg-background">
<Header /> {/* flex-shrink-0 h-14 */}
<main class="flex-1 overflow-y-auto overflow-x-hidden">
{/* pb-20 md:pb-6 for TabBar clearance */}
</main>
<MobileNav /> {/* md:hidden */}
</div>
使用示例:
import { ScrollablePageLayout } from '@/shared/components/layout';
export function DocumentsPage() {
return (
<ScrollablePageLayout hideBottomNav={false}>
{/* Content scrolls globally */}
<DocumentList />
</ScrollablePageLayout>
);
}适用页面:ChatPage、RealTimeApp
结构:
┌──────────────────────────────────────┐
│ Header (flex-shrink-0) │ 56px
├──────────────────────────────────────┤
│ Sidebar │ │
│ (w-64) │ Main Content (flex-1) │
│ │ - overflow-y-auto │
│ │ - Independent scroll │
│ ├──────────────────────────┤
│ │ Footer / Input (shrink) │
└──────────────────────────────────────┘
H5: Sidebar hidden via md:hidden
TabBar: 64px (H5 only)
关键 CSS 属性:
{/* 外层 */}
<div class="flex flex-col h-screen overflow-hidden">
{/* 内层 flex 容器 */}
<div class="flex flex-1 overflow-hidden min-h-0">
{/* Sidebar - optional, Web only */}
<aside class="hidden md:flex w-64">...</aside>
{/* Chat Content */}
<div class="flex flex-col flex-1 min-w-0 overflow-hidden">
{/* Header */}
<header class="flex-shrink-0 h-14">...</header>
{/* Messages - 关键!min-h-0 让 flex 正确计算 */}
<div class="flex-1 overflow-y-auto overflow-x-hidden min-h-0">
{/* Content */}
</div>
{/* Input */}
<div class="flex-shrink-0 border-t bg-background">...</div>
</div>
</div>
</div>
min-h-0 的作用:
❌ 没有 min-h-0:
<div class="flex-1 overflow-y-auto">
内容超出容器,scrollbar 无效,不滚动
✅ 有 min-h-0:
<div class="flex-1 overflow-y-auto min-h-0">
flex 正确计算高度,scrollbar 有效,可滚动
使用示例:
import { InteractivePageLayout } from '@/shared/components/layout';
export function ChatPage() {
return (
<InteractivePageLayout
sidebar={<ChatSidebar />}
header={<ChatHeader />}
content={<MessagesList />}
footer={<ChatInput />}
/>
);
}适用页面:复杂后台系统、DetailPage + Sidebar
特点:
- 支持左/右侧边栏
- 支持顶部/底部固定区域
- 灵活的内容滚动策略(全局 or 内部)
/* 使用 CSS 变量 */
env(safe-area-inset-top) /* 顶部安全距离 */
env(safe-area-inset-bottom) /* 底部安全距离 */
env(safe-area-inset-left) /* 左侧安全距离 */
env(safe-area-inset-right) /* 右侧安全距离 */
/* 应用到 TabBar */
.mobile-nav {
padding-bottom: calc(1rem + max(0px, env(safe-area-inset-bottom)));
/* 或 Tailwind: safe-area-pb */
}// 监听虚拟键盘弹起
const handleInput = (e) => {
// iOS 虚拟键盘会推起页面
// 解决方案:使用 InteractivePageLayout,Input 固定在底部
e.currentTarget.scrollIntoView({ behavior: 'smooth' });
};
// Viewport Meta 标签
<meta name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover,
minimum-scale=1, maximum-scale=5, user-scalable=yes" />{/* H5 内容需要 pb-20(md:pb-6)为 TabBar 留空 */}
<main className="pb-20 md:pb-6">
<div className="max-w-7xl mx-auto p-4">
{/* Content */}
</div>
</main>
{/* TabBar */}
<nav className="fixed bottom-0 left-0 right-0 md:hidden h-16">
{/* Items */}
</nav>// tailwind.config.ts
export default {
theme: {
screens: {
'sm': '640px', // 手机横屏
'md': '768px', // 平板 / Web
'lg': '1024px', // 桌面
'xl': '1280px', // 大桌面
'2xl': '1536px', // 超大屏
},
},
};
// 使用示例
<div className="w-full md:w-64 lg:w-80">
{/* Mobile: full width, Tablet/Web: 256px or 320px */}
</div>// Web 端(≥768px)
const webHeight = `calc(100vh - 56px)`;
// H5 端(<768px)
const h5Height = `calc(100vh - 56px - 64px - max(0px, env(safe-area-inset-bottom)))`;
// 消息区高度(Content 在 flex-1)
// 自动计算 = 总高度 - Header - Footer❌ <div class="flex-1 overflow-y-auto">
✅ <div class="flex-1 overflow-y-auto min-h-0">
// min-h-0 让 flex 容器正确计算高度// ✅ 使用 InteractivePageLayout
<InteractivePageLayout
footer={<ChatInput />} // 固定在底部,键盘弹起时会推起
/>
// 手动处理:
input.addEventListener('focus', () => {
setTimeout(() => {
input.scrollIntoView({ behavior: 'smooth' });
}, 300); // 等待键盘弹起
});// ✅ 使用响应式隐藏
<aside className="hidden md:flex w-64">
{/* Sidebar - H5 隐藏 */}
</aside>// ✅ 为底部内容预留空间
<main className="pb-20 md:pb-6">
{/* pb-20 在 H5,pb-6 在 Web */}
</main>// ✅ 使用 transition 和 duration
<div className="bg-background transition-colors duration-300">
{/* 背景颜色平滑切换 */}
</div>- ✅ 使用
min-h-0在 flex 容器中避免计算错误 - ✅ 用
overflow-x-hidden代替overflow-hidden防止横向滚动条 - ✅ 消息列表使用虚拟滚动(大数据集)
- ✅ 使用
useCallback避免不必要的重新渲染 - ✅ 图片使用 lazy loading 和 CDN 优化
- ✅ 消息区域使用
scroll-behavior: smooth平滑滚动 - ✅ H5 上使用
-webkit-overflow-scrolling: touch启用惯性滚动
.messages-container {
-webkit-overflow-scrolling: touch; /* H5 惯性滚动 */
scroll-behavior: smooth; /* 平滑滚动 */
}import { InteractivePageLayout } from '@/shared/components/layout';
export function ChatPage() {
const [messages, setMessages] = useState([]);
const messagesRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// 自动滚到底部
messagesRef.current?.scrollTo({
top: messagesRef.current.scrollHeight,
behavior: 'smooth',
});
}, [messages]);
return (
<InteractivePageLayout
sidebar={<ChatSidebar />}
header={
<div className="h-14 px-4 flex items-center">
<h1>Chat</h1>
</div>
}
content={
<div ref={messagesRef} className="space-y-4 p-4">
{messages.map(msg => (
<MessageBubble key={msg.id} message={msg} />
))}
</div>
}
footer={
<div className="border-t bg-background p-4">
<ChatInput onSend={handleSend} />
</div>
}
/>
);
}import { ScrollablePageLayout } from '@/shared/components/layout';
export function DocumentsPage() {
const [documents] = useState([]);
return (
<ScrollablePageLayout hideBottomNav={false}>
<div className="space-y-6">
<h1>My Documents</h1>
<DocumentList documents={documents} />
</div>
</ScrollablePageLayout>
);
}- 是否需要固定 Input/Footer? → InteractivePageLayout
- 是否全局可滚动? → ScrollablePageLayout
- 是否支持侧边栏? → HybridPageLayout
// 旧
import { MainLayout } from '@/shared/components/layout/MainLayout';
// 新
import { ScrollablePageLayout } from '@/shared/components/layout';
// 或
import { InteractivePageLayout } from '@/shared/components/layout';// 旧
<MainLayout hideBottomNav={false} fullWidth={false}>
<YourContent />
</MainLayout>
// 新(数据展示类)
<ScrollablePageLayout hideBottomNav={false}>
<YourContent />
</ScrollablePageLayout>
// 新(实时交互类)
<InteractivePageLayout
header={<Header />}
content={<Content />}
footer={<Footer />}
sidebar={<Sidebar />}
/>// 检查布局高度
function debugLayoutHeights() {
const viewport = window.innerHeight;
const header = document.querySelector('header')?.offsetHeight;
const tabbar = document.querySelector('[role="navigation"]')?.offsetHeight;
const content = document.querySelector('[role="main"]')?.offsetHeight;
console.log({
viewport,
header,
tabbar,
content,
calculated: viewport - (header || 0) - (tabbar || 0),
});
}
// 响应式断点监听
const mq = window.matchMedia('(min-width: 768px)');
mq.addEventListener('change', (e) => {
console.log(e.matches ? 'Web mode' : 'H5 mode');
});| 特性 | ScrollablePageLayout | InteractivePageLayout | HybridPageLayout |
|---|---|---|---|
| 全局滚动 | ✅ | ❌ | ✅(可配置) |
| 内部滚动 | ❌ | ✅ | ✅(可配置) |
| Sidebar | ❌ | ✅ | ✅ |
| Header 固定 | ✅ | ✅ | ✅ |
| Footer 固定 | ❌ | ✅ | ✅ |
| 适合聊天 | ❌ | ✅⭐ | |
| 适合列表 | ✅⭐ | ❌ | |
| 适合后台 | ❌ | ✅⭐ | |
| H5 友好 | ✅ | ✅⭐ | ✅ |
| 复杂度 | ⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
版权声明:本规范参考业界标准应用(ChatGPT Web、WeChat、Slack 等),遵循 React 最佳实践。