Commit b248f8ae by chunhong.mu

chore: add architecture document

parent f088983f
......@@ -11,11 +11,10 @@ build/
# Environment files
.env
.env.*
.env.*.local
!.env.example
# IDE
.vscode/
.idea/
*.swp
*.swo
......
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[json]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[markdown]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
\ No newline at end of file
# 项目架构与开发规范
## 一、项目结构
```
common-web/
├── common/ # 公共代码库(跨项目复用)
│ ├── utils/ # 纯工具函数、常量、类型定义
│ └── vue-kit/ # Vue 相关代码(组件、组合式函数、指令)
├── packages/ # 具体项目
│ └── official-site-web/ # 官网项目
├── scripts/ # 构建脚本(不参与 lint/format)
├── .husky/ # Git 钩子
├── .vscode/ # VS Code 配置
│ └── settings.json # 编辑器格式化配置
├── eslint.config.mjs # 根 ESLint 配置(@antfu/eslint-config)
├── .lintstagedrc # lint-staged 配置
├── commitlint.config.mjs # commitlint 配置
└── package.json # 根依赖与脚本
```
## 二、目录职责
### `common/` — 公共代码库
存放**跨项目复用**的代码。所有子包通过 `workspace:*` 引用,支持按需引入。
### `packages/` — 具体项目
存放**独立可运行**的 Web 项目。每个项目有自己的 `package.json``.env` 文件、构建配置。
### `scripts/` — 辅助脚本
存放项目辅助脚本(如项目选择器、自动化脚本等),不参与代码检查(已在 `eslint.config.mjs` 中忽略)。
---
## 三、common 子包规范
### 3.1 `@common/utils` — 纯工具函数包
**存放内容**
- 纯函数(无副作用、无框架依赖)
- 通用常量
- 通用类型定义
- 数据格式化工具、加密工具、正则工具等
**示例**
```
common/utils/src/
├── format.ts # 日期/数字格式化
├── crypto.ts # 加密/解密
├── regex.ts # 正则表达式
├── constants.ts # 通用常量
└── index.ts # 统一导出
```
**边界条件**
- ✅ 可以放:不依赖 Vue/React 等框架的纯 JS/TS 代码
- ❌ 不能放:Vue 组件、hooks、指令、响应式逻辑
- ❌ 不能放:业务相关代码(如登录逻辑、API 调用)
**为什么**
- 纯工具函数可以在任何项目(Web、Node.js)中复用
- 与框架解耦,降低依赖,提升可测试性
---
### 3.2 `@common/vue-kit` — Vue 相关代码包
**存放内容**
- Vue 组件(通用 UI 组件)
- Vue 组合式函数(Composables)
- Vue 自定义指令
**目录结构**
```
common/vue-kit/src/
├── components/ # 通用组件(如 Button、Modal、Loading)
│ └── index.ts
├── composables/ # 组合式函数(如 useLogin、useAuth)
│ ├── index.ts
│ └── login-controller.ts
└── directives/ # 自定义指令(如 v-permission、v-lazy)
└── index.ts
```
**边界条件**
- ✅ 可以放:跨项目复用的 Vue 组件、hooks、指令
- ✅ 可以放:依赖 Vue 响应式系统的逻辑(如 `ref``computed``watch`
- ❌ 不能放:业务强相关的组件(如特定页面的表单)
- ❌ 不能放:纯工具函数(应放 `@common/utils`
- ❌ 不能放:平台特定代码(如浏览器特定 API 封装)
**为什么**
- Vue 相关代码需要 Vue 作为 peer dependency,与纯工具函数分离
- 统一升级 Vue 版本时只需更新一个包
- 按需引入:`import { useLogin } from '@common/vue-kit/composables'`
---
## 四、packages 项目规范
### 4.1 Nuxt 项目目录结构(SSR 项目,如官网)
```
packages/official-site-web/
├── pages/ # 页面(Nuxt 文件路由)
├── components/ # 组件(Nuxt 自动导入)
│ ├── business/ # 业务组件
│ └── layout/ # 布局组件
├── composables/ # 组合式函数(Nuxt 自动导入)
├── api/ # API 层(HTTP 调用)
│ ├── request.ts # axios 实例封装
│ └── xxx.ts # 按模块划分的接口
├── services/ # 服务层(业务逻辑编排)
├── stores/ # 状态管理(Pinia)
├── utils/ # 工具函数(Nuxt 自动导入)
├── config/ # 项目配置(常量、环境配置)
├── types/ # 类型定义
├── assets/ # 静态资源(Nuxt 约定)
├── middleware/ # Nuxt 中间件(Nuxt 约定)
├── plugins/ # Nuxt 插件(Nuxt 约定)
├── nuxt.config.ts # Nuxt 配置
├── package.json
└── tsconfig.json
```
### 4.2 纯 Vue3 项目目录结构(SPA 项目,如后台管理、客户端应用)
```plaintext
packages/admin-web/
├── pages/ # 页面(路由入口)
├── components/ # 组件
│ ├── business/ # 业务组件
│ └── layout/ # 布局组件
├── composables/ # 组合式函数
├── api/ # API 层(HTTP 调用)
│ ├── request.ts # axios 实例封装
│ └── xxx.ts # 按模块划分的接口
├── services/ # 服务层(业务逻辑编排)
├── stores/ # 状态管理(Pinia)
├── utils/ # 工具函数
├── config/ # 项目配置(常量、环境配置)
├── types/ # 类型定义
├── assets/ # 静态资源
├── router/ # 路由配置(Vue Router)
├── App.vue
├── main.ts
├── vite.config.ts
├── package.json
└── tsconfig.json
```
### 4.3 项目内各层职责
| 目录 | 职责 | 示例 |
| -------------- | ------------------------ | --------------------------------- |
| `pages/` | 页面入口,路由组装组件 | index.vue、about.vue |
| `components/` | 页面内复用的 UI 组件 | Header、Footer、Card |
| `composables/` | 组合式函数,复用逻辑 | useFetch、useAuth |
| `api/` | 封装统一请求方法,按模块导出接口 | request.ts、user.ts |
| `services/` | 业务逻辑编排,组合多个 API | auth.ts(登录重定向)、upload.ts |
| `stores/` | 全局状态管理 | pinia store |
| `utils/` | 项目私有工具函数 | 格式化工具 |
| `config/` | 项目配置 | 环境配置、常量 |
| `types/` | 类型定义 | 接口响应类型、业务类型 |
| `router/` | 路由配置(纯 Vue3 项目) | 路由守卫、路由配置 |
| `middleware/` | Nuxt 中间件 | 鉴权中间件 |
| `plugins/` | Nuxt 插件 | 全局插件注册 |
### 4.4 `api/` 与 `services/` 职责划分
**`api/` — 统一请求方法封装层**
核心职责是**封装统一的 HTTP 请求方法**,按模块导出接口。
- `request.ts`:创建 axios 实例 + 拦截器 + 导出统一请求方法(get、post、put、delete)
- `xxx.ts`:只导出业务接口函数,调用 `request` 方法
- 不包含业务逻辑,不操作 store,不跳转路由
```typescript
// api/request.ts — 统一请求方法封装
import axios from 'axios'
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
})
// 请求拦截器:注入 token
request.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器:统一错误处理
request.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 401) {
// token 过期,跳转登录
}
return Promise.reject(error)
},
)
export { request }
```
```typescript
// api/user.ts — 业务接口函数
import { request } from './request'
export function getUserInfo() {
return request.get<UserInfo>('/api/user/info')
}
export function updateUser(data: UserInfo) {
return request.put('/api/user', data)
}
```
**`services/` — 服务层(业务逻辑编排)**
- 组合多个 API 调用 + 业务逻辑
- 可以操作 store、跳转路由
- 处理复杂业务流程(如登录重定向、token 刷新)
```typescript
// services/auth.ts
import { getUserInfo } from '@/api/user'
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'
export async function handleLoginRedirect() {
const userStore = useUserStore()
const router = useRouter()
await userStore.fetchUserInfo()
router.push(userStore.redirectUrl || '/')
}
```
### 4.5 代码放置决策树
```
新代码应该放哪里?
├─ 是否跨项目复用?
│ ├─ 是 → 是否依赖 Vue?
│ │ ├─ 是 → @common/vue-kit/
│ │ └─ 否 → @common/utils/
│ │
│ └─ 否 → 放在当前项目 packages/xxx/
│ │
│ ├─ 是页面? → pages/
│ ├─ 是 UI 组件? → components/
│ ├─ 是复用逻辑? → composables/
│ ├─ 是 HTTP 调用? → api/
│ ├─ 是业务逻辑编排? → services/
│ ├─ 是状态管理? → stores/
│ ├─ 是路由配置? → router/(纯 Vue3 项目)
│ ├─ 是工具函数? → utils/
│ ├─ 是项目配置? → config/
│ └─ 是类型定义? → types/
```
---
## 五、技术栈
### 5.1 核心技术
| 技术 | 用途 | 版本 |
| ------------ | -------------- | ------- |
| Nuxt 3 | 框架 | ^3.x |
| Vue 3 | UI 框架 | ^3.4.x |
| TypeScript | 类型系统 | ^5.x |
| UnoCSS | 原子化 CSS | ^0.58.x |
| Ant Design Vue | UI 组件库 | ^4.x |
| Pinia | 状态管理 | ^2.x |
### 5.2 启动与构建
**根目录统一入口**(推荐):
```bash
pnpm dev:project --project official-site-web
pnpm build:project --project official-site-web
```
**子包独立执行**
```bash
cd packages/official-site-web
pnpm run dev
pnpm run build
```
### 5.3 环境变量
**环境命名**
| 环境 | 说明 | 对应分支 | 部署线 |
|------|------|---------|--------|
| `dev` | 开发环境,本地调试 | `dev` | - |
| `test` | 测试环境,QA 测试 | `dev` | test |
| `staging` | 预发环境,镜像验证 | `staging` | staging |
| `production` | 生产环境 | `release` | production |
**环境文件**
位于各子包根目录:`.env.development``.env.test``.env.staging``.env.production`
**环境变量示例**
```bash
# .env.development
APP_ENV=dev
NODE_ENV=development
# .env.test
APP_ENV=test
NODE_ENV=production
# .env.staging
APP_ENV=staging
NODE_ENV=production
# .env.production
APP_ENV=production
NODE_ENV=production
```
**主要差异**:各环境 API 地址不同,通过 `VITE_API_BASE_URL` 区分。
### 5.4 代码检查与格式化
本项目使用 `@antfu/eslint-config` 统一处理代码检查和格式化,无需 Prettier。
**代码风格配置**
- 4 空格缩进
- 双引号
- 不使用分号
- 支持 JSX
```bash
pnpm lint # ESLint 检查
pnpm lint:fix # ESLint 检查并自动修复(包含格式化)
```
**VS Code 配置**
项目已提供 `.vscode/settings.json`,配置了保存时自动使用 ESLint 格式化。安装 VS Code ESLint 插件后即可生效。
提交时自动执行 `lint-staged`(仅检查暂存文件)和 `commitlint`(检查 commit 信息格式)。
### 5.5 依赖管理
- **根目录**:ESLint(@antfu/eslint-config)、Husky、TypeScript 等通用工具
- **子包**:项目特定依赖(Nuxt、Vue、Ant Design Vue 等)
- **common 子包**:只声明 `peerDependencies`,不安装具体版本
安装根目录依赖使用 `pnpm add -wD <package>`
### 5.6 按需引入
common 子包通过 `package.json``exports` 字段实现按需引入:
```typescript
// ✅ 按需引入(推荐)
import { useLogin } from "@common/vue-kit/composables"
import { formatDate } from "@common/utils"
// ❌ 全量引入(不推荐)
import * as VueKit from "@common/vue-kit"
```
---
## 六、Git 分支管理
### 6.1 分支模型(Monorepo 多项目版)
```
release ← 生产环境,受保护分支
staging ← 预发环境,测试通过后合并
dev ← 总开发分支(release 环境)
dev/official-site-web ← 官网项目开发分支
dev/admin-web ← 管理后台开发分支
hotfix/xxx ← 紧急修复分支
```
### 6.2 分支职责
| 分支 | 用途 | 保护规则 | 部署环境 |
|------|------|---------|---------|
| `release` | 生产代码,只接受来自 `staging` 的合并 | 禁止直接提交,需 PR | production |
| `staging` | 预发验证,只接受来自 `dev` 的合并 | 禁止直接提交 | staging |
| `dev` | 总开发分支,所有项目合并入口 | 允许合并 dev/项目名 | release |
| `dev/项目名` | 项目开发分支,日常开发 | 允许直接提交 | - |
| `hotfix/项目名-问题` | 紧急修复 | 无 | - |
### 6.3 分支命名规范
| 类型 | 命名格式 | 示例 |
|------|---------|------|
| 项目开发分支 | `dev/项目名` | `dev/official-site-web``dev/admin-web` |
| 紧急修复 | `hotfix/项目名-问题` | `hotfix/official-site-web-payment-fail` |
| 重构分支 | `refactor/模块名` | `refactor/auth-module` |
### 6.4 工作流
**1. 新功能开发**
```bash
# 从 dev 创建项目开发分支
git checkout dev
git pull
git checkout -b dev/official-site-web
# 日常开发直接在 dev/official-site-web 提交
# 小 bug 也直接在此分支修复
# 开发完成后合并回 dev
git checkout dev
git merge dev/official-site-web --no-ff
git push origin dev
```
**2. 测试通过,发布预发**
```bash
# dev 合并到 staging
git checkout staging
git merge dev --no-ff
git push origin staging
```
**3. 预发验证通过,发布生产**
```bash
# staging 合并到 release,打 tag
git checkout release
git merge staging --no-ff
git tag -a v1.0.0 -m "release v1.0.0"
git push origin release --tags
```
**4. 紧急修复(hotfix)**
```bash
# 从 release 创建 hotfix 分支
git checkout release
git checkout -b hotfix/official-site-web-payment-fail
# 修复完成后,同时合并回 release 和 dev
git checkout release
git merge hotfix/official-site-web-payment-fail --no-ff
git checkout dev
git merge hotfix/official-site-web-payment-fail --no-ff
git branch -d hotfix/official-site-web-payment-fail
```
### 6.5 提交规范
Commit 信息格式:`type(scope): description`
| type | 说明 |
| ---------- | --------- |
| `feat` | 新功能 |
| `fix` | Bug 修复 |
| `refactor` | 重构 |
| `style` | 代码格式 |
| `docs` | 文档 |
| `chore` | 构建/工具 |
| `test` | 测试 |
示例:`feat(official-site-web): 添加首页 banner 功能`
......@@ -90,6 +90,7 @@ export default antfu(
'**/package-lock.json',
'**/pnpm-lock.yaml',
'**/yarn.lock',
'ARCHITECTURE.md',
],
},
)
APP_ENV=dev
NODE_ENV=development
APP_ENV=production
NODE_ENV=production
APP_ENV=staging
NODE_ENV=production
APP_ENV=test
NODE_ENV=production
import axios from 'axios'
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
})
request.interceptors.request.use(
(config) =>
{
return config
},
(error) =>
{
return Promise.reject(error)
},
)
request.interceptors.response.use(
(response) =>
{
return response.data
},
(error) =>
{
return Promise.reject(error)
},
)
export { request }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment