公司用了这个叫做 jeecg 的快速开发框架,我不知道有多少公司在用这个框架,园子里有的可以吱一声.个人觉得这框架唯一优势就是可以让不会 ssh 的人也能进行开发,只要你会 J2SE,有 web 后台发开经验即可.
框架的优劣这里不做说明,但是官方文档真的写的很粗糙,很多时候需要自己额外添加一些功能的时候会有一点无处下手的感觉.接触了一段时间后,也踩了不少的坑,现在记录一下,以飨读者.
jeecg 版本:3.7.1
Tips
前端
权限管理设置中,按钮权限需要对相应的按钮设置 OperateCode 字段,然后在后台菜单管理 - 页面权限控制中配置相应的规则,接着去角色管理中分配权限,注意 checkbox 选中状态下为显示该按钮(此处与文档中描述的相反!).
dgToolBar 中对应的 funname 中的方法(例如 add,update),都在 curdtools_zh-cn.js 文件中,写新的方法时可以去那里面复制.
针对于 中的显示, 中如果有表示状态的字段,数据库可能存 int,而显示需要中文,可以使用 dictionary 属性,如果对应的中文直接添加在系统后台的数据字典中(系统管理 - 数据字典),则直接
dictionary = [字典名称]
;如果数据库中存在代码表,则
dictionary = [表名, 编码, 显示文本]
针对于表单中的显示,状态选择可以使用下拉控件 <t:dictSelect>,其中 typeGroupCode 属性填写数据字典名称.
文件上传推荐使用 <t:webUploader> 控件,具体代码见 Snippets.t:webUploader 是 h5 的,兼容性较好.
在 <t:formvalid> 表单中,需要手动提交表单,需要一个 id 为 btn_sub 的按钮.
表单页面中,设置 input 设置
disabled="disabled"
后,该元素的内容不会提交表单,如果需要提交,但不可编辑,请使用
readonly="readonly"
使用 <t:dgOpenOpt> 时注意,默认的 openMode 为 OpenWin,需要为其设置 width 和 height,否则报错;OpenTab 时则不需要设置.
jeecg 所有封装的控件的 urlfont 属性为图标设置,可以更换 Font Awesome 中的所有图标.
后台
SpringMVC 路由默认采用 param 形式,即
xxController.do ? getList
曾经一度想改成 xx/getList,尝试多次后失败,事实证明代码关联太强,不推荐修改.
数据表设计中如果包含添加人,添加时间的可以直接使用 jeecg 指定字段
(create_time, create_by, create_name, update_time, update_by, update_name等)
,jeecg 自带 aop 绑定,更新时会 自动赋值.具体查看
DataBaseConstant.java和HiberAspect.java
.
GUI 代码生成器中,若 pk 为 uuid,主键生成策略选择 uuid,若为自增的 id,则选择 identity.
GUI 代码生成器中,form 风格个人推荐选择 div 风格,使用表格时,Validform 会有坑.
GUI 代码生成器中,推荐使用一对一关系来建表,需要一对多等别的关系时,可以添加注解来实现(
路由的全局拦截器文件为
@OneToMany,
@ManyToOne)
AuthInterceptor.java和SignInterceptor.java
,在里面添加系统的拦截规则.
后台可以配置过滤器来解决全局跨域问题.代码见 Snippets.
清理 jeecg 自带版本号和 logo 信息,注意他的国际化内容,文字信息均存在数据表 t_s_muti_lang 中,无法直接在源代码中搜索到.
定时任务有 bug,暂未解决,存在实例化多次的情况.
事务处理,添加注解
1. 跨域过滤器
@Transactional(rollbackFor = Exception.class)
Snippets
} < !--方法绑定--><input class = "inputxt"onclick = "selectUser(this,'user');"placeholder = "点击选择客户"id = "user.mobile"name = "appUser.mobile"value = "${borrowInfoPage.appUser.mobile}" / >
public class CorsFilter implements Filter {@Override public void init(FilterConfig filterConfig) throws ServletException {}@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,
ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.addHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Appkey");
filterChain.doFilter(servletRequest, servletResponse);
}@Override public void destroy() {}
} < !--web.xml中配置--><filter > <filter - name > cors < /filter-name>
<filter-class>cn.crenative.afloan.core.controller.CorsFilter</filter - class > </filter>
<filter-mapping>
<filter-name>cors</filter - name > <url - pattern >
/*</url-pattern>
</filter-mapping>
2. 文件上传
@RequestMapping(params = "doUpload", method = RequestMethod.POST)@ResponseBody public AjaxJson doUpload(MultipartHttpServletRequest request, String path) {
logger.info("后台上传文件");
AjaxJson j = new AjaxJson();
String fileName = null;
String ctxPath = request.getSession().getServletContext().getRealPath(path);
File file = new File(ctxPath);
if (!file.exists()) {
file.mkdir(); // 创建文件根目录
}
Map < String,
MultipartFile > fileMap = request.getFileMap();
for (Map.Entry < String, MultipartFile > entity: fileMap.entrySet()) {
MultipartFile mf = entity.getValue(); // 获取上传文件对象
fileName = mf.getOriginalFilename(); // 获取文件名
String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String newFileName = df.format(new Date()) + "_" + new Random().nextInt(1000) + "." + fileExt;
String savePath = file.getPath() + "/" + newFileName; // 上传后的文件绝对路径
System.out.println("上传后路径:" + savePath);
File savefile = new File(savePath);
try {
// String imageUrl = "http://" + request.getServerName() + ":" + request.getLocalPort() + request.getContextPath() + path + "/" + newFileName;
String imageUrl = request.getContextPath() + path + "/" + newFileName;
logger.info("输出路径:" + imageUrl);
mf.transferTo(savefile);
j.setObj(imageUrl);
} catch(IOException e) {
e.printStackTrace();
}
}
j.setMsg("上传成功");
return j;
}
<t:webUploader url="upload.do?doUpload&path=[相对路径]" name="[数据库字段]" extensions="" auto="true" pathValues="${后端set的attribute名称}"/>
<!-- eg:-->
<t:webUploader url="upload.do?doUpload&path=/upload/afloan/users/attachment" name="credentialPhoto" extensions="" auto="true" pathValues="${attachmentPage.credentialPhoto}"/>
3.dictSelect
<t:dictSelect field="credentialType" type="select" defaultVal="${attachmentPage.credentialType}" typeGroupCode="attachment" hasLabel="false"/>
4. 全局表单元素的隐藏
$(":input").attr("disabled", "true");
$('select').attr('disabled', true);
5. 添加一个提示的窗口
layer.open({
title: title, //弹窗title
content: content, //弹窗内容
icon: 7,
yes: function (index) {
//回调函数
},
btn: ['确定', '取消'],
btn2: function (index) {
layer.close(index);
}
});
6. 选择 datagrid 中选中的行.
var rowsData = $('#' + id).datagrid('getSelections');
//获取具体的字段名,推荐第二种取值形式,如果使用一对多,name字段名可能长这样(user.id),使用第一种方式会报错
console.log(rowData[0].fieldname)
console.log(rowData[0]['fieldname')
7. 选择 datagrid 中选中的行
// 在方法中添加index,控件会自动添加选择的行号
<t:dgFunOpt title="删除" funname="deleteOne()" urlclass="ace_button" urlfont="fa-trash-o"/>
function deleteOne(index) {
console.log(index);![Alt text](./popup.gif)
var row = $("#usersList").datagrid('getData').rows[index];
}
8. 添加一个新的标签页
//function addOneTab(subtitle, url, icon),该方法定义在curdtools_zh-cn.js中
function openAuditTab(id, mobile) {
addOneTab("用户" + mobile + "的档案", "userInfo?userInfo&mode=claim&userId=" + id);
}
9.popup,弹框选择相应的记录,并回调到父页面.
//设置表单内容
function setUser(obj, rowTag, selected) {
if (selected == '' || selected == null) {
alert("请选择");
return false;
} else {
var str = "";
var name = "";
var idNo = "";
$.each(selected,
function(i, n) {
str += n.mobile;
name += n.realName;
idNo += n.idcardNo;
});
$("input[id='" + rowTag + ".mobile']").val(str);
$("input[id='" + rowTag + ".realName']").val(name);
$("input[id='" + rowTag + ".idcardNo']").val(idNo);
return true;
}
}
/**
* 弹出popup窗口获取
* @param obj
* @param rowTag 行标记
* @param code 动态报表配置ID
*/
function selectUser(obj, rowTag) {
if (rowTag == null) {
alert("popup参数配置不全");
return;
}
console.log($('#mobile').val());
var inputClickUrl = basePath + "/users?userSelect";
if (typeof(windowapi) == 'undefined') { //页面弹出popup
$.dialog({
content: "url:" + inputClickUrl,
zIndex: getzIndex(),
lock: true,
title: "选择客户",
width: 1000,
height: 300,
cache: false,
ok: function() {
iframe = this.iframe.contentWindow;
var selected = iframe.getSelectRows(); //重要,此处获取行数据
return setUserMobile(obj, rowTag, selected);
},
cancelVal: '关闭',
cancel: true //为true等价于function(){}
});
} else { //popup内弹出popup
$.dialog({
content: "url:" + inputClickUrl,
zIndex: getzIndex(),
lock: true,
title: "选择客户",
width: 1000,
height: 300,
parent: windowapi,
//设置弹出popup的openner
cache: false,
ok: function() {
iframe = this.iframe.contentWindow;
var selected = iframe.getSelectRows(); //重要,此处获取行数据
return setUserMobile(obj, rowTag, selected);
},
cancelVal: '关闭',
cancel: true //为true等价于function(){}
});
}
10. 一对多关系的使用
具体例子:借款订单(afl_borrow_info)中存在用户表(afl_user)外键,通过 user_id 关联.
关联之后,所有的查询(service 层)和页面渲染(jsp),均不再使用 user_id 而是使用 appUser.id,别的字段同理.
@Entity
@Table(name = "afl_borrow_info", schema = "")
@DynamicUpdate(true)
@DynamicInsert(true)
@SuppressWarnings("serial")
public class BorrowInfoEntity implements java.io.Serializable {
private UsersEntity appUser;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
public UsersEntity getAppUser() {
return appUser;
}
public void setAppUser(UsersEntity appUser) {
this.appUser = appUser;
}
}
来源: https://www.cnblogs.com/Sinte-Beuve/p/8290200.html