Files
soul-yongping/开发文档/4、前端/模块详解.md
2026-02-09 15:09:29 +08:00

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>
  )
}

总结: 前端模块以组件化为核心,每个模块职责清晰,组件可复用。核心模块包括首页(展示)、匹配(社交)、阅读(内容)、我的(用户中心)、支付(变现)、后台(管理)。所有模块都遵循统一的设计规范和交互模式。