公司用了这个叫作jeecg的快速开发框架,我不知道有多少公司在用这个框架,园子里有的能够吱一声。我的以为这框架惟一优点就是可让不会ssh的人也能进行开发,只要你会J2SE,有web后台发开经验便可。html
框架的优劣这里不作说明,可是官方文档真的写的很粗糙,不少时候须要本身额外添加一些功能的时候会有一点无处下手的感受。接触了一段时间后,也踩了很多的坑,如今记录一下,以飨读者。前端
jeecg版本:3.7.1java
OperateCode
字段,而后在后台菜单管理-页面权限控制中配置相应的规则,接着去角色管理中分配权限,注意checkbox选中状态下为显示该按钮(此处与文档中描述的相反!)。dgToolBar
中对应的funname
中的方法(例如add、update),都在curdtools_zh-cn.js
文件中,写新的方法时能够去那里面复制。<t:datagrid>
中的显示,<t:dgCol/>
中若是有表示状态的字段,数据库可能存int,而显示须要中文,可使用dictionary
属性,若是对应的中文直接添加在系统后台的数据字典中(系统管理-数据字典),则直接dictionary=[字典名称]
;若是数据库中存在代码表,则dictionary=[表名,编码,显示文本]
<t:dictSelect>
,其中typeGroupCode
属性填写数据字典名称。<t:webUploader>
控件,具体代码见Snippets。t:webUploader是h5的,兼容性较好。<t:formvalid>
表单中,须要手动提交表单,须要一个id为btn_sub
的按钮。disabled="disabled"
后,该元素的内容不会提交表单,若是须要提交,但不可编辑,请使用readonly="readonly"
<t:dgOpenOpt>
时注意,默认的openMode
为OpenWin
,须要为其设置width和height,不然报错;OpenTab
时则不须要设置。urlfont
属性为图标设置,能够更换Font Awesome中的全部图标。param
形式,即xxController.do?getList
曾经一度想改为xx/getList
,尝试屡次后失败,事实证实代码关联太强,不推荐修改。(create_time,create_by,create_name,update_time,update_by,update_name等)
,jeecg自带aop绑定,更新时会 自动赋值。具体查看DataBaseConstant.java和HiberAspect.java
。@OneToMany,@ManyToOne
)AuthInterceptor.java和SignInterceptor.java
,在里面添加系统的拦截规则。t_s_muti_lang
中,没法直接在源代码中搜索到。@Transactional(rollbackFor=Exception.class)
。t:datagrid
查询问题,对时间查询,若是时间格式是年月日+时分秒,则没法查询,须要修改代码文件,将原来的区间格式由xxxbegin一、xxxend2
改为xxxbegin一、xxxend2
。t:datagrid
查询问题,若是使用多对多的关系进行查询,直接对字段添加query=true
,若是关系多于二级则没法查询。例如放款表中有一个借款表外键,借款表有一个用户表的外键。在显示的时候,显示字段的field=borrow.user.name
,那么,就算设置了query=true
,查询也是无效的。1.跨域过滤器web
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.全局表单元素的隐藏api
$(":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中选中的行。app
var rowsData = $('#' + id).datagrid('getSelections'); //获取具体的字段名,推荐第二种取值形式,若是使用一对多,name字段名可能长这样(user.id),使用第一种方式会报错 console.log(rowData[0].fieldname) console.log(rowData[0]['fieldname')
7. 选择datagrid中选中的行cors
// 在方法中添加index,控件会自动添加选择的行号 <t:dgFunOpt title="删除" funname="deleteOne()" urlclass="ace_button" urlfont="fa-trash-o"/> function deleteOne(index) { console.log(index); 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(){} }); } } <!-- 方法绑定 --> <input class="inputxt" onclick="selectUser(this,'user');" placeholder="点击选择客户" id="user.mobile" name="appUser.mobile" value="${borrowInfoPage.appUser.mobile}"/>
10.一对多关系的使用
具体例子:借款订单(afl_borrow_info)中存在用户表(afl_user)外键,经过user_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; } }
关联以后,全部的查询(service层)和页面渲染(jsp),均再也不使用user_id
而是使用appUser.id
,别的字段同理。