vben-admin
29
总安装量
29
周安装量
#7221
全站排名
安装命令
npx skills add https://github.com/m19803261706/springboot-vben-admin --skill vben-admin
Agent 安装分布
opencode
24
claude-code
23
trae
21
gemini-cli
19
github-copilot
18
Skill 文档
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;
}
å表页
<!-- views/{module}/index.vue -->
<template>
<Page>
<template #header>
<PageHeader title="{模ååç§°}" />
</template>
<!-- æç´¢åºå -->
<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>
</Page>
</template>
<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>
表å页
<!-- views/{module}/form.vue -->
<template>
<Page>
<template #header>
<PageHeader :title="isEdit ? 'ç¼è¾{模å}' : 'æ°å¢{模å}'" @back="handleBack" />
</template>
<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>
</Page>
</template>
<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