SuperAPI AI Directory

vben-admin

Frontend & websites

View repository

Updated 10-16 14:59

About this skill

Vben Admin Development Specification

This project uses the Vben Admin 5.x (Ant Design Vue) + TypeScript + Vite technology stack.

Technology Stack

Technology Version Purpose
Vue 3.5.x UI Framework
TypeScript 5.x Type System
Ant Design Vue 4.x Component Library
Vite 7.x Build Tool
Pinia 3.x State Management
Vue Router 4.x Routing
Axios - HTTP Client

Directory Structure

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

Code Templates

API Interfaces

// 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}`,
  });
}

Type Definitions

// 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;
}

List Page

<!-- 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>

Form Page

<!-- 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>

Route Configuration

// 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;

Naming Conventions

Location Convention Example
Folder kebab-case user-management/
Component File PascalCase.vue UserList.vue
Utility File camelCase.ts formatDate.ts
Type File camelCase.ts user.ts
Component Name PascalCase UserList
Variable Name camelCase userName
Constant Name UPPER_SNAKE MAX_PAGE_SIZE
Event Name handle + action handleSubmit

Best Practices

  1. TypeScript: All files use TypeScript, define complete types
  2. Composition API: Use <script setup lang="ts">
  3. Chinese Comments: Add Chinese comments to all functions and variables
  4. API Encapsulation: Uniformly use defHttp to encapsulate requests
  5. Type Definitions: Interface data types are placed in the types/ directory
  6. Component Naming: Use defineOptions({ name: 'XxxComponent' })

Project: Emergency Management System (yingjiguanli) Technology Stack: Vben Admin 5.x + Vue 3 + TypeScript