主要更新: 1. 按H5网页端完全重构匹配功能(match页面) - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募 - 资源对接等类型弹出手机号/微信号输入框 - 去掉重新匹配按钮,改为返回按钮 2. 修复所有卡片对齐和宽度问题 - 目录页附录卡片居中 - 首页阅读进度卡片满宽度 - 我的页面菜单卡片对齐 - 推广中心分享卡片统一宽度 3. 修复目录页图标和文字对齐 - section-icon固定40rpx宽高 - section-title与图标垂直居中 4. 更新真实完整文章标题(62篇) - 从book目录读取真实markdown文件名 - 替换之前的简化标题 5. 新增文章数据API - /api/db/chapters - 获取完整书籍结构 - 支持按ID获取单篇文章内容
16 KiB
16 KiB
前端模块详解 - Soul创业实验项目
核心模块: 首页、匹配、阅读、我的、支付、分销
我是卡若。
这里记录每个前端模块的实现细节,方便以后维护和扩展。
1. 首页模块
路径: /app/page.tsx
1.1 核心功能
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 关键组件
书籍封面:
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>
)
}
数据亮点:
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 核心功能
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 关键组件
星空背景:
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 核心功能
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渲染
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 核心功能
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 分销中心
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 核心流程
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 支付方式组件
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 概览页面
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 内容管理
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组件
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组件
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>
)
}
总结: 前端模块以组件化为核心,每个模块职责清晰,组件可复用。核心模块包括首页(展示)、匹配(社交)、阅读(内容)、我的(用户中心)、支付(变现)、后台(管理)。所有模块都遵循统一的设计规范和交互模式。