Jeecg踩坑不徹底指南

公司用了這個叫作jeecg的快速開發框架,我不知道有多少公司在用這個框架,園子裏有的能夠吱一聲。我的以爲這框架惟一優點就是可讓不會ssh的人也能進行開發,只要你會J2SE,有web後臺發開經驗便可。html

框架的優劣這裏不作說明,可是官方文檔真的寫的很粗糙,不少時候須要本身額外添加一些功能的時候會有一點無處下手的感受。接觸了一段時間後,也踩了很多的坑,如今記錄一下,以饗讀者。前端

jeecg版本:3.7.1java

Tips

前端

  1. 權限管理設置中,按鈕權限須要對相應的按鈕設置OperateCode字段,而後在後臺菜單管理-頁面權限控制中配置相應的規則,接着去角色管理中分配權限,注意checkbox選中狀態下爲顯示該按鈕(此處與文檔中描述的相反!)。
  2. dgToolBar中對應的funname中的方法(例如add、update),都在curdtools_zh-cn.js文件中,寫新的方法時能夠去那裏面複製。
  3. 針對於<t:datagrid>中的顯示,<t:dgCol/>中若是有表示狀態的字段,數據庫可能存int,而顯示須要中文,可使用dictionary屬性,若是對應的中文直接添加在系統後臺的數據字典中(系統管理-數據字典),則直接dictionary=[字典名稱];若是數據庫中存在代碼表,則dictionary=[表名,編碼,顯示文本]
  4. 針對於表單中的顯示,狀態選擇可使用下拉控件<t:dictSelect>,其中typeGroupCode屬性填寫數據字典名稱。
  5. 文件上傳推薦使用<t:webUploader>控件,具體代碼見Snippets。t:webUploader是h5的,兼容性較好。
  6. <t:formvalid>表單中,須要手動提交表單,須要一個id爲btn_sub的按鈕。
  7. 表單頁面中,設置input設置disabled="disabled" 後,該元素的內容不會提交表單,若是須要提交,但不可編輯,請使用readonly="readonly"
  8. 使用<t:dgOpenOpt>時注意,默認的openModeOpenWin,須要爲其設置width和height,不然報錯;OpenTab時則不須要設置。
  9. jeecg全部封裝的控件的urlfont屬性爲圖標設置,能夠更換Font Awesome中的全部圖標。

後臺

  1. SpringMVC路由默認採用param形式,即xxController.do?getList曾經一度想改爲xx/getList,嘗試屢次後失敗,事實證實代碼關聯太強,不推薦修改。
  2. 數據表設計中若是包含添加人,添加時間的能夠直接使用jeecg指定字段(create_time,create_by,create_name,update_time,update_by,update_name等),jeecg自帶aop綁定,更新時會 自動賦值。具體查看DataBaseConstant.java和HiberAspect.java
  3. GUI代碼生成器中,若pk爲uuid,主鍵生成策略選擇uuid,若爲自增的id,則選擇identity。
  4. GUI代碼生成器中,form風格我的推薦選擇div風格,使用表格時,Validform會有坑。
  5. GUI代碼生成器中,推薦使用一對一關係來建表,須要一對多等別的關係時,能夠添加註解來實現(@OneToMany,@ManyToOne
  6. 路由的全局攔截器文件爲AuthInterceptor.java和SignInterceptor.java,在裏面添加系統的攔截規則。
  7. 後臺能夠配置過濾器來解決全局跨域問題。代碼見Snippets。
  8. 清理jeecg自帶版本號和logo信息,注意他的國際化內容,文字信息均存在數據表t_s_muti_lang中,沒法直接在源代碼中搜索到。
  9. 定時任務有bug,暫未解決,存在實例化屢次的狀況。
  10. 事務處理,添加註解@Transactional(rollbackFor=Exception.class)
  11. t:datagrid查詢問題,對時間查詢,若是時間格式是年月日+時分秒,則沒法查詢,須要修改代碼文件,將原來的區間格式由xxxbegin一、xxxend2改爲xxxbegin一、xxxend2
  12. t:datagrid 查詢問題,若是使用多對多的關係進行查詢,直接對字段添加query=true,若是關係多於二級則沒法查詢。例如放款表中有一個借款表外鍵,借款表有一個用戶表的外鍵。在顯示的時候,顯示字段的field=borrow.user.name,那麼,就算設置了query=true,查詢也是無效的。

Snippets

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);![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(){}
        });
    }
}
<!-- 方法綁定 -->
<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,別的字段同理。

相關文章
相關標籤/搜索