Skip to content

UseCrud - 增删改查

组件介绍

UseCrud 是基于 UseElForm 和 UseElTable 二次封装的 CRUD 一体化业务组件,整合查询表单、操作栏、数据表格、新增 / 编辑弹窗四大核心模块,统一配置入口 configs,内置请求管理、字典加载、批量 / 单行删除、自动取消请求、列过滤排序、弹窗切换等通用能力,标准化扩展配置体系,大幅减少后台增删改查页面重复模板代码,开箱即用且高度可定制插槽。

核心能力

  1. 统一字段配置入口 configs,一套配置驱动表单 + 表格

仅需传入一份 configs 数组,组件内部自动拆分生成查询表单列、表格列;支持单独配置查询标签queryLabel、查询字段queryProp、仅搜索字段onlySearch、表格排序权重tableOrder;内置工具自动过滤有效表单项、自动按权重排序表格列,重复排序值自动打印警告,兼容逗号分割多字段拆分场景。

  1. 完整请求生命周期管控,自动防竞态、可中止请求

内置 AbortController 管理列表请求,每次查询自动中止上一次未完成请求,避免新旧接口数据覆盖;提供 autoCancelQueryStrategy 策略配置,支持页面卸载 / 路由失活 / 两者均触发时自动取消请求;提供查询前置钩子onQueryBefore可拦截请求,内置查询失败回调onQueryError,分页接口自动拼接页码、页大小参数,非分页接口直接返回完整数组。

  1. 内置单行 / 批量删除能力,统一 loading 状态管理

对外暴露 handleDelete 方法,同时支持单条 id、批量 ids 两种传参模式;自动清洗空请求参数,支持自定义成功回调、失败回调、成功提示文案;可绑定行 loading 或批量按钮 loading,删除完成默认自动刷新列表,缺失deleteApi时控制台给出明确错误提示。

  1. 双弹窗模式无缝切换(Drawer 抽屉 / Dialog 对话框)

通过 extConfig.modal.type 控制弹窗类型,默认抽屉;提供handleShowModal打开弹窗、handleCloseModal关闭弹窗;完整转发弹窗头部 / 底部 / 内容插槽,支持自定义弹窗标题、底部按钮;弹窗渲染基于nextTick保证 DOM 实例就绪,使用shallowRef缓存弹窗组件减少性能开销。

  1. 可定制操作栏,内置展开 / 收起查询表单按钮

操作栏支持自定义外边距、左右反转布局、底部分割线;内置「展开 / 收起查询」内置按钮,自动根据查询表单状态切换图标与文字;支持自定义actionBar插槽放置新增、批量删除等业务按钮;可控制内置按钮是否显示,布局采用 flex 自适应对齐。

  1. 全局 + 局部双层扩展配置合并体系

内置defaultExtConfig默认扩展配置,业务传入extConfig自动与默认配置、表格扩展配置合并(MergedExtConfig);扩展配置覆盖查询请求、弹窗、操作栏、分页等全场景参数,遵循「业务局部配置 > 内置默认配置」优先级,TS 完整类型约束。

  1. 字典自动收集派发,联动外部字典加载逻辑

监听configs配置变化,自动提取所有useDict字典标识,通过onDictKeys事件抛出字典 key 数组;业务侧可监听事件批量加载字典,注入组件dictMap实现下拉、单选等字典渲染联动。

  1. 丰富插槽透传,全场景自定义无限制

完整透传所有子组件插槽(查询表单、表格、操作栏、弹窗头部 / 内容 / 底部);支持自定义表格区域customArea插槽替代原生表格;所有插槽自动透传插槽作用域参数,子组件属性通过v-bind="$attrs"透传,兼容 Element Plus 原生全部属性。

  1. 自适应高度计算,适配页面 / 弹窗两种布局模式

通过extConfig.mode.type区分页面模式、弹窗内嵌模式,自动读取全局注入的表格高度配置;根容器采用 flex 弹性布局,表格区域自适应剩余高度,最小高度兜底避免滚动异常。

  1. 完整 TS 类型闭环,强类型约束

分层定义Configs/ExtConfig/MergedExtConfig/UseCrudState/UseCrudReturn全套类型;props、emits、返回实例、扩展配置均有完备类型推导;defineExpose对外暴露全部核心方法与状态,外部 TS 调用有完整类型提示,无any泛滥问题。

  1. 查询逻辑标准化,分页自动重置

点击查询自动重置为第一页,区分表单原始模型formModel与接口请求参数formParams;提供onQuery方法供查询按钮、重置按钮调用,支持回调函数等待请求完成;无查询接口时直接执行回调,不发起无效请求。

  1. 内存与生命周期安全处理

组件卸载、路由失活时自动中止进行中的列表请求,释放AbortController;watch 监听配置使用deep: true+immediate: true初始化解析列配置;所有 ref、shallowRef 合理区分使用场景,避免不必要的响应式开销,无事件内存泄漏风险。

Demo

Demo预览 | Demo源码

Props

ts
import type { PropType } from 'vue'
import type { Configs, MergedExtConfig } from './types'

/**
 * 组件props
 */
export default {
  /**
   * 配置 必传
   * 1、该configs是【formColumns】和【tableColumns】的统一配置入口
   * 2、该configs在【formColumns】和【tableColumns】的配置基础上新增了【queryLabel】、【onlySearch】和【tableOrder】配置项用于兼容特殊应用场景
   */
  configs: {
    type: Array as PropType<Configs[]>,
    required: true
  },
  /**
   * 扩展配置 如【操作栏/弹窗】等
   * 该extConfig是本组件extConfig和UseElTable组件extConfig合并后的扩展配置
   */
  extConfig: {
    type: Object as PropType<MergedExtConfig>,
    default: () => ({})
  }
}

Types

ts
import type {
  Component,
  ComputedRef,
  ExtractPropTypes,
  Ref,
  ShallowRef
} from 'vue'
import componentProps from './props'
import type { FormColumn } from '../UseElForm/types'
import type {
  TableColumn,
  ExtConfig as TableExtConfig
} from '../UseElTable/types'

/**
 * props类型
 */
export type Props = ExtractPropTypes<typeof componentProps>

/**
 * emits类型
 */
export type Emits = {
  /** dictKeys事件 */
  (e: 'onDictKeys', value?: string[]): void
}

/**
 * 基础配置类型
 */
export type BaseConfigs = FormColumn & TableColumn

/**
 * 配置项接口
 */
export interface Configs extends BaseConfigs {
  /** 查询表单项标签 当查询表单项标签和表格项标签不一致使使用 */
  queryLabel?: string
  /** 查询表单项属性 当查询表单项属性和表格项属性不一致使使用,可传以逗号分隔的字符串,组件内部自动拆解*/
  queryProp?: string
  /** 是否仅搜索 */
  onlySearch?: boolean
  /** 表格排序 */
  tableOrder?: number
}

/**
 * 分割线配置接口
 */
export interface Divider {
  /** 是否显示分割线 */
  show?: boolean
  /** 分割线边框样式 */
  borderStyle?: 'none' | 'solid' | 'hidden' | 'dashed'
}

/**
 * 操作栏配置接口
 */
export interface ActionBar {
  /** 操作栏外边距 */
  margin?: string
  /** 是否显示【收起/展开查询】按钮 */
  showControlFormDisplayBtn?: boolean
  /** 【收起/展开查询】按钮展开时图标 */
  controlFormDisplayBtnWithOpenedIcon?: Component
  /** 【收起/展开查询】按钮收起时图标 */
  controlFormDisplayBtnWithClosedIcon?: Component
  /** 是否反转操作栏按钮 仅showControlFormDisplayBtn为true时生效 */
  reverse?: boolean
  /** 操作栏底部分割线 */
  divider?: Divider
}

/**
 * extConfig接口
 */
export interface ExtConfig {
  /** 增删改查请求配置项 */
  crudFetch?: {
    /** 查询api */
    queryApi?: Function
    /** 是否立即查询 */
    queryImmediate?: boolean
    /** 自动取消查询策略 配合signal实现接口取消请求 */
    autoCancelQueryStrategy?: 'none' | 'unmount' | 'deactivate' | 'both'
    /** 开始查询前钩子 用于注入字典数据等 */
    onQueryBefore?: (done: () => void) => void
    /** 查询错误钩子  */
    onQueryError?: (err: any) => void
    /** 删除api */
    deleteApi?: Function
  }
  /** 操作栏配置项 */
  actionBar?: ActionBar
  /** 弹窗配置项 */
  modal?: {
    /** 弹窗类型 支持Drawer和Dialog */
    type?: 'Drawer' | 'Dialog'
    /** 其他配置项由于组件内部已进行v-bind透传,故不在此重复声明接口,详见UseElDrawer和UseElDialog组件types */
  }
}

/**
 * 合并后的extConfig类型 本组件ExtConfig和UseElTable组件ExtConfig合并
 */
export type MergedExtConfig = ExtConfig & TableExtConfig

/**
 * Crud状态类型
 */
export interface UseCrudState {
  /** 表单列配置 */
  formColumns: FormColumn[]
  /** 表格列配置 */
  tableColumns: TableColumn[]
  /** 表单模型 */
  formModel: Record<string, any>
  /** 表格loading */
  tableLoading: boolean
  /** 表格数据 */
  tableData: any[]
  /** 当前页码 */
  currentPage: number
  /** 分页大小 */
  pageSize: number
  /** 分页总数 */
  total: number
  /** 字典映射 */
  dictMap: Record<string, any>
}

/**
 * Crud返回类型
 */
export interface UseCrudReturn {
  state: Ref<UseCrudState>
  heightComputed: ComputedRef<string>
  fetchTableData: (
    options?: { params?: Record<string, any>; isPageApi?: boolean },
    cb?: () => void
  ) => Promise<any>
  onQuery: (e: Record<string, unknown>, done?: () => void) => void
  onCancelQueryRequest: () => void
  handleDelete: (
    payload: { id?: string | number; ids?: string | Array<string | number> },
    loadingConfig: { source?: Record<string, unknown>; key?: string },
    successCb?: () => void,
    errorCb?: (err: unknown) => void,
    successMsg?: string
  ) => Promise<void>
  margin: ComputedRef<string | undefined>
  actionBar: any
  handleDisplayQueryForm: () => void
  showModal: Ref<boolean>
  modalComponent: ShallowRef<Component | null>
  handleShowModal: () => Promise<void>
  handleCloseModal: ComputedRef<() => void>
}

DefaultExtConfig

ts
import type { MergedExtConfig } from './types'

/**
 * 默认扩展配置
 */
export default {
  crudFetch: { queryImmediate: true, autoCancelQueryStrategy: 'unmount' },
  mode: { type: 'PAGE' },
  actionBar: {
    margin: '20px 0',
    showControlFormDisplayBtn: true,
    reverse: false,
    divider: { show: false, borderStyle: 'solid' }
  },
  modal: { type: 'Drawer' }
} satisfies MergedExtConfig

DefineExpose

ts
defineExpose({
  state,
  onQuery,
  onCancelQueryRequest,
  handleDelete,
  modalRef,
  handleShowModal,
  handleCloseModal
})

基于 MIT 许可发布