722 lines
16 KiB
Markdown
722 lines
16 KiB
Markdown
|
|
# 前端模块详解 - Soul创业实验项目
|
||
|
|
|
||
|
|
> **核心模块**: 首页、匹配、阅读、我的、支付、分销
|
||
|
|
|
||
|
|
**我是卡若。**
|
||
|
|
|
||
|
|
这里记录每个前端模块的实现细节,方便以后维护和扩展。
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. 首页模块
|
||
|
|
|
||
|
|
**路径**: `/app/page.tsx`
|
||
|
|
|
||
|
|
### 1.1 核心功能
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export default function HomePage() {
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen">
|
||
|
|
{/* 1. 品牌标签 */}
|
||
|
|
<SoulBadge text="Soul · 派对房" />
|
||
|
|
|
||
|
|
{/* 2. 书籍封面 */}
|
||
|
|
<BookCover src="/images/book-cover.jpg" />
|
||
|
|
|
||
|
|
{/* 3. 核心数据 */}
|
||
|
|
<DataHighlights
|
||
|
|
price="¥9.9"
|
||
|
|
cases="64个"
|
||
|
|
insights="100+"
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 4. 作者信息 */}
|
||
|
|
<AuthorInfo
|
||
|
|
name="卡若"
|
||
|
|
time="06:00-09:00"
|
||
|
|
platform="Soul派对房"
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 5. 行动按钮 */}
|
||
|
|
<CTAButton text="立即阅读" />
|
||
|
|
|
||
|
|
{/* 6. 寄语卡片 */}
|
||
|
|
<MessageCard />
|
||
|
|
|
||
|
|
{/* 7. 章节列表 */}
|
||
|
|
<ChapterList chapters={bookData} />
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1.2 关键组件
|
||
|
|
|
||
|
|
**书籍封面**:
|
||
|
|
```typescript
|
||
|
|
function BookCover({ src }: { src: string }) {
|
||
|
|
return (
|
||
|
|
<div className="relative w-64 h-96 mx-auto">
|
||
|
|
<Image
|
||
|
|
src={src}
|
||
|
|
alt="书籍封面"
|
||
|
|
fill
|
||
|
|
className="object-cover rounded-2xl shadow-2xl"
|
||
|
|
priority
|
||
|
|
/>
|
||
|
|
{/* 3D效果 */}
|
||
|
|
<div className="absolute inset-0 bg-gradient-to-tr from-black/20 to-transparent rounded-2xl" />
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**数据亮点**:
|
||
|
|
```typescript
|
||
|
|
function DataHighlights({ price, cases, insights }: Props) {
|
||
|
|
return (
|
||
|
|
<div className="grid grid-cols-3 gap-4 p-6">
|
||
|
|
<div className="text-center">
|
||
|
|
<div className="text-3xl font-bold text-[var(--app-brand)]">
|
||
|
|
{price}
|
||
|
|
</div>
|
||
|
|
<div className="text-sm text-gray-400">全书价格</div>
|
||
|
|
</div>
|
||
|
|
<div className="text-center">
|
||
|
|
<div className="text-3xl font-bold text-[var(--app-brand)]">
|
||
|
|
{cases}
|
||
|
|
</div>
|
||
|
|
<div className="text-sm text-gray-400">商业案例</div>
|
||
|
|
</div>
|
||
|
|
<div className="text-center">
|
||
|
|
<div className="text-3xl font-bold text-[var(--app-brand)]">
|
||
|
|
{insights}
|
||
|
|
</div>
|
||
|
|
<div className="text-sm text-gray-400">商业洞察</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 匹配模块
|
||
|
|
|
||
|
|
**路径**: `/app/match/page.tsx`
|
||
|
|
|
||
|
|
### 2.1 核心功能
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export default function MatchPage() {
|
||
|
|
const [isMatching, setIsMatching] = useState(false)
|
||
|
|
const [matchResult, setMatchResult] = useState(null)
|
||
|
|
|
||
|
|
const handleMatch = async () => {
|
||
|
|
setIsMatching(true)
|
||
|
|
// 模拟匹配过程
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
||
|
|
setMatchResult({
|
||
|
|
name: '创业者小王',
|
||
|
|
mbti: 'ENTJ',
|
||
|
|
interests: ['私域运营', '内容创业'],
|
||
|
|
matchRate: 85
|
||
|
|
})
|
||
|
|
setIsMatching(false)
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen flex flex-col items-center justify-center">
|
||
|
|
{/* 星空背景 */}
|
||
|
|
<StarfieldBackground />
|
||
|
|
|
||
|
|
{/* 中央星球 */}
|
||
|
|
<PlanetAnimation />
|
||
|
|
|
||
|
|
{/* 标题 */}
|
||
|
|
<h1 className="text-2xl font-bold mb-8">
|
||
|
|
寻找创业合作伙伴
|
||
|
|
</h1>
|
||
|
|
|
||
|
|
{/* 匹配按钮或结果 */}
|
||
|
|
{!matchResult ? (
|
||
|
|
<Button onClick={handleMatch} loading={isMatching}>
|
||
|
|
开始匹配
|
||
|
|
</Button>
|
||
|
|
) : (
|
||
|
|
<MatchResultCard result={matchResult} />
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* 快捷操作 */}
|
||
|
|
<QuickActions
|
||
|
|
onAddWechat={() => {}}
|
||
|
|
onJoinGroup={() => {}}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.2 关键组件
|
||
|
|
|
||
|
|
**星空背景**:
|
||
|
|
```typescript
|
||
|
|
function StarfieldBackground() {
|
||
|
|
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const canvas = canvasRef.current
|
||
|
|
const ctx = canvas?.getContext('2d')
|
||
|
|
if (!canvas || !ctx) return
|
||
|
|
|
||
|
|
// 创建星星
|
||
|
|
const stars = Array.from({ length: 100 }, () => ({
|
||
|
|
x: Math.random() * canvas.width,
|
||
|
|
y: Math.random() * canvas.height,
|
||
|
|
radius: Math.random() * 2,
|
||
|
|
opacity: Math.random()
|
||
|
|
}))
|
||
|
|
|
||
|
|
// 绘制动画
|
||
|
|
function animate() {
|
||
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||
|
|
stars.forEach(star => {
|
||
|
|
ctx.beginPath()
|
||
|
|
ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2)
|
||
|
|
ctx.fillStyle = `rgba(255, 255, 255, ${star.opacity})`
|
||
|
|
ctx.fill()
|
||
|
|
})
|
||
|
|
requestAnimationFrame(animate)
|
||
|
|
}
|
||
|
|
animate()
|
||
|
|
}, [])
|
||
|
|
|
||
|
|
return <canvas ref={canvasRef} className="absolute inset-0" />
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. 阅读模块
|
||
|
|
|
||
|
|
**路径**: `/app/read/[id]/page.tsx`
|
||
|
|
|
||
|
|
### 3.1 核心功能
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export default async function ReadPage({ params }: Props) {
|
||
|
|
const { id } = params
|
||
|
|
const section = await getSection(id)
|
||
|
|
|
||
|
|
if (!section) {
|
||
|
|
notFound()
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="max-w-3xl mx-auto p-6">
|
||
|
|
{/* 返回按钮 */}
|
||
|
|
<BackButton />
|
||
|
|
|
||
|
|
{/* 章节标题 */}
|
||
|
|
<h1 className="text-3xl font-bold mb-4">
|
||
|
|
{section.title}
|
||
|
|
</h1>
|
||
|
|
|
||
|
|
{/* 章节信息 */}
|
||
|
|
<SectionMeta
|
||
|
|
chapter={section.chapter}
|
||
|
|
readTime="10分钟"
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* Markdown内容 */}
|
||
|
|
<MarkdownContent content={section.content} />
|
||
|
|
|
||
|
|
{/* 章节导航 */}
|
||
|
|
<ChapterNavigation
|
||
|
|
prev={section.prev}
|
||
|
|
next={section.next}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 分享按钮 */}
|
||
|
|
<ShareButton title={section.title} />
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.2 Markdown渲染
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { marked } from 'marked'
|
||
|
|
|
||
|
|
function MarkdownContent({ content }: { content: string }) {
|
||
|
|
const html = marked(content)
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className="prose prose-invert max-w-none"
|
||
|
|
dangerouslySetInnerHTML={{ __html: html }}
|
||
|
|
/>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
// CSS样式
|
||
|
|
.prose {
|
||
|
|
@apply text-gray-300 leading-relaxed;
|
||
|
|
}
|
||
|
|
|
||
|
|
.prose h1 {
|
||
|
|
@apply text-3xl font-bold text-white mt-8 mb-4;
|
||
|
|
}
|
||
|
|
|
||
|
|
.prose h2 {
|
||
|
|
@apply text-2xl font-bold text-white mt-6 mb-3;
|
||
|
|
}
|
||
|
|
|
||
|
|
.prose p {
|
||
|
|
@apply mb-4;
|
||
|
|
}
|
||
|
|
|
||
|
|
.prose ul {
|
||
|
|
@apply list-disc list-inside mb-4;
|
||
|
|
}
|
||
|
|
|
||
|
|
.prose code {
|
||
|
|
@apply bg-gray-800 px-2 py-1 rounded text-[var(--app-brand)];
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. 我的模块
|
||
|
|
|
||
|
|
**路径**: `/app/my/page.tsx`
|
||
|
|
|
||
|
|
### 4.1 核心功能
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export default function MyPage() {
|
||
|
|
const { user, isLoggedIn } = useStore()
|
||
|
|
|
||
|
|
if (!isLoggedIn) {
|
||
|
|
return <LoginPrompt />
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen p-6">
|
||
|
|
{/* 用户信息卡片 */}
|
||
|
|
<UserCard user={user} />
|
||
|
|
|
||
|
|
{/* 阅读统计 */}
|
||
|
|
<ReadingStats
|
||
|
|
readChapters={user.purchasedSections.length}
|
||
|
|
totalChapters={64}
|
||
|
|
readTime="3小时"
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 分销中心(重点突出) */}
|
||
|
|
<ReferralCenter
|
||
|
|
code={user.referralCode}
|
||
|
|
earnings={user.earnings}
|
||
|
|
referralCount={user.referralCount}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 功能菜单 */}
|
||
|
|
<MenuList items={[
|
||
|
|
{ icon: '📚', title: '我的购买', path: '/my/purchases' },
|
||
|
|
{ icon: '💰', title: '收益管理', path: '/my/earnings' },
|
||
|
|
{ icon: '📊', title: '推广数据', path: '/my/referral' },
|
||
|
|
{ icon: '⚙️', title: '设置', path: '/my/settings' },
|
||
|
|
]} />
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 分销中心
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
function ReferralCenter({ code, earnings, referralCount }: Props) {
|
||
|
|
return (
|
||
|
|
<div className="glass-card p-6 mb-6">
|
||
|
|
<h2 className="text-xl font-bold mb-4">分销中心</h2>
|
||
|
|
|
||
|
|
{/* 收益概览 */}
|
||
|
|
<div className="grid grid-cols-2 gap-4 mb-6">
|
||
|
|
<div>
|
||
|
|
<div className="text-3xl font-bold text-[var(--app-brand)]">
|
||
|
|
¥{earnings.toFixed(2)}
|
||
|
|
</div>
|
||
|
|
<div className="text-sm text-gray-400">累计收益</div>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<div className="text-3xl font-bold text-[var(--app-brand)]">
|
||
|
|
{referralCount}
|
||
|
|
</div>
|
||
|
|
<div className="text-sm text-gray-400">推荐人数</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 邀请码 */}
|
||
|
|
<div className="mb-4">
|
||
|
|
<label className="text-sm text-gray-400">我的邀请码</label>
|
||
|
|
<div className="flex items-center gap-2 mt-2">
|
||
|
|
<input
|
||
|
|
value={code}
|
||
|
|
readOnly
|
||
|
|
className="flex-1 bg-gray-800 px-4 py-2 rounded-lg"
|
||
|
|
/>
|
||
|
|
<CopyButton text={code} />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 生成海报 */}
|
||
|
|
<Button onClick={() => generatePoster(code)} className="w-full">
|
||
|
|
生成推广海报
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. 支付模块
|
||
|
|
|
||
|
|
**路径**: `/components/payment-modal.tsx`
|
||
|
|
|
||
|
|
### 5.1 核心流程
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export function PaymentModal({
|
||
|
|
isOpen,
|
||
|
|
amount,
|
||
|
|
type,
|
||
|
|
onSuccess
|
||
|
|
}: Props) {
|
||
|
|
const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>('alipay')
|
||
|
|
const [showQRCode, setShowQRCode] = useState(false)
|
||
|
|
const [isProcessing, setIsProcessing] = useState(false)
|
||
|
|
|
||
|
|
// 发起支付
|
||
|
|
const handlePayment = async () => {
|
||
|
|
setShowQRCode(true)
|
||
|
|
|
||
|
|
// 调用支付API
|
||
|
|
const order = await createOrder({
|
||
|
|
amount,
|
||
|
|
type,
|
||
|
|
paymentMethod
|
||
|
|
})
|
||
|
|
|
||
|
|
// 展示支付二维码
|
||
|
|
showPaymentQRCode(order.qrCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 确认支付
|
||
|
|
const confirmPayment = async () => {
|
||
|
|
setIsProcessing(true)
|
||
|
|
|
||
|
|
// 购买逻辑
|
||
|
|
const success = await purchaseItem(type)
|
||
|
|
|
||
|
|
if (success) {
|
||
|
|
onSuccess()
|
||
|
|
// 自动跳转到读者群
|
||
|
|
openWechatGroup()
|
||
|
|
}
|
||
|
|
|
||
|
|
setIsProcessing(false)
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Modal isOpen={isOpen}>
|
||
|
|
{!showQRCode ? (
|
||
|
|
<PaymentMethodSelection
|
||
|
|
selected={paymentMethod}
|
||
|
|
onSelect={setPaymentMethod}
|
||
|
|
onConfirm={handlePayment}
|
||
|
|
/>
|
||
|
|
) : (
|
||
|
|
<QRCodeDisplay
|
||
|
|
method={paymentMethod}
|
||
|
|
amount={amount}
|
||
|
|
onConfirm={confirmPayment}
|
||
|
|
isProcessing={isProcessing}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</Modal>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.2 支付方式组件
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
function PaymentMethodSelection({ selected, onSelect, onConfirm }: Props) {
|
||
|
|
const methods = [
|
||
|
|
{
|
||
|
|
id: 'wechat',
|
||
|
|
name: '微信支付',
|
||
|
|
icon: <WechatIcon />,
|
||
|
|
color: '#07C160'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'alipay',
|
||
|
|
name: '支付宝',
|
||
|
|
icon: <AlipayIcon />,
|
||
|
|
color: '#1677FF'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'usdt',
|
||
|
|
name: 'USDT (TRC20)',
|
||
|
|
icon: <BitcoinIcon />,
|
||
|
|
color: '#26A17B'
|
||
|
|
}
|
||
|
|
]
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-4">
|
||
|
|
{methods.map(method => (
|
||
|
|
<button
|
||
|
|
key={method.id}
|
||
|
|
onClick={() => onSelect(method.id)}
|
||
|
|
className={`w-full p-4 rounded-xl flex items-center gap-4 ${
|
||
|
|
selected === method.id
|
||
|
|
? 'glass-card-light border-[var(--app-brand)]'
|
||
|
|
: 'glass-card'
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
<div
|
||
|
|
className="w-12 h-12 rounded-xl flex items-center justify-center"
|
||
|
|
style={{ backgroundColor: `${method.color}15`, color: method.color }}
|
||
|
|
>
|
||
|
|
{method.icon}
|
||
|
|
</div>
|
||
|
|
<span className="flex-1 text-left">{method.name}</span>
|
||
|
|
{selected === method.id && <CheckIcon />}
|
||
|
|
</button>
|
||
|
|
))}
|
||
|
|
|
||
|
|
<Button onClick={onConfirm} className="w-full">
|
||
|
|
确认支付
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. 后台管理模块
|
||
|
|
|
||
|
|
**路径**: `/app/admin/page.tsx`
|
||
|
|
|
||
|
|
### 6.1 概览页面
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export default function AdminDashboard() {
|
||
|
|
const [stats, setStats] = useState<Stats | null>(null)
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
fetchStats()
|
||
|
|
}, [])
|
||
|
|
|
||
|
|
async function fetchStats() {
|
||
|
|
const res = await fetch('/api/admin', {
|
||
|
|
headers: {
|
||
|
|
'Authorization': `Bearer ${getAdminToken()}`
|
||
|
|
}
|
||
|
|
})
|
||
|
|
const data = await res.json()
|
||
|
|
setStats(data)
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!stats) return <Loading />
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="p-6">
|
||
|
|
{/* 概览卡片 */}
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||
|
|
<StatCard
|
||
|
|
title="总收入"
|
||
|
|
value={`¥${stats.payment.totalRevenue}`}
|
||
|
|
trend="+12.5%"
|
||
|
|
/>
|
||
|
|
<StatCard
|
||
|
|
title="总用户"
|
||
|
|
value={stats.users.totalUsers}
|
||
|
|
trend="+23"
|
||
|
|
/>
|
||
|
|
<StatCard
|
||
|
|
title="今日订单"
|
||
|
|
value={stats.payment.todayOrders}
|
||
|
|
trend="+5"
|
||
|
|
/>
|
||
|
|
<StatCard
|
||
|
|
title="分销推广者"
|
||
|
|
value={stats.referral.totalReferrers}
|
||
|
|
trend="+8"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 图表 */}
|
||
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
|
|
<RevenueChart data={stats.revenueHistory} />
|
||
|
|
<UserGrowthChart data={stats.userGrowth} />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6.2 内容管理
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
function ContentManagement() {
|
||
|
|
const [chapters, setChapters] = useState([])
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div>
|
||
|
|
{/* 操作栏 */}
|
||
|
|
<div className="flex justify-between mb-6">
|
||
|
|
<h1 className="text-2xl font-bold">内容管理</h1>
|
||
|
|
<Button onClick={() => syncContent()}>
|
||
|
|
同步book目录
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 章节列表 */}
|
||
|
|
<Table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>章节ID</th>
|
||
|
|
<th>标题</th>
|
||
|
|
<th>状态</th>
|
||
|
|
<th>价格</th>
|
||
|
|
<th>操作</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
{chapters.map(chapter => (
|
||
|
|
<tr key={chapter.id}>
|
||
|
|
<td>{chapter.id}</td>
|
||
|
|
<td>{chapter.title}</td>
|
||
|
|
<td>
|
||
|
|
<Badge color={chapter.isFree ? 'green' : 'blue'}>
|
||
|
|
{chapter.isFree ? '免费' : '付费'}
|
||
|
|
</Badge>
|
||
|
|
</td>
|
||
|
|
<td>¥{chapter.price}</td>
|
||
|
|
<td>
|
||
|
|
<Button size="sm" onClick={() => editChapter(chapter)}>
|
||
|
|
编辑
|
||
|
|
</Button>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
))}
|
||
|
|
</tbody>
|
||
|
|
</Table>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. 通用组件库
|
||
|
|
|
||
|
|
### 7.1 Button组件
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface ButtonProps {
|
||
|
|
children: React.ReactNode
|
||
|
|
onClick?: () => void
|
||
|
|
loading?: boolean
|
||
|
|
disabled?: boolean
|
||
|
|
variant?: 'primary' | 'secondary' | 'ghost'
|
||
|
|
size?: 'sm' | 'md' | 'lg'
|
||
|
|
}
|
||
|
|
|
||
|
|
export function Button({
|
||
|
|
children,
|
||
|
|
onClick,
|
||
|
|
loading,
|
||
|
|
disabled,
|
||
|
|
variant = 'primary',
|
||
|
|
size = 'md'
|
||
|
|
}: ButtonProps) {
|
||
|
|
const baseClasses = 'rounded-xl font-semibold transition-all'
|
||
|
|
|
||
|
|
const variantClasses = {
|
||
|
|
primary: 'bg-[var(--app-brand)] text-white hover:opacity-90',
|
||
|
|
secondary: 'bg-[var(--app-bg-secondary)] text-white',
|
||
|
|
ghost: 'bg-transparent text-[var(--app-brand)] hover:bg-[var(--app-brand-light)]'
|
||
|
|
}
|
||
|
|
|
||
|
|
const sizeClasses = {
|
||
|
|
sm: 'px-4 py-2 text-sm',
|
||
|
|
md: 'px-6 py-3 text-base',
|
||
|
|
lg: 'px-8 py-4 text-lg'
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<button
|
||
|
|
onClick={onClick}
|
||
|
|
disabled={disabled || loading}
|
||
|
|
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
|
||
|
|
>
|
||
|
|
{loading ? <Spinner /> : children}
|
||
|
|
</button>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7.2 Modal组件
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export function Modal({
|
||
|
|
isOpen,
|
||
|
|
onClose,
|
||
|
|
children
|
||
|
|
}: ModalProps) {
|
||
|
|
if (!isOpen) return null
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center">
|
||
|
|
{/* 背景蒙层 */}
|
||
|
|
<div
|
||
|
|
className="absolute inset-0 bg-black/60 backdrop-blur-md"
|
||
|
|
onClick={onClose}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 内容区域 */}
|
||
|
|
<div className="relative w-full sm:max-w-md glass-card overflow-hidden safe-bottom">
|
||
|
|
{/* 顶部把手 (仅移动端) */}
|
||
|
|
<div className="flex justify-center pt-3 pb-2 sm:hidden">
|
||
|
|
<div className="w-9 h-1 rounded-full bg-gray-400" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 关闭按钮 */}
|
||
|
|
<button
|
||
|
|
onClick={onClose}
|
||
|
|
className="absolute top-4 right-4 w-8 h-8 rounded-full bg-gray-800 flex items-center justify-center"
|
||
|
|
>
|
||
|
|
<X className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
|
||
|
|
{/* 内容 */}
|
||
|
|
<div className="p-6">
|
||
|
|
{children}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**总结**: 前端模块以**组件化**为核心,每个模块职责清晰,组件可复用。核心模块包括首页(展示)、匹配(社交)、阅读(内容)、我的(用户中心)、支付(变现)、后台(管理)。所有模块都遵循统一的设计规范和交互模式。
|