基于vue3和element-plus封装的curd组件,支持el-table和el-tree
基于vue3和element-plus封装的curd组件,支持el-table和el-tree,类似avue-curd
本文连接:
写这个的主要目的还是为了学习vue3,为了快速学习,我从gitee找到了一个非常优秀的基于VUE3和element-plus的前端admin框架,地址:https://gitee.com/asaasa/vue3-element-admin,并进行了部分优化
对于前端快速发展,vue3.x的出现让很多基于vue开发的UI框架失去了兼容性,特别是avue-curd这样优秀的组件无法使用,而最新的技术肯定相对于老技术有一定的优势,我本想查找一些现有的基于vue3.x的curd组件来直接使用,找了半天也不尽人意,于是自己写一个类似avue-curd的组件,基本上兼容curd组件使用的element-plus的组件的所有属性,如果哪里有疑问查询element-plus对应组件api就行,这里主要以azi模块介绍使用方法,组件源码在最后面。
支持el-table和el-tree,绑定参数: crudType="tree"、默认值:“table”
有帮助的话给个关注,有问题的画帮忙指出问题
安装
npm i ve-curd
import VeCurd from "ve-curd";
app.use(VeCurd);
源码下载
git clone https://gitee.com/muphy1112/ve-curd.git
先看效果
分别点击查看,新增、编辑和删除:
表格默认支持过滤,排序和分页
el-table和el-pagination所有特性基本上都可以使用,红色圈内是新增的,其他还有什么尽管绑定即可,另外冲突的disabled需要分开
表单组件根据表格列对象配置的type值区分组合了:el-input、el-cascader、el-checkbox、el-data-picker,这些组件的属性都通过表格列对象配置,也支持字典
增删改查表单的label和value都可以被自定义,对应加上功能名称,如列对象prop配置为username:那么自定义包括username、usernameValue、usernameLabel、usernameForm、usernameView、usernameEdit、usernameAdd等
表单本身也支持自定义:editForm、viewForm、addForm等
还有一些就不列举了,哪里有问题直接改组件源码
测试页面:examples/components/HelloWorld.vue
curd
curd-type="tree"
:data="treeData"
:columns="tableColumns"
:props="{
label: 'realName',
children: 'children',
}"
@delete-sure="handleDelete"
@edit-sure="handleEdit"
@add-sure="handleAdd"
>
curd
:model="form"
show-message
:data="tableData"
:columns="tableColumns"
:total="page.total"
v-model:page-size="page.pageSize"
v-model:current-page="page.currentPage"
v-model:ascs="page.ascs"
v-model:descs="page.descs"
v-model:filters="page.filters"
@sort-change="handleChange"
@size-change="handleChange"
@current-change="handleChange"
@filter-change="searchChange"
@search-change="searchChange"
@delete-sure="handleDelete"
@edit-sure="handleEdit"
@add-sure="handleAdd"
>
examples/api/user.js
export function getPage(query) { return Promise.resolve({ data: { success: true, data: { total: 58, records: [{"admin":true,"id":"1","lastTime":1642969720288,"password":"123456","realName":"超级管理员","roleIds":"1","status":"1","username":"admin"},{"admin":false,"id":"11","lastTime":1642969720288,"password":"440","realName":"jjj","roleIds":"2","status":"0","username":"qqq"},{"admin":false,"id":"13","lastTime":1642969720288,"password":"111111","realName":"宝宝","roleIds":"2","status":"0","username":"aaa"},{"admin":false,"id":"2","lastTime":1642969720288,"password":"123456","realName":"管理员","roleIds":"2","status":"1","username":"test"},{"admin":false,"id":"3","lastTime":1642969720288,"realName":"安安","roleIds":"","status":"0"},{"admin":false,"id":"4","lastTime":1642969720288,"realName":"静静","roleIds":"","status":"0","username":"静静"},{"admin":false,"id":"5","lastTime":1642969720288,"realName":"阿朱","roleIds":"","status":"0"},{"admin":false,"id":"6","lastTime":1642969720288,"realName":"武怡","roleIds":"","status":"0"},{"admin":false,"id":"7","lastTime":1642969720288,"password":"123","realName":"阿紫","roleIds":"2","status":"0","username":"azi"},{"admin":false,"id":"8","lastTime":1642969720288,"realName":"爱玲","roleIds":""}]}}}); // return request({ // url: '/gw/user/page', // method: 'get', // params: query // }) } export function getList(query) { return Promise.resolve({ data: { success: true, data: [{"admin":true,"id":"1","lastTime":1642969720288,"password":"123456","realName":"超级管理员","roleIds":"1","status":"1","username":"admin"},{"admin":false,"id":"11","lastTime":1642969720288,"password":"440","realName":"jjj","roleIds":"2","status":"0","username":"qqq"},{"admin":false,"id":"13","lastTime":1642969720288,"password":"111111","realName":"宝宝","roleIds":"2","status":"0","username":"aaa"},{"admin":false,"id":"2","lastTime":1642969720288,"password":"123456","realName":"管理员","roleIds":"2","status":"1","username":"test"},{"admin":false,"id":"3","lastTime":1642969720288,"realName":"安安","roleIds":"","status":"0"},{"admin":false,"id":"4","lastTime":1642969720288,"realName":"静静","roleIds":"","status":"0","username":"静静"},{"admin":false,"id":"5","lastTime":1642969720288,"realName":"阿朱","roleIds":"","status":"0"},{"admin":false,"id":"6","lastTime":1642969720288,"realName":"武怡","roleIds":"","status":"0"},{"admin":false,"id":"7","lastTime":1642969720288,"password":"123","realName":"阿紫","roleIds":"2","status":"0","username":"azi"},{"admin":false,"id":"8","lastTime":1642969720288,"realName":"爱玲","roleIds":""},{"admin":false,"id":"9","lastTime":1642969720288,"realName":"剑圣","roleIds":""}]}}); // return request({ // url: '/gw/user/list', // method: 'get', // params: query // }) }
CURD组件可以使用全局注册或者单独import都可以,文件路径和内容:packages/ve-curd/src/VeCurd.vue
if="curdType === 'tree'">
input
v-model="treeQuery"
clearable
:suffix-icon="Search"
placeholder="请输入..."
@input="onTreeQueryChanged"
>
button
:icon="Plus"
@click="handleTreeAdd(null, {})"
>
tree
ref="treeRef"
:indent="10"
empty-text="当前没有数据"
v-bind="$attrs"
:data="data"
:props="props"
:filter-method="filterHandle"
>
default="scope">
if="!scope.node.isLeaf" />
else />
tooltip
:content="scope.node.label"
placement="bottom"
:show-after="1000"
effect="light"
>
{{ scope.node.label }}
item
:icon="Plus"
@click="
handleTreeAdd(
scope.node,
scope.data
)
"
>
{{
(permission &&
permission.add &&
permission.add.name) ||
"新增"
}}
item
:icon="Delete"
@click="
handleTreeDelete(
scope.node,
scope.data
)
"
>
{{
(permission &&
permission.del &&
permission.del.name) ||
"删除"
}}
item
:icon="Edit"
@click="
handleTreeEdit(
scope.node,
scope.data
)
"
>
{{
(permission &&
permission.edit &&
permission.edit.name) ||
"编辑"
}}
item
:icon="View"
@click="
handleTreeView(
scope.node,
scope.data
)
"
>
查看
else>
card
v-show="searchShow && searchColumns.length > 0"
style="margin-bottom: 20px"
>
form
ref="queryFormRef"
:model="$attrs.model"
:inline="inline"
:label-position="labelPosition"
:label-width="labelWidth"
:label-suffix="labelSuffix"
:hide-required-asterisk="hideRequiredAsterisk"
:show-message="showMessage"
:inline-message="inlineMessage"
:status-icon="statusIcon"
:validate-on-rule-change="validateOnRuleChange"
:size="size"
:disabled="formDisabled"
:validate="validate"
:validateField="validateField"
:resetFields="resetFields"
:scrollToField="scrollToField"
:clearValidate="clearValidate"
@validate="formValidate"
>
default>
<template
v-for="(item, index) in searchColumns"
:key="index"
>
item
:prop="item.prop"
:label="item.label"
:label-width="item.labelWidth"
:required="item.required"
:error="item.error"
:show-message="item.showMessage"
:inline-message="item.inlineMessage"
:size="item.size"
:resetField="item.resetField"
:clearValidate="item.clearValidate"
>
default>
picker
v-if="item.type === 'daterange'"
clearable
v-model="
$attrs.model[item.prop]
"
type="daterange"
range-separator="~"
start-placeholder="开始日期"
end-placeholder="结束日期"
:value-format="
item.valueFormat ||
'YYYY-MM-DD'
"
>
picker
v-else-if="
item.type ===
'datetimerange'
"
clearable
v-model="
$attrs.model[item.prop]
"
type="datetimerange"
range-separator="~"
start-placeholder="开始时间"
end-placeholder="结束时间"
:value-format="
item.valueFormat ||
'YYYY-MM-DD hh:mm:ss'
"
>
checkbox
v-else-if="
item.type === 'checkbox'
"
clearable
v-model="
$attrs.model[item.prop]
"
:label="$attrs.model[item.prop]"
style="width: 220px"
>
cascader
v-else-if="
item.type === 'select' ||
item.type ===
'multiselect' ||
item.type === 'cascader'
"
clearable
filterable
v-bind="item.dict"
style="width: 220px"
v-model="
$attrs.model[item.prop]
"
>
input
v-else
clearable
autosize
placeholder="请输入..."
style="width: 220px"
v-bind="item"
:type="item.type || 'text'"
:show-password="
item.type === 'password'
"
v-model="
$attrs.model[item.prop]
"
>
<slot
:name="item.prop + 'Label'"
:key="scope.key"
:label="scope.label"
>
{{ scope.label }}
<slot
:name="item.prop + 'Error'"
:error="scope.error"
>
{{ scope.error }}
button
type="primary"
@click="handleSearch"
>
查询
button
@click="resetForm"
style="margin-left: 30px"
>
重置
button
type="primary"
:icon="Plus"
@click="handleAdd"
>
{{
(permission &&
permission.add &&
permission.add.name) ||
"新增"
}}
button
type="text"
:icon="Search"
@click="handleSearchShow"
>
button
type="text"
:icon="Expand"
@click="initDrawerColumnTree"
>
table
style="width: 100%"
v-bind="$attrs"
:data="data"
@sort-change="handleSortChange"
@filter-change="handleFilterChange"
>
<template
v-for="(item, index) in tableColumns"
:key="index"
>
column
v-bind="item"
v-if="item.hide !== true"
:column-key="item.prop"
>
<template
#default="scope"
v-if="
item.type !== 'selection' &&
item.type !== 'index' &&
item.type !== 'expand'
"
>
<slot
:name="item.prop + 'Value'"
:row="scope.row"
:column="item"
:$index="index"
>
{{ getLabel(scope.row[item.prop], item) }}
<slot
:name="item.prop + 'Header'"
:column="item"
:$index="index"
>
{{ scope.column.label }}
default="scope">
<slot
name="viewButton"
:row="scope.row"
:$index="scope.$index"
>
button
size="small"
type="text"
:icon="View"
@click="handleView(scope.row, scope.$index)"
>
{{
(permission &&
permission.search &&
permission.search.name) ||
"查看"
}}
<slot
name="editButton"
:row="scope.row"
:$index="scope.$index"
>
button
size="small"
type="text"
:icon="Edit"
@click="handleEdit(scope.row, scope.$index)"
>
{{
(permission &&
permission.edit &&
permission.edit.name) ||
"编辑"
}}
<slot
name="delButton"
:row="scope.row"
:$index="scope.$index"
>
button
size="small"
type="text"
style="color: red"
:icon="Delete"
@click="
handleDelete(scope.row, scope.$index)
"
>
{{
(permission &&
permission.del &&
permission.del.name) ||
"删除"
}}
<slot
name="operate"
:$index="scope.$index"
:row="scope.row"
>
pagination
:small="small"
:background="background"
:total="total"
:page-count="pageCount"
:pager-count="pagerCount"
:page-sizes="pageSizes"
:popper-class="popperClass"
:prev-text="prevText"
:next-text="nextText"
:disabled="paginationDisabled"
:hide-on-single-page="hideOnSinglePage"
:default-current-page="defaultCurrentPage"
:layout="layout"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
drawer
v-model="drawerColumnShow"
direction="rtl"
title="自定义列显示或隐藏"
>
tree
ref="drawerColumnTree"
:data="tableColumns"
:props="{ label: 'label' }"
node-key="prop"
show-checkbox
@check-change="drawerTreeChange"
/>
dialog
v-model="dialogViewFormVisible"
:title="viewTitle"
@open="handleViewOpen"
@opened="handleViewOpened"
@close="handleViewClose"
@closed="handleViewClosed"
>
{{ viewTitle }}
form
ref="viewFormRef"
:model="form"
:inline="inline"
:label-position="labelPosition"
:label-width="labelWidth"
:label-suffix="labelSuffix"
:status-icon="statusIcon"
:size="size"
:disabled="true"
>
default>
<template
v-for="(item, index) in viewColumns"
:key="index"
>
item
v-if="item.viewable !== false"
:prop="item.prop"
:label="item.label"
:label-width="item.labelWidth"
:size="item.size"
>
default>
picker
v-if="item.type === 'daterange'"
v-model="form[item.prop]"
type="daterange"
range-separator="~"
start-placeholder="开始日期"
end-placeholder="结束日期"
disabled
:value-format="
item.valueFormat || 'YYYY-MM-DD'
"
>
picker
v-else-if="
item.type === 'datetimerange'
"
disabled
v-model="form[item.prop]"
type="datetimerange"
range-separator="~"
start-placeholder="开始时间"
end-placeholder="结束时间"
:value-format="
item.valueFormat ||
'YYYY-MM-DD hh:mm:ss'
"
>
checkbox
v-else-if="item.type === 'checkbox'"
v-model="form[item.prop]"
:label="form[item.prop]"
style="width: 220px"
disabled
>
cascader
v-else-if="
item.type === 'select' ||
item.type === 'multiselect' ||
item.type === 'cascader'
"
v-bind="item.dict"
v-model="form[item.prop]"
style="width: 220px"
disabled
>
input
v-else
clearable
autosize
placeholder="请输入..."
style="width: 220px"
v-bind="item"
:type="item.type || 'text'"
:show-password="
item.type === 'password'
"
v-model="form[item.prop]"
disabled
>
<slot
:name="item.prop + 'Label'"
:key="scope.key"
:label="scope.label"
>
{{ scope.label }}
dialog
v-model="dialogEditFormVisible"
:title="editTitle"
@open="handleEditOpen"
@opened="handleEditOpened"
@close="handleEditClose"
@closed="handleEditClosed"
>
{{ editTitle }}
form
ref="editFormRef"
:model="form"
:rules="rules"
:inline="inline"
:label-position="labelPosition"
:label-width="labelWidth"
:label-suffix="labelSuffix"
:hide-required-asterisk="hideRequiredAsterisk"
:show-message="showMessage"
:inline-message="inlineMessage"
:status-icon="statusIcon"
:validate-on-rule-change="validateOnRuleChange"
:size="size"
:disabled="formDisabled"
:validate="validate"
:validateField="validateField"
:resetFields="resetFields"
:scrollToField="scrollToField"
:clearValidate="clearValidate"
@validate="formValidate"
>
default>
<template
v-for="(item, index) in editColumns"
:key="index"
>
item
v-if="item.editable"
:prop="item.prop"
:label="item.label"
:label-width="item.labelWidth"
:required="item.required"
:rules="item.rules"
:error="item.error"
:show-message="item.showMessage"
:inline-message="item.inlineMessage"
:size="item.size"
:resetField="item.resetField"
:clearValidate="item.clearValidate"
>
default>
picker
v-if="item.type === 'daterange'"
clearable
v-model="form[item.prop]"
type="daterange"
range-separator="~"
start-placeholder="开始日期"
end-placeholder="结束日期"
:value-format="
item.valueFormat || 'YYYY-MM-DD'
"
>
picker
v-else-if="
item.type === 'datetimerange'
"
clearable
v-model="form[item.prop]"
type="datetimerange"
range-separator="~"
start-placeholder="开始时间"
end-placeholder="结束时间"
:value-format="
item.valueFormat ||
'YYYY-MM-DD hh:mm:ss'
"
>
checkbox
v-else-if="item.type === 'checkbox'"
clearable
v-model="form[item.prop]"
:label="form[item.prop]"
style="width: 220px"
>
cascader
v-else-if="
item.type === 'select' ||
item.type === 'multiselect' ||
item.type === 'cascader'
"
clearable
filterable
v-bind="item.dict"
style="width: 220px"
v-model="form[item.prop]"
>
input
v-else
clearable
autosize
placeholder="请输入..."
style="width: 220px"
v-bind="item"
:type="item.type || 'text'"
:show-password="
item.type === 'password'
"
v-model="form[item.prop]"
>
<slot
:name="item.prop + 'Label'"
:key="scope.key"
:label="scope.label"
>
{{ scope.label }}
<slot
:name="item.prop + 'Error'"
:error="scope.error"
>
{{ scope.error }}
dialog
v-model="dialogAddFormVisible"
:title="addTitle"
@open="handleAddOpen"
@opened="handleAddOpened"
@close="handleAddClose"
@closed="handleAddClosed"
>
{{ addTitle }}
form
ref="addFormRef"
:model="form"
:rules="rules"
:inline="inline"
:label-position="labelPosition"
:label-width="labelWidth"
:label-suffix="labelSuffix"
:hide-required-asterisk="hideRequiredAsterisk"
:show-message="showMessage"
:inline-message="inlineMessage"
:status-icon="statusIcon"
:validate-on-rule-change="validateOnRuleChange"
:size="size"
:disabled="formDisabled"
:validate="validate"
:validateField="validateField"
:resetFields="resetFields"
:scrollToField="scrollToField"
:clearValidate="clearValidate"
@validate="formValidate"
>
default>
<template
v-for="(item, index) in addColumns"
:key="index"
>
item
v-if="item.addable"
:prop="item.prop"
:label="item.label"
:label-width="item.labelWidth"
:required="item.required"
:rules="item.rules"
:error="item.error"
:show-message="item.showMessage"
:inline-message="item.inlineMessage"
:size="item.size"
:resetField="item.resetField"
:clearValidate="item.clearValidate"
>
default>
picker
v-if="item.type === 'daterange'"
v-model="form[item.prop]"
type="daterange"
range-separator="~"
start-placeholder="开始日期"
end-placeholder="结束日期"
:value-format="
item.valueFormat || 'YYYY-MM-DD'
"
clearable
>
picker
v-else-if="
item.type === 'datetimerange'
"
v-model="form[item.prop]"
type="datetimerange"
range-separator="~"
start-placeholder="开始时间"
end-placeholder="结束时间"
:value-format="
item.valueFormat ||
'YYYY-MM-DD hh:mm:ss'
"
clearable
>
checkbox
v-else-if="item.type === 'checkbox'"
v-model="form[item.prop]"
:label="form[item.prop]"
style="width: 220px"
clearable
>
cascader
v-else-if="
item.type === 'select' ||
item.type === 'multiselect' ||
item.type === 'cascader'
"
clearable
filterable
style="width: 220px"
v-bind="item.dict"
v-model="form[item.prop]"
>
input
v-else
clearable
autosize
placeholder="请输入..."
style="width: 220px"
v-bind="item"
:type="item.type || 'text'"
:show-password="
item.type === 'password'
"
v-model="form[item.prop]"
>
<slot
:name="item.prop + 'Label'"
:key="scope.key"
:label="scope.label"
>
{{ scope.label }}
<slot
:name="item.prop + 'Error'"
:error="scope.error"
>
{{ scope.error }}
dialog
v-model="dialogDeleteVisible"
:title="deleteTitle"
width="30%"
>
{{ deleteTitle }}
确认删除吗?