XiaZheStudy 前端解读
前端用 Next.js 14 + Tailwind CSS 写的,App Router 架构。看完这篇,你就知道前端代码是怎么组织的。
技术栈
| 组件 | 技术 | 说明 |
|---|---|---|
| 框架 | Next.js 14 | App Router,React 全栈框架 |
| 样式 | Tailwind CSS | 原子化 CSS |
| 组件库 | shadcn/ui | 基于 Radix UI 的组件库 |
| 状态管理 | Zustand | 轻量级状态管理 |
| 数据请求 | React Query | 服务端状态管理 |
| 动画 | Framer Motion | React 动画库 |
| 认证 | 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 架构:
- App Router - 文件即路由,简洁直观
- Context + Zustand - 全局状态管理
- shadcn/ui - 高质量组件库
- Tailwind CSS - 快速样式开发
- SSE 流式 - AI 生成的实时体验
亮点:
- 完整的认证流程(邮箱 + Google OAuth)
- SSE 流式响应处理
- 清晰的 API 客户端封装
- 响应式设计
适合作为 Next.js 项目的参考模板。