1. 简介
EzAdmin 列表表单工具使用 JSON 配置列表与表单页面。一个 JSON 对应一个页面,修改配置后即可展示,适合快速开发和维护后台业务页面,内置 layui 框架,可以修改为任意其他UI框架,支持Skills配置。
本系统的 DSL 配置位于 src/main/resources/topezadmin/config/layui/dsl/:列表放在 list/,表单放在 form/。页面中的查询和保存逻辑由 Express 脚本完成。
2. 快速开始
下面以“学生管理”为例,只需要引入依赖、新建列表和表单 JSON、增加配置,启动后即可访问。
2.1 引入 Maven 包
在项目的 pom.xml 中加入 EzAdmin Starter;本系统使用的版本为 3.1.2。
<dependency>
<groupId>io.github.ezadmin126</groupId>
<artifactId>ezadmin-java17-spring-starter</artifactId>
<version>最新版</version>
</dependency>
2.2 新增学生列表和学生表单
分别创建 list/page-student.json 和 form/page-student.json。示例默认使用 T_STUDENT 表,表中需要有 ID、STUDENT_NO、STUDENT_NAME、GENDER、MAJOR、DELETE_FLAG 字段。
学生列表:list/page-student.json
{
"id": "student-list",
"name": "学生列表",
"dataSource": "dataSource",
"body": {"emptyShow": "-", "showIndex": true, "selectable": false, "rowActionWidth": 160},
"search": [{"row": [
{"item_name": "STUDENT_NAME", "label": "姓名", "component": "input", "operator": "like"},
{"item_name": "MAJOR", "label": "专业", "component": "input", "operator": "like"}
]}],
"column": [
{"item_name": "STUDENT_NO", "label": "学号", "component": "tdText"},
{"item_name": "STUDENT_NAME", "label": "姓名", "component": "tdText"},
{"item_name": "GENDER", "label": "性别", "component": "tdSelect", "initData": {"dataJson": [{"value": 1, "label": "男"}, {"value": 2, "label": "女"}]}},
{"item_name": "MAJOR", "label": "专业", "component": "tdText"}
],
"tableButton": [{"item_name": "新增", "label": "新增", "component": "button-table", "props": {"url": "form/page-student/student-form", "opentype": "MODAL", "windowname": "新增学生"}}],
"rowButton": [{"item_name": "编辑", "label": "编辑", "component": "button-normal", "props": {"url": "form/page-student/student-form?ID={{= d.ID}}", "opentype": "MODAL", "windowname": "编辑学生"}}],
"express": {
"main": "StringBuilder sql = new StringBuilder();\nsql.append(\"SELECT A.ID,A.STUDENT_NO,A.STUDENT_NAME,A.GENDER,A.MAJOR \");\nsql.append(\"FROM T_STUDENT A WHERE A.DELETE_FLAG=0\");\nreturn search(sql);",
"orderBy": "ORDER BY A.ID DESC",
"groupBy": ""
},
"appendHead": "",
"appendFoot": ""
}
学生表单:form/page-student.json
{
"id": "student-form",
"name": "学生表单",
"dataSource": "dataSource",
"successUrl": "reload",
"cardList": [{
"type": "card",
"label": "基本信息",
"fieldList": [{"row": [
{"item_name": "STUDENT_NO", "label": "学号", "component": "input", "classAppend": "layui-col-md12", "props": {"validate": {"rule": {"required": true}, "message": {"required": "请输入学号"}}}},
{"item_name": "STUDENT_NAME", "label": "姓名", "component": "input", "classAppend": "layui-col-md12", "props": {"validate": {"rule": {"required": true}, "message": {"required": "请输入姓名"}}}},
{"item_name": "GENDER", "label": "性别", "component": "radio", "classAppend": "layui-col-md12", "initData": {"dataJson": [{"value": 1, "label": "男"}, {"value": 2, "label": "女"}]}},
{"item_name": "MAJOR", "label": "专业", "component": "input", "classAppend": "layui-col-md12"}
]}],
"buttonList": []
}],
"buttonList": [],
"initExpress": "StringBuilder sql = new StringBuilder();\nsql.append(\"SELECT ID,STUDENT_NO,STUDENT_NAME,GENDER,MAJOR \");\nsql.append(\"FROM T_STUDENT WHERE ID=${ID} AND DELETE_FLAG=0\");\nreturn select(sql).get(0);",
"submitExpress": "import top.ezadmin.plugins.express.jdbc.InsertParam;\nimport top.ezadmin.plugins.express.jdbc.UpdateParam;\n\nif (isBlank(\"ID\")) {\n param = new InsertParam();\n param.table(\"T_STUDENT\");\n param.add(\"#{STUDENT_NO}\");\n param.add(\"#{STUDENT_NAME}\");\n param.add(\"#{GENDER}\");\n param.add(\"#{MAJOR}\");\n param.add(\"#{DELETE_FLAG,value=0}\");\n return insertSimple(param);\n}\nparam = new UpdateParam();\nparam.table(\"T_STUDENT\");\nparam.add(\"#{STUDENT_NO}\");\nparam.add(\"#{STUDENT_NAME}\");\nparam.add(\"#{GENDER}\");\nparam.add(\"#{MAJOR}\");\nparam.where(\"WHERE ID=#{ID}\");\nupdateSimple(param);\nreturn $(\"ID\");",
"deleteExpress": "",
"statusExpress": [],
"appendHead": "",
"appendFoot": ""
}
2.3 增加 topezadmin 配置
在 application.yml 中增加或确认以下配置:
topezadmin:
datasourceBeanNames: dataSource
prefixUrl: /p/
启动应用后,访问 /p/list/page-student.json 和 /p/form/page-student.json 即可。
3. 组件介绍
每个字段通过 item_name 关联参数或数据库字段,通过 label 显示名称,通过 component 选择渲染方式。下表以框架 component 目录为准,列出全部 36 个内置组件;项目自行注册的业务组件不在此清单内。
3.1 搜索组件
| 组件 | 场景 | 说明 |
|---|---|---|
input | 文本、编码、名称搜索 | 最常用;文本条件通常配 operator: "like"。 |
inputrange | 输入值区间 | 用于数值范围类条件,按项目已有页面确认参数格式后使用。 |
select | 单选条件 | 固定选项用 dataJson,数据库选项用 dataSql。 |
select-multiple / xmselect | 多选或可搜索条件 | 同一个多选控件的两个名称;大数据量表单可配 remoteSearch 与 dataUrl。 |
cascader | 分类、地区树条件 | 内部使用 lay-Cascader;查询 SQL 必须返回 parent_id、value、label。 |
date | 单日期或时间范围 | 列表范围查询使用 operator: "BETWEEN" 与 props.range: true。 |
unioninput | 合并文本搜索 | 下拉选择字段后输入关键字,例如名称、型号、规格共用一个搜索框。 |
uniondate | 合并日期搜索 | 下拉选择日期字段后输入同一时间范围,例如下单、审核、发货时间。 |
hidden | 搜索辅助参数 | 配合 unioninput / uniondate 承接真实查询字段,不展示在搜索栏。 |
3.2 表单与详情组件
| 组件 | 用途 | 常用配置 |
|---|---|---|
input | 单行输入 | placeholder、maxlength、props.validate。 |
textarea | 备注、说明 | rows、maxlength,通常使用 layui-col-md12。 |
hidden | 主键、父表外键 | 不展示但参与提交;主键通常为 layui-col-md0。 |
span / select-span | 只读文本、枚举、外键名称 | 前者用于普通文本,后者按 dataJson 或 dataSql 映射名称。 |
date / date-span | 编辑、只读日期 | 日期字段使用 jdbcType: "DATE" 和 yyyy-MM-dd 格式。 |
select / radio / checkbox | 枚举选择 | select 支持动态数据;radio 适合少量单选;checkbox 适合少量平铺多选。 |
select-multiple / xmselect | 多选、远程搜索 | 支持 dataSql 或 dataUrl,max: 1 可限制单选。 |
cascader | 级联选择 | 内部使用 lay-Cascader;props.props.checkStrictly 控制是否允许选择非叶子节点。 |
pop-list-select | 弹窗跨页选择 | 打开列表页选择一行或多行,下方表格回显选中数据。 |
tinymce | 富文本正文 | 保存 HTML 字符串,通常占满一行。 |
upload / upload-span | 上传、只读附件展示 | 前者控制文件类型、数量和校验;后者仅展示已有附件。 |
cardList → fieldList → row 是表单的固定层级。一个 row 内放一组字段,字段的 classAppend 控制栅格宽度;简单业务直接使用 layui-col-md12 即可。
3.3 列表列渲染组件
| 组件 | 用途 |
|---|---|
tdText | 普通文本、日期、数字;日期和数值按 jdbcType、props.format 格式化。 |
tdUText | HTML 文本展示;只用于内容已可信的场景,避免直接展示未经处理的用户输入。 |
tdLink | 可点击文本链接,通过 props.url、opentype 打开页面。 |
tdSelect / tdSelectMultiple | 单值枚举、逗号分隔多值枚举展示。 |
tdCascader | 把级联 ID 展示为完整路径,例如“一级分类 / 二级分类”。 |
tdPic | 图片或附件缩略图,支持 width、minWidth。 |
3.4 按钮组件
| 组件 | 使用位置 | 用途 |
|---|---|---|
button-table / button-toolbar | tableButton | 列表上方主操作和工具栏操作。 |
button-normal / button-single | rowButton | 常规行操作和强调样式行操作。 |
button-bread / button-span | rowButton | 面包屑样式操作或仅展示文本。 |
button-dropdown | tableButton / rowButton | 收纳次要操作的下拉菜单。 |
button-form | 表单 | 表单内置操作按钮组件;常规保存、返回优先沿用系统默认按钮。 |
按钮通过 props.opentype 决定行为:FORM 为全屏表单,MODAL 为弹框,CONFIRM_AJAX_LIST 为确认后提交并刷新列表。iframe 子列表中打开弹框使用 parent.MODAL。
4. 常见业务处理
4.1 多选与级联:xmselect / lay-Cascader
xmselect 是 select-multiple 的别名;cascader 内部使用 lay-Cascader。二者都可作为搜索项、表单字段和列表展示的配套组件:搜索使用原组件,列表分别使用 tdSelectMultiple 与 tdCascader。
// list.search:品牌多选与分类级联搜索
{"item_name":"BRAND_ID","label":"品牌","component":"select-multiple","operator":"like",
"initData":{"dataSql":"SELECT BRAND_ID value, BRAND_NAME label FROM T_BASE_BRAND WHERE COMPANY_ID=${COMPANY_ID} AND DELETE_FLAG=0","dataSource":"dataSource"}}
{"item_name":"CATEGORY_ID","label":"分类","component":"cascader","operator":"like",
"initData":{"dataSql":"SELECT PARENT_ID parent_id, CATEGORY_ID value, CATEGORY_NAME label FROM T_BASE_CATEGORY WHERE COMPANY_ID=${COMPANY_ID} AND DELETE_FLAG=0","dataSource":"dataSource"}}
// list.column:多选值与级联路径展示
{"item_name":"BRAND_IDS","label":"品牌","component":"tdSelectMultiple",
"initData":{"dataSql":"SELECT BRAND_ID value, BRAND_NAME label FROM T_BASE_BRAND WHERE DELETE_FLAG=0","dataSource":"dataSource"}}
{"item_name":"CATEGORY_ID","label":"分类","component":"tdCascader",
"initData":{"dataSql":"SELECT PARENT_ID parent_id, CATEGORY_ID value, CATEGORY_NAME label FROM T_BASE_CATEGORY WHERE DELETE_FLAG=0","dataSource":"dataSource"}}
// form:大数据量厂家远程搜索,以及可选任意层级的地区
{"item_name":"MANUFACTURER_ID","label":"厂家","component":"xmselect","classAppend":"layui-col-md12",
"props":{"max":1,"remoteSearch":true},"initData":{"dataUrl":"/jxc/manufacturer/search.html"}}
{"item_name":"REGION_ID","label":"地区","component":"cascader","classAppend":"layui-col-md12",
"initData":{"dataSql":"SELECT REGION_FULL_ID value, PARENT_ID parent_id, REGION_FULL_NAME label FROM T_BASE_REGION WHERE DELETE_FLAG=0","dataSource":"dataSource"},
"props":{"props":{"checkStrictly":true}}}
parent_id、value、label。多选下拉数据量很大时改用 remoteSearch: true 与业务接口 dataUrl,不要用 dataSql 一次性加载全表。4.2 表单内弹框跨页选择:pop-list-select
pop-list-select 用于打开一个列表页选择一行或多行。组件在隐藏字段保存 ID,按钮下方自动以表格回显 displayFields;编辑页会按 initUrl 回查并回显已有 ID。
{
"item_name": "MANUFACTURER_IDS",
"label": "生产厂家",
"component": "pop-list-select",
"classAppend": "layui-col-md12",
"props": {
"url": "list/page-med/manufacturer-select",
"idField": "ID",
"multiple": true,
"displayFields": "MANUFACTURER_NAME,PRODUCT_COMPANY_LICENCE",
"title": "请选择生产厂家",
"width": "900px",
"height": "620px"
}
}
url指向选择列表;默认初始化接口将list/page-转为list/data-,路由不同时显式配置initUrl。- 单选保存一个 ID,多选保存逗号分隔的 ID;选择列表接口的每行必须返回
idField。 - 同一表单多个弹框选择器时,
item_name必须不同。外部脚本可使用window.EZ_POP_LIST_SELECT.setValue()、clear()设置或清空选值。
4.3 富文本:tinymce
正文、公告等长篇内容使用 tinymce,保存的是 HTML 字符串。字段占满一行,提交时直接作为普通文本字段保存;列表不要直接展示完整正文,应由查询 SQL 截取摘要后使用 tdText 展示。
{
"item_name": "CONTENT",
"label": "内容",
"component": "tinymce",
"classAppend": "layui-col-md12"
}
4.4 图片上传与自定义上传、下载请求
DSL 中的 upload 控制字段级限制,上传和下载请求地址由 topezadmin 的全局配置注入到页面。需要接入自有对象存储、文件服务时,修改这两个地址并让自定义接口兼容当前页面的上传、下载响应格式;不要在单个字段的 props 中假设存在 uploadUrl 或 downloadUrl 覆盖项。
// application.yml:自定义全局上传、下载请求
topezadmin:
uploadUrl: /system/upload.html
downloadUrl: /core/downloadDesc.html?ossId=
// form 字段:只定义字段级限制
{
"item_name": "PROD_PIC_IDS",
"label": "产品图片",
"component": "upload",
"classAppend": "layui-col-md12",
"props": {
"accept": "images",
"multiple": true,
"size": 2048,
"number": 5,
"description": "支持图片,最多上传 5 张,每张不超过 2MB",
"validate": {"rule": {"uploadMax": 5}, "message": {"uploadMax": "最多上传 5 张图片"}}
}
}
number 是组件层的数量限制;同时配置 validate.rule.uploadMax 才能在提交时可靠阻止超量。字段保存的是 OSS 文件 ID 或逗号分隔 ID,不保存文件二进制。4.5 合并搜索项
unioninput 与 uniondate 把多个同类查询字段合并为一个控件。options 指定下拉可选字段,value 为真正的查询字段,operator 指定该字段的比较方式;对应真实字段作为 hidden 搜索项保留。
{
"item_name": "PROD_NAME_PROD_MODEL_PROD_SPEC",
"label": "名称/型号/规格",
"component": "unioninput",
"operator": "like",
"props": {"placeholder": "请输入名称、型号、规格"},
"options": [
{"label": "产品名称", "value": "PROD_NAME", "operator": "like"},
{"label": "产品型号", "value": "PROD_MODEL", "operator": "like"},
{"label": "产品规格", "value": "PROD_SPEC", "operator": "like"}
]
}
{"item_name":"PROD_NAME","component":"hidden","operator":"like"}
{"item_name":"PROD_MODEL","component":"hidden","operator":"like"}
{"item_name":"PROD_SPEC","component":"hidden","operator":"like"}
合并日期将 component 换为 uniondate,options 的 value 改为日期列名,并使用 operator: "BETWEEN"、props.range: true。
4.6 自定义搜索与选择搜索项
先使用 search[].row[] 中的标准组件表达可由框架自动拼接的条件。需要让用户选择搜索字段时使用 unioninput / uniondate;需要自定义 SQL、关联表、状态组合或权限条件时,将主查询拆到 express.main 对应的 .ql 文件中实现。
{
"search": [{"row": [
{"item_name":"KEYWORD","label":"关键字","component":"input",
"props":{"placeholder":"姓名、学号或专业"}}
]}],
"express": {
"main": "@import(student-list_express_main.ql)",
"orderBy": "ORDER BY A.ID DESC",
"groupBy": ""
}
}
- 单字段条件优先放在
search中,并配置正确的operator,由search(sql)统一处理分页和条件。 - 一个关键字匹配多个字段、跨表查询、数据权限等复杂场景,在
.ql中读取请求参数并按同模块已有的参数绑定、查询工具写法组合 SQL。 - 自定义实现必须处理空值和权限条件,不能直接把未经处理的用户输入通过字符串拼接进 SQL。