Files
MrleeAdmin/frontend/admin/AI_CONTEXT.md
T
2026-08-31 11:33:59 +08:00

25 KiB
Raw Blame History

CRUD 功能模块开发模板

📋 概述

本模板提供了一套完整的 CRUD 功能模块开发规范,适用于当前项目框架(基于 Vue3 + TypeScript + Element Plus + VxeTable)。通过统一的目录结构、命名规范和代码模板,确保项目代码的一致性和可维护性。

📁 目录结构规范

标准结构(基于当前项目框架)

src/apps/{模块名}/
├── api/                   # 模块API接口
├── assets/                # 模块静态资源
├── components/            # 模块私有组件
├── lang/                  # 模块国际化文件
├── mock/                  # 模块模拟数据
├── routes/                # 模块路由配置
├── schemas/               # 模块CRUD配置
└── views/                 # 模块页面视图
    ├── {实体名}/
    │   ├── index.vue      # 页面入口文件
    │   ├── schemas/       # Schema配置
    │   └── components/    # 私有组件(可选)

核心文件位置

  • 页面入口: src/apps/{模块名}/views/{实体名}/index.vue
  • Schema配置: src/apps/{模块名}/views/{实体名}/schemas/index.tsx
  • API服务: src/apps/{模块名}/api/{实体名}/index.ts
  • 组件库: src/components/crud/(核心CRUD组件)

⚠️ 重要注意事项

  • 禁止增加不必要的子目录,避免目录结构混乱
  • 保持目录结构简洁统一
  • 模块名使用小写字母,多个单词用连字符分隔
  • 实体名使用小写字母,多个单词用连字符分隔
  • 文件名统一使用小写字母和连字符
  • 目录结构必须严格遵循此规范

🔤 命名规范

命名规则

  • 数据库表名:使用下划线命名法,如 system_user、system_user_role
  • 模块名:使用表名中下划线前的部分,如 system
  • 实体名:使用表名中下划线后的部分,如 user,多个部分用中横线连接,如 user-role
  • 文件名:使用中横线命名法,如 user-role.ts

命名示例

表名 模块名 实体名 实体小驼峰名 API 服务文件 API 服务类名
system_user system user user ./src/app/api/system/user.ts UserService
system_user_role system user-role userRole ./src/app/api/system/user-role.ts UserRoleService

📏 约束规则

  1. ID字段:不在列表上显示
  2. 列宽度:默认自适应,无需手动设置
  3. 备注字段:字段过多时不在列表显示
  4. 表单布局:
    • 字段较少时:colSpan 默认为 24(全宽)
    • 字段较多时:可设置 colSpan 为 12(半宽)

🔧 API 服务模板

基础服务模板

文件路径: src/apps/{模块名}/api/{实体名}/index.ts

import { BaseService } from '@/utils/request'
import { {EntityName}Row } from '@/apps/{模块名}/api/{实体名}/types'

/**
 * {实体名} API 服务
 */
export const {EntityName}Service = new BaseService<{EntityName}Row>('/{模块名}/{实体名}')

export default {EntityName}Service

BaseService 配置说明

  • baseUrl: 接口基础路径
  • allowedMethods: 允许的方法列表(空数组表示允许所有)
  • forbiddenMethods: 禁止的方法列表(如 ['export'] 表示禁用导出功能)

默认包含的接口方法

  • page(): 分页查询
  • save(): 新增
  • update(): 修改
  • remove(): 删除
  • detail(): 详情
  • export(): 导出(可禁用)

📋 Schema 配置模板

基础配置模板

文件路径: src/apps/{模块名}/views/{实体名}/schemas/index.tsx

import { CrudSchema } from '@/components/crud'
import { {EntityName}Service } from '@/apps/{模块名}/api/{实体名}'
import { $t } from '@/locales'

/**
 * {实体名} CRUD Schema 配置
 */
export const crudSchema = (): CrudSchema => {
  return {
    // 接口配置
    api: {EntityName}Service.page,

    // CRUD API 映射
    crudApi: {
      add: {EntityName}Service.save,
      edit: {EntityName}Service.update,
      remove: {EntityName}Service.remove,
      view: {EntityName}Service.detail
    },

    // 功能开关
    useCrud: true,
    hasAdd: true,
    hasRemove: true,
    hasEdit: true,
    hasView: true,

    // 权限控制
    addAuth: '{模块名}:{实体名}:create',
    removeAuth: '{模块名}:{实体名}:delete',
    editAuth: '{模块名}:{实体名}:update',
    viewAuth: '{模块名}:{实体名}:read',

    // 表格列定义
    columns: [
      // 选择列
      { type: 'selection' },

      // 示例列定义
      {
        prop: 'name',
        label: $t('{模块名}.{实体名}.list.table.columns.name'),
        minWidth: 120
      },

      // 更多列定义...
    ],

    // 搜索表单配置
    searchFormSchema: {
      schema: [
        {
          prop: 'LIKE_name',
          label: $t('{模块名}.{实体名}.list.query.name'),
          component: 'Input',
          componentProps: {
            clearable: true,
            placeholder: $t('{模块名}.{实体名}.list.query.placeholder.name')
          },
          colSpan: 6
        }
        // 更多搜索字段...
      ]
    },

    // 弹窗配置
    dialogProps: {
      dialogType: 'drawer',
      width: '50%'
    },

    // 表单配置
    formSchema: {
      showDefaultActions: false,
      labelWidth: '100px',
      schema: [
        // 表单字段定义...
      ]
    }
  }
}

配置项说明

配置项 说明 默认值
useCrud 是否启用 CRUD 功能 true
hasAdd 是否显示新增按钮 true
hasRemove 是否显示删除按钮 true
hasEdit 是否显示编辑按钮 true
hasView 是否显示详情按钮 true
labelWidth 表单标签宽度 100px
dialogProps.width 弹窗宽度 50%

🖥️ 页面入口模板

基础页面模板

文件路径: src/apps/{模块名}/views/{实体名}/index.vue

<template>
  <div class="{模块名}-{实体名}-page art-full-height">
    <BasicCrud />
  </div>
</template>

<script setup lang="tsx">
  import { useCrud } from '@/components/crud'
  import { crudSchema } from './schemas'

  // 使用 CRUD 组件
  const [BasicCrud] = useCrud(crudSchema())
</script>

<style lang="scss" scoped>
  .{模块名}-{实体名}-page {
    height: 100%;
  }
</style>

操作按钮配置说明

配置项 说明 示例
toolbarActions 表格上方工具栏按钮 导出、批量操作等
dropDownToolbarActions 工具栏下拉菜单 更多操作
tableActions 表格行操作按钮 编辑、删除、详情等
dropDownActions 行操作下拉菜单 扩展操作

⚙️ 关键配置说明

表格列配置 (columns)

{
  prop: '字段名',           // 字段名称
  label: '列标题',          // 列显示标题
  component: '组件名',      // 渲染组件,如 'ApiDict', 'ApiSelect' 等
  componentProps: {         // 组件属性
    code: '字典编码',       // 字典组件特有
    renderType: 'Select'    // 渲染类型
  },
  width: 120,               // 列宽度(可选)
  fixed: 'left'             // 固定列(可选)
}

常用列属性:

  • type: 'selection': 选择列
  • type: 'index': 序号列
  • prop: 数据字段名
  • label: 列标题
  • component: 渲染组件
  • width: 列宽度
  • minWidth: 最小宽度
  • fixed: 固定列位置

表单字段配置 (formSchema.schema)

{
  label: '字段标签',
  prop: '字段名',
  component: '组件名',      // 如 'Input', 'Select', 'ApiDict' 等
  colSpan: 12,             // 栅格占比 (1-24)
  rules: [                 // 校验规则
    {
      required: true,
      message: '校验提示信息'
    }
  ],
  componentProps: {        // 组件属性
    placeholder: '占位符',
    clearable: true
  },
  ifShow(values) {         // 动态显示条件
    return !!values.条件字段
  }
}

常用表单属性:

  • colSpan: 栅格宽度(1-24)
  • rules: 校验规则数组
  • componentProps: 组件特有属性
  • ifShow: 条件显示函数
  • show: 静态显示控制

搜索表单配置 (searchFormSchema.schema)

{
  prop: '操作符_字段名',     // 如 'LIKE_name', 'EQ_status'
  label: '搜索字段标签',
  component: 'Input',       // 搜索组件
  componentProps: {
    clearable: true,
    placeholder: '请输入搜索条件'
  },
  colSpan: 6               // 栅格占比
}

常用搜索操作符:

  • EQ_: 等于
  • LIKE_: 模糊匹配
  • GT_: 大于
  • LT_: 小于
  • IN_: 包含

树形结构配置 (tree)

对于具有树形结构的数据(如菜单、组织架构等),需要配置 tree 属性:

export const crudSchema: CrudSchema = {
  // 使用 tree 接口而不是 page 接口
  api: {EntityName}Service.tree,

  // 树型是全量数据,不需要搜索
  useSearchForm: false,

  // 树形结构配置
  tree: {
    id: 'id',              // 树形节点唯一标识字段名
    pid: 'parentId',       // 父节点字段名
    children: 'children'   // 子节点字段名
  },

  // 其他配置...
}

树形配置注意事项:

  • 必须使用 tree 接口而非 page 接口
  • 禁用搜索表单(useSearchForm: false)
  • 确保 API 返回树形结构数据

🧩 常用组件

基础表单组件

组件 说明 常用属性
Input 普通输入框 placeholder, clearable
InputNumber 数字输入框 min, max, precision
Select 下拉选择框 options, clearable
RadioGroup 单选框组 options
CheckboxGroup 多选框组 options
DatePicker 日期选择器 type, format
Divider 分割线 content-position

异步数据组件

组件 说明 常用属性
ApiDict 字典组件 code, renderType
ApiSelect 异步下拉选择 api, labelField, valueField
ApiCascader 异步级联选择 api, checkStrictly
ApiTreeSelect 异步树形选择 api, checkStrictly
ApiRadioGroup 异步单选框组 api
ApiCheckboxGroup 异步多选框组 api

特殊功能组件

组件 说明 常用属性
IconPicker 图标选择器 -
Editor 富文本编辑器 -
Upload 文件上传组件 accept, limit
Avatar 头像上传组件 shape, size

ApiDict 组件配置示例

{
  label: '状态',
  prop: 'status',
  component: 'ApiDict',
  componentProps: {
    code: 'yes_no',           // 字典编码
    renderType: 'RadioGroup', // 渲染类型:Select/RadioGroup/Checkbox
    isBtn: true               // 按钮样式(RadioGroup特有)
  }
}

ApiSelect 组件配置示例

{
  label: '用户',
  prop: 'userId',
  component: 'ApiSelect',
  componentProps: {
    api: '/system/user/select', // 接口地址
    labelField: 'name',         // 显示字段
    valueField: 'id',           // 值字段
    placeholder: '请选择用户'
  }
}

🔒 权限控制

权限码配置

通过以下属性控制功能权限,权限码格式为 {模块名}:{实体名}:{操作}:

权限属性 说明 示例
addAuth 新增权限码 system:user:create
editAuth 编辑权限码 system:user:update
removeAuth 删除权限码 system:user:delete
viewAuth 查看权限码 system:user:read

权限配置示例

export const crudSchema: CrudSchema = {
  // ... 其他配置
  addAuth: 'system:user:create',
  removeAuth: 'system:user:delete',
  editAuth: 'system:user:update',
  viewAuth: 'system:user:read'
  // ...
}

🔄 CRUD 操作方法

useCrud 返回方法

通过 useCrud 返回的方法可以控制 CRUD 行为:

方法 说明 使用场景
reload() 刷新表格数据 手动刷新
setState() 设置组件状态 动态控制
removeByApi() 通过 API 删除 自定义删除逻辑
refreshData() 全量刷新数据 数据同步
refreshCreate() 新增后刷新 新增操作后
refreshUpdate() 更新后刷新 编辑操作后
refreshRemove() 删除后刷新 删除操作后

方法使用示例

const [BasicCrud, { reload, refreshCreate }] = useCrud({
  // ... 配置
})

// 手动刷新表格
const handleRefresh = () => {
  reload()
}

// 新增后刷新
const handleCreateSuccess = () => {
  refreshCreate()
}

📚 完整示例

示例一:普通表 - 岗位管理 (sys_post)

1. 接口文件

文件路径: src/apps/system/api/post/index.ts

import { BaseService } from '@/utils/request'
import { PostRow } from '@/apps/system/api/post/types'

/**
 * 岗位管理 API 服务
 */
export const PostService = new BaseService<PostRow>('/system/post')

export default PostService

2. Schema 配置文件

文件路径: src/apps/system/views/post/schemas/index.tsx

import { CrudSchema } from '@/components/crud'
import { PostService } from '@/apps/system/api/post'
import { $t } from '@/locales'

/** 岗位管理 CRUD 配置 */
export const crudSchema = (): CrudSchema => {
  return {
    // 基础配置
    api: PostService.page,
    dialogTitle: '岗位',

    // API 映射
    crudApi: {
      add: PostService.save,
      edit: PostService.update,
      remove: PostService.remove,
      view: PostService.detail
    },

    // 功能开关
    useCrud: true,
    hasAdd: true,
    hasRemove: true,
    hasEdit: true,
    hasView: true,

    // 权限控制
    addAuth: 'system:post:create',
    removeAuth: 'system:post:delete',
    editAuth: 'system:post:update',
    viewAuth: 'system:post:read',

    // 表格列定义
    columns: [
      { type: 'selection' },
      {
        prop: 'name',
        label: $t('system.post.list.table.columns.name'),
        minWidth: 120
      },
      {
        prop: 'code',
        label: $t('system.post.list.table.columns.code'),
        minWidth: 120
      },
      {
        prop: 'sort',
        label: $t('system.post.list.table.columns.sort'),
        minWidth: 80
      },
      {
        prop: 'enabled',
        label: $t('system.post.list.table.columns.enabled'),
        component: 'ApiDict',
        componentProps: { code: 'yes_no' },
        minWidth: 100
      },
      {
        prop: 'remark',
        label: $t('system.post.list.table.columns.remark'),
        minWidth: 150
      }
    ],

    // 搜索表单
    searchFormSchema: {
      schema: [
        {
          prop: 'LIKE_name',
          label: $t('system.post.list.query.name'),
          component: 'Input',
          componentProps: {
            clearable: true,
            placeholder: $t('system.post.list.query.placeholder.name')
          },
          colSpan: 6
        },
        {
          prop: 'EQ_enabled',
          label: $t('system.post.list.query.enabled'),
          component: 'ApiDict',
          componentProps: {
            code: 'yes_no',
            placeholder: $t('system.post.list.query.placeholder.enabled')
          },
          colSpan: 6
        }
      ]
    },

    // 弹窗配置
    dialogProps: {
      dialogType: 'drawer',
      width: '50%'
    },

    // 表单配置
    formSchema: {
      showDefaultActions: false,
      labelWidth: '100px',
      schema: [
        {
          label: $t('system.post.form.fields.id'),
          prop: 'id',
          component: 'Input',
          colSpan: 24,
          show: false,
          ifDetail: false
        },
        {
          label: $t('system.post.form.fields.name'),
          prop: 'name',
          component: 'Input',
          rules: [{ required: true, message: $t('system.post.form.rules.name.required') }],
          componentProps: {
            placeholder: $t('system.post.form.placeholder.name')
          },
          colSpan: 24
        },
        {
          label: $t('system.post.form.fields.code'),
          prop: 'code',
          component: 'Input',
          rules: [{ required: true, message: $t('system.post.form.rules.code.required') }],
          componentProps: {
            placeholder: $t('system.post.form.placeholder.code')
          },
          colSpan: 24
        },
        {
          label: $t('system.post.form.fields.sort'),
          prop: 'sort',
          component: 'InputNumber',
          componentProps: {
            min: 0,
            placeholder: $t('system.post.form.placeholder.sort')
          },
          colSpan: 24
        },
        {
          label: $t('system.post.form.fields.enabled'),
          prop: 'enabled',
          component: 'ApiDict',
          componentProps: {
            code: 'yes_no',
            isBtn: true,
            renderType: 'RadioGroup'
          },
          rules: [{ required: true, message: $t('system.post.form.rules.enabled.required') }],
          colSpan: 24
        },
        {
          label: $t('system.post.form.fields.remark'),
          prop: 'remark',
          component: 'Input',
          componentProps: {
            type: 'textarea',
            placeholder: $t('system.post.form.placeholder.remark')
          },
          colSpan: 24
        }
      ]
    }
  }
}

3. 页面入口文件

文件路径: src/apps/system/views/post/index.vue

<template>
  <div class="system-post-page art-full-height">
    <BasicCrud />
  </div>
</template>

<script setup lang="tsx">
  import { useCrud } from '@/components/crud'
  import { crudSchema } from './schemas'

  // 使用 CRUD 组件
  const [BasicCrud] = useCrud(crudSchema())
</script>

<style lang="scss" scoped>
  .system-post-page {
    height: 100%;
  }
</style>

示例二:树型表 - 部门管理 (sys_dept)

1. 接口文件

文件路径: src/apps/system/api/dept/index.ts

import { BaseService } from '@/utils/request'
import { DeptRow } from '@/apps/system/api/dept/types'

/**
 * 部门管理 API 服务
 */
export const DeptService = new BaseService<DeptRow>('/system/dept')

// 扩展树形接口
export const getDeptTree = (args?: Record<string, any>) => {
  const params = { ...args, format: 'tree' }
  return DeptService.list<DeptRow[]>(params)
}

export default DeptService

2. Schema 配置文件

文件路径: src/apps/system/views/dept/schemas/index.tsx

import { CrudSchema } from '@/components/crud'
import { DeptService, getDeptTree } from '@/apps/system/api/dept'
import { $t } from '@/locales'

/** 部门管理 CRUD 配置 */
export const crudSchema = (): CrudSchema => {
  return {
    // 接口配置:使用 tree 接口以支持树形结构
    api: getDeptTree,

    // CRUD API 映射
    crudApi: {
      add: DeptService.save,
      edit: DeptService.update,
      remove: DeptService.remove,
      view: DeptService.detail
    },

    // 功能开关
    useCrud: true,
    hasAdd: true,
    hasRemove: true,
    hasEdit: true,
    hasView: true,

    // 权限控制
    addAuth: 'system:dept:create',
    removeAuth: 'system:dept:delete',
    editAuth: 'system:dept:update',
    viewAuth: 'system:dept:read',

    // 禁用搜索表单,因为树形结构是全量数据
    useSearchForm: false,

    // 树形结构配置
    tree: {
      id: 'id',
      pid: 'pid',
      children: 'children'
    },

    columns: [
      {
        type: 'selection'
      },
      {
        prop: 'name',
        label: $t('system.dept.list.table.columns.name'),
        component: 'Input',
        minWidth: 150
      },
      {
        prop: 'code',
        label: $t('system.dept.list.table.columns.code'),
        component: 'Input',
        minWidth: 120
      },
      {
        prop: 'sort',
        label: $t('system.dept.list.table.columns.sort'),
        component: 'InputNumber',
        minWidth: 80
      },
      {
        prop: 'enabled',
        label: $t('system.dept.list.table.columns.enabled'),
        component: 'ApiDict',
        componentProps: {
          code: 'yes_no',
          renderType: 'RadioGroup'
        },
        minWidth: 100
      }
    ],

    searchFormSchema: {
      schema: []
    },

    // 弹窗配置
    dialogProps: {
      dialogType: 'drawer',
      width: '50%'
    },

    // 表单配置
    formSchema: {
      showDefaultActions: false,
      labelWidth: '100px',
      schema: [
        {
          label: $t('system.dept.form.fields.id'),
          prop: 'id',
          component: 'Input',
          colSpan: 24,
          show: false,
          ifDetail: false
        },
        {
          label: $t('system.dept.form.fields.pid'),
          prop: 'pid',
          component: 'ApiTreeSelect',
          componentProps: {
            api: '/system/dept/tree',
            labelField: 'name',
            valueField: 'id',
            // 任意节点可选
            checkStrictly: true,
            clearable: true,
            placeholder: '请选择父级部门'
          },
          colSpan: 24,
          rules: [
            {
              required: true,
              message: '请选择父级部门'
            }
          ]
        },
        {
          label: '部门名称',
          prop: 'name',
          component: 'Input',
          componentProps: {
            placeholder: '请输入部门名称'
          },
          colSpan: 24,
          rules: [
            {
              required: true,
              message: '请输入部门名称'
            }
          ]
        },
        {
          label: '唯一编码',
          prop: 'code',
          component: 'Input',
          componentProps: {
            placeholder: '请输入唯一编码'
          },
          colSpan: 24,
          rules: [
            {
              required: true,
              message: '请输入唯一编码'
            }
          ]
        },
        {
          label: '排序',
          prop: 'sort',
          component: 'InputNumber',
          componentProps: {
            min: 0,
            max: 9999
          },
          colSpan: 24,
          rules: [
            {
              required: true,
              message: '请输入排序值'
            }
          ]
        },
        {
          label: '是否启用',
          prop: 'enabled',
          component: 'ApiDict',
          componentProps: {
            code: 'yes_no',
            isBtn: true,
            renderType: 'RadioGroup'
          },
          colSpan: 24,
          rules: [
            {
              required: true,
              message: '请选择启用状态'
            }
          ]
        },
        {
          label: '部门负责人',
          prop: 'leaderIdList',
          component: 'ApiSelect',
          componentProps: {
            api: '/system/user/select',
            placeholder: '请选择负责人',
            multiple: true
          },
          colSpan: 24
        },
        {
          label: '分管领导',
          prop: 'main_leader_id',
          component: 'ApiSelect',
          componentProps: {
            api: '/system/user/select',
            placeholder: '请选择分管领导'
          },
          colSpan: 24
        },
        {
          label: '备注',
          prop: 'remark',
          component: 'Input',
          componentProps: {
            type: 'textarea',
            rows: 3,
            placeholder: '请输入备注'
          },
          colSpan: 24
        }
      ]
    }
  }
}

3. 页面入口文件

文件路径: src/apps/system/views/dept/index.vue

<template>
  <div class="sys-dept-page art-full-height">
    <BasicCrud />
  </div>
</template>

<script setup lang="tsx">
  import { useCrud } from '@/components/crud'
  import { crudSchema } from './schemas'

  const [BasicCrud] = useCrud(crudSchema())
</script>

<style lang="scss" scoped>
  .sys-dept-page {
    height: 100%;
  }
</style>