18 KiB
18 KiB
Vben Admin Web-Antd AI 开发规则手册
项目概述
本项目基于 Vben Admin 5.x 框架开发,使用 Vue 3 + TypeScript + Ant Design Vue。项目采用 monorepo 结构,当前应用位于 apps/web-antd,依赖 packages 目录下的框架核心包。
技术栈
- 框架: Vue 3.5+ (Composition API)
- 语言: TypeScript 5.8+
- UI 库: Ant Design Vue 4.x
- 状态管理: Pinia
- 路由: Vue Router 4.x
- 构建工具: Vite 6.x
- 包管理器: pnpm (必须使用,不能用 npm/yarn)
- 图标: Iconify + Lucide
项目结构
apps/web-antd/
├── src/
│ ├── api/ # API 请求封装
│ │ ├── core/ # 核心 API (auth, menu, user)
│ │ ├── request.ts # 请求客户端配置
│ │ └── index.ts # 导出所有 API
│ ├── router/ # 路由配置
│ │ ├── routes/ # 路由定义
│ │ │ ├── core/ # 核心路由 (login, 404等)
│ │ │ └── modules/ # 业务路由模块
│ │ └── index.ts # 路由实例
│ ├── store/ # Pinia 状态管理
│ ├── views/ # 页面组件
│ │ ├── _core/ # 核心页面 (login, 404)
│ │ ├── dashboard/ # 仪表盘
│ │ └── demos/ # 示例页面
│ ├── layouts/ # 布局组件(如需自定义)
│ ├── locales/ # 国际化配置
│ ├── adapter/ # 组件适配器
│ ├── app.vue # 根组件
│ ├── main.ts # 入口文件
│ └── preferences.ts # 应用偏好设置
├── package.json
└── vite.config.ts
核心开发规范
1. 命名规范
文件命名
- 组件文件: PascalCase,如
UserProfile.vue,DataTable.vue - 工具文件: kebab-case,如
format-date.ts,use-table.ts - API 文件: kebab-case,如
user-api.ts,order-api.ts - 路由文件: kebab-case,如
user-management.ts
变量命名
- 组件变量: PascalCase,如
const UserTable = defineComponent(...) - 普通变量: camelCase,如
const userInfo = ref({}) - 常量: UPPER_SNAKE_CASE,如
const API_BASE_URL = '...' - 类型/接口: PascalCase,如
interface UserInfo { ... }
2. Vue 组件规范
必须使用 Composition API + <script setup>:
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import type { UserInfo } from '#/api/types';
// 定义组件名(可选,但推荐)
defineOptions({ name: 'UserProfile' });
// Props 定义
interface Props {
userId: string;
showActions?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
showActions: true,
});
// Emits 定义
const emit = defineEmits<{
update: [user: UserInfo];
delete: [id: string];
}>();
// 响应式数据
const loading = ref(false);
const userInfo = ref<UserInfo | null>(null);
// 计算属性
const displayName = computed(() => {
return userInfo.value?.name || 'Unknown';
});
// 方法
async function fetchUser() {
loading.value = true;
try {
const data = await getUserApi(props.userId);
userInfo.value = data;
} finally {
loading.value = false;
}
}
// 生命周期
onMounted(() => {
fetchUser();
});
</script>
<template>
<div class="user-profile">
<a-spin :spinning="loading">
<h3>{{ displayName }}</h3>
</a-spin>
</div>
</template>
<style scoped>
.user-profile {
padding: 16px;
}
</style>
3. 导入路径别名
使用 #/ 别名指向 src/ 目录:
// ✅ 正确
import { useAuthStore } from '#/store';
import type { UserInfo } from '#/api/types';
import { $t } from '#/locales';
// ❌ 错误 - 不要使用相对路径
import { useAuthStore } from '../../../store';
4. 框架组件导入
框架提供的组件从 @vben/* 包导入:
// UI 组件
import { Button, Modal, Drawer } from '@vben/common-ui';
import { Page, Card } from '@vben/common-ui';
import { VbenForm, VbenTable } from '@vben/common-ui';
// 布局组件
import { BasicLayout, PageWrapper } from '@vben/layouts';
// 图标
import { SvgIcon, IconSvg } from '@vben/icons';
// Hooks
import { usePreferences, useAppConfig } from '@vben/hooks';
// 工具函数
import { formatDate, formatMoney } from '@vben/utils';
// 权限
import { useAccess } from '@vben/access';
// Store
import { useAccessStore, useUserStore } from '@vben/stores';
// 请求
import { RequestClient } from '@vben/request';
开发模式详解
1. 创建新页面
步骤 1: 创建视图组件
在 src/views/ 下创建页面组件:
<!-- src/views/user/user-list.vue -->
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { VbenTable } from '@vben/common-ui';
import type { VbenTableColumn } from '@vben/common-ui';
import { getUserListApi } from '#/api/user';
defineOptions({ name: 'UserList' });
const loading = ref(false);
const dataSource = ref([]);
const columns: VbenTableColumn[] = [
{ title: '用户名', dataIndex: 'username', key: 'username' },
{ title: '邮箱', dataIndex: 'email', key: 'email' },
{ title: '状态', dataIndex: 'status', key: 'status' },
];
async function fetchData() {
loading.value = true;
try {
const data = await getUserListApi();
dataSource.value = data;
} finally {
loading.value = false;
}
}
onMounted(() => {
fetchData();
});
</script>
<template>
<div class="p-5">
<VbenTable
:columns="columns"
:data-source="dataSource"
:loading="loading"
/>
</div>
</template>
步骤 2: 添加路由配置
在 src/router/routes/modules/ 下创建路由模块:
// src/router/routes/modules/user.ts
import type { RouteRecordRaw } from 'vue-router';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
meta: {
icon: 'lucide:users',
title: '用户管理',
order: 10, // 菜单排序
},
name: 'User',
path: '/user',
children: [
{
name: 'UserList',
path: '/user/list',
component: () => import('#/views/user/user-list.vue'),
meta: {
icon: 'lucide:user-cog',
title: '用户列表',
},
},
{
name: 'UserDetail',
path: '/user/detail/:id',
component: () => import('#/views/user/user-detail.vue'),
meta: {
hideInMenu: true, // 不在菜单中显示
title: '用户详情',
activeMenu: '/user/list', // 激活父菜单
},
},
],
},
];
export default routes;
路由配置要点:
- 必须
export default routes component使用动态导入() => import(...)- 使用
#/views/别名 - 图标使用 Iconify 格式,如
lucide:users meta.order控制菜单顺序(数字越小越靠前)
步骤 3: 添加国际化(可选)
// src/locales/lang/zh-CN/user.ts
export default {
userManagement: '用户管理',
userList: '用户列表',
userName: '用户名',
email: '邮箱',
};
2. API 请求规范
在 src/api/ 下创建 API 模块:
// src/api/user.ts
import { requestClient } from './request';
export interface UserInfo {
id: string;
username: string;
email: string;
status: 'active' | 'inactive';
}
export interface UserListParams {
page?: number;
pageSize?: number;
keyword?: string;
}
/**
* 获取用户列表
*/
export async function getUserListApi(params?: UserListParams) {
return requestClient.get<UserInfo[]>('/user/list', { params });
}
/**
* 获取用户详情
*/
export async function getUserDetailApi(id: string) {
return requestClient.get<UserInfo>(`/user/${id}`);
}
/**
* 创建用户
*/
export async function createUserApi(data: Partial<UserInfo>) {
return requestClient.post<UserInfo>('/user', data);
}
/**
* 更新用户
*/
export async function updateUserApi(id: string, data: Partial<UserInfo>) {
return requestClient.put<UserInfo>(`/user/${id}`, data);
}
/**
* 删除用户
*/
export async function deleteUserApi(id: string) {
return requestClient.delete(`/user/${id}`);
}
API 规范:
- 使用
requestClient发起请求(已配置拦截器、token、错误处理) - 函数名以
Api结尾 - 使用 TypeScript 定义请求参数和返回类型
- 添加 JSDoc 注释说明用途
3. 状态管理 (Pinia)
创建 Store:
// src/store/modules/user.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { UserInfo } from '#/api/types';
import { getUserDetailApi } from '#/api/user';
export const useUserStore = defineStore('user', () => {
// State
const userInfo = ref<UserInfo | null>(null);
const loading = ref(false);
// Getters
const isLoggedIn = computed(() => !!userInfo.value);
const userName = computed(() => userInfo.value?.username || 'Guest');
// Actions
async function fetchUserInfo() {
loading.value = true;
try {
const data = await getUserDetailApi('me');
userInfo.value = data;
} finally {
loading.value = false;
}
}
function clearUser() {
userInfo.value = null;
}
return {
// State
userInfo,
loading,
// Getters
isLoggedIn,
userName,
// Actions
fetchUserInfo,
clearUser,
};
});
使用 Store:
<script setup lang="ts">
import { useUserStore } from '#/store';
const userStore = useUserStore();
// 读取状态
console.log(userStore.userName);
// 调用方法
userStore.fetchUserInfo();
</script>
4. 使用框架表单组件 (VbenForm)
<script setup lang="ts">
import { ref } from 'vue';
import { VbenForm } from '@vben/common-ui';
import type { VbenFormSchema } from '@vben/common-ui';
import { message } from 'ant-design-vue';
const formSchema: VbenFormSchema[] = [
{
component: 'Input',
label: '用户名',
fieldName: 'username',
required: true,
rules: [{ required: true, message: '请输入用户名' }],
},
{
component: 'Input',
label: '邮箱',
fieldName: 'email',
required: true,
componentProps: {
type: 'email',
},
rules: [
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '邮箱格式不正确' },
],
},
{
component: 'Select',
label: '状态',
fieldName: 'status',
componentProps: {
options: [
{ label: '启用', value: 'active' },
{ label: '禁用', value: 'inactive' },
],
},
},
{
component: 'DatePicker',
label: '出生日期',
fieldName: 'birthday',
},
];
const formValue = ref({});
async function handleSubmit(values: any) {
console.log('提交数据:', values);
message.success('保存成功');
}
</script>
<template>
<div class="p-5">
<VbenForm
v-model="formValue"
:schema="formSchema"
@submit="handleSubmit"
/>
</div>
</template>
5. 使用框架表格组件 (VbenTable)
<script setup lang="ts">
import { ref } from 'vue';
import { VbenTable } from '@vben/common-ui';
import type { VbenTableColumn } from '@vben/common-ui';
import { Button, Tag } from 'ant-design-vue';
const columns: VbenTableColumn[] = [
{
title: '用户名',
dataIndex: 'username',
key: 'username',
width: 200,
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
customRender: ({ text }) => {
const color = text === 'active' ? 'green' : 'red';
return <Tag color={color}>{text}</Tag>;
},
},
{
title: '操作',
key: 'action',
width: 200,
customRender: ({ record }) => (
<>
<Button type="link" onClick={() => handleEdit(record)}>编辑</Button>
<Button type="link" danger onClick={() => handleDelete(record.id)}>删除</Button>
</>
),
},
];
const dataSource = ref([
{ id: '1', username: 'admin', email: 'admin@example.com', status: 'active' },
{ id: '2', username: 'user', email: 'user@example.com', status: 'inactive' },
]);
function handleEdit(record: any) {
console.log('编辑:', record);
}
function handleDelete(id: string) {
console.log('删除:', id);
}
</script>
<template>
<VbenTable
:columns="columns"
:data-source="dataSource"
:pagination="{ pageSize: 10 }"
/>
</template>
6. 权限控制
<script setup lang="ts">
import { useAccess } from '@vben/access';
const { hasAccessByCodes } = useAccess();
// 检查权限
const canEdit = hasAccessByCodes(['user:edit']);
const canDelete = hasAccessByCodes(['user:delete']);
</script>
<template>
<div>
<!-- 使用 v-access 指令控制显示 -->
<a-button v-access="'user:create'">创建用户</a-button>
<a-button v-access="'user:edit'" v-if="canEdit">编辑</a-button>
<a-button v-access="'user:delete'" v-if="canDelete">删除</a-button>
</div>
</template>
7. 使用图标
<script setup lang="ts">
import { IconSvg } from '@vben/icons';
import { SvgUserIcon, SvgSettingsIcon } from '@vben/icons';
</script>
<template>
<div>
<!-- 方式 1: 使用 IconSvg 组件 + Iconify 名称 -->
<IconSvg icon="lucide:user" class="text-xl" />
<IconSvg icon="mdi:settings" :size="24" />
<!-- 方式 2: 使用预定义的 SVG 组件 -->
<SvgUserIcon class="text-xl" />
<SvgSettingsIcon :size="24" />
</div>
</template>
常用图标集:
lucide:*- Lucide 图标(推荐)mdi:*- Material Design Iconscarbon:*- Carbon Iconsheroicons:*- Heroicons
浏览图标:https://icon-sets.iconify.design/
样式规范
1. 使用 Tailwind CSS
优先使用 Tailwind CSS 工具类:
<template>
<div class="p-5 bg-white rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-4">标题</h2>
<div class="flex items-center gap-4">
<a-button type="primary">按钮</a-button>
</div>
</div>
</template>
2. Scoped 样式
需要自定义样式时使用 <style scoped>:
<style scoped>
.custom-class {
/* 自定义样式 */
}
/* 深度选择器 - 影响子组件 */
:deep(.ant-btn) {
border-radius: 8px;
}
/* 插槽选择器 */
:slotted(.slot-content) {
color: red;
}
</style>
3. 响应式设计
使用 Tailwind 响应式前缀:
<template>
<!-- 移动端单列,平板双列,桌面三列 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
常见问题和注意事项
1. ❌ 不要做的事
- 不要使用 Options API(必须用 Composition API)
- 不要使用相对路径导入(使用
#/别名) - 不要直接修改
packages/下的框架代码 - 不要在组件中直接操作 DOM(使用 Vue 的响应式系统)
- 不要使用 yarn/npm(必须用 pnpm)
- 不要在路由配置中使用
require(使用import())
2. ✅ 最佳实践
- 组件拆分: 单个组件不超过 300 行,复杂组件拆分成子组件
- 类型安全: 所有 API 响应、组件 Props、函数参数都定义 TypeScript 类型
- 错误处理: 使用
try-catch处理异步错误 - 加载状态: 异步操作要有 loading 状态
- 国际化: 所有用户可见的文本使用
$t()函数 - 权限控制: 敏感操作添加权限检查
3. 性能优化
<script setup lang="ts">
import { computed, ref } from 'vue';
// ✅ 使用 computed 缓存计算结果
const filteredList = computed(() => {
return list.value.filter(item => item.status === 'active');
});
// ✅ 大列表使用虚拟滚动
import { VirtualList } from '@vben/common-ui';
</script>
<template>
<!-- ✅ 使用 v-show 而不是 v-if(频繁切换的元素) -->
<div v-show="visible">内容</div>
<!-- ✅ 长列表使用 key -->
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</template>
4. 调试技巧
// 开发环境打印日志
if (import.meta.env.DEV) {
console.log('调试信息:', data);
}
// 使用 Vue Devtools
// Chrome 扩展:Vue.js devtools
开发命令
# 启动开发服务器
pnpm run dev:antd
# 构建生产版本
pnpm run build:antd
# 类型检查
pnpm run check:type
# 代码格式化
pnpm run format
# 代码检查
pnpm run lint
# 单元测试
pnpm run test:unit
API Mock 数据(开发阶段)
如果后端接口未就绪,可以使用 Mock 数据:
// src/api/user.ts
import { requestClient } from './request';
export async function getUserListApi() {
// 开发环境使用 mock 数据
if (import.meta.env.DEV) {
return Promise.resolve([
{ id: '1', username: 'admin', email: 'admin@example.com', status: 'active' },
{ id: '2', username: 'user', email: 'user@example.com', status: 'inactive' },
]);
}
// 生产环境调用真实 API
return requestClient.get('/user/list');
}
环境变量配置
配置文件位置:
.env- 所有环境.env.development- 开发环境.env.production- 生产环境
# .env.development
VITE_APP_TITLE=DevOps 管理系统
VITE_API_URL=http://localhost:8080/api
使用方式:
import { useAppConfig } from '@vben/hooks';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
console.log('API地址:', apiURL);
总结
遵循本手册的规范,可以确保:
- ✅ 代码风格统一,易于维护
- ✅ 充分利用框架能力,避免重复造轮子
- ✅ 类型安全,减少运行时错误
- ✅ 性能优化,用户体验良好
- ✅ 团队协作顺畅,代码可读性强
开发新功能前,请先参考 playground 和 demos 目录下的示例代码!