Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

XiaZheStudy 前端解读

前端用 Next.js 14 + Tailwind CSS 写的,App Router 架构。看完这篇,你就知道前端代码是怎么组织的。


技术栈

组件技术说明
框架Next.js 14App Router,React 全栈框架
样式Tailwind CSS原子化 CSS
组件库shadcn/ui基于 Radix UI 的组件库
状态管理Zustand轻量级状态管理
数据请求React Query服务端状态管理
动画Framer MotionReact 动画库
认证Supabase Auth用户认证

目录结构

frontend/src/
├── app/                    # 页面(App Router)
│   ├── (auth)/             # 认证相关页面组
│   │   ├── sign-in/        # 登录页
│   │   └── sign-up/        # 注册页
│   ├── auth/               # OAuth 回调
│   ├── learn/              # 学习相关页面
│   │   ├── course-creation/  # 创建课程
│   │   ├── courses/          # 课程详情
│   │   ├── my-courses/       # 我的课程
│   │   └── my-document/      # 我的文档
│   ├── fun-square/         # 课程广场
│   ├── user-center/        # 用户中心
│   ├── pricing/            # 定价页
│   ├── referral/           # 邀请页
│   ├── notifications/      # 通知页
│   ├── layout.tsx          # 根布局
│   ├── page.tsx            # 首页
│   └── globals.css         # 全局样式
├── components/             # 组件
│   ├── ui/                 # shadcn/ui 组件
│   ├── layout/             # 布局组件
│   ├── navbar.tsx          # 导航栏
│   └── sidebar.tsx         # 侧边栏
├── contexts/               # React Context
│   └── auth-context.tsx    # 认证上下文
├── lib/                    # 工具库
│   ├── api-client.ts       # API 客户端
│   ├── supabase.ts         # Supabase 客户端
│   └── utils.ts            # 工具函数
├── stores/                 # Zustand 状态
└── types/                  # TypeScript 类型

核心文件解读

1. API 客户端 - lib/api-client.ts

封装了所有后端 API 调用:

export class APIClient {
  private baseURL: string
  private token: string | null = null

  // 设置认证 token
  setToken(token: string | null) {
    this.token = token
  }

  // 通用请求方法
  private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
    }

    // 自动带上 token
    if (this.token) {
      headers['Authorization'] = `Bearer ${this.token}`
    }

    const response = await fetch(`${this.baseURL}${endpoint}`, {
      ...options,
      headers,
    })

    if (!response.ok) {
      const error = await response.json()
      throw new Error(error.detail)
    }

    return response.json()
  }

  // 具体的 API 方法
  async login(email: string, password: string) { ... }
  async getCourses() { ... }
  async uploadDocument(file: File) { ... }
}

// 导出单例
export const apiClient = new APIClient()

亮点:

  • 统一的请求封装
  • 自动携带认证 token
  • 统一的错误处理

2. SSE 流式请求 - AI 生成

// 处理 SSE 流式响应
private async *processStreamResponse(response: Response) {
  const reader = response.body?.getReader()
  const decoder = new TextDecoder()
  let buffer = ''

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    buffer += decoder.decode(value, { stream: true })
    const lines = buffer.split('\n')
    buffer = lines.pop() || ''

    for (const line of lines) {
      if (!line.startsWith('data: ')) continue
      const data = JSON.parse(line.slice(6))

      if (data.token) {
        yield data.token  // 逐个返回 token
      }
    }
  }
}

// 调用 AI 生成
async* generateFromText(text: string, style: string) {
  const response = await fetch(`${this.baseURL}/api/v1/ai/generate/text`, {
    method: 'POST',
    body: JSON.stringify({ text, style }),
  })

  yield* this.processStreamResponse(response)
}

使用方式:

// 在组件中使用
for await (const token of apiClient.generateFromText(text, style)) {
  setContent(prev => prev + token)  // 实时更新 UI
}

3. 认证上下文 - contexts/auth-context.tsx

interface AuthContextType {
  user: User | null           // 后端用户信息
  supabaseUser: SupabaseUser | null  // Supabase 用户
  loading: boolean
  signIn: (email: string, password: string) => Promise<void>
  signUp: (email: string, password: string, username: string) => Promise<void>
  signOut: () => Promise<void>
  refreshUser: () => Promise<void>
}

export function AuthProvider({ children }) {
  const [user, setUser] = useState<User | null>(null)
  const [supabaseUser, setSupabaseUser] = useState<SupabaseUser | null>(null)

  useEffect(() => {
    // 初始化:检查现有会话
    supabase.auth.getSession().then(({ data: { session } }) => {
      if (session?.access_token) {
        fetchUserProfile(session.access_token)
      }
    })

    // 监听认证状态变化
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        if (session?.access_token) {
          await fetchUserProfile(session.access_token)
        } else {
          setUser(null)
        }
      }
    )

    return () => subscription.unsubscribe()
  }, [])

  const signIn = async (email: string, password: string) => {
    // 1. 调用后端登录接口
    const response = await apiClient.login(email, password)

    // 2. 设置 Supabase 会话
    await supabase.auth.setSession({
      access_token: response.access_token,
      refresh_token: response.refresh_token,
    })

    // 3. 获取用户信息
    await fetchUserProfile(response.access_token)
  }

  return (
    <AuthContext.Provider value={{ user, signIn, signOut, ... }}>
      {children}
    </AuthContext.Provider>
  )
}

// 使用 Hook
export function useAuth() {
  return useContext(AuthContext)
}

使用方式:

function MyComponent() {
  const { user, signIn, signOut } = useAuth()

  if (!user) return <LoginButton />
  return <div>欢迎, {user.username}</div>
}

页面路由结构

App Router 路由规则

app/
├── page.tsx              → /
├── (auth)/
│   ├── sign-in/page.tsx  → /sign-in
│   └── sign-up/page.tsx  → /sign-up
├── learn/
│   ├── layout.tsx        → 学习页面共用布局
│   ├── course-creation/page.tsx  → /learn/course-creation
│   ├── courses/[id]/page.tsx     → /learn/courses/123
│   ├── my-courses/page.tsx       → /learn/my-courses
│   └── my-document/page.tsx      → /learn/my-document
├── fun-square/page.tsx   → /fun-square
└── user-center/page.tsx  → /user-center

路由组 (auth)

  • 括号包裹的文件夹不会出现在 URL 中
  • 用于组织相关页面,共享布局

动态路由 [id]

  • /learn/courses/[id] 匹配 /learn/courses/123
  • 在页面中通过 params.id 获取

首页解读 - app/page.tsx

export default function HomePage() {
  const { user, supabaseUser } = useAuth()
  const isLoggedIn = !!user || !!supabaseUser

  return (
    <div>
      {/* Hero Section */}
      <header>
        <motion.h1 variants={itemVariants}>
          让复杂的知识
          <span className="bg-gradient-to-r from-purple-600 to-pink-500">
            像动画一样生动
          </span>
        </motion.h1>

        {/* CTA 按钮 - 根据登录状态显示不同内容 */}
        <Link href={isLoggedIn ? "/learn/course-creation" : "/sign-up"}>
          <Button>
            {isLoggedIn ? "进入工作台" : "开始创作"}
          </Button>
        </Link>
      </header>

      {/* Features Grid */}
      <section>
        {features.map((feature, idx) => (
          <motion.div
            key={idx}
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
          >
            <Card>{feature.title}</Card>
          </motion.div>
        ))}
      </section>
    </div>
  )
}

关键点:

  • 使用 Framer Motion 做滚动动画
  • 根据登录状态显示不同的 CTA
  • Tailwind 渐变文字效果

组件库使用 - shadcn/ui

已安装的组件

components/ui/
├── button.tsx
├── card.tsx
├── dialog.tsx
├── dropdown-menu.tsx
├── input.tsx
├── label.tsx
├── select.tsx
├── tabs.tsx
├── toast.tsx
└── ...

使用示例

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"

function MyForm() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>创建课程</CardTitle>
      </CardHeader>
      <CardContent>
        <Input placeholder="输入课程标题" />
        <Button>提交</Button>
      </CardContent>
    </Card>
  )
}

环境变量

# .env.local

# 后端 API 地址
NEXT_PUBLIC_API_URL=https://api.xiazhestudy.com

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJxxx...

注意: NEXT_PUBLIC_ 前缀的变量才能在客户端使用。


数据流总结

用户操作
    ↓
组件调用 useAuth() 或 apiClient
    ↓
apiClient 发送请求到后端
    ↓
后端返回数据
    ↓
更新组件状态 / Context
    ↓
UI 重新渲染

SSE 流式数据流

用户点击"生成"
    ↓
apiClient.generateFromText()
    ↓
后端 SSE 流式返回 token
    ↓
前端逐个接收 token
    ↓
实时更新 UI(打字机效果)
    ↓
生成完成,保存结果

代码规范总结

目录组织

app/        - 页面和路由
components/ - 可复用组件
contexts/   - React Context
lib/        - 工具库和 API 客户端
stores/     - Zustand 状态管理
types/      - TypeScript 类型定义

命名规范

  • 文件名:小写 + 连字符(api-client.ts
  • 组件名:大驼峰(AuthProvider
  • Hook 名:use 开头(useAuth
  • 类型名:大驼峰(AuthContextType

样式规范

  • 使用 Tailwind CSS 类名
  • 避免自定义 CSS(除非必要)
  • 响应式用 Tailwind 断点(sm:, md:, lg:

小结

这个前端项目是标准的 Next.js 14 架构:

  1. App Router - 文件即路由,简洁直观
  2. Context + Zustand - 全局状态管理
  3. shadcn/ui - 高质量组件库
  4. Tailwind CSS - 快速样式开发
  5. SSE 流式 - AI 生成的实时体验

亮点:

  • 完整的认证流程(邮箱 + Google OAuth)
  • SSE 流式响应处理
  • 清晰的 API 客户端封装
  • 响应式设计

适合作为 Next.js 项目的参考模板。