由于之前的一个项目公司外包出去但是没有做好,于是公司打算自己做,使用的是 JEECG 框架,所以用这篇文章记录下使用这个框架的一些总结,刚开始只是随手记,遇到什么写什么,等内容差不多了再分类整理。
文章有点长,可以根据目录查看相应的模块。在文章的右侧工具栏可以快捷地查看目录:
文章写得还不全, 如果没能解决问题, 可以移步 JEECG 官方文档, 不过官方文档格式有些乱。
Datagrid
是 JEECG 生成列表的通用解决方案之一,用户可以根据自己的需求自定义列表展示的效果:
官网文档 对各个参数介绍很详细,这边主要就贴下整体代码(代码生成的页面和上图无关,上图代码在框架的UserController.java
类的user()
中):
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@include file="/context/mytags.jsp" %>
<t:base type="jquery,easyui,tools,DatePicker"/>
<div class="easyui-layout" fit="true">
<div region="center" style="padding:0;border:0">
<t:datagrid name="userList" checkbox="true" title="用户管理"
actionUrl="userController.do?datagrid&user.departId=${departId}" idField="id" sortName="user.createDate"
rowStyler="gridRowStyler" autoLoadData="false"
fitColumns="false" fit="true" singleSelect="true" queryMode="group">
<t:dgCol title="主键" field="id" hidden="true" queryMode="single" width="120" />
<t:dgCol title="姓名" field="user.name" queryMode="single" width="120" />
<t:dgCol title="人员类型" field="user.userType" queryMode="single" dictionary="biz_user_type,code,name" width="120" />
<t:dgCol title="出生日期" field="user.birthday" formatter="yyyy-MM-dd hh:mm:ss" queryMode="single" width="140" />
<t:dgCol title="手机号" field="user.phone" queryMode="single" width="120" />
<t:dgCol title="性别" field="user.sex" queryMode="single" dictionary="biz_sex_type,code,name" width="120" />
<t:dgCol title="籍贯" field="user.birthplace" queryMode="single" dictionary="p_basic_place,id,name" width="120" />
<t:dgCol title="年龄" field="user.age" queryMode="single" width="100" formatterjs="calcAge"/>
t:datagrid>
<div id="userListtb" style="padding: 3px; height: 27px;border-bottom:1px solid #D7D7D7">
<div style="float: left;" class="searchColums">
姓名:<input class="inuptxt ac_input" type="text" name="user.name" value="${name}">
人员类型:<t:dictSelect field="user.userType" type="list"
extendJson="{class:'form-control',style:'width:158px'}"
datatype="*"
typeGroupCode="biz_user_type"
hasLabel="false" title="人员类型"/>
出生日期:
<input type="text" id="birthday_begin" name="user.birthday_begin" class="Wdate" value="${startTime}" autocomplete="off"
style="width: 145px; height: 20px;" onclick="WdatePicker({dateFmt: 'yyyy-MM-dd',maxDate:'#F{$dp.$D(\'formDate_end\',{d:-1});}'});" /> ~
<input type="text" id="birthday_end" name="user.birthday_end" class="Wdate" value="${endTime}" autocomplete="off"
style="width: 145px; height: 20px; margin-right: 20px;" onclick="WdatePicker({dateFmt: 'yyyy-MM-dd',minDate:'#F{$dp.$D(\'formDate_begin\',{d:1});}'});" />
div>
<div align="right">
<a href="#" class="easyui-linkbutton" iconCls="icon-search" onclick="userListsearch();">查询a>
<a href="#" class="easyui-linkbutton" iconCls="icon-reload" onclick="searchReset('userList')">重置a>
div>
div>
<div id="userListtb2" style="padding: 3px; height: 25px">
<div style="float: left;">
<a href="#" class="easyui-linkbutton" plain="true" icon="icon-chakan" onclick="masterView('userController.do?goView', 'userList', 'user.id');">查看a>
<a href="#" class="easyui-linkbutton" plain="true" icon="icon-remove" onclick="masterDelete('userController.do?doBatchDel', 'userList', 'user.id');">删除a>
div>
div>
div>
div>
<script type="text/javascript">
window.onload = function () {
userListsearch();
};
script>
当页面初始化后框架会自动生成一个 name + search()
的函数,如果 autoLoadData
属性为 true
则页面加载后就会自动调用该函数查询数据并展示出来,如果为false
则需要手动调用该函数查询数据,可以看到「查询」按钮也是绑定该函数。
该框架在生成页面的时候会生成一些函数和样式,所以框架中大部分页面的问题都可以考虑从已经生成的页面上去查看代码,寻找解决方案。
后端查询数据接口 userController.do?datagrid
按照一定的规范进行编写,命名统一是datagrid
:
UserController.java
/**
* easyui AJAX 请求数据
*/
@RequestMapping(params = "datagrid")
public void datagrid(UserEntity user, HttpServletRequest request, HttpServletResponse response, DataGrid dataGrid) {
userService.datagrid(user, request.getParameterMap(), dataGrid);
TagUtil.datagrid(response, dataGrid);
}
UserService.java
// UserEntity 需要对应到数据库中的表
public void datagrid(UserEntity user, Map<String, String[]> parameterMap, DataGrid dataGrid){
CriteriaQuery cq = new CriteriaQuery(UserEntity.class, dataGrid);
//查询条件组装器
org.jeecgframework.core.extend.hqlsearch.HqlGenerateUtil.installHql(cq, user, parameterMap);
try{
//自定义追加查询条件
}catch (Exception e) {
throw new BusinessException(e.getMessage());
}
cq.add();
this.getDataGridReturn(cq, true);
}
UserEntity.java
@Entity
@Table(name = "p_basic_user", schema = "")
public class UserEntity implements Serializable {
属性...
}
AppendGrid
是 JEECG 实现数据列表的又一解决方案,相比 DataGrid
这种列表可以实现动态增加行、删除行、编辑行功能,可以满足特定的业务需求,效果图如下:
这个模块主要是引用了开源的 AppendGrid 插件, 可以看看 官方示例,不知道是不是用的人不多,遇到问题的时候很少能找到解决方案
这里就以上图为例,演示下大致的实现逻辑,有些地方会用注释加以说明(这里由于长度限制省略部分代码):
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@include file="/context/mytags.jsp" %>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<t:base type="jquery,bootstrap,bootstrap-form,common-form,DatePicker"/>
head>
<body>
<div class="container">
<div class="panel-body">
<t:formvalid formid="formObj" dialog="true" layout="table" tiptype="6"
action="storagePlanController.do?doAdd" styleClass="form-horizontal" >
<input type="hidden" name="id" value='${user.id}'>
<div class="row">
<div class="bt-item col-xs-12">
<div class="row">
<div class="col-xs-1 bt-label required-img">供货商:div>
<div class="col-xs-2 bt-content">
<input id="supplierName" name="supplierName" type="text" value='${supplierName}' class="form-control Wpopup"
datatype="*" ignore="checked" onclick="popupClick(this,'id,name','supplierId,supplierName','basic_supplier_select')"/>
<input id="supplierId" name="supplierId" type="hidden" value='${supplierId}'>
div>
<div class="col-xs-1 bt-label required-img">日期:div>
<div class="col-xs-2 bt-content">
<input id="formDate" name="formDate" type="text"
class="form-control Wdate" onClick="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss',isShowToday:true})" datatype="date" ignore="checked"
value="' ${formDate}' type='date' pattern='yyyy-MM-dd HH:mm:ss'/>"/>
div>
单据号...
制单人...
div>
div>
<div class="bt-item col-xs-12">
<div class="row">
<div class="col-xs-1 bt-label required-img">审核:div>
<div class="col-xs-2 bt-content">
<t:userSelect title="审核选择" datatype="*"
selectedNamesInputId="auditUserName"
selectedIdsInputId="auditUserId"
userNamesDefalutVal="${auditUserName}"
userIdsDefalutVal="${auditUserId}"
windowWidth="1000px" windowHeight="600px" />
div>
验收...
仓管...
财务...
div>
div>
div>
<div id="detail_tab" class="tab-wrapper">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active" tab-ajax-url="storagePlanController.do?datagridDetail&field=id,itemEntity.itemCategoryName,itemEntity.id,itemEntity.code,itemEntity.name,itemEntity.specification,itemEntity.unitName,unitPrice,batchNo,quantity,sumAmount&rows=50&user.id=${user.id}">
<a href="#con-wrapper0" role="tab" data-toggle="tab">采购计划物品a>
li>
<li role="presentation" id="tabAddData">
<button id="addDetailBtn" type="button" class="btn btn-default"><span class="glyphicon glyphicon-plus" aria-hidden="true">span>button>
li>
ul>
<div class="tab-content">
<div id="con-wrapper0" role="tabpanel" class="tab-pane active table-responsive">
<table id="tblAppendGrid" class="table">table>
div>
div>
div>
t:formvalid>
div>
div>
<script src="plug-in/layer/layer.js">script>
<script src="plug-in/jquery-plugs/AppendGrid.js">script>
<script type="text/javascript">
var myAppendGrid = null;
var detailList = [];
$(document).ready(function () {
// 1. 初始化 AppendGrid 对象
myAppendGrid = new AppendGrid($.extend({}, AppendGridGlobalParam, {
element: "tblAppendGrid",
idPrefix: "storagePlanDetailList",
maxRowsAllowed: 5,
maxNumRowsReached: function () {
// Show an alert message
alert('You cannot add more than 5 rows!');
},
// columns: 设置字段, 如果必填, 增加 datatype 属性 (可以是任意值)
columns: [
{ name: "id", display: "id", type: "hidden" },
{ name: "itemId", display: "itemId", type: "hidden" },
{ name: "itemCategoryName", display: "类别", type: "readonly", ctrlClass: "form-control input-sm", ctrlAttr: { disabled: true } },
{ name: "itemName", display: "名称", type: "readonly", ctrlClass: "form-control input-sm", ctrlAttr: { disabled: true } },
{ name: "specification", display: "规格", type: "readonly", ctrlClass: "form-control input-sm", ctrlAttr: { disabled: true } },
{ name: "unitName", display: "单位", type: "readonly", ctrlClass: "form-control input-sm", ctrlAttr: { disabled: true } },
{ name: "batchNo", display: "批次", type: "text", ctrlClass: "form-control input-sm", ctrlAttr: { "datatype": "*" } },
{ name: "quantity", display: "数量", type: "number", ctrlClass: "form-control input-sm", ctrlAttr: { "datatype": "num" }, events: {
change: function(e) {// 自定义处理函数, 在值发生变化后触发, 还有 click 事件
// 获取当前行的下标, 从 0 开始
var rowIdx = myAppendGrid.getRowIndex(e.uniqueIndex);
// 获取当前行中的字段值
var quantity = myAppendGrid.getCtrlValue("quantity", rowIdx);
var unitPrice = myAppendGrid.getCtrlValue("unitPrice", rowIdx);
// 设置当前行中某个字段值
myAppendGrid.setCtrlValue("sumAmount", rowIdx, (quantity * unitPrice).toFixed(3));
}
}
},
{ name: "unitPrice", display: "单价", type: "text", ctrlClass: "form-control input-sm", ctrlAttr: { "datatype": "num" }},
{ name: "sumAmount", display: "金额", type: "text", ctrlClass: "form-control input-sm", ctrlAttr: { disabled: true } }
]
}));
// 2. 初始化: 查询列表数据, 并动态添加到列表中
initDetail();
});
function initDetail() {
var tabHead = $("#detail_tab li:first");
// 通过这个地址查询列表数据
var url = tabHead.attr("tab-ajax-url");
$.get(url, {}, function (data) {
detailList = [];
// 组装数据
$.each( data.rows, function(i, n){
var dataMap = {};
dataMap.id = n['id'];
dataMap.itemId = n['itemEntity.id'];
dataMap.itemCategoryName = n['itemEntity.itemCategoryName'];
dataMap.itemCode = n['itemEntity.code'];
dataMap.itemName = n['itemEntity.name'];
dataMap.specification = n['itemEntity.specification'];
dataMap.unitName = n['itemEntity.unitName'];
dataMap.unitPrice = n['unitPrice'];
dataMap.batchNo = n['batchNo'];
dataMap.quantity = n['quantity'];
dataMap.sumAmount = (n['quantity'] * n['unitPrice']).toFixed(3);
detailList.push(dataMap);
});
// 将数据添加到列表中
myAppendGrid.appendRow(detailList);
});
}
script>
body>
html>
先看官方介绍:
报表配置可以通过在线配置,无需编写代码生成数据报表页面。JEECG Online 报表开发是一种配置式的报表开发模式,省去了报表的开发工作,将报表的开发简化为 SQL + 配置的形式。
意思就是在网页上进行配置,然后直接调用就可以生成一张简单的列表页面,如下图中 2 所示:
上图是点击加号后弹出配置好的弹窗,要实现这种效果要分两步走,第一步配置报表的基本信息,第二部在前端页面中引用报表,接下来以上图例子来实现以下。
首先是报表配置,官网介绍的很清楚了,见 Online 数据报表配置,这里简单讲一下:
配置好以后,就在前端页面中编写弹窗代码,先写一个「+」按钮:
<li role="presentation" id="tabAddData">
<button id="addBtn" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-plus" aria-hidden="true">span>
button>
li>
绑定 onclick 事件,事件中进行弹窗:
// 定义 click 事件
$('#addBtn').bind('click', function(){
var departId = $("input[name=departId]").val();
if (isEmpty(departId)) {
tip("请先选择部门。");
return false;
}
// 这里的 'sys_user_select' 就是报表的编码
popupClick2('sys_user_select', departId);
return false;
});
function popupClick(pcode, departId) {
// 该地址格式固定, 可以在后面添加参数如 depart_id
var url = "cgReportController.do?popup&id=" +pcode + "&depart_id="+departId;
$.dialog({
content: "url:"+url,
zIndex: getzIndex(),
lock : true,
title:$.i18n.prop('common.select'),
width:800,
height: 400,
parent:windowapi,
cache:false,
// 定义弹窗中确认按钮的事件
ok: function() {
// 获取弹窗中选择的行
iframe = this.iframe.contentWindow;
var selected = iframe.getSelectRows();
if (selected == '' || selected == null ){
alert($.i18n.prop('common.select.please'));
return false;
}
// 获取当前所以行的关联 ID
var rows = myAppendGrid.getAllValue();
var cascadeIds = "";
if (!isEmpty(rows)) {
for (var i = 0; i < rows.length; i++) {
cascadeIds += rows[i]["cascadeId"] + ",";
}
}
// 本来到这里就差不多了, 可以拿到选择到的行的所有信息, 可以根据自己的需要写回页面
// 下面这些是结合 AppendGrid 动态添加行的代码, 如果没有用到可以忽略
var isSelected = false;
$.each(selected, function(i, n) {
if (cascadeIds.indexOf(n['request_detail_id']) !== -1) {
isSelected = true;
}
});
if (isSelected) {
tip("该人员已存在,请重新选择");
} else {
detailList = [];
$.each( selected, function(i, n) {
if (isEmpty(parseInt(n['name']))) {
tip("数据问题");
return true;
}
var dataMap = {};
dataMap.id = "";
dataMap.cascadeId = n['request_detail_id'];
dataMap.name = n['name'];
dataMap.age = n['age'];
dataMap.phone = n['phone'];
dataMap.address = n['address'];
dataMap.sex = n['sex'];
detailList.push(dataMap);
});
myAppendGrid.appendRow(detailList);
}
return true;
},
cancelVal: $.i18n.prop('dialog.close'),
cancel: true
});
}
到这里就已经完成了一个基本的报表配置和使用了。
点击标签后弹出用户选择弹窗,该弹窗数据来源 t_s_user
表
在使用时需要先引入 JEECG 自定义标签:
<%@ taglib prefix="t" uri="/easyui-tags"%>
<div class="col-xs-1 bt-label required-img">审核:div>
<div class="col-xs-2 bt-content">
<t:userSelect title="审核选择" datatype="*"
selectedNamesInputId="auditByName"
selectedIdsInputId="auditBy"
userNamesDefalutVal="${auditByName}"
userIdsDefalutVal="${auditBy}"
windowWidth="1000px" windowHeight="600px" />
div>
这是 JEECG 自定义的标签,如下图:
对应的处理该标签的 Java 类是
标签中的UserSelectTag
类,该类继承了 JSP 包中的TagSupport
类,并重写了父类的方法。在doEndTag()
中将end()
组装的页面进行输出,该页面作用是弹窗,定义「确定」按钮绑定的事件
这里还有一个userUrl
参数, 通过它就可以获取弹窗内显示的具体内容, 也就是人员选择页面。
JEECG 框架导入导出表格主要是通过@Excel
注解来完成的, 注解作用在属性上, 这里说下具体使用步骤。
前端部分主要就是写一个导出按钮, 有两种写法:
<div id="storageOutListtb2" style="padding: 3px; height: 25px">
<div style="float: left;">
<a href="#" class="easyui-linkbutton" plain="true" icon="icon-putout"
onclick="ExportXls('storageStoreController.do?exportXls', 'storageStoreList','id')">导出a>
div>
div>
<t:datagrid name="storagePlanList" checkbox="true" ...>
<t:dgCol title="主键" field="id" hidden="true" queryMode="single" width="120" />
<t:dgCol title="单据号" field="formNo" queryMode="single" query="true" width="120" />
...
<t:dgToolBar title="导出" icon="icon-putout" onclick="\"masterExport('storageStoreController.do?exportXls', 'storageStoreList', 'id');\"" funname="ExportXls" />
t:datagrid>
生成的按钮如图:
两种方法中的onclick
绑定了两种方法, 它们的实现都代码基本一致, 在common.js
中; 方法都需要有三个入参, 第一个入参是进行导出处理的 URL, 第二个参数是datagrid
列表的name
属性值, 第三个参数是传递到后端的参数名。
后端代码主要分三步:
@Excel
进行注解接下来使用一个完整的例子来说明, 首先是对需要导出的字段添加@Excel
注解:
public class SysUser implements Serializable {
/**id*/
private String id;
/**登录账号 */
@Excel(name = "登录账号", width = 15)
private String username;
/**真实姓名*/
@Excel(name = "真实姓名", width = 15)
private String realname;
/**生日*/
@Excel(name = "生日", width = 15, format = "yyyy-MM-dd")
private Date birthday;
/**性别(1:男 2:女)*/
@Excel(name = "性别", width = 15,dicCode="sex")
private Integer sex;
/**状态 (1:正常 2:冻结)*/
@Excel(name = "状态", width = 15,replace={"正常_1","冻结_0"})
private Integer status;
// 忽略 getter、setter 方法
}
不需要导出的不用加注解,注解的各个属性说明可以参照 JEECG Excel 注解说明。
接着在 service 层查询需要导出的数据, 可以直接使用 SQL 进行查询或者 JEECG 统一的datagrid()
查询:
/**
* 入参说明参考下面第三点里的代码便知
*/
public void datagrid(SysUser user, Map<String, String[]> parameterMap, DataGrid dataGrid) {
CriteriaQuery cq = new CriteriaQuery(SysUser.class, dataGrid);
//查询条件组装器
org.jeecgframework.core.extend.hqlsearch.HqlGenerateUtil.installHql(cq, SysUser, parameterMap);
try {
//自定义追加查询条件
String birthday= StringUtil.getRequestParamValue(parameterMap, "birthday");
cq.createAlias("sysUser", "sysUser");
if (StringUtils.isNotBlank(birthday)) {
cq.ge("sysUser.birthday", DateUtils.parseDate(birthday, "yyyy-MM-dd"));
}
cq.add();
} catch (Exception e) {
throw new BusinessException(e.getMessage());
}
cq.add();
this.getDataGridReturn(cq, true);
}
最后在 controller 层将所有数据组装起来:
/**
* 导出 Excel
*/
@RequestMapping(params = "exportXls")
public String exportXls(SysUser user, HttpServletRequest request, DataGrid dataGrid, ModelMap map) {
// 1. 查询数据, 对象的属性必须带有 @Excel 注解, 否则无法导出
userService.datagrid(user, request.getParameterMap(), dataGrid);
List<SysUser> dataList = dataGrid.getResults();
// 2. 将基本信息组装起来
String format = org.apache.tools.ant.util.DateUtils.format(currentTimeMillis(), "yyyy-MM-dd");
map.put(NormalExcelConstants.FILE_NAME, "用户表"+format);
// 要导出的对象类信息
map.put(NormalExcelConstants.CLASS, SysUser.class);
String tableName = "用户表";
map.put(NormalExcelConstants.PARAMS, new ExportParams(tableName, "导出人:" + ResourceUtil.getSessionUser().getUserName(),
"导出信息"));
map.put(NormalExcelConstants.DATA_LIST, dataList);
// 3. 跳转导出
return NormalExcelConstants.JEECG_EXCEL_VIEW;
}
不能将
Map
类型作为数据来导出, 因为缺少@Excel
注解, 框架无法解析
用来注解 Dao 层接口的自定义注解, 如
该注解对应的注解处理器是MiniDaoHandler.java
, 被注解所标注接口中的方法如果有加@Sql
注解则查询用的 SQL 语句就在其属性中, 若只有@ResultType
注解, 则需要创建一个名字为接口名_方法名.sql
的 SQL 文件, 如:
这里有一个getDepartAuthGroupList()
方法, 则它所对应的文件是DepartAuthGroupDao_getDepartAuthGroupList.sql
, 文件中只有简单的一句查询语句: