vben-admin

📁 m19803261706/springboot-vben-admin 📅 Jan 23, 2026
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

最佳实践

  1. TypeScript: 所有文件使用 TypeScript,定义完整类型
  2. 组合式 API: 使用 <script setup lang="ts">
  3. 中文注释: 所有函数、变量添加中文注释
  4. API 封装: 统一使用 defHttp 封装请求
  5. 类型定义: 接口数据类型放在 types/ 目录
  6. 组件命名: 使用 defineOptions({ name: 'XxxComponent' })

项目: 应急管理系统 (yingjiguanli) 技术栈: Vben Admin 5.x + Vue 3 + TypeScript