自定义页面
Rspress 允许您创建自定义页面布局,超越默认的文档格式。本章探讨如何使用 React 组件构建独特的自定义页面。
什么是自定义页面?
概述
自定义页面允许您创建不遵循标准文档布局的页面。它们非常适合:
- 着陆页
- 产品展示
- 仪表板
- 交互式演示
- 营销页面
- 定制工具
标准页面 vs 自定义页面
标准页面:
markdown
# 文档标题
普通的 Markdown 内容...自定义页面:
tsx
export default function CustomPage() {
return (
<div className="custom-layout">
<Hero />
<Features />
<CTA />
</div>
);
}创建自定义页面
基本自定义页面
创建带有自定义布局的页面:
tsx
// docs/custom-page.tsx
import React from 'react';
export default function CustomPage() {
return (
<div className="custom-page">
<header className="hero">
<h1>欢迎来到我的项目</h1>
<p>使用 Rspress 构建出色的文档</p>
<button className="cta-button">开始使用</button>
</header>
<section className="features">
<div className="feature">
<h3>快速</h3>
<p>闪电般快速的构建</p>
</div>
<div className="feature">
<h3>灵活</h3>
<p>完全可定制</p>
</div>
<div className="feature">
<h3>现代</h3>
<p>现代开发体验</p>
</div>
</section>
</div>
);
}带样式的自定义页面
使用 CSS 模块添加样式:
tsx
// docs/landing.tsx
import React from 'react';
import styles from './landing.module.css';
export default function LandingPage() {
return (
<div className={styles.container}>
<div className={styles.hero}>
<h1 className={styles.title}>下一代文档</h1>
<p className={styles.subtitle}>
快速、现代且强大
</p>
<div className={styles.actions}>
<a href="/guide/quick-start" className={styles.primary}>
快速开始
</a>
<a href="/guide/introduction" className={styles.secondary}>
了解更多
</a>
</div>
</div>
</div>
);
}css
/* docs/landing.module.css */
.container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.hero {
text-align: center;
max-width: 900px;
padding: 0 20px;
}
.title {
font-size: 4rem;
font-weight: bold;
margin-bottom: 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 1.5rem;
color: #666;
margin-bottom: 2rem;
}
.actions {
display: flex;
gap: 1rem;
justify-content: center;
}
.primary, .secondary {
padding: 12px 32px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: all 0.3s;
}
.primary {
background: #667eea;
color: white;
}
.primary:hover {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.secondary {
background: white;
color: #667eea;
border: 2px solid #667eea;
}
.secondary:hover {
background: #f0f0f0;
}页面布局模式
全宽布局
创建不受容器限制的全宽布局:
tsx
// docs/showcase.tsx
import React from 'react';
export default function Showcase() {
return (
<div style={{ width: '100vw', margin: 0, padding: 0 }}>
<section style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
padding: '100px 20px',
color: 'white',
textAlign: 'center'
}}>
<h1>展示</h1>
<p>使用我们的技术构建的令人惊叹的项目</p>
</section>
<section style={{ padding: '60px 20px' }}>
<div style={{
maxWidth: '1200px',
margin: '0 auto',
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '20px'
}}>
<ProjectCard
title="项目 1"
description="令人惊叹的文档站点"
image="/project1.png"
/>
<ProjectCard
title="项目 2"
description="交互式指南"
image="/project2.png"
/>
<ProjectCard
title="项目 3"
description="API 文档"
image="/project3.png"
/>
</div>
</section>
</div>
);
}
function ProjectCard({ title, description, image }) {
return (
<div style={{
border: '1px solid #eee',
borderRadius: '8px',
overflow: 'hidden',
transition: 'transform 0.3s'
}}>
<img src={image} alt={title} style={{ width: '100%', height: '200px', objectFit: 'cover' }} />
<div style={{ padding: '20px' }}>
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
);
}分栏布局
创建多列布局:
tsx
// docs/comparison.tsx
import React from 'react';
export default function Comparison() {
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '40px 20px' }}>
<h1 style={{ textAlign: 'center', marginBottom: '60px' }}>
选择正确的方案
</h1>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '30px'
}}>
<PricingCard
title="免费"
price="$0"
features={[
'最多 5 个项目',
'基本支持',
'社区访问'
]}
/>
<PricingCard
title="专业"
price="$29"
features={[
'无限项目',
'优先支持',
'高级功能',
'自定义域名'
]}
highlighted
/>
<PricingCard
title="企业"
price="联系我们"
features={[
'一切专业功能',
'专属支持',
'SLA 保证',
'定制开发'
]}
/>
</div>
</div>
);
}
function PricingCard({ title, price, features, highlighted = false }) {
return (
<div style={{
border: highlighted ? '3px solid #667eea' : '1px solid #eee',
borderRadius: '12px',
padding: '30px',
textAlign: 'center',
position: 'relative',
transform: highlighted ? 'scale(1.05)' : 'scale(1)',
boxShadow: highlighted ? '0 10px 40px rgba(0,0,0,0.1)' : 'none'
}}>
{highlighted && (
<div style={{
position: 'absolute',
top: '-15px',
left: '50%',
transform: 'translateX(-50%)',
background: '#667eea',
color: 'white',
padding: '5px 20px',
borderRadius: '20px',
fontSize: '12px',
fontWeight: 'bold'
}}>
最受欢迎
</div>
)}
<h3 style={{ fontSize: '24px', marginBottom: '10px' }}>{title}</h3>
<div style={{ fontSize: '36px', fontWeight: 'bold', marginBottom: '20px' }}>
{price}
</div>
<ul style={{ listStyle: 'none', padding: 0, marginBottom: '30px' }}>
{features.map((feature, i) => (
<li key={i} style={{ padding: '10px 0', borderBottom: '1px solid #f0f0f0' }}>
✓ {feature}
</li>
))}
</ul>
<button style={{
width: '100%',
padding: '12px',
background: highlighted ? '#667eea' : '#f0f0f0',
color: highlighted ? 'white' : '#333',
border: 'none',
borderRadius: '8px',
fontSize: '16px',
fontWeight: '600',
cursor: 'pointer'
}}>
开始使用
</button>
</div>
);
}交互式页面
带状态的交互页面
创建具有交互性的页面:
tsx
// docs/demo.tsx
import React, { useState } from 'react';
export default function DemoPage() {
const [activeTab, setActiveTab] = useState('overview');
const [code, setCode] = useState('console.log("Hello, World!");');
const [output, setOutput] = useState('');
const runCode = () => {
try {
// 警告: eval 在生产中不安全
const result = eval(code);
setOutput(String(result));
} catch (error) {
setOutput(`错误: ${error.message}`);
}
};
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '40px 20px' }}>
<h1>交互式演示</h1>
{/* 标签页导航 */}
<div style={{
display: 'flex',
gap: '10px',
borderBottom: '2px solid #eee',
marginBottom: '30px'
}}>
{['概览', '代码', '输出'].map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab.toLowerCase())}
style={{
padding: '10px 20px',
background: 'none',
border: 'none',
borderBottom: activeTab === tab.toLowerCase() ? '3px solid #667eea' : 'none',
color: activeTab === tab.toLowerCase() ? '#667eea' : '#666',
fontWeight: activeTab === tab.toLowerCase() ? 'bold' : 'normal',
cursor: 'pointer'
}}
>
{tab}
</button>
))}
</div>
{/* 标签页内容 */}
{activeTab === 'overview' && (
<div>
<h2>概览</h2>
<p>这是一个交互式代码演示场。尝试编写一些 JavaScript 代码并运行它!</p>
</div>
)}
{activeTab === 'code' && (
<div>
<h2>代码编辑器</h2>
<textarea
value={code}
onChange={(e) => setCode(e.target.value)}
style={{
width: '100%',
height: '300px',
fontFamily: 'monospace',
fontSize: '14px',
padding: '15px',
border: '1px solid #ddd',
borderRadius: '8px'
}}
/>
<button
onClick={runCode}
style={{
marginTop: '10px',
padding: '10px 20px',
background: '#667eea',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: 'pointer'
}}
>
运行代码
</button>
</div>
)}
{activeTab === 'output' && (
<div>
<h2>输出</h2>
<pre style={{
background: '#f5f5f5',
padding: '20px',
borderRadius: '8px',
minHeight: '100px'
}}>
{output || '尚无输出'}
</pre>
</div>
)}
</div>
);
}表单页面
创建带验证的表单:
tsx
// docs/contact.tsx
import React, { useState } from 'react';
export default function ContactPage() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const [errors, setErrors] = useState({});
const [submitted, setSubmitted] = useState(false);
const validate = () => {
const newErrors = {};
if (!formData.name) newErrors.name = '姓名为必填项';
if (!formData.email) newErrors.email = '电子邮件为必填项';
else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = '电子邮件无效';
}
if (!formData.message) newErrors.message = '消息为必填项';
return newErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
const newErrors = validate();
if (Object.keys(newErrors).length === 0) {
// 提交表单
console.log('表单提交:', formData);
setSubmitted(true);
} else {
setErrors(newErrors);
}
};
if (submitted) {
return (
<div style={{
maxWidth: '600px',
margin: '100px auto',
textAlign: 'center',
padding: '40px',
background: '#f0f8ff',
borderRadius: '12px'
}}>
<h2>感谢您!</h2>
<p>您的消息已成功发送。我们会尽快回复您。</p>
</div>
);
}
return (
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '40px 20px' }}>
<h1>联系我们</h1>
<p>有问题吗?我们很乐意听取您的意见。</p>
<form onSubmit={handleSubmit} style={{ marginTop: '30px' }}>
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
姓名
</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
style={{
width: '100%',
padding: '10px',
border: errors.name ? '2px solid red' : '1px solid #ddd',
borderRadius: '6px'
}}
/>
{errors.name && <span style={{ color: 'red', fontSize: '14px' }}>{errors.name}</span>}
</div>
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
电子邮件
</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
style={{
width: '100%',
padding: '10px',
border: errors.email ? '2px solid red' : '1px solid #ddd',
borderRadius: '6px'
}}
/>
{errors.email && <span style={{ color: 'red', fontSize: '14px' }}>{errors.email}</span>}
</div>
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
消息
</label>
<textarea
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
rows={6}
style={{
width: '100%',
padding: '10px',
border: errors.message ? '2px solid red' : '1px solid #ddd',
borderRadius: '6px',
fontFamily: 'inherit'
}}
/>
{errors.message && <span style={{ color: 'red', fontSize: '14px' }}>{errors.message}</span>}
</div>
<button
type="submit"
style={{
padding: '12px 40px',
background: '#667eea',
color: 'white',
border: 'none',
borderRadius: '8px',
fontSize: '16px',
fontWeight: '600',
cursor: 'pointer'
}}
>
发送消息
</button>
</form>
</div>
);
}使用 Rspress 功能
访问主题上下文
在自定义页面中使用主题功能:
tsx
// docs/themed-page.tsx
import React from 'react';
import { usePageData } from 'rspress/runtime';
export default function ThemedPage() {
const pageData = usePageData();
return (
<div style={{ padding: '40px 20px' }}>
<h1>页面信息</h1>
<p>当前页面: {pageData.page.title}</p>
<p>路由路径: {pageData.page.routePath}</p>
<h2>站点配置</h2>
<p>站点标题: {pageData.siteData.title}</p>
<p>站点描述: {pageData.siteData.description}</p>
</div>
);
}包含导航
在自定义页面中添加导航:
tsx
// docs/custom-with-nav.tsx
import React from 'react';
import { Link } from 'rspress/runtime';
export default function CustomWithNav() {
return (
<div>
{/* 自定义导航 */}
<nav style={{
background: '#667eea',
padding: '20px',
color: 'white'
}}>
<div style={{
maxWidth: '1200px',
margin: '0 auto',
display: 'flex',
gap: '30px',
alignItems: 'center'
}}>
<Link to="/" style={{ color: 'white', textDecoration: 'none', fontWeight: 'bold' }}>
首页
</Link>
<Link to="/guide" style={{ color: 'white', textDecoration: 'none' }}>
指南
</Link>
<Link to="/api" style={{ color: 'white', textDecoration: 'none' }}>
API
</Link>
</div>
</nav>
{/* 页面内容 */}
<div style={{ maxWidth: '1200px', margin: '40px auto', padding: '0 20px' }}>
<h1>自定义内容</h1>
<p>这个页面有自定义导航,但仍然使用 Rspress 路由。</p>
</div>
</div>
);
}页面模板
创建可重用模板
构建可重用的页面模板:
tsx
// components/PageTemplate.tsx
import React from 'react';
interface PageTemplateProps {
title: string;
subtitle?: string;
children: React.ReactNode;
fullWidth?: boolean;
}
export function PageTemplate({
title,
subtitle,
children,
fullWidth = false
}: PageTemplateProps) {
return (
<div>
{/* 页眉 */}
<header style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
padding: '60px 20px',
color: 'white',
textAlign: 'center'
}}>
<h1 style={{ fontSize: '3rem', marginBottom: '10px' }}>{title}</h1>
{subtitle && <p style={{ fontSize: '1.25rem', opacity: 0.9 }}>{subtitle}</p>}
</header>
{/* 内容 */}
<main style={{
maxWidth: fullWidth ? '100%' : '1200px',
margin: '0 auto',
padding: '40px 20px'
}}>
{children}
</main>
{/* 页脚 */}
<footer style={{
background: '#f5f5f5',
padding: '40px 20px',
textAlign: 'center',
marginTop: '60px'
}}>
<p>© 2024 我的项目。保留所有权利。</p>
</footer>
</div>
);
}使用模板:
tsx
// docs/features.tsx
import React from 'react';
import { PageTemplate } from '../components/PageTemplate';
export default function FeaturesPage() {
return (
<PageTemplate
title="功能"
subtitle="探索使我们的产品强大的功能"
>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '30px',
marginTop: '40px'
}}>
<FeatureCard
icon="⚡"
title="闪电般快速"
description="使用 Vite 构建,实现极快的开发和构建时间"
/>
<FeatureCard
icon="🎨"
title="可定制"
description="完全的主题控制和样式定制"
/>
<FeatureCard
icon="📱"
title="响应式"
description="在所有设备上都完美运行"
/>
</div>
</PageTemplate>
);
}
function FeatureCard({ icon, title, description }) {
return (
<div style={{
padding: '30px',
border: '1px solid #eee',
borderRadius: '12px',
textAlign: 'center',
transition: 'transform 0.3s, box-shadow 0.3s'
}}>
<div style={{ fontSize: '3rem', marginBottom: '20px' }}>{icon}</div>
<h3 style={{ marginBottom: '10px' }}>{title}</h3>
<p style={{ color: '#666' }}>{description}</p>
</div>
);
}最佳实践
性能优化
- 代码分割
tsx
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
export default function Page() {
return (
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
);
}- 图片优化
tsx
import { Image } from 'rspress/runtime';
export default function Page() {
return (
<Image
src="/large-image.jpg"
alt="描述"
loading="lazy"
width={800}
height={600}
/>
);
}响应式设计
使用媒体查询创建响应式页面:
tsx
export default function ResponsivePage() {
return (
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '20px',
padding: '20px'
}}>
{/* 内容自动调整到屏幕大小 */}
</div>
);
}SEO 优化
为自定义页面添加元数据:
tsx
// docs/landing.tsx
import { Head } from 'rspress/runtime';
export default function LandingPage() {
return (
<>
<Head>
<title>我的着陆页 - 项目名称</title>
<meta name="description" content="这是一个令人惊叹的着陆页" />
<meta property="og:title" content="我的着陆页" />
<meta property="og:description" content="这是一个令人惊叹的着陆页" />
</Head>
<div>{/* 页面内容 */}</div>
</>
);
}故障排除
常见问题
- 样式冲突
tsx
// 使用 CSS 模块避免冲突
import styles from './page.module.css';
export default function Page() {
return <div className={styles.container}>内容</div>;
}- 路由问题
tsx
// 使用 Rspress Link 组件进行正确的路由
import { Link } from 'rspress/runtime';
<Link to="/guide">转到指南</Link>- 状态管理
tsx
// 对于复杂状态,使用 React Context
import { createContext, useContext } from 'react';
const AppContext = createContext();
export function useAppContext() {
return useContext(AppContext);
}下一步
最佳实践
- 保持自定义页面性能良好
- 为自定义页面使用 TypeScript
- 实现适当的错误处理
- 测试所有设备上的响应性
- 为 SEO 添加适当的元数据
注意事项
- 避免在自定义页面中使用 eval()
- 清理所有用户输入
- 谨慎处理敏感数据
- 测试跨浏览器兼容性