基于vue3和elementPlus的可快速通过json配置的形式,开发增删改查页面的项目。
v0.6.5 - v0.7.4 2024-12-22
1, 添加上传文件时即时删除功能。
2,弹框组件支持展示 table 类型数据
3, 优化 table 组件操作列中按钮样式
4, 添加_matchType_字段收集在query配置时 prop 字段的匹配值:like/eq/..,请求时处理的默认参数
5, 解决表单重复添加问题,itemSlotIdx 至少配置为 1
6, 添加支持 table组件,前端分页功能
v0.4.1 - v0.6.4 2024-12-16
1, 适配项目, fetch钩子添加responseStatusKey/successCode/resultMsg/successLogic/responseType(json)字段
2, 优化 emit(up)处理函数
3,修复 table 组件翻页 bug、操作栏单独渲染并添加宽度和默认固定右侧配置
4, 修复 dialog 多层弹框 bug
v0.3.9 - 0.4.0 2024-11-30
1, 修复在没有声明 slot 时,意外添加formItem的 bug
2, 各组件operate中添加 hookFn 配置,直接调用通过最外层组件传入的hooks对象中的函数
3,修复 dialog 组件数据不跟随点击更新的 bug
v0.3.4 - v0.3.8 2024-11-29
1, 添加对外提供/query、closeDialog、openDialog、getDialogData、updateTableData、getTableSelection, getTableLength, refreshTable, updateTableData方法。
2,dailog组件修改getDialogData方法为 getData
3,添加监听 form 组件的按钮
4,对外提供的 event 监听方法,添加更多监听项:query组件、dialog组件、table组件的operation、pagination(实时)
v0.3.3 2024-11-11
1,对外开放 getTableSelection/getTableLength/getQueryData方法,支持外部使用
2, 添加v3Operation的插槽功能,支持配置items:表单结构(array)、operationSlotIdx:插入表单位置,支持 array
3, 新增配置文件中 config 字段,可区别配置 table /query /operation /dialog
v0.3.1 - v0.3.2 2024-11-01
1,添加v3Form的插槽功能,支持配置items:表单结构(array)、itemSlotIdx:插入表单位置,支持 array
2,添加v3Table的插槽功能,支持配置columns:表格结构(array)、columnSlotIdx:插入列位置,支持 array
3,添加v3Query的插槽功能,支持配置items:表单结构(array)、querySlotIdx(内部使用时为querySlotIdx字段,区分于v3Form的itemSlotIdx):插入表单位置,支持 array
4,开放v3Form/v3Table/v3Query/pageInfo,支持外部使用
5,v3Dialog 无法支持 Slot,dialog组件是动态生成的,一个视图下面可能会包含 n 多个 dialog,插槽位置无法固定,所以不支持
v0.3.0 2024-08-05
1,添加支持tabs下,标签页根据配置的逻辑(showLogic)显示
v0.2.7 - v0.2.9 2024-07-11
1, 解决el-input配置clearable时,再输入内容后,自动变长的问题
2,优化点击查询按钮后,自动查询第一页数据
3,添加自动解析get请求api中包含${}中的内容
4, table组件中添加pageInfo的storage和assert数据,补充列数据
5,添加支持operatios中配置dropdownMenus字段(按钮下拉的形式<dropdown>)
6,form/table组件添加支持prop字段的x.xx.xxx取值方式。form组件提交时自动转换为{x:{xx:{xxx:1}}}
v0.2.6 2024-07-08
1, 优化fields中字段配置,select组件和table组件共享config中的enums属性,避免冗余
2, 优化select的选中逻辑,改为优先选中enums中的key,config中配置reqVal的值为“value"时,选中enums中的value
3, 优化form表单的checkbox组件
4, 添加rangeFnExcuteJs方法,参数是配置rangeFn函数的返回值,增强rangeFn的额外执行能力
5, fetch请求默认拦截['0', '400', '401', '403', '404', '500']中的code
v0.2.2-v0.2.5: 2024-07-03
1,优化报错提示信息
2,添加按钮的显示逻辑operationLogic,可根据上下文控制是否显示该字段(上下文具体是_assert_、_storage_所携带的字段)
3,弹框的fileds添加showLogin属性,可根据上下文控制弹框内字段在初次渲染时,是否显示
4, operations对象下的query数组的操作属性添加支持notImmediate: true,打开页面时,不立即执行请求操作
5,上下文信息是从地址栏和浏览器缓存中获取请注意编码问题
v0.2.1:2024-03-15
1,修改查询时携带地址栏的_query_数据bug
2,dialog添加模板解析,添加字段:headTip/footTip,支持字符串、html代码:<div>我是底部${data}</div>
3,添加对外暴露event事件,补充组件的完整性
项目整体采用widget、component、page三层结构:
page 解析schema对象,针对解析结果做页面级的效果,比如:是否需要标签页、解析地址栏参数、添加字段的展示逻辑、处理各个子组件之间的调用等;
range 主要集成了各个widget,并根据schema解析后的数据控制widget的展示隐藏、默认数据(config)提前处理.
widget 主要是对elementPlus中表单组件的增强和融合,对外提供各个零碎功能;如:asyncSelect、datePicker、selectTree等;
- yarn add vpagecrud
- import vPagecrud from vpagecrud
- app.use(vpagecrud)
- vPagecrud组件接收schema对象(对象字段如下展示),对外暴露fetch方法处理请求错误时的特殊需求。
- 页面包含搜索区、页码区、table展示区、操作按钮区。
- 暴露tool对象,可方便项目其他地方使用。
- fetch请求使用Axios插件,baseURL配置为 VITE_BASE_URL || BASE_URL || '/',可灵活使用
page: 整个页面,组装range
range: page下的各个区块,组装widget;值包含:table/dialog/query/operation
|- table: 表格, 提供的方法:update--更新当前table,getSelection--获取勾选的值,getLength--获取勾选的长度,delete--删除当前项
|- dialog:弹框,提供的方法:close--关闭当前dialog,open--打开当前dialog,submit--提交弹框内formData,getData--获取弹框内formData
|- query:查询,提供的方法:getData--获取所有查询formdata, reset--重置查询formdata
|- operation:操作,提供的方法:
widget: 完成某一功能的具体(最小)单元
title:标题(单页面时可省,多标签页面时必填)
showLogic:显示逻辑,用于控制在初次渲染时是否显示当前tab页,支持两种方式:
1. 直接赋值一个布尔值,表示是否显示该部分,默认展示;
2. 赋值一个函数体,该函数返回一个布尔值,取值是地址栏中的_assert_对象和浏览器的storage对象。
fields:一级fields对象
operations: 操作按钮
|- default: 默认操作区(列表头部所有按钮)
|- label:按钮名称
|- actOn:按钮触发的range,值为 range 中的某一项,表示:点击按钮后,触发该range中的某一项的rangeFn
|- rangeFn:欲触发range中的某方法
|- request:请求
|- query: 查询区
字段同default
|- table: 列表区
字段同default
- 导出配置为数组时,需配置
title
属性,用于显示在标签页;导出配置为对象时,则为单页面,不需要配置title
属性。 - 配置中,大部分(form)属性遵循elementPlus的配置字段,如:rules规则;
1, label:fields对象属性:显示的中文字段
2, prop:fields对象属性:取值的英文字段 ‘|’后加方法名可对当前取的值进行处理后显示:updateTime | dateFormatter2YMD:表示用dateFormatter2YMD方法对updateTime进行处理
3, showIn:fields对象属性:显示此字段到什么位置目前支持(table/query);
4, tag: fields对象属性:渲染标签的类型。可用:input/select/asyncSelect/autocomplete/date/daterange/month/datetimerange/fileUpload/imgUpload/radio/checkbox/tree/button
5, config:fields对象属性:表单、列表组件的配置项信息,配置elementPlus的配置项,如:rules规则;
6, showLogic:fields对象属性:显示逻辑,用于控制在初次渲染时是否显示当前字段,支持两种方式:
1. 直接赋值一个布尔值,表示是否显示该部分,默认展示;
2. 赋值一个函数体,该函数返回一个布尔值,函数的参数是地址栏中的_assert_对象和浏览器的storage对象。
7, queryLogic:fields对象中config对象的属性:用于控制何时在当前查询区域中显示该字段,支持两种方式:
1. 直接赋值一个布尔值,表示是否显示该部分,默认展示;
2. 赋值一个函数体,该函数返回一个布尔值,函数的参数是当前查询模块所有字段的值对象。
8, tableLogic: fields对象中config对象的属性:用于控制何时在table表中显示该字段和显示什么值以及值的样式,支持两种方式:
1. 直接赋值一个布尔值,表示是否显示该部分,默认展示;
2. 赋值一个函数体,该函数返回一个布尔值,函数的参数是当前table中的row对象:
1. obj?.groupName == '小组零' # value('优'); obj?.groupName == '小组六' # value('良');
2. obj?.roleName == '管理员' && obj?.postName == '后端开发' # [class('p-1 p-3'), value('优秀'), style('color: red;font-size: 16px;')];
3. obj?.roleName == '管理员' # [class('p-2 p-4'), value('秀'), style('color: red;font-size: 16px;')]; obj?.roleName == '普通成员' # [class('p-1'), value('好'), style('color: green;font-size: 14px;')]
三种逻辑是根据row对象中的某字段的值修改展示的内容和样式;注意';'表示语句的间隔;每条语句的value/rowClass/cellClass/style字段切勿重复,否则会样式紊乱。
9, value/rowClass/cellClass/style: logic中的自定义配置,用于修改table中的当前字段的值/行样式/单元格样式/值样式
10, afterExcuteJsStr/beforeExcuteJsStr: request对象属性,用于在请求前和请求后对params或response执行string类型的js代码,参数为params或response
"beforeExcuteJsStr": "function(obj){Object.keys(obj).map(el => {obj[el]+=1;console.log(obj)});return obj;}(obj)";可在请求前对params的各个参数都做+1处理。
"afterExcuteJsStr": "function(obj){obj.list.map(el => el['phone']+='555');return obj;}(obj)";对手机号后缀都加了'+86'字符
11, 列表的响应取值为responseKey字段; 也可在afterExcuteJsStr中自行处理,本期不做特殊配置
12, _query_、_submit_、_assert_:地址栏中的对象
1. _query_:当前页面的首次查询信息,查询组件的默认值
2. _submit_:提交到当前页面的对象
3. _assert_:当前页面的断言信息,配合showLogic字段使用
4._storage_:浏览器的缓存数据(包括cookie、localstorage和sessionStorage)
[{
"title": "用户管理",
"fields": [
{ "label": "用户名称", "prop": "name.a", "showIn":["table", "query"], "tag": "input", "config":
{
"clearable": true, "placeholder": "请输入用户名称"
}
},
{ "label": "手机号码", "prop": "phone", "showIn":["table", "query"], 'config': { "tableLogic": "obj?.phone == '3' # rowClass('p-1')"}},
{ "label": "状态", "prop": "status", "showIn":["table", "query"], "tag": "select",
"config": {
"reqField": "status",
"enums": {
"active": '激活',
"inactive": '禁用',
"blocked": '锁定'
}
}
},
{ "label": "邮箱", "prop": "email"},
{ "label": "地址", "prop": "address"},
{ "label": "创建时间", "prop": "createdAt | dateFormatter2YMDHMS", "showIn":["table", "query"], "tag": "datetimerange"},
{ "label": "修改时间", "prop": "updatedAt | dateFormatter2YMDHMS"}
],
"operations": {
"default": [
{
"title": "新增用户", "label": "新增", "actOn": "dialog", "rangeFn": "open", "btnClass": "add",
"fields": [
{"label": "用户名称", "prop": "name", "config": {
"rules": [{ "required": true, "message": '请输入用户名称', "trigger": 'blur' }]
}},
{"label": "昵称", "prop": "nickName"},
{"label": "密码", "prop": "password"},
{"label": "手机号码", "prop": "phone"},
{"label": "邮箱", "prop": "email"},
{"label": "地址", "prop": "address"},
{"label": "状态", "prop": "status", "tag": "select",
"config": {
"reqField": "status",
"enums": {
"active": '激活',
"inactive": '禁用',
"blocked": '锁定'
}
}
}
],
"operations": [{
"label": "确认", "actOn": "dialog", "rangeFn": "commit", "operationLogic": "obj?.name == 2",
"request": { "url": "/api/user/create", "method": "post", "successMsg": "创建成功!", "successCb":
{ "actOn": "table", "rangeFn": "update", "request": {"url": "/api/user/query", "method": "post" }, "params": {}},
}
}, {
"label": "取消", "rangeFn": "close", "actOn": "dialog"
}]
}, {
"label": "table处理", "actOn": "table", "rangeFn": "getSelection", "operationLogic": "obj?.name == 2",
"request": { "url": "/api/user/query", "method": "post", "fields": ["id", "name", "phone", "groupId"] }
}, {
"label": "按钮下拉", "actOn": "table", "rangeFn": "getSelection",
"list": [
{
"label": "新增", "actOn": "dialog", "rangeFn": "open",
"fields": [{"label": "姓名", "prop": "name", "tag": "input"}, {"label": "年龄", "prop": "age", "tag": "input"}],
"operations": [{
"label": "确认", "actOn": "dialog", "rangeFn": "commit", "operationLogic": "obj?.name == 2",
"request": { "url": "/api/user/query", "method": "post" }
}, {
"label": "取消", "actOn": "dialog", "rangeFn": "close"
}
]
}
]
}, {
"label": "query处理", "actOn": "query", "rangeFn": "getQueryData", "request": { "url": "/api/user/query", "method": "post" }
}, {
"label": "table + dialog处理", "actOn": "table", "rangeFn": "getSelection",
"next": {
"label": "table + dialog处理", "actOn": "dialog", "rangeFn": "open", "headTip": "处理${selectedSize}",
"fields": [{"label": "姓名", "prop": "name", "tag": "input"}, {"label": "年龄", "prop": "age", "tag": "input"}, {"label": "性别", "prop": "sex", "tag":
"input"}],
"operations": [{
"label": "确认", "actOn": "dialog", "rangeFn": "commit",
"request": { "url": "/api/user/query", "method": "post" }
}, {
"label": "取消", "actOn": "dialog", "rangeFn": "close"
}
]
}
}, {
"label": "query + dialog处理", "actOn": "query", "rangeFn": "getQueryData",
"next": {
"label": "query + dialog处理", "actOn": "dialog", "rangeFn": "open",
"fields": [{"label": "手机号", "prop": "phone", "tag": "input"}, {"label": "邮箱", "prop": "email", "tag": "input"}, {"label": "地址", "prop": "address", "tag":
"input"}],
"operations": [{
"label": "确认", "actOn": "dialog", "rangeFn": "commit",
"request": { "url": "/api/user/query", "method": "post" }
}, {
"label": "取消", "actOn": "dialog", "rangeFn": "close"
}
]
}
}, {
"label": "query + table + dialog处理", "actOn": "query", "rangeFn": "getQueryData",
"next": {
"actOn": "table", "rangeFn": "getSelection",
"next": {
"actOn": "dialog", "rangeFn": "open", "label": "query + table + dialog处理",
"fields": [
{"label": "姓名", "prop": "name", "tag": "input"},
{"label": "年龄", "prop": "age", "tag": "input"},
{"label": "电话", "prop": "phone", "tag": "input"},
{"label": "性别", "prop": "sex", "tag": "input"}
],
"operations": [{
"label": "确认", "actOn": "dialog", "rangeFn": "commit",
"request": { "url": "/api/user/query", "method": "post" }
}, {
"label": "取消", "actOn": "dialog", "rangeFn": "close"
}, {
"label": "下一步", "actOn": "dialog", "rangeFn": "getDialogData", "showLogic": "obj?.aaa == 22 && obj?.bbb == 111",
"next": {
"actOn": "dialog", "rangeFn": "open",
"fields": [
{"label": "姓名1", "prop": "name1", "tag": "input"},
{"label": "年龄2", "prop": "age1", "tag": "input"},
{"label": "电话3", "prop": "phone1", "tag": "input"},
{"label": "性别4", "prop": "sex1", "tag": "input"}
],
"operations": [{
"label": "确认", "actOn": "dialog", "rangeFn": "commit",
"request": { "url": "/api/user/query", "method": "post" }
}, {
"label": "取消", "actOn": "dialog", "rangeFn": "close"
},{
"label": "下一步", "actOn": "dialog", "rangeFn": "getDialogData",
"next": {
"actOn": "dialog", "rangeFn": "open",
"fields": [
{"label": "姓名11", "prop": "name2", "tag": "input"},
{"label": "年龄22", "prop": "age2", "tag": "input"},
{"label": "电话33", "prop": "phone2", "tag": "input"},
{"label": "性别44", "prop": "sex2", "tag": "input"}
],
"operations": [{
"label": "确认", "actOn": "dialog", "rangeFn": "commit",
"request": { "url": "/api/user/query", "method": "post" }
}, {
"label": "取消", "actOn": "dialog", "rangeFn": "close"
}]
}
}]
}
}
],
}
}
},
{
"label": "自定义处理", "actOn": "query", "rangeFn": "getQueryData", "next": {
"actOn": "table", "rangeFn": "getSelection", "rangeFnExcuteJs": "function(obj) {console.log(obj);return obj}(obj)", "next": {
"isCustom": true, "request": { "url": "/api/user/query", "method": "post" }
}
}
},
],
"table": [
{ "label": "编辑", "actOn": "dialog", "rangeFn": "open", "showLogic": "obj.permissions.includes(1)", "btnClass": "edit",
"fields": [
{"label": "用户ID", "prop": "id", "config": {
"disabled": true
}},
{"label": "用户名称", "prop": "name.a"},
{"label": "手机号码", "prop": "phone"},
{"label": "角色", "prop": "roleName", "tag": "select", "config":
{
"reqField": "roleId",
"enum": {
"b578e8c0e8295ec295e7cc89447271ab": '管理员',
"9886d03139161f1329dfb6049e3a1910": '普通用户',
"70545e008dd490edea15bdd228e566fc": '超级管理员'
}
}
},
{ "label": "所属部门", "prop": "zoneId", "tag": "asyncSelect", "config":
{
"url": "/api/zone/select", "method": "post", "fieldKey": "name", "fieldValue": "id",
}
},
{ "label": "所属组", "prop": "groupId", "tag": "asyncSelect", "config":
{
"url": "/api/group/select", "method": "post", "fieldKey": "name", "fieldValue": "id",
}
},
{ "label": "所属岗位", "prop": "postId", "tag": "asyncSelect", "config":
{
"url": "/api/post/select", "method": "post", "fieldKey": "name", "fieldValue": "id",
}
},
{ "label": "备注", "prop": "description", "config":
{
"type": "textarea",
}
}
],
"operations": [{
"label": "确认", "rangeFn": "commit", "actOn": "dialog", "isCloseDialog": true,
"request": { "url": "/user/update", "method": "post", "fetchMsg": "创建成功!", "successCb": { "actOn": "table", "rangeFn": "update", "request": {"url": "/user/query", "method": "post" }, "params": {}}}
}, {
"label": "取消", "actOn": "dialog", "rangeFn": "close"
}]
},
{ "label": "删除", "actOn": "table", "rangeFn": "delete", "request": { "url": "/api/user/del/${id}", "method": "post", "successCb": { "rangeFn": "update", "request": {"url": "/user/query", "method": "post" }, "params": {}}}}
],
"query": [
{ "label": "重置", "actOn": "query", "rangeFn": "reset", "btnClass": "reset" },
{ "label": "查询", "actOn": "table", "rangeFn": "update", "btnLoading": true, "request": {
"url": "/api/user/query",
"method": "post",
"params": {"pageNum": 1},
"beforeExcuteJsStr": "function(obj){return obj;}(obj)",
"afterExcuteJsStr": "function(obj){obj.body.forEach((el,idx) => {el.name = {'a': idx}});console.log(obj);return obj;}(obj)"
}
}]
}
}
],
"table": [
{ "label": "编辑", "actOn": "dialog", "rangeFn": "open", "showLogic": "obj.roleName === '管理员'", "className": "edit",
"fields": [
{ "label": "成员ID", "prop": "id", "config": { "disabled": true } },
{"label": "成员名称", "prop": "name"},
{"label": "手机号码", "prop": "phone"},
{"label": "角色", "prop": "roleName", "tag": "select", "config":
{
"reqField": "roleId",
"enum": {
"b578e8c0e8295ec295e7cc89447271ab": '管理员',
"9886d03139161f1329dfb6049e3a1910": '普通成员',
"70545e008dd490edea15bdd228e566fc": '超级管理员'
}
}
},
{ "label": "所属部门", "prop": "zoneId", "tag": "asyncSelect", "config":
{
"url": "/api/zone/select", "method": "post", "fieldKey": "name", "fieldValue": "id", "notMsg": true,
}
},
{ "label": "所属组", "prop": "groupId", "tag": "asyncSelect", "config":
{
"url": "/api/group/select", "method": "post", "fieldKey": "name", "fieldValue": "id", "notMsg": true,
}
},
{ "label": "所属岗位", "prop": "postId", "tag": "asyncSelect", "config":
{
"url": "/api/post/select", "method": "post", "fieldKey": "name", "fieldValue": "id", "notMsg": true,
}
},
{ "label": "备注", "prop": "description", "config": { "type": "textarea" } }
],
"operations": [
{ "label": "确认", "rangeFn": "commit", "actOn": "dialog", "isCloseDialog": true, "request":
{
"url": "/member/update", "method": "post", "successCb": {
"actOn": "table", "rangeFn": "update", "request":
{
"url": "/member/list", "method": "post"
}, "params": {}
}
}
},
{ "label": "取消", "actOn": "dialog", "rangeFn": "close" }
]
},
{ "label": "删除", "actOn": "table", "rangeFn": "delete", "request":
{
"url": "/member/delete", "method": "post", "successCb": { "rangeFn": "update", "request": { "url": "/member/list", "method": "post" }, "params": {} }
}
}
],
"query": [
{ "rangeFn": "reset", "label": "重置", "className": "reset"},
{ "label": "查询", "actOn": "table", "rangeFn": "update", "btnLoading": true, "request": {
"url": "/api/member/list",
"method": "post",
"data": {},
"beforeExcuteJsStr": "function(obj){Object.keys(obj).map(el => {obj[el]+=1;console.log(obj)});return obj;}(obj)",
"afterExcuteJsStr": "function(obj){obj.list.map(el => el['phone']+='+86');return obj;}(obj)"
}
}
]
}
}, {
"title": "XXXX",
...
},{
"title": "YYYY",
...
}]
如上所示,一个完整的schema配置得到的页面包含table,dialog,query,operation四部分,包含了项目内的所有功能。