feat: 项目初始化
This commit is contained in:
421
README.md
421
README.md
@@ -1,3 +1,420 @@
|
||||
# 36-character-flower
|
||||
# React SPA Template
|
||||
|
||||
36字花
|
||||
这是一个用于后台系统或通用 SPA 项目的前端基础脚手架。它已经接好了以下底座能力:
|
||||
|
||||
- `Vite + React 19 + TypeScript`
|
||||
- `TanStack Router` 文件路由
|
||||
- `TanStack Query + ky` 请求与缓存
|
||||
- `Zustand` 会话状态基座
|
||||
- `Tailwind CSS` 原子化样式
|
||||
- `i18next` 多语言
|
||||
- `Head / Metadata` 动态标题与元信息
|
||||
- `Biome + Husky + commitlint` 代码规范与提交规范
|
||||
|
||||
这个模板现在已经被“净化”为一个空白 scaffold。
|
||||
你拿到它之后,应该在这个基础上替换成自己的页面、接口、登录逻辑和测试,而不是继续沿用示例业务。
|
||||
|
||||
## 1. 快速开始
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
常用命令:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
pnpm build
|
||||
pnpm lint
|
||||
pnpm lint:fix
|
||||
pnpm generate-routes
|
||||
pnpm commit
|
||||
```
|
||||
|
||||
## 2. 目录结构
|
||||
|
||||
核心目录说明:
|
||||
|
||||
- `src/routes`
|
||||
TanStack Router 文件路由目录。你新增页面,优先在这里建文件。
|
||||
- `src/lib/api`
|
||||
通用请求层。`api-client.ts` 是所有接口调用的基础入口。
|
||||
- `src/lib/auth`
|
||||
会话初始化、401 清理、refresh token、受保护路由 helper。
|
||||
- `src/lib/head`
|
||||
页面标题、description、Open Graph、Twitter metadata。
|
||||
- `src/lib/query`
|
||||
TanStack Query 全局默认配置。
|
||||
- `src/store`
|
||||
全局状态。目前主要是认证状态。
|
||||
- `src/styles.css`
|
||||
全局 Tailwind 入口。当前项目只保留这一份样式文件。
|
||||
- `src/locales`
|
||||
多语言文案。
|
||||
|
||||
## 3. 新项目落地后先改哪里
|
||||
|
||||
如果你要基于这个模板开一个新项目,建议按这个顺序改:
|
||||
|
||||
### 第一步:改首页
|
||||
|
||||
先改这个文件:
|
||||
|
||||
- `src/routes/$lang/index.tsx`
|
||||
|
||||
这是当前默认首页。通常你会:
|
||||
|
||||
- 改成你的项目欢迎页
|
||||
- 或直接改成 dashboard / 工作台入口
|
||||
- 或拆成自己的页面布局
|
||||
|
||||
同时对应修改多语言文案:
|
||||
|
||||
- `src/locales/zh-CN/common.ts`
|
||||
- `src/locales/en-US/common.ts`
|
||||
|
||||
### 第二步:改接口基础地址
|
||||
|
||||
先看这些文件:
|
||||
|
||||
- `.env.development`
|
||||
- `.env.production`
|
||||
- `src/vite-env.d.ts`
|
||||
|
||||
至少确认这些变量:
|
||||
|
||||
- `VITE_APP_ENV`
|
||||
- `VITE_API_BASE_URL`
|
||||
- `VITE_ENABLE_QUERY_DEVTOOLS`
|
||||
- `VITE_ENABLE_REQUEST_LOG`
|
||||
|
||||
### 第三步:改全局样式入口
|
||||
|
||||
当前模板已经切到 Tailwind CSS,并移除了旧的 `App.css` / `index.css`。
|
||||
|
||||
你主要会改:
|
||||
|
||||
- `src/styles.css`
|
||||
|
||||
这里通常只放:
|
||||
|
||||
- `@import "tailwindcss";`
|
||||
- 少量全局 base 样式
|
||||
- 你的主题变量
|
||||
|
||||
### 第四步:接你的业务模块
|
||||
|
||||
建议按“功能模块”建目录,而不是把所有请求、类型和 hooks 混在一起。
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
src/features/user/api/user-api.ts
|
||||
src/features/user/hooks/use-current-user.ts
|
||||
src/features/user/types/user.ts
|
||||
src/routes/$lang/users/index.tsx
|
||||
```
|
||||
|
||||
### 第五步:接你的登录体系
|
||||
|
||||
优先改这些文件:
|
||||
|
||||
- `src/store/auth-store.ts`
|
||||
- `src/lib/auth/auth-session.ts`
|
||||
- `src/lib/api/api-client.ts`
|
||||
|
||||
## 4. Head / Metadata 怎么用
|
||||
|
||||
模板已经提供了一个通用 hook:
|
||||
|
||||
- `src/lib/head/document-metadata.ts`
|
||||
|
||||
在页面组件里直接调用:
|
||||
|
||||
```tsx
|
||||
import { useDocumentMetadata } from '@/lib/head/document-metadata'
|
||||
|
||||
function UserPage() {
|
||||
useDocumentMetadata({
|
||||
title: '用户管理',
|
||||
description: '用户管理页面',
|
||||
})
|
||||
|
||||
return <div>User Page</div>
|
||||
}
|
||||
```
|
||||
|
||||
它会自动更新:
|
||||
|
||||
- `document.title`
|
||||
- `meta[name="description"]`
|
||||
- `meta[name="robots"]`
|
||||
- `og:title`
|
||||
- `og:description`
|
||||
- `twitter:title`
|
||||
- `twitter:description`
|
||||
|
||||
适用场景:
|
||||
|
||||
- 列表页标题
|
||||
- 详情页标题
|
||||
- 登录页 / 404 页
|
||||
- 分享卡片基础描述
|
||||
|
||||
## 5. 登录怎么接
|
||||
|
||||
这个模板的认证层不是“现成登录系统”,而是“认证骨架”。
|
||||
|
||||
它已经有:
|
||||
|
||||
- `accessToken`
|
||||
- `refreshToken`
|
||||
- `currentUser`
|
||||
- `status`
|
||||
- `401` 后清会话
|
||||
- refresh token 的可插拔入口
|
||||
|
||||
核心文件:
|
||||
|
||||
- `src/store/auth-store.ts`
|
||||
- `src/lib/auth/auth-session.ts`
|
||||
- `src/lib/api/api-client.ts`
|
||||
|
||||
### 5.1 登录成功后怎么写入会话
|
||||
|
||||
假设你的登录接口返回:
|
||||
|
||||
```ts
|
||||
{
|
||||
accessToken: string
|
||||
refreshToken: string
|
||||
user: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
那么登录成功后可以这样写:
|
||||
|
||||
```ts
|
||||
import { useAuthStore } from '@/store/auth-store'
|
||||
|
||||
useAuthStore.getState().startSession({
|
||||
accessToken: response.accessToken,
|
||||
refreshToken: response.refreshToken,
|
||||
currentUser: response.user,
|
||||
})
|
||||
```
|
||||
|
||||
### 5.2 刷新 token 怎么接
|
||||
|
||||
模板里预留了 refresh 的注册入口:
|
||||
|
||||
```ts
|
||||
import { registerRefreshSessionHandler } from '@/lib/auth/auth-session'
|
||||
|
||||
registerRefreshSessionHandler(async (refreshToken) => {
|
||||
const response = await fetch('/auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
}).then((result) => result.json())
|
||||
|
||||
return {
|
||||
accessToken: response.accessToken,
|
||||
refreshToken: response.refreshToken,
|
||||
currentUser: response.user ?? null,
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
当请求返回 `401` 时,请求层会:
|
||||
|
||||
1. 尝试执行这个 refresh handler
|
||||
2. 如果 refresh 成功,则自动重试原请求一次
|
||||
3. 如果 refresh 失败,则清空本地会话
|
||||
|
||||
### 5.3 当前用户初始化怎么接
|
||||
|
||||
如果你的项目在刷新页面后,需要根据已有 token 主动请求一次“当前用户信息”,可以注册:
|
||||
|
||||
```ts
|
||||
import { registerCurrentUserInitializer } from '@/lib/auth/auth-session'
|
||||
|
||||
registerCurrentUserInitializer(async () => {
|
||||
const response = await fetch('/auth/me').then((result) => result.json())
|
||||
|
||||
return response.user
|
||||
})
|
||||
```
|
||||
|
||||
这样在应用启动时,模板会在已有 token 且尚未拿到 `currentUser` 的情况下,自动初始化一次用户信息。
|
||||
|
||||
## 6. 怎么加受保护路由
|
||||
|
||||
模板已经提供了路由保护 helper:
|
||||
|
||||
- `src/lib/auth/require-auth.ts`
|
||||
|
||||
用法是,在路由的 `beforeLoad` 里调用它。
|
||||
|
||||
示例:
|
||||
|
||||
```tsx
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { requireAuthenticatedSession } from '@/lib/auth/require-auth'
|
||||
|
||||
export const Route = createFileRoute('/$lang/dashboard')({
|
||||
beforeLoad: async () => {
|
||||
await requireAuthenticatedSession()
|
||||
},
|
||||
component: DashboardPage,
|
||||
})
|
||||
|
||||
function DashboardPage() {
|
||||
return <div>Dashboard</div>
|
||||
}
|
||||
```
|
||||
|
||||
它的行为是:
|
||||
|
||||
- 如果当前已经登录,正常进入页面
|
||||
- 如果当前未登录,重定向到 `/$lang`
|
||||
|
||||
如果你想把未登录用户导向专门的登录页,可以直接修改:
|
||||
|
||||
- `src/lib/auth/require-auth.ts`
|
||||
|
||||
把重定向目标从 `/$lang` 改成你的登录页路由,例如 `/$lang/login`。
|
||||
|
||||
## 7. 怎么写第一个接口模块
|
||||
|
||||
推荐模式是:
|
||||
|
||||
1. 在 `src/features/.../types` 定义类型
|
||||
2. 在 `src/features/.../api` 写请求函数
|
||||
3. 在 `src/features/.../hooks` 写 query / mutation hook
|
||||
4. 在 `src/routes/...` 页面里消费
|
||||
|
||||
示例:
|
||||
|
||||
```ts
|
||||
// src/features/user/api/user-api.ts
|
||||
import { api } from '@/lib/api/api-client'
|
||||
|
||||
export interface CurrentUser {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export function getCurrentUser() {
|
||||
return api.get<CurrentUser>('users/me').then((response) => response.data)
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// src/features/user/hooks/use-current-user.ts
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { getCurrentUser } from '@/features/user/api/user-api'
|
||||
import type { ApiError } from '@/lib/api/api-error'
|
||||
|
||||
export function useCurrentUserQuery() {
|
||||
return useQuery<Awaited<ReturnType<typeof getCurrentUser>>, ApiError>({
|
||||
queryKey: ['current-user'],
|
||||
queryFn: getCurrentUser,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 路由相关说明
|
||||
|
||||
这个模板使用 TanStack Router 文件路由。
|
||||
|
||||
路由目录:
|
||||
|
||||
- `src/routes`
|
||||
|
||||
生成文件:
|
||||
|
||||
- `src/routeTree.gen.ts`
|
||||
|
||||
你通常不需要手改 `src/routeTree.gen.ts`。
|
||||
新增、删除、修改路由文件后,执行:
|
||||
|
||||
```bash
|
||||
pnpm generate-routes
|
||||
```
|
||||
|
||||
`pnpm dev` 和 `pnpm build` 也都会自动先生成一次路由树。
|
||||
|
||||
## 9. 代码规范和提交流程
|
||||
|
||||
## 9.1 Tailwind 说明
|
||||
|
||||
当前模板使用的是官方 Vite 接法:
|
||||
|
||||
- `tailwindcss`
|
||||
- `@tailwindcss/vite`
|
||||
|
||||
核心文件:
|
||||
|
||||
- `vite.config.ts`
|
||||
- `src/styles.css`
|
||||
|
||||
新增页面时,优先直接写 Tailwind utility class,而不是再恢复成多个分散的 CSS 文件。
|
||||
|
||||
格式化与 lint:
|
||||
|
||||
```bash
|
||||
pnpm lint
|
||||
pnpm lint:fix
|
||||
pnpm format
|
||||
```
|
||||
|
||||
提交规范:
|
||||
|
||||
```bash
|
||||
pnpm commit
|
||||
```
|
||||
|
||||
仓库已经包含:
|
||||
|
||||
- `Biome`
|
||||
- `Husky`
|
||||
- `lint-staged`
|
||||
- `commitlint`
|
||||
- `Commitizen`
|
||||
|
||||
## 10. 你后续最可能改动的文件
|
||||
|
||||
如果你刚接手这个模板,优先关注这几个文件:
|
||||
|
||||
- `src/routes/$lang/index.tsx`
|
||||
- `src/routes/$lang/route.tsx`
|
||||
- `src/lib/api/api-client.ts`
|
||||
- `src/lib/auth/auth-session.ts`
|
||||
- `src/lib/auth/require-auth.ts`
|
||||
- `src/store/auth-store.ts`
|
||||
- `src/lib/head/document-metadata.ts`
|
||||
- `src/locales/zh-CN/common.ts`
|
||||
- `src/locales/en-US/common.ts`
|
||||
|
||||
## 11. 当前状态
|
||||
|
||||
当前模板已经验证通过:
|
||||
|
||||
- `pnpm lint`
|
||||
- `pnpm build`
|
||||
|
||||
如果你继续往前整理,下一步通常就是:
|
||||
|
||||
1. 接入真实登录接口
|
||||
2. 增加 `login`、`dashboard`、`403` 等实际页面
|
||||
3. 按业务模块拆分 `features`
|
||||
4. 按项目实际需要补充测试方案
|
||||
|
||||
Reference in New Issue
Block a user