Vben Admin 开发规范
本项目使用 Vben Admin 5.x (Ant Design Vue) + TypeScript + Vite 技术栈。
技术栈 技术 版本 用途 Vue 3.5.x UI 框架 TypeScript 5.x 类型系统 Ant Design Vue 4.x 组件库 Vite 7.x 构建工具 Pinia 3.x 状态管理 Vue Router 4.x 路由 Axios - HTTP 客户端 目录结构 frontend/apps/web-antd/src/ ├── api/ # API 接口 │ └── {module}/ │ └── index.ts ├── views/ # 页面视图 │ └── {module}/ │ ├── index.vue # 列表页 │ ├── detail.vue # 详情页 │ └── form.vue # 表单页 ├── components/ # 公共组件 ├── stores/ # 状态管理 │ └── {module}.ts ├── router/ # 路由配置 │ └── routes/ │ └── {module}.ts ├── hooks/ # 自定义 Hooks ├── utils/ # 工具函数 └── types/ # 类型定义 └── {module}.ts
代码模板 API 接口 // api/{module}/index.ts import { defHttp } from '@vben/request'; import type { {EntityName}, {EntityName}ListParams, {EntityName}FormData } from '@/types/{module}';
/* * {模块}API / enum Api { List = '/api/{module}', Detail = '/api/{module}/', Create = '/api/{module}', Update = '/api/{module}/', Delete = '/api/{module}/', }
/* * 获取{实体}列表 / export function get{EntityName}List(params: {EntityName}ListParams) { return defHttp.get<{EntityName}[]>({ url: Api.List, params, }); }
/*
* 获取{实体}详情
/
export function get{EntityName}Detail(id: number) {
return defHttp.get<{EntityName}>({
url: ${Api.Detail}${id},
});
}
/* * 创建{实体} / export function create{EntityName}(data: {EntityName}FormData) { return defHttp.post<{EntityName}>({ url: Api.Create, data, }); }
/*
* 更新{实体}
/
export function update{EntityName}(id: number, data: {EntityName}FormData) {
return defHttp.put<{EntityName}>({
url: ${Api.Update}${id},
data,
});
}
/*
* 删除{实体}
/
export function delete{EntityName}(id: number) {
return defHttp.delete({
url: ${Api.Delete}${id},
});
}
类型定义 // types/{module}.ts
/ * {实体}类型 */ export interface {EntityName} { / ID / id: number; / 名称 / name: string; / 创建时间 */ createdAt: string; / 更新时间 */ updatedAt: string; }
/ * {实体}列表查询参数 */ export interface {EntityName}ListParams { / 页码 / page?: number; / 每页数量 / size?: number; /* 关键词 / keyword?: string; }
/ * {实体}表单数据 */ export interface {EntityName}FormData { / 名称 */ name: string; }
列表页
<!-- 搜索区域 -->
<Card class="mb-4">
<Form :model="searchForm" layout="inline">
<FormItem label="关键词">
<Input v-model:value="searchForm.keyword" placeholder="请输入关键词" />
</FormItem>
<FormItem>
<Space>
<Button type="primary" @click="handleSearch">查询</Button>
<Button @click="handleReset">重置</Button>
</Space>
</FormItem>
</Form>
</Card>
<!-- 操作区域 -->
<Card>
<template #title>
<Button type="primary" @click="handleCreate">
<PlusOutlined /> 新增
</Button>
</template>
<!-- 表格 -->
<Table
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
>
<template #action="{ record }">
<Space>
<Button type="link" @click="handleEdit(record)">编辑</Button>
<Popconfirm title="确定删除吗?" @confirm="handleDelete(record.id)">
<Button type="link" danger>删除</Button>
</Popconfirm>
</Space>
</template>
</Table>
</Card>
<script setup lang="ts"> import { ref, reactive, onMounted } from 'vue'; import { useRouter } from 'vue-router'; import { message } from 'ant-design-vue'; import { PlusOutlined } from '@ant-design/icons-vue'; import { Page, PageHeader } from '@vben/components'; import { get{EntityName}List, delete{EntityName} } from '@/api/{module}'; import type { {EntityName} } from '@/types/{module}'; /** * {模块}列表页 */ defineOptions({ name: '{EntityName}List' }); const router = useRouter(); // 搜索表单 const searchForm = reactive({ keyword: '', }); // 表格数据 const loading = ref(false); const dataSource = ref<{EntityName}[]>([]); const pagination = reactive({ current: 1, pageSize: 10, total: 0, }); // 表格列配置 const columns = [ { title: 'ID', dataIndex: 'id', width: 80 }, { title: '名称', dataIndex: 'name' }, { title: '创建时间', dataIndex: 'createdAt', width: 180 }, { title: '操作', key: 'action', width: 150, slots: { customRender: 'action' } }, ]; /** * 加载数据 */ async function loadData() { loading.value = true; try { const res = await get{EntityName}List({ page: pagination.current, size: pagination.pageSize, ...searchForm, }); dataSource.value = res.content; pagination.total = res.totalElements; } finally { loading.value = false; } } /** * 搜索 */ function handleSearch() { pagination.current = 1; loadData(); } /** * 重置 */ function handleReset() { searchForm.keyword = ''; handleSearch(); } /** * 表格变化 */ function handleTableChange(pag: any) { pagination.current = pag.current; pagination.pageSize = pag.pageSize; loadData(); } /** * 新增 */ function handleCreate() { router.push('/{module}/form'); } /** * 编辑 */ function handleEdit(record: {EntityName}) { router.push(`/{module}/form/${record.id}`); } /** * 删除 */ async function handleDelete(id: number) { await delete{EntityName}(id); message.success('删除成功'); loadData(); } onMounted(() => { loadData(); }); </script>
表单页
<Card>
<Form
ref="formRef"
:model="formData"
:rules="rules"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 12 }"
>
<FormItem label="名称" name="name">
<Input v-model:value="formData.name" placeholder="请输入名称" />
</FormItem>
<FormItem :wrapper-col="{ offset: 4, span: 12 }">
<Space>
<Button type="primary" :loading="submitting" @click="handleSubmit">
保存
</Button>
<Button @click="handleBack">取消</Button>
</Space>
</FormItem>
</Form>
</Card>
<script setup lang="ts"> import { ref, reactive, computed, onMounted } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { message } from 'ant-design-vue'; import { Page, PageHeader } from '@vben/components'; import { get{EntityName}Detail, create{EntityName}, update{EntityName} } from '@/api/{module}'; import type { {EntityName}FormData } from '@/types/{module}'; /** * {模块}表单页 */ defineOptions({ name: '{EntityName}Form' }); const route = useRoute(); const router = useRouter(); const formRef = ref(); // 是否编辑模式 const isEdit = computed(() => !!route.params.id); const editId = computed(() => Number(route.params.id)); // 表单数据 const formData = reactive<{EntityName}FormData>({ name: '', }); // 表单验证规则 const rules = { name: [{ required: true, message: '请输入名称', trigger: 'blur' }], }; // 提交状态 const submitting = ref(false); /** * 加载详情 */ async function loadDetail() { if (!isEdit.value) return; const res = await get{EntityName}Detail(editId.value); Object.assign(formData, res); } /** * 提交表单 */ async function handleSubmit() { await formRef.value?.validate(); submitting.value = true; try { if (isEdit.value) { await update{EntityName}(editId.value, formData); message.success('更新成功'); } else { await create{EntityName}(formData); message.success('创建成功'); } handleBack(); } finally { submitting.value = false; } } /** * 返回列表 */ function handleBack() { router.push('/{module}'); } onMounted(() => { loadDetail(); }); </script>
路由配置 // router/routes/{module}.ts import type { RouteRecordRaw } from 'vue-router';
/* * {模块}路由 / const routes: RouteRecordRaw[] = [ { path: '/{module}', name: '{EntityName}', meta: { title: '{模块名称}', icon: 'ant-design:appstore-outlined', }, children: [ { path: '', name: '{EntityName}List', component: () => import('@/views/{module}/index.vue'), meta: { title: '{模块}列表', }, }, { path: 'form/:id?', name: '{EntityName}Form', component: () => import('@/views/{module}/form.vue'), meta: { title: '{模块}表单', hideMenu: true, }, }, ], }, ];
export default routes;
命名规范 位置 规范 示例 文件夹 kebab-case user-management/ 组件文件 PascalCase.vue UserList.vue 工具文件 camelCase.ts formatDate.ts 类型文件 camelCase.ts user.ts 组件名 PascalCase UserList 变量名 camelCase userName 常量名 UPPER_SNAKE MAX_PAGE_SIZE 事件名 handle + 动作 handleSubmit 最佳实践 TypeScript: 所有文件使用 TypeScript,定义完整类型 组合式 API: 使用 <script setup lang="ts"> 中文注释: 所有函数、变量添加中文注释 API 封装: 统一使用 defHttp 封装请求 类型定义: 接口数据类型放在 types/ 目录 组件命名: 使用 defineOptions({ name: 'XxxComponent' })
项目: 应急管理系统 (yingjiguanli) 技术栈: Vben Admin 5.x + Vue 3 + TypeScript