Files
laravel_swoole/.clinerules/admin-rule.md
2026-02-08 22:38:13 +08:00

870 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Vue3 后台管理项目开发规范
## 项目概述
本项目是一个基于 Vue3 的后台管理系统,采用现代化技术栈构建,提供高效、美观的管理界面。
## 技术栈
- **Vue 3**: 渐进式 JavaScript 框架
- **Vite**: 下一代前端构建工具
- **Ant Design Vue**: 基于 Vue 3 的 UI 组件库
- **Vue Router**: Vue.js 官方路由管理器
- **Pinia**: Vue 3 官方状态管理库
- **Axios**: HTTP 客户端
- **JavaScript**: 主要开发语言(非 TypeScript
- **Composition API**: 组合式 API 开发模式
## 图标系统
项目采用 Ant Design Vue 图标库,已全局引入:
- **Ant Design Vue Icons**: Ant Design Vue 官方图标库
**重要提示**: 图标已全局默认引入,开发时请勿重复引入或按需引入,直接使用即可。
## 开发规范
### 1. 项目结构
```
resources/admin/
├── src/
│ ├── api/ # API 接口层
│ │ ├── auth.js # 认证相关接口
│ │ ├── menu.js # 菜单接口
│ │ └── system.js # 系统相关接口
│ ├── assets/ # 静态资源
│ │ ├── images/ # 图片资源
│ │ └── style/ # 全局样式
│ ├── components/ # 公共组件
│ │ ├── scEditor/ # 富文本编辑器
│ │ ├── scForm/ # 表单组件
│ │ ├── scIconPicker/ # 图标选择器
│ │ ├── scTable/ # 表格组件
│ │ └── scUpload/ # 上传组件
│ ├── config/ # 配置文件
│ │ ├── index.js # 主配置
│ │ ├── routes.js # 路由配置
│ │ └── upload.js # 上传配置
│ ├── hooks/ # 组合式 API Hooks
│ │ ├── useI18n.js # 国际化 Hook
│ │ └── useTable.js # 表格 Hook
│ ├── i18n/ # 国际化配置
│ │ ├── index.js # i18n 配置
│ │ └── locales/ # 语言包
│ ├── layouts/ # 布局组件
│ │ ├── components/ # 布局子组件
│ │ ├── other/ # 其他布局
│ │ └── index.vue # 主布局
│ ├── pages/ # 页面组件
│ │ ├── auth/ # 认证页面
│ │ ├── home/ # 首页
│ │ ├── login/ # 登录页
│ │ ├── system/ # 系统管理
│ │ └── ucenter/ # 个人中心
│ ├── router/ # 路由配置
│ │ ├── index.js # 主路由
│ │ └── systemRoutes.js # 系统路由
│ ├── stores/ # 状态管理
│ │ ├── index.js # Store 入口
│ │ ├── persist.js # 持久化配置
│ │ └── modules/ # Store 模块
│ ├── utils/ # 工具函数
│ │ ├── request.js # Axios 封装
│ │ └── tool.js # 工具函数
│ ├── App.vue # 根组件
│ ├── boot.js # 引导文件
│ ├── main.js # 入口文件
│ └── style.css # 全局样式
├── public/ # 公共资源
├── index.html # HTML 模板
├── package.json # 依赖配置
├── vite.config.js # Vite 配置
└── README.md # 项目说明
```
### 2. 组件开发规范
#### 组件命名
- **单文件组件**: 使用 PascalCase 命名,如 `UserList.vue`
- **公共组件**: 以 `sc` 开头,如 `scTable.vue`
- **页面组件**: 使用语义化命名,如 `UserManagement.vue`
#### 组件结构
```vue
<template>
<!-- 模板内容 -->
</template>
<script setup>
// 导入
import { ref, computed, onMounted } from 'vue'
// 响应式数据
const state = ref({})
// 计算属性
const computedValue = computed(() => {})
// 方法
const handleAction = () => {}
// 生命周期
onMounted(() => {})
</script>
<style scoped>
/* 样式 */
</style>
```
### 3. API 接口开发规范
#### API 文件组织
每个业务模块对应一个 API 文件,统一放在 `src/api/` 目录下。
```javascript
// src/api/auth.js
import request from '@/utils/request'
export default {
// 认证相关
login: {
post: async function (params) {
return await request.post('auth/login', params)
},
},
logout: {
post: async function () {
return await request.post('auth/logout')
},
},
me: {
get: async function () {
return await request.get('auth/me')
},
},
// 权限和菜单
permissions: {
menu: {
get: async function () {
return await request.get('permissions/menu')
},
},
tree: {
get: async function () {
return await request.get('permissions/tree')
},
},
},
}
```
#### 使用示例
```javascript
import authApi from '@/api/auth'
const login = async () => {
try {
const res = await authApi.login.post({
username: 'admin',
password: '123456'
})
// 处理响应
} catch (error) {
// 处理错误
}
}
// 获取用户菜单
const getMenu = async () => {
const res = await authApi.permissions.menu.get()
return res.data
}
```
### 4. 路由开发规范
#### 路由类型
项目包含两类路由:
1. **静态路由**: 定义在 `src/router/systemRoutes.js` 中的基础路由如登录页、404 页面等
2. **动态路由**: 用户登录后,通过 API 获取菜单数据,动态添加到路由中
#### 静态路由定义
```javascript
// src/router/systemRoutes.js
export default [
{
path: '/login',
name: 'Login',
component: () => import('@/pages/login/index.vue'),
meta: {
title: '登录',
hidden: true
}
},
{
path: '/',
name: 'Layout',
component: () => import('@/layouts/index.vue'),
redirect: '/dashboard',
children: [
// 动态路由将被添加到这里
]
}
]
```
#### 动态路由加载
用户登录后,系统会自动执行以下流程:
1. 调用后端 API 获取用户菜单和权限信息
2. 将菜单数据转换为路由格式
3. 将动态路由添加到路由器中
4. 生成菜单树用于侧边栏展示
**路由元信息 (meta)**:
- `title`: 页面标题
- `icon`: 菜单图标(使用 Ant Design Vue 图标名称)
- `hidden`: 是否隐藏菜单
- `noAuth`: 是否不需要认证
- `keepAlive`: 是否缓存页面
- `affix`: 是否固定标签页
**后端菜单数据格式**:
```javascript
{
path: '/system',
name: 'System',
title: '系统管理',
icon: 'Setting',
component: 'views/system', // 组件路径,相对于 pages 目录
redirect: '/system/user',
children: [
{
path: 'user',
name: 'SystemUser',
title: '用户管理',
icon: 'User',
component: 'views/system/user'
}
]
}
```
**路由转换逻辑**:
```javascript
// 将后端菜单转换为路由格式
function transformMenusToRoutes(menus) {
return menus.map(menu => {
const route = {
path: menu.path,
name: menu.name,
meta: {
title: menu.title,
icon: menu.icon,
hidden: menu.hidden,
keepAlive: menu.keepAlive || false
}
}
if (menu.component) {
route.component = loadComponent(menu.component)
}
if (menu.children) {
route.children = transformMenusToRoutes(menu.children)
}
return route
})
}
```
#### 路由守卫
系统通过路由守卫实现权限控制和动态路由加载:
```javascript
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
const isLoggedIn = userStore.isLoggedIn()
// 白名单直接放行
if (whiteList.includes(to.path)) {
return next()
}
// 未登录跳转登录页
if (!isLoggedIn) {
return next({ path: '/login', query: { redirect: to.fullPath } })
}
// 动态路由加载
if (!isDynamicRouteLoaded) {
const menus = userStore.getMenu()
const dynamicRoutes = transformMenusToRoutes(menus)
// 添加动态路由
dynamicRoutes.forEach(route => {
router.addRoute('Layout', route)
})
isDynamicRouteLoaded = true
next({ ...to, replace: true })
} else {
next()
}
})
```
### 5. 状态管理规范
#### Pinia Store 定义
使用组合式 API 定义 Store。
#### User Store用户认证与权限
`src/stores/modules/user.js` 负责管理用户认证信息和权限数据:
```javascript
// src/stores/modules/user.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { resetRouter } from '../../router'
export const useUserStore = defineStore('user', () => {
// State
const token = ref('') // 访问令牌
const refreshToken = ref('') // 刷新令牌
const userInfo = ref(null) // 用户信息
const menu = ref([]) // 用户菜单
const permissions = ref([]) // 用户权限节点
// Getters
const isLoggedIn = () => !!token.value
// Actions
function setToken(newToken) {
token.value = newToken
}
function setUserInfo(info) {
userInfo.value = info
}
// 设置菜单(合并静态菜单和后端菜单)
function setMenu(newMenu) {
const staticMenus = userRoutes || []
let mergedMenus = [...staticMenus]
if (newMenu && newMenu.length > 0) {
const menuMap = new Map()
// 添加静态菜单
staticMenus.forEach(m => {
if (m.path) menuMap.set(m.path, m)
})
// 添加后端菜单(覆盖重复路径)
newMenu.forEach(m => {
if (m.path) menuMap.set(m.path, m)
})
mergedMenus = Array.from(menuMap.values())
}
menu.value = mergedMenus
}
function getMenu() {
return menu.value
}
function setPermissions(data) {
permissions.value = data
}
function hasPermission(permission) {
if (!permissions.value || permissions.value.length === 0) {
return false
}
return permissions.value.includes(permission)
}
function logout() {
token.value = ''
refreshToken.value = ''
userInfo.value = null
menu.value = []
resetRouter()
}
return {
token,
refreshToken,
userInfo,
menu,
permissions,
setToken,
setUserInfo,
setMenu,
getMenu,
setPermissions,
hasPermission,
logout,
isLoggedIn
}
})
```
#### Store 使用
```javascript
import { useUserStore } from '@/stores/modules/user'
const userStore = useUserStore()
// 使用 state
console.log(userStore.token)
console.log(userStore.menu)
console.log(userStore.permissions)
// 调用 action
userStore.setToken('xxx')
userStore.setMenu(menus)
userStore.setPermissions(permissions)
// 检查权限
if (userStore.hasPermission('user.create')) {
// 有权限,执行操作
}
// 登出
userStore.logout()
```
#### Store 持久化
使用 `pinia-plugin-persistedstate` 实现数据持久化:
```javascript
{
persist: {
key: 'user-store',
storage: customStorage,
pick: ['token', 'refreshToken', 'userInfo', 'menu']
}
}
```
#### 权限指令
项目提供权限指令,用于在模板中控制元素显示:
```vue
<template>
<!-- 只有拥有 user.create 权限时显示 -->
<a-button v-permission="'user.create'">新增</a-button>
<!-- 拥有多个权限之一时显示 -->
<a-button v-permission="['user.create', 'user.update']">编辑</a-button>
</template>
<script setup>
import { useUserStore } from '@/stores/modules/user'
const userStore = useUserStore()
</script>
```
### 6. 表格开发规范
#### 使用 useTable Hook
项目提供了 `useTable` Hook 简化表格开发:
```javascript
import { useTable } from '@/hooks/useTable'
import { ref } from 'vue'
const {
loading,
dataSource,
pagination,
handleSearch,
handleReset,
handlePageChange
} = useTable({
api: userApi.getList, // API 方法
immediate: true // 是否立即加载
})
// 搜索参数
const searchParams = ref({
keyword: '',
status: ''
})
```
#### scTable 组件使用
```vue
<template>
<sc-table
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
@page-change="handlePageChange"
>
<template #action="{ record }">
<a-button @click="handleEdit(record)">编辑</a-button>
</template>
</sc-table>
</template>
```
### 7. 表单开发规范
#### scForm 组件使用
```vue
<template>
<sc-form
ref="formRef"
:model="formData"
:rules="formRules"
:items="formItems"
@submit="handleSubmit"
/>
</template>
<script setup>
import { ref } from 'vue'
const formRef = ref(null)
const formData = ref({
username: '',
email: ''
})
const formRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }
]
}
const formItems = [
{
type: 'input',
prop: 'username',
label: '用户名',
placeholder: '请输入用户名'
},
{
type: 'input',
prop: 'email',
label: '邮箱',
placeholder: '请输入邮箱'
}
]
const handleSubmit = async () => {
await formRef.value.validate()
// 提交逻辑
}
</script>
```
### 8. 图标使用规范
#### Ant Design Vue Icons
```vue
<!-- 直接使用无需导入 -->
<template>
<a-icon type="user" />
<a-icon type="setting" />
</template>
```
#### 常用图标
- `user`: 用户
- `setting`: 设置
- `delete`: 删除
- `edit`: 编辑
- `plus`: 添加
- `search`: 搜索
- `reload`: 刷新
- `download`: 下载
- `upload`: 上传
- `eye`: 查看
- `eye-invisible`: 隐藏
- `check-circle`: 成功
- `close-circle`: 失败
- `info-circle`: 信息
- `warning`: 警告
### 9. 国际化 (i18n) 规范
#### 使用 i18n
```javascript
import { useI18n } from '@/hooks/useI18n'
const { t } = useI18n()
// 使用
console.log(t('common.save'))
console.log(t('user.deleteConfirm'))
```
#### 语言文件组织
```javascript
// src/i18n/locales/zh.js
export default {
common: {
save: '保存',
cancel: '取消',
confirm: '确认',
delete: '删除'
},
user: {
deleteConfirm: '确定要删除该用户吗?'
}
}
```
### 10. 文件上传规范
#### scUpload 组件使用
```vue
<template>
<sc-upload
v-model="imageUrl"
:limit="1"
accept="image/*"
list-type="picture-card"
/>
</template>
<script setup>
import { ref } from 'vue'
const imageUrl = ref('')
</script>
```
### 11. 富文本编辑器规范
#### scEditor 组件使用
```vue
<template>
<sc-editor
v-model="content"
:height="400"
:toolbar="toolbarConfig"
/>
</template>
<script setup>
import { ref } from 'vue'
const content = ref('')
const toolbarConfig = [
'bold', 'italic', 'underline', 'strike',
'list', 'orderedList', 'quote', 'codeBlock',
'image', 'link'
]
</script>
```
### 12. 样式规范
#### 全局样式
`src/style.css` 中定义全局样式。
#### 组件样式
使用 `scoped` 避免样式污染:
```vue
<style scoped>
.user-list {
padding: 20px;
}
.user-list .table {
margin-top: 20px;
}
</style>
```
#### 命名规范
- 使用 BEM 命名法
- 类名使用 kebab-case
### 13. 工具函数使用
#### request.js (HTTP 请求)
```javascript
import request from '@/utils/request'
// GET 请求
request.get('/api/users', { params: { page: 1 } })
// POST 请求
request.post('/api/users', { name: 'test' })
// PUT 请求
request.put('/api/users/1', { name: 'updated' })
// DELETE 请求
request.delete('/api/users/1')
```
#### tool.js (工具函数)
```javascript
import { formatDate, deepClone } from '@/utils/tool'
// 格式化日期
formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
// 深拷贝
deepClone(originalObject)
```
### 14. 开发流程
#### 登录流程
1. 用户输入用户名和密码
2. 调用 `authApi.login.post()` 发送登录请求
3. 后端返回 `token``refreshToken``userInfo``menu``permissions`
4. 前端保存数据到 Store持久化
5. 路由守卫检测到登录状态,加载动态路由
6. 跳转到首页或重定向页
```javascript
import { useUserStore } from '@/stores/modules/user'
import authApi from '@/api/auth'
const userStore = useUserStore()
const handleLogin = async () => {
try {
const res = await authApi.login.post({
username: 'admin',
password: '123456'
})
// 保存 token
userStore.setToken(res.data.token)
userStore.setRefreshToken(res.data.refreshToken)
// 保存用户信息
userStore.setUserInfo(res.data.user)
// 保存菜单(合并静态菜单)
userStore.setMenu(res.data.menu)
// 保存权限节点
userStore.setPermissions(res.data.permissions)
// 跳转首页
router.push('/dashboard')
} catch (error) {
message.error('登录失败')
}
}
```
#### 添加新页面
1.`src/pages/` 对应模块下创建页面组件
2.`src/api/` 中创建对应的 API 文件
3. 在后端添加对应的菜单和权限配置
4. 前端会自动加载动态路由(无需手动配置路由)
#### 添加新组件
1.`src/components/` 或对应子目录下创建组件
2. 遵循组件结构规范
3. 添加必要的 Props 和 Emits
4. 编写组件文档
### 15. 常用命令
```bash
# 安装依赖
npm install
# 开发环境启动
npm run dev
# 生产环境构建
npm run build
# 预览生产构建
npm run preview
# 代码格式化
npm run format
# 代码检查
npm run lint
```
### 16. 注意事项
1. **禁止重复引入图标**: Ant Design Vue 图标已全局引入,直接使用即可
2. **使用组合式 API**: 新代码统一使用 `<script setup>` 语法
3. **组件复用**: 优先使用项目提供的公共组件scTable、scForm 等)
4. **API 统一管理**: 所有接口统一在 `src/api/` 目录下管理
5. **路由懒加载**: 路由组件必须使用动态导入
6. **环境变量**: 通过 `import.meta.env` 访问环境变量
7. **不要编写 demo**: 开发过程中不编写示例代码
8. **测试提示**: 如需测试,提示用户是否运行测试,不主动运行
### 17. 代码质量
- 遵循 Vue 3 官方风格指南
- 保持代码简洁、可读
- 适当添加注释说明复杂逻辑
- 使用语义化的变量和函数命名
- 避免过多的嵌套层级
### 18. 性能优化
- 合理使用 `v-if``v-show`
- 列表渲染必须设置 `key`
- 大列表使用虚拟滚动
- 图片懒加载
- 路由懒加载
- 组件按需引入(除图标外)