Guns二次開發(九):商品分類管理之【改】字訣

 

關於博客中使用的Guns版本問題請先閱讀   Guns二次開發目錄     

 

      在上一篇博客中,我們實現了後臺管理系統的商品分類管理模塊的添加功能,這篇博客我們開始來實現修改的功能。

 

1、預期實現的效果

這是預期實現的效果圖:

 

 

       此時能夠修改的字段其實只有兩個,而分類狀態的修改則被我獨立出來,一方面是爲了提高體驗,更主要的其實只是爲了簡化後端接口的邏輯,將複雜的邏輯拆分爲兩個獨立的事件,並交由兩個接口去實現。分類狀態的值有兩個,一個是啓用,一個是停用。啓用的時候需要同時啓用被停用的父級分類;停用則要判斷當前分類下是否有商品,只有在沒有上架商品的情況下才能停用當前分類(當然,這個邏輯不是現在考慮的,只是預留出一個位置,等待商品模塊開發出來後再做補充)。

 

2、修改分類功能的實現

(1)首先是修改按鈕的點擊事件

當點擊【修改】按鈕時 ,會調用下面的這個 Category.openCategoryDetail()函數,因爲我數據庫保存的時間都是13位的unix時間戳,所以需要格式化時間,要格式化時間,就要獲取當前瀏覽器的時區偏移量。而下面的這個moment()函數在前面的博客中有介紹過,引用的是moment.js中函數。

 

(2)查詢分類詳情的後端接口

      上面的函數執行完成之後,會跳轉到分類詳情的接口,這個接口的主要作用是根據分類id查詢到當前分類的詳情信息,爲beetl的頁面渲染(springboot,確切的說是由springmvc自動完成模板渲染)提供數據。

 

       獲取數據的操作,有兩個地方要說明,一個是 DBUtil.selectById()方法,這個工具方法主要是用來判斷前端傳來的id字段是否合法,也就是能否在數據庫中查到記錄,因爲後續很多模塊的接口都要執行此操作,所以我將他單獨抽取出來作爲一個工具類,其核心功能是使用反射實現的,同時也涉及到一個非spring管理的類調用spring管理的類的實現方法。TimeUtil.unixTimestampToLocalDate()方法則是我自定義的時間格式化函數。工具類具體的實現邏輯,可以查看代碼。

 

 

 

 

 

 

(3)分類詳情頁面

       分類詳情頁面,需要對字段進行填充,使用的是beetl模板的語法。除此之外,我還加了一些input隱藏標籤,用來存儲一些重要的數據,這些數據都是在提交修改分類接口的時候後端需要的。

 

(4)修改頁面提交按鈕調用的js函數

       提交修改的js邏輯和添加時一樣,不過需要注意修改成功之後,關閉模態框和刷新列表的操作。 因爲category_info.js 是在 category_edit.html頁面引入的,而 category.js則沒有在 category_edit.html頁面引入,在category_edit.html頁面要想調用 category.js的函數,需要加一個 window.parent 前綴:

 

(5)商品分類修改接口的實現

 

 

      修改商品分類接口有使用到樂觀鎖,其邏輯是:假設我們在分類詳情頁面看到的數據是version=3的版本,此時修改的時候,也要攜帶這個version=3這個參數,意思是我要修改的是version=3這個版本的數據。在編寫修改的 sql語句的時候,要同步更新version(即 sql的set 子句部分 要加上  version=version+1),同時其 where子句也要添加一個 version=3 的條件。併發情況下,當你點擊提交按鈕之前,假設這條記錄被另一個人修改過了,此時記錄的version就必然會大於3,那麼version=3這個條件就不成立,所以修改就不能成功,拋出運行時異常後,前面已經執行的sql都將會被事務回滾。此時前端再提示用戶重新打開詳情一面,一方面是爲了查看最新的詳情信息,另一方面也是爲了讓頁面獲取最新的version字段的值,只有攜帶最新的version才能修改成功。關於樂觀鎖的使用,Mybatis-plus提供了更簡便的方法,可以參考這篇博客 mybatis-plus中使用樂觀鎖 。我在項目中使用的方法可能顯得有些笨拙,但這也是由實際需求決定的。

 

3、啓用/停用功能的實現

(1)在頁面添加【啓用/停用】

首先,這個這個按鈕的實現不是簡單的在頁面加一行顯示按鈕的代碼就可以了,而應該遵守【修改】和【刪除】按鈕的規範,如下圖:

 

具體實現的步驟是:

 

 

 

最後退出當前賬號後重新登錄,就能看到這個 【啓用/停用】按鈕了。

 

(2)編寫【啓用/停用】按鈕的點擊事件

       點擊【啓用/停用】按鈕後觸發的js函數的內部邏輯,要通過當前記錄的狀態,如果當前狀態是“停用”,那麼此時的請求就是“啓用”,反之,如果當前記錄的狀態已經是“啓用”,那這個點擊事件觸發的就是“停用”請求。啓用與停用的操作,也需要攜帶樂觀鎖字段(本例的字段名是 version),樂觀鎖的使用前面已經提過,此處不再贅述。

啓用/停用 按鈕觸發的點擊事件:

 

 

這裏有一點要說明,這裏的version字段其實是隱藏值,這個需要修改bootstrap源碼才能實現,修改的bootstrap源碼文件是下面這個,此處我先分享出來,具體修改的過程留到下一篇博客說明,因爲這一篇的篇幅有點大。

 

 

 

(3)啓用/停用的後端接口

 

 

 

啓用和停用接口的邏輯會有些複雜,前面已經分析過,此處不再贅述,大家直接看後面的代碼即可。

 

 

4、實現修改時的更新時間自動填充

首先請看下面這段代碼,這是我執行修改商品分類時拼接的語句:

 

然後看具體執行的sql:

 

可以發現,我雖然沒有設置updateTime這個字段的值,但是執行sql的時候卻自動加上了。

這是通過 mybatis-plus的公共字段自動填充 功能實現的。下面是具體的實現代碼:

(1)首先是實體類中對要自動填充的自動添加註解

 

(2)自定義實現類 MyMetaObjectHandler

 

 

 

 

5、源碼

注:此處分享的代碼,只跟本篇博客的主題 ,即修改商品分類的實現有關,所以,即使你全部照搬這篇博客裏提供的代碼,也可能會出現因爲缺少某些類而導致項目無法啓動,因爲有些代碼要在前面幾遍博客中才會出現。建議你看的時候,重點關注修改點相對於的代碼,這纔是本篇博客的靈魂啊。

 

 

 

 

 

 

 

 

 

 

 

 

 

(1)StatusConstant.java

package cn.stylefeng.guns.elephish.constants;
 
/**
 * 數據庫中的表格的 status字段狀態的值的常量
 */
public interface StatusConstant {
 
 
 
    /**
     * 商品分類表 mall_product_category 中的status字段:
     * 1,啓用中;2,停用中;3,已刪除
     */
    int PRODUCT_CATEGORY_STATUS_START = 1;//表示 啓用中 含義的值。
    int PRODUCT_CATEGORY_STATUS_STOP = 2;//表示 停用中 含義的值
    int PRODUCT_CATEGORY_STATUS_DELETE = 3;//表示 已刪除 含義的值
 
 
}

 

(2)CategoryController.java

package cn.stylefeng.guns.elephish.controller;

import cn.stylefeng.guns.core.common.annotion.BussinessLog;
import cn.stylefeng.guns.core.common.annotion.Permission;
import cn.stylefeng.guns.core.common.node.ZTreeNode;
import cn.stylefeng.guns.core.log.LogObjectHolder;
import cn.stylefeng.guns.elephish.bean.PageInfo;
import cn.stylefeng.guns.elephish.bean.QueryParam;
import cn.stylefeng.guns.elephish.constants.dictmaps.CategoryDict;
import cn.stylefeng.guns.elephish.form.CategoryForm;
import cn.stylefeng.guns.elephish.utils.DBUtil;
import cn.stylefeng.guns.elephish.wrapper.CategoryWrapper;
import cn.stylefeng.roses.core.base.controller.BaseController;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.ui.Model;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestParam;
import cn.stylefeng.guns.elephish.service.ICategoryService;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;

/**
 * 分類管理控制器
 *
 * @author fengshuonan
 * @Date 2020-04-13 11:02:46
 */
@Controller
@RequestMapping("/category")
public class CategoryController extends BaseController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private String PREFIX = "/elephish/category/";

    @Autowired
    private ICategoryService categoryService;


    /**
     * 跳轉到添加分類管理
     */
    @RequestMapping("/category_add")
    public String categoryAdd(Integer parentId,String parentName,Integer depth,
                              Integer currentPage,HttpServletRequest request) {

        request.setAttribute("parentId",parentId);
        request.setAttribute("parentName",parentName);
        request.setAttribute("depth",depth);
        request.setAttribute("currentPage",currentPage);
        return PREFIX + "category_add.html";
    }

    /**
     * 跳轉到修改分類管理
     */
    @RequestMapping("/category_update")
    public String categoryUpdate(@RequestParam("id") int id,@RequestParam("timeZone") String timeZone,
                                 @RequestParam("currentPage") int currentPage,Model model) {
//        Category map = categoryService.selectById(categoryId);
//        LogObjectHolder.me().set(category);

        Map<String,Object> map = categoryService.getCategoryDetails(id,timeZone);
        map.put("currentPage",currentPage);//當前頁碼
        model.addAttribute("item",map);
        return PREFIX + "category_edit.html";
    }

    /**
     * 跳轉到分類管理首頁
     */
    @RequestMapping("")
    public String index() {

        return PREFIX + "category.html";
    }


    /**
     * 獲取分類管理列表
     */
    @RequestMapping(value = "/list")
    @ResponseBody
    public Object list(QueryParam queryParam,PageInfo pageInfo) {

        List<Map<String, Object>> list = categoryService.listCategory(queryParam,pageInfo);

        //因爲是自定義分頁,所以返回的數據格式需要做特殊封裝,主要是兩個屬性名的定義要固定
        JSONObject jo=new JSONObject();//也可以使用 Map<String,Object>

        //屬性名必須是【data】,對應的值是List<Map<String, Object>>格式
        jo.put("data",new CategoryWrapper(list).wrap());
        jo.put("pageInfo",pageInfo);//屬性名必須是 pageInfo,

        return jo;
    }

    /**
     * 停用或啓用商品分類及其所有子類
     * @param id
     * @param version
     * @param status
     * @return
     */
    @RequestMapping(value = "/status")
    @ResponseBody
    public Object updateStatus(@RequestParam("id")int id,
                               @RequestParam("version")int version,
                               @RequestParam("status")int status) {

        categoryService.updateStatus(id,version,status);
        return SUCCESS_TIP;
    }

    /**
     * 新增分類管理
     */
    @RequestMapping(value = "/add")
    @ResponseBody
    public Object add(@Valid CategoryForm categoryForm) {

        /**
         * 1、修改接收數據的實體類,因爲如果直接使用DAO層的實體類來接收,
         * 會導致一些不需要的數據被寫進數據庫
         * 2、對必傳數據要判斷是否爲空
         * 3、只接收需要的數據,比如這個CategoryForm實體類,id這個字段我是不需要的,但是隻是
         * 添加這個接口不需要,我修改接口是需要的,此時不能在CategoryForm這個類中不定義id這個屬性。
         * 所以,正確的做法是,在添加接口的具體邏輯裏,我不在乎你是否傳了id,因爲我壓根不會操作這個字段
         */
        categoryService.addCategory(categoryForm);
        return SUCCESS_TIP;
    }


    /**
     * 刪除分類管理
     */
    @RequestMapping(value = "/delete")
    @ResponseBody
    public Object delete(@RequestParam Integer categoryId) {
        categoryService.deleteById(categoryId);
        return SUCCESS_TIP;
    }

    /**
     * 修改分類管理
     *
     * 邏輯:
     * (1)已廢棄的商品分類不能修改
     * (2)允許修改的地方:分類名稱,排序數字
     * (3)如果修改的分類名稱已經存在,修改失敗
     * (4)其它情況修改成功
     */
    @RequestMapping(value = "/update")
    @ResponseBody
    public Object update(@Valid CategoryForm categoryForm) {

        //修改流水暫時不處理,後面專門使用單獨的篇幅演示

        categoryService.updateCategory(categoryForm);
        return SUCCESS_TIP;
    }

    /**
     * 分類管理詳情
     */
//    @RequestMapping(value = "/detail/{categoryId}")
//    @ResponseBody
//    public Object detail(@PathVariable("categoryId") Integer categoryId) {
//        return categoryService.selectById(categoryId);
//    }

    /**
     * 獲取菜單列表(選擇父級菜單用)
     */
    @RequestMapping(value = "/selectCategoryTreeList")
    @ResponseBody
    public List<ZTreeNode> selectMenuTreeList() {
        List<ZTreeNode> roleTreeList = categoryService.categoryTreeList();
        roleTreeList.add(ZTreeNode.createParent());
        return roleTreeList;
    }
}

 

(3)Category.java

package cn.stylefeng.guns.elephish.model;

import com.baomidou.mybatisplus.annotations.Version;
import com.baomidou.mybatisplus.enums.FieldFill;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**

 * @author hqq
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("mall_category")
public class Category extends Model<Category> {

    private static final long serialVersionUID = 1L;

    /**
     * 類別id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * 父類別id,當id=0時,說明是根節點,一級類別
     */
    @TableField("parent_id")
    private Integer parentId;
    /**
     * 當前類別的所有父類別id,包含多層級別的父類別id,方便查找
     */
    @TableField("parent_ids")
    private String parentIds;
    /**
     * 類別名稱
     */
    private String name;
    /**
     * 類別狀態。1,正常;2,已廢棄
     */
    private Integer status;
    /**
     * 排序編號,同類展示順序,數值相等則自然排序
     */
    @TableField("sort")
    private Integer sort;
    /**
     * 深度,也就是菜單層級,1表示第一級,2表示第二級,以此類推
     */
    private Integer depth;
    /**
     * 版本(樂觀鎖保留字段)
     */
    @Version
    private Integer version;
    /**
     * 創建時間,單位是毫秒
     */
    @TableField(value = "create_time",fill = FieldFill.INSERT)
    private Long createTime;
    /**
     * 更新時間,單位是毫秒
     */
    @TableField(value="update_time",fill = FieldFill.UPDATE)
    private Long updateTime;


    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}

 

 

(4)CategoryServiceImpl.java

package cn.stylefeng.guns.elephish.service.impl;

import cn.stylefeng.guns.core.common.constant.factory.ConstantFactory;
import cn.stylefeng.guns.core.common.exception.BizExceptionEnum;
import cn.stylefeng.guns.core.common.node.ZTreeNode;
import cn.stylefeng.guns.elephish.bean.PageInfo;
import cn.stylefeng.guns.elephish.bean.QueryParam;
import cn.stylefeng.guns.elephish.constants.LimitationConstant;
import cn.stylefeng.guns.elephish.constants.StatusConstant;
import cn.stylefeng.guns.elephish.constants.WrapperDictNameConstant;
import cn.stylefeng.guns.elephish.form.CategoryForm;
import cn.stylefeng.guns.elephish.model.Category;
import cn.stylefeng.guns.elephish.dao.CategoryMapper;
import cn.stylefeng.guns.elephish.service.ICategoryService;
import cn.stylefeng.guns.elephish.utils.DBUtil;
import cn.stylefeng.guns.elephish.utils.StringUtil;
import cn.stylefeng.guns.elephish.utils.TimeUtil;
import cn.stylefeng.roses.kernel.model.exception.ServiceException;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.sun.javafx.sg.prism.NGEllipse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * <p>
 *  服務實現類
 * </p>
 *
 * @author hqq
 */
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category>
        implements ICategoryService,StatusConstant {

    @Autowired
    private CategoryMapper categoryMapper;


    @Transactional
    @Override
    public void updateStatus(int id, int version, int status) {

        //判斷狀態參數是否正確
        if(!(status==PRODUCT_CATEGORY_STATUS_START || status == PRODUCT_CATEGORY_STATUS_STOP)){
            //ILLEGAL_PARAM(400,"非法參數"),
            throw new ServiceException(BizExceptionEnum.ILLEGAL_PARAM);
        }

        //判斷id是否存在
        Category category= DBUtil.selectById(id,"分類id",CategoryMapper.class);

        //判斷版本是否衝突,如果衝突,則直接返回
        if(version != category.getVersion()){
//            FAIL_UPDATE_CONCURRENT(500,"併發修改異常,請稍後重試"),
            throw new ServiceException(BizExceptionEnum.FAIL_UPDATE_CONCURRENT);
        }

        //如果當前狀態已經是這樣了,則不需要修改
        if(status == category.getStatus()){
            return;
        }

        //如果是啓用,並且當前分類不是頂級分類菜單,就要同時啓用已停用的父類菜單,
        // 如果父類菜單有被刪除的,則無法啓用當前分類
        if(status == PRODUCT_CATEGORY_STATUS_START && category.getParentId()!= 0 ){

            //判斷當前分類的所有父類是否有已被刪除的
            List<Integer> ids = StringUtil.chunkSplitInt(category.getParentIds(), ",");
            ids.remove(0);//去掉頂級菜單標識
            Integer[] idArr = ids.toArray(new Integer[ids.size()]);
            Wrapper<Category> wrapper = new EntityWrapper<>();
            wrapper.ne("status",PRODUCT_CATEGORY_STATUS_DELETE)
                    .in("id",idArr);
            Integer count = categoryMapper.selectCount(wrapper);
            if(count != idArr.length){
                //CATEGORY_PARENT_DELETED(500,"有父類菜單已被刪除,無法執行此操作"),
                throw new ServiceException(BizExceptionEnum.CATEGORY_PARENT_DELETED);
            }

            //同時啓用當前分類的所有父類
            Category cg = new Category();
            cg.setStatus(PRODUCT_CATEGORY_STATUS_START);

            wrapper = new EntityWrapper<>();
            wrapper.eq("status",PRODUCT_CATEGORY_STATUS_STOP)
                    .in("id",idArr);
            categoryMapper.update(cg,wrapper);

        }


        //遞歸修改所有的子節點狀態
        recursionUpdateStatus(id,version,status);

    }

    /**
     * 遞歸修改所有的子節點的狀態
     * @param id
     * @param version
     * @param status
     */
    private void recursionUpdateStatus(int id, int version, int status) {


        if(status==PRODUCT_CATEGORY_STATUS_STOP){
            //如果是停用,則要判斷當前分類下是否有上架中的商品
            //。。。。。。


        }

        //對當前分類狀態做狀態修改,只要有一個失敗了,就全部失敗
        Category category = new Category();
        category.setVersion(version+1);
        category.setStatus(status);
        Wrapper<Category> wrapper = new EntityWrapper<>();
        wrapper.eq("id",id)
                .eq("version",version)
                .ne("status",PRODUCT_CATEGORY_STATUS_DELETE);
        int count = categoryMapper.update(category,wrapper);
        if(count==0){
            //FAIL_UPDATE_CONCURRENT(500,"併發修改異常,請稍後重試"),
            throw new ServiceException(BizExceptionEnum.FAIL_UPDATE_CONCURRENT);
        }


        //查詢所有需要上架/下架的所有子類分類,並遞歸執行上架和下架的操作
        wrapper = new EntityWrapper<>();
        wrapper.eq("parent_id",id)
                .eq("status",status == PRODUCT_CATEGORY_STATUS_START?
                        PRODUCT_CATEGORY_STATUS_STOP:PRODUCT_CATEGORY_STATUS_START)
                .setSqlSelect("id","version");//只查詢id和version字段,提高封裝效率
        List<Category> list = categoryMapper.selectList(wrapper);
        Category temp = null;
        for(int i = 0 ;i<list.size() ; i++){
            temp = list.get(i);
            recursionUpdateStatus(temp.getId(),temp.getVersion(),status);
        }
    }
 

    /**
     * 更新分類管理菜單的信息
     * @param form
     */
    @Transactional
    @Override
    public void updateCategory(CategoryForm form) {

        /**
         * 做到給商品分類添加屬性組的時候,發現如果修改分類所屬的父類時,
         * 後期的業務邏輯會變得很複雜,爲簡化業務邏輯的複雜度,
         * 修改商品分類的時候,不允許用戶更換父類菜單。
         */
        //校驗參數合法性,其實可以省略,但是後面因爲要校驗同名記錄時需要用到父類id
        Category category = DBUtil.selectById(form.getId(),"分類id",CategoryMapper.class);
        if(category.getStatus() == PRODUCT_CATEGORY_STATUS_DELETE){
            //CATEGORY_ERROR_CATEGORY_DELETED(500,"不能修改已廢棄的商品份分類"),
            throw new ServiceException(BizExceptionEnum.CATEGORY_ERROR_CATEGORY_DELETED);
        }


        //封裝要修改的內容,默認不更換父類菜單,只是修改名稱或修改排序編號
        Category cg = new Category();
        cg.setName(form.getName());
        cg.setSort(form.getSort());
        cg.setVersion(form.getVersion()+1);

        Wrapper<Category> wrapper = new EntityWrapper<>();
        wrapper.eq("id",form.getId())
                .eq("version", form.getVersion())
                .ne("status",PRODUCT_CATEGORY_STATUS_DELETE);//不能修改已刪除的記錄
        Integer count= categoryMapper.update(cg,wrapper);
        if(count==0){
            //FAIL_ADD_RECORD(500,"數據庫中新增數據失敗"),
            throw new ServiceException(BizExceptionEnum.FAIL_ADD_RECORD);
        }

        //要判斷除了自身之外,同一個父類菜單下,是否有重名記錄,
        //如果不加判斷的修改,會產生重名記錄
        //判斷分類名稱是否已經存在。status=1 ,2  時纔算重複
       wrapper = new EntityWrapper<>();
        wrapper.eq("parent_id",category.getParentId())
                .eq("name",form.getName())
                .in("status",new Integer[]{PRODUCT_CATEGORY_STATUS_START,PRODUCT_CATEGORY_STATUS_STOP});

        count = categoryMapper.selectCount(wrapper);
        if(count>1){
            //ERROR_EXISTED_SAME_NAME_RECORD(500,"數據庫中已經存在同名的記錄"),
            throw new ServiceException(BizExceptionEnum.ERROR_EXISTED_SAME_NAME_RECORD);
        }

    }

    /**
     * 獲取分類管理的詳情信息
     */
    @Override
    public Map<String, Object> getCategoryDetails(Integer categoryId, String timeZone) {

        Category category = DBUtil.selectById(categoryId,"分類id",CategoryMapper.class);

        JSONObject jo = JSONObject.parseObject(JSONObject.toJSONString(category), JSONObject.class);

        //獲取父類信息
        Integer parentId = category.getParentId();
        String parentName = "頂級";//頂級的分類信息
        StringBuilder parentNames = new StringBuilder("[頂級]");//所有的分類信息
        int parentDepth = 0;

        //查找分類狀態的字典解釋
        String statusName = ConstantFactory.me().getDictById(
                WrapperDictNameConstant.MALL_CATEGORY_STATUS_ID, category.getStatus().toString());
        jo.put("statusName",statusName);

        //格式化時間的操作
        int zoneHour = TimeUtil.formatZoneHour(timeZone);
        if(category.getCreateTime()!=null){
            jo.put("createTime",TimeUtil.unixTimestampToLocalDate(category.getCreateTime(),zoneHour));
        }
        if(category.getUpdateTime()!=null){
            jo.put("updateTime",TimeUtil.unixTimestampToLocalDate(category.getUpdateTime(),zoneHour));
        }

        //獲取所有的父類名稱,並拼接成字符串如:'[體育]->[籃球]'
        if(parentId!=0){

            //獲取父類信息
            Category parent = DBUtil.selectById(parentId,"分類id",CategoryMapper.class);
            parentName = parent.getName();
            parentDepth = parent.getDepth();

            //獲取所有的父類信息
            List<Integer> ids = StringUtil.chunkSplitInt(category.getParentIds(), ",");

            String name = null;

            int num = 0;
            for(int i=0;i<ids.size();i++){
                num = ids.get(i);
                //頂級菜單欄不用查
                if(num==0){
                    continue;
                }
                name = categoryMapper.findCategoryNameById(num);
                if(StringUtils.isBlank(name)){
                    continue;
                }
                parentNames.append("->[").append(name).append("]");

            }
        }

        //再加上自己
        parentNames.append("->[").append(category.getName()).append("]");

        jo.put("parentName",parentName);//保存父類名稱
        jo.put("parentDepth",parentDepth);//保存父類深度
        jo.put("parentNames",parentNames.toString());//保存所有的父類名稱

        return jo;
    }

    @Override
    public List<ZTreeNode> categoryTreeList() {
        return categoryMapper.categoryTreeList(LimitationConstant.MALL_CATEGORY_TREE_MAX_DEPTH);
    }

    /**
     * 自定義邏輯的添加商品分類實現
     * @param form
     */
    @Transactional
    @Override
    public void addCategory(CategoryForm form) {

        int parentId = form.getParentId();
        int status = form.getStatus();

        //判斷status字段是否合法
        if(!(status==PRODUCT_CATEGORY_STATUS_START || status==PRODUCT_CATEGORY_STATUS_STOP)){
            //ILLEGAL_PARAM(400,"非法參數"),
            throw new ServiceException(BizExceptionEnum.ILLEGAL_PARAM);
        }

        /**
         *雖然我前端做了必傳字段的半段,但是我在以往的很多博客中都說過,
         *永遠不要相信前端傳來的數據(即便前後端代碼都是同一個人寫),
         *該做的判斷還是要判斷,
         */
        //設置parentIds,默認值是0
        String parentIds = "[0],";
        int depth = 1;

        //判斷parentId是否合法
        if(parentId>0){
            Category cg = DBUtil.selectById(parentId,"分類id",CategoryMapper.class);

            if(cg.getStatus() == PRODUCT_CATEGORY_STATUS_STOP){
                //CATEGORY_ERROR_PARENT_DELETED(500,"不能爲已廢棄的父類菜單添加子菜單"),
                throw new ServiceException(BizExceptionEnum.CATEGORY_ERROR_PARENT_DELETED);
            }

            //判斷當前是否是葉子節點,如果是,則無法添加子分類
            if(cg.getDepth() >= LimitationConstant.MALL_CATEGORY_TREE_MAX_DEPTH){
                //CATEGORY_ERROR_NO_NEXT_NODE(500,"當前節點已是葉子節點,無法繼續添加子分類"),
                throw new ServiceException(BizExceptionEnum.CATEGORY_ERROR_NO_NEXT_NODE);
            }

            parentIds = cg.getParentIds()+ "["+parentId+"],";
            depth = cg.getDepth()+1;
        }

        //開始執行添加操作
        Category category = new Category();
        category.setParentId(parentId);
        category.setParentIds(parentIds);
        category.setDepth(depth);
        category.setSort(form.getSort());
        category.setName(form.getName());
        category.setVersion(1);
        category.setStatus(status);//設置默認狀態
        int count = categoryMapper.insert(category);
        if(count==0){
            // ERROR_EXISTED_SAME_NAME_RECORD(500,"數據庫中已經存在同名的記錄"),
            throw new ServiceException(BizExceptionEnum.ERROR_EXISTED_SAME_NAME_RECORD);
        }


        //判斷分類名稱是否已經存在。status=1 ,2  時纔算重複
        Wrapper<Category> wrapper = new EntityWrapper<>();
        wrapper.eq("parent_id",parentId)
                .eq("name",form.getName())
                .in("status",new Integer[]{PRODUCT_CATEGORY_STATUS_START,PRODUCT_CATEGORY_STATUS_STOP});
        count = categoryMapper.selectCount(wrapper);
        //前面已經添加一次了,這時數據庫應該只有一條同名記錄,如果大於一條,說明名字重複
        //此時拋出異常,那麼整個事務都會回滾,添加操作失敗
        if(count>1){
            //ERROR_EXISTED_SAME_NAME_RECORD(500,"數據庫中已經存在同名的記錄"),
            throw new ServiceException(BizExceptionEnum.ERROR_EXISTED_SAME_NAME_RECORD);
        }
    }


    @Override
    public List<Map<String, Object>> listCategory(QueryParam queryParam, PageInfo pageInfo) {

        //設置排序
        String sortField = "sort";//排序字段
        boolean isAsc = true;//是否正序排序

        //構建查詢條件
        Wrapper<Category> wrapper = buildWrapper(queryParam, sortField,isAsc);

        Page<Map<String,Object>> page=new Page<>(pageInfo.getCurrentPage(),pageInfo.getLimit());
        List<Map<String, Object>> maps = categoryMapper.selectMapsPage(page, wrapper);

        //設置總頁數
        int total = (int) page.getTotal();
        pageInfo.setTotalPage((int)Math.ceil(1.0*total/pageInfo.getLimit()));//總頁數
        pageInfo.setTotalCount(total);//總記錄數

        if(maps.isEmpty()){
            return maps;
        }

        //設置查詢到的本頁記錄數,因爲默認值爲0,所以大於0的時候才需要設置
        pageInfo.setSize(maps.size());

        //如果不查詢子類菜單,直接返回
        if(!queryParam.isSearchChild()){
            return maps;
        }

        //遍歷查詢其子類
        List<Map<String, Object>> list = new ArrayList<>();
        for(int i = 0; i<maps.size();i++){
            findChildCategory(list, maps.get(i),sortField,isAsc,queryParam.getStatus());
        }

        return list;
    }

    /**
     * 封裝 category 的查詢條件,
     * 注意:這些查詢條件是針對頂級菜單的,
     * 子級菜單的查詢條件只有一個parent_id和排序方式
     * @param queryParam
     * @return
     */
    private Wrapper<Category> buildWrapper(QueryParam queryParam,String sortField,boolean isAsc){

        int status = queryParam.getStatus();

        Wrapper<Category> wrapper = new EntityWrapper<>();

        //設置排序字段和排序方式
        wrapper.orderBy(sortField,isAsc);

        //是否按照層級查詢
        Integer depth =queryParam.getDepth();
        if(depth != null){
            wrapper.eq("depth", depth);
        }

        //是否按照分類名稱查詢
        if(StringUtils.isNotBlank(queryParam.getName())){
            wrapper.like("name",queryParam.getName());
        }else{
            //只有不按分類名稱查詢,並且沒有指定深度,才設置默認的parentId爲0
            if(depth == null){
                wrapper.eq("parent_id", 0);
            }
        }

        //是否按照狀態查詢
        if(status> 0){
            if(!(status==PRODUCT_CATEGORY_STATUS_START || status==PRODUCT_CATEGORY_STATUS_STOP)){
                //ILLEGAL_STATUS_VALUE(400,"狀態字段的值異常"),
                throw new ServiceException(BizExceptionEnum.ILLEGAL_STATUS_VALUE);
            }

            wrapper.eq("status",queryParam.getStatus());
        }else{
            //否則,只查詢未刪除的記錄
            wrapper.in("status",new Integer[]{PRODUCT_CATEGORY_STATUS_START,PRODUCT_CATEGORY_STATUS_STOP});
        }

        return wrapper;

    }


    /**
     * 遞歸算法,算出子級菜單
     */
    private List<Map<String, Object>> findChildCategory(List<Map<String, Object>> result,
                  Map<String, Object> category,String sortField, boolean isAsc, int status){
        result.add(category);

        //封裝子級菜單的查詢條件,
        // 子級菜單的查詢條件只有一個parent_id和排序方式
        Wrapper<Category> wrapper = new EntityWrapper<>();
        wrapper.orderBy(sortField,isAsc)
                .eq("parent_id", new Integer(category.get("id").toString()));

        if(status>0){
            wrapper.eq("status",status);
        }else{
            //否則,只查詢未刪除的記錄
            wrapper.in("status",new Integer[]{PRODUCT_CATEGORY_STATUS_START,PRODUCT_CATEGORY_STATUS_STOP});

        }

        //查找子節點,遞歸算法一定要有一個退出的條件
        List<Map<String, Object>> childList = categoryMapper.selectMaps(wrapper);
        for (Map<String, Object> temp : childList) {
            findChildCategory(result,temp,sortField,isAsc, status);
        }
        return result;
    }
}

 

 

(5)ICategoryService.java

package cn.stylefeng.guns.elephish.service;

import cn.stylefeng.guns.core.common.node.ZTreeNode;
import cn.stylefeng.guns.elephish.bean.PageInfo;
import cn.stylefeng.guns.elephish.bean.QueryParam;
import cn.stylefeng.guns.elephish.form.CategoryForm;
import cn.stylefeng.guns.elephish.model.Category;
import com.baomidou.mybatisplus.service.IService;

import java.util.List;
import java.util.Map;

/**
 * <p>
 *  服務類
 * </p>
 *
 * @author hqq
 */
public interface ICategoryService extends IService<Category> {

    /**
     * 獲取分類管理列表
     */
    List<Map<String,Object>> listCategory(QueryParam queryParam, PageInfo pageInfo);

    /**
     * 自定義邏輯的添加商品分類實現
     */
    void addCategory(CategoryForm categoryForm);

    /**
     * 獲取分類管理的詳情信息
     */
    Map<String,Object> getCategoryDetails(Integer categoryId, String timeZone);

    /**
     * 獲取菜單列表樹
     */
    List<ZTreeNode> categoryTreeList();

    /**
     * 更新分類管理菜單的信息
     * @param categoryForm
     */
    void updateCategory(CategoryForm categoryForm);


    /**
     * 停用或啓用商品分類及其所有子類
     * @param id
     * @param version
     * @param status
     */
    void updateStatus(int id, int version, int status);
}

 

(6)DBUtil.java

package cn.stylefeng.guns.elephish.utils;

import cn.hutool.core.util.ReflectUtil;
import cn.stylefeng.roses.core.util.SpringContextHolder;
import cn.stylefeng.roses.kernel.model.exception.ServiceException;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;

/**
 * 公共的db對象操作類
 * Created by hqq  
 */
public class DBUtil {

    /**
     * 通過id查詢去數據庫查詢對應的記錄。
     * 建議只是校驗參數是否合法時使用
     * @param id 記錄的id
     * @param fieldName 出錯字段的名稱
     * @param clazz 對應的mapper實現類的類型
     * @return 如果獲取的對象爲null,則拋出異常,否則返回可用的對象
     */
    public static <T,V>T selectById(int id,String fieldName,Class<V> clazz){

        //獲取對應的BaseMapper實現類
        Object obj = SpringContextHolder.getBean(clazz);

        //獲取baseMapper的 selectById 方法對象
//        Method method = ReflectionUtils.findMethod(obj.getClass(), "selectById", Integer.class);
        Method method = ReflectUtil.getMethod(obj.getClass(), "selectById");

        //Object invokeMethod(Method method, Object target, Object... args)
        //在指定對象(target)上,使用指定參數(args),執行方法(method);
        Object o = ReflectionUtils.invokeMethod(method, obj, id);
        if(o==null){
            //ILLEGAL_PARAM(400,"非法參數"),
            throw new ServiceException(400,"參數【"+fieldName+"】不合法");
        }
        return (T)o;
    }

}

 

(7)StringUtil.java

package cn.stylefeng.guns.elephish.utils;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.velocity.runtime.directive.Foreach;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定義的字符串工具類
 * Created by hqq  
 */
public class StringUtil {


    /**
     * 自定義字符串split方法,並去除每一個元素的首尾空格
     * 如果傳入的參數是null或空串,容量爲0的字符串集合
     * 如果分隔後的結果爲null或空數組,返回容量爲0的集合,防止調用者空指針異常
     *
     * 示例如:split("aa,  bb  ,c  c," , ",");
     * 得到結果: ["aa","bb","c  c"]
     *
     * @param mutiString  需要進行分割的字符串
     * @param separatorChars 分割標誌
     * @return
     */
    public static List<String> split(String mutiString,final String separatorChars){


        //定義一個list封裝最後的結果
        List<String> result = new ArrayList<>();

        if(StringUtils.isBlank(mutiString)){
            return result;
        }

        //使用性能更好的StringUtils.split方法
        String[] split = StringUtils.split(mutiString,separatorChars);
        if(split==null || split.length<1){
            return result;
        }

        //去除首尾空格
        for (int i = 0; i<split.length; i++) {
            if(StringUtils.isBlank(split[i])){
                continue;
            }
            result.add(split[i].trim());
        }

        return result;
    }


    /**
     * 將字符串"[1],[2],[3]," 轉換爲集合封裝 {1,2,3}
     * 如果數組中有不能轉換成int類型的元素,則會被忽略。如:"[1],[abc],[3],"得到的結果是:[1,3]
     * 如果最終沒有符合條件的數,返回容量爲0的空集合,防止調用者空指針異常
     * @param mutiString 需要進行分割的字符串
     * @param separatorChars 分割標誌
     * @return
     */
    public static List<Integer> chunkSplitInt(String mutiString,final String separatorChars){

        //定義一個list封裝最後的結果
        List<Integer> result = new ArrayList<>();

        //分割成數組
        List<String> list = StringUtil.split(mutiString, separatorChars);
        if(list.isEmpty()){
            return result;
        }

        //操作每一個元素
        String temp = null;
        int length = 0;
        for (int i = 0 ; i<list.size(); i++) {
            temp = list.get(i);

            length = temp.length();
            //既然每個元素都是[]包圍的,那麼長度肯定大於2
            if(length<3){
                continue;
            }

            //去除首尾的"[]"
            temp = temp.substring(1,length-1);

            //判斷字符串中是否全爲數字,示例如下:
            //NumberUtils.isDigits("0000000000.596");//false
            //NumberUtils.isDigits("0000000000596");//true
            if(!NumberUtils.isDigits(temp)){
                continue;
            }

            result.add(new Integer(temp));

        }

        return result;

    }

}

 

(8)TimeUtil.java

package cn.stylefeng.guns.elephish.utils;

import org.apache.commons.lang3.StringUtils;

import java.time.*;
import java.time.format.DateTimeFormatter;

/**
 * Created by hqq 
 */
public class TimeUtil {

    private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";

    /**
     * 將 "+08:00"類型的字符串,轉換成對應的數字 8
     * 如果轉換失敗,會返回 -20 ,因爲-20並不是時間偏移數,
     * 如果運用到時間轉換中,會報錯
     * @param zoneStr 要轉化的參數
     * @return
     */
    public static int formatZoneHour(String zoneStr){

        int errorNum = -20;
        if(StringUtils.isBlank(zoneStr)){
            return errorNum;
        }

        try{
            return new Integer(zoneStr.replaceAll(":00", "").trim());

        }catch (Exception e){

        }

        return errorNum;
    }

    /**
     * 獲取當前時間戳,注意,新建的項目,
     * 後臺統一使用這個方法生成的時間戳
     * @return
     */
    public static long currentTimeMillis(){
        return Instant.now().toEpochMilli();
//        return CurrentTimeMillisClock.getInstance().now();
    }

    public static void main(String[] args) {
        System.out.println(currentTimeMillis());
        System.out.println(System.currentTimeMillis());
        System.out.println(Instant.now().toEpochMilli());
    }



    /**
     * 將unix時間戳格式化爲 yyyy-MM-dd HH:mm:ss 格式的時間
     * 如果轉換失敗,原樣返回時間戳
     * @param unixTimestamp unix時間戳,單位是毫秒
     * @param zoneHour 時區偏移的小時數,返回是 -12 到 +12
     * @return
     */
    public static String unixTimestampToLocalDate(long unixTimestamp , int zoneHour){

        try {

            //傳入的時區範圍錯誤
            if(!(zoneHour >=-12 && zoneHour<=12)){
                return ""+unixTimestamp;
            }

            Instant instant = Instant.ofEpochMilli(unixTimestamp);

            DateTimeFormatter ftf = DateTimeFormatter
                    .ofPattern(DEFAULT_DATE_PATTERN).withZone(ZoneOffset.ofHours(zoneHour));

            return ftf.format(instant);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //如果失敗,返回原時間戳
        return ""+unixTimestamp;

    }

}

 

(9)CategoryWrapper.java

package cn.stylefeng.guns.elephish.wrapper;

import cn.stylefeng.guns.core.common.constant.factory.ConstantFactory;
import cn.stylefeng.guns.elephish.constants.WrapperDictNameConstant;
import cn.stylefeng.roses.core.base.warpper.BaseControllerWrapper;

import java.util.List;
import java.util.Map;

/**
 * 商品分類管理的包裝類
 *
 * Created by hqq  
 */
public class CategoryWrapper extends BaseControllerWrapper {

    public CategoryWrapper(List<Map<String, Object>> multi) {
        super(multi);
    }

    /**
     * 此處自定義添加需要包裝的字段的字典信息
     */
    @Override
    protected void wrapTheMap(Map<String, Object> map) {
        //狀態名稱
        map.put("statusName", ConstantFactory.me().getDictById(
                WrapperDictNameConstant.MALL_CATEGORY_STATUS_ID, map.get("status").toString()));

    }
}

 

(10)bootstrap-treetable.js

/**
 * 查找當前這個節點的所有節點(包含子節點),並進行摺疊或者展開操作
 *
 * @param item 被點擊條目的子一級條目
 * @param target 整個bootstrap tree table實例
 * @param globalCollapsedFlag 如果爲true,則表示當前操作是收縮(摺疊),如果是false,表示當前操作是展開
 * @param options 存放了一些常量,例如展開和收縮的class
 */
function extracted($, item, target, globalCollapsedFlag, options) {

    var itemCodeName = $(item).find("td[name='"+options.code+"']").text();
    // var itemCodeName = $(item).find("td[name='code']").text();
    var subItems = target.find("tbody").find(".tg-" + itemCodeName);//下一級,改爲下所有級別

    if (subItems.size() > 0) {
        $.each(subItems, function (nIndex, nItem) {
            extracted($, nItem, target, globalCollapsedFlag, options);
        });
    }
    $.each(subItems, function (pIndex, pItem) {

        //如果是展開,判斷當前箭頭是開啓還是關閉
        var expander = $(item).find("td[name='name']").find(".treetable-expander");
        if (!globalCollapsedFlag) {
            var hasExpander = expander.hasClass(options.expanderExpandedClass);
            if (hasExpander) {
                $(pItem).css("display", "table");
            } else {
                $(pItem).css("display", "none");
            }
        } else {
            //如果是摺疊,就把當前開着的都摺疊掉
            $(pItem).css("display", "none");
            expander.removeClass(options.expanderExpandedClass);
            expander.addClass(options.expanderCollapsedClass);
        }
    });
}

(function ($) {
    "use strict";

    $.fn.bootstrapTreeTable = function (options, param) {
        var allData = null;//用於存放格式化後的數據

        // 如果是調用方法
        if (typeof options == 'string') {
            return $.fn.bootstrapTreeTable.methods[options](this, param);
        }
        // 如果是初始化組件
        options = $.extend({}, $.fn.bootstrapTreeTable.defaults, options || {});

        // 是否有radio或checkbox
        var hasSelectItem = false;
        var target = $(this);

        // 在外層包裝一下div,樣式用的bootstrap-table的
        var _main_div = $("<div class='bootstrap-tree-table fixed-table-container'></div>");
        target.before(_main_div);
        _main_div.append(target);
        target.addClass("table table-hover treetable-table table-bordered");
        if (options.striped) {
            target.addClass('table-striped');
        }
        // 工具條在外層包裝一下div,樣式用的bootstrap-table的
        if (options.toolbar) {
            var _tool_div = $("<div class='fixed-table-toolbar'></div>");
            var _tool_left_div = $("<div class='bs-bars pull-left'></div>");
            _tool_left_div.append($(options.toolbar));
            _tool_div.append(_tool_left_div);
            _main_div.before(_tool_div);
        }
        // 格式化數據,優化性能
        target.formatData = function (data) {

            var _root = options.rootCodeValue ? options.rootCodeValue : null
            $.each(data, function (index, item) {
                // 添加一個默認屬性,用來判斷當前節點有沒有被顯示
                item.isShow = false;
                // 這裏兼容幾種常見Root節點寫法
                // 默認的幾種判斷
                var _defaultRootFlag = item[options.parentCode] == '0'
                    || item[options.parentCode] == 0
                    || item[options.parentCode] == null
                    || item[options.parentCode] == '';
                if (!item[options.parentCode] || (_root ? (item[options.parentCode] == options.rootCodeValue) : _defaultRootFlag)) {
                    if (!allData["_root_"]) {
                        allData["_root_"] = [];
                    }
                    allData["_root_"].push(item);
                } else {
                    if (!allData["_n_" + item[options.parentCode]]) {
                        allData["_n_" + item[options.parentCode]] = [];
                    }
                    allData["_n_" + item[options.parentCode]].push(item);
                }
            });
        }
        // 得到根節點
        target.getRootNodes = function () {
            return allData["_root_"];
        };
        // 遞歸獲取子節點並且設置子節點
        target.handleNode = function (parentNode, lv, tbody) {
            var _ls = allData["_n_" + parentNode[options.code]];
            var tr = target.renderRow(parentNode, _ls ? true : false, lv);
            tbody.append(tr);
            if (_ls) {
                $.each(_ls, function (i, item) {
                    target.handleNode(item, (lv + 1), tbody)
                });
            }
        };


        //### 添加隱藏參數修改點1(共3處修改):獲取需要修改的列
        var hiddenFields = "";
        //獲取是否有需要隱藏的字段,獲取到的值的示例如: "[version],"
        if(options.columns[0] && options.columns[0].hiddenField ){

            hiddenFields = options.columns[0].hiddenField;

            //如果這個隱藏字段的類型不是字符串,則忽略,默認爲沒有需要修改的字段
            if(typeof(hiddenFields)!='string' ){
                hiddenFields = "";
            }
        }
        //###

        // 繪製行
        target.renderRow = function (item, isP, lv) {
            // 標記已顯示
            item.isShow = true;
            var tr = $('<tr class="tg-' + item[options.parentCode] + '"></tr>');
            var _icon = options.expanderCollapsedClass;
            if (options.expandAll) {
                tr.css("display", "table");
                _icon = options.expanderExpandedClass;
            } else if (options.expandFirst && lv <= 2) {
                tr.css("display", "table");
                _icon = (lv == 1) ? options.expanderExpandedClass : options.expanderCollapsedClass;
            } else {
                tr.css("display", "none");
                _icon = options.expanderCollapsedClass;
            }
            $.each(options.columns, function (index, column) {
                // 判斷有沒有選擇列
                if (index == 0 && column.field == 'selectItem') {
                    hasSelectItem = true;
                    var td = $('<td style="text-align:center;width:36px"></td>');
                    if (column.radio) {
                        var _ipt = $('<input name="select_item" type="radio" value="' + item[options.id] + '"></input>');
                        td.append(_ipt);
                    }
                    if (column.checkbox) {
                        var _ipt = $('<input name="select_item" type="checkbox" value="' + item[options.id] + '"></input>');
                        td.append(_ipt);
                    }

                    tr.append(td);
                } else {

                    //### 添加隱藏參數修改點2(共3處修改):修改列中的數據爲隱藏項
                    //判斷一個字符串中是否包含另一個字符串,假設第一步獲取到 hiddenFields="[version],"
                    //而此時的 column.field = 'version' ,前後拼接[]後得到的是 "[version]"
                    //此時 "[version],".indexOf("[version]") 得到的值肯定大於-1,於是這個if判斷成立
                    if(hiddenFields.indexOf("["+column.field+"]")!=-1){
                        //拼裝自定義的<td>標籤,這個標籤和其它的一樣,不同點是多了一個隱藏屬性 style="display: none;",
                        // 也正是通過這個屬性達到隱藏效果,但這還不夠,表頭的列也必須加上隱藏屬性,否則排版會出問題
                        var td=$('<td title="' + item[column.field] + '" name="' + column.field
                            + '" style="display: none;">'+item[column.field]+'</td>');
                        tr.append(td);
                        return true;//結束本次循環,進入下一個循環
                    }
                    //###


                    var td = $('<td title="' + item[column.field] + '" name="' + column.field + '" style="'
                        + ((column.width) ? ('width:' + column.width) : '') + '"></td>');
                    // 增加formatter渲染
                    if (column.formatter) {
                        td.html(column.formatter.call(this, item[column.field], item, index));
                    } else {
                        td.text(item[column.field]);
                    }

                    if (options.expandColumn == index) {
                        if (!isP) {
                            td.prepend('<span class="treetable-expander"></span>')
                        } else {
                            td.prepend('<span class="treetable-expander ' + _icon + '"></span>')
                        }
                        for (var int = 0; int < (lv - 1); int++) {
                            td.prepend('<span class="treetable-indent"></span>')
                        }
                    }

                    tr.append(td);
                }
            });
            return tr;
        }
        // 加載數據
        target.load = function (parms) {
            // 加載數據前先清空
            allData = {};
            // 加載數據前先清空
            target.html("");
            // 構造表頭
            var thr = $('<tr></tr>');

            $.each(options.columns, function (i, item) {
                var th = null;
                // 判斷有沒有選擇列
                if (i == 0 && item.field == 'selectItem') {
                    hasSelectItem = true;
                    th = $('<th style="width:36px"></th>');
                } else {
                    th = $('<th style="' + ((item.width) ? ('width:' + item.width) : '') + '"></th>');
                }

                //### 添加隱藏參數修改點3(共3處修改):修改表頭的列爲隱藏
                //爲了保證排版不出問題,表頭的列也必須有,這個這個列的屬性也是隱藏的
                if(hiddenFields.indexOf("["+item.field+"]")!= -1){
                    th = $('<th style="display: none;"></th>');
                }
                //###

                th.text(item.title);
                thr.append(th);
            });
            var thead = $('<thead class="treetable-thead"></thead>');
            thead.append(thr);
            target.append(thead);
            // 構造表體
            var tbody = $('<tbody class="treetable-tbody"></tbody>');
            target.append(tbody);
            // 添加加載loading
            var _loading = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">正在努力地加載數據中,請稍候……</div></td></tr>'
            tbody.html(_loading);
            // 默認高度
            if (options.height) {
                tbody.css("height", options.height);
            }

            $.ajax({
                type: options.type,
                url: options.url,
                data: parms ? parms : options.ajaxParams,
                dataType: "JSON",
                success: function (result, textStatus, jqXHR) {

                    //### 開始修改guns原來的bootstrap-treetable.js ###
                    var data =result ;

                    //判斷是否是標準的列表查詢,這個很重要,
                    // 因爲我是直接在bootstrap-treetable.js修改的,
                    //新作的修改必須保證原來的功能不受影響。
                    if(result && typeof(result.pageInfo)!='undefined'){
                        data = result.data;

                        PageTool.buildPageDiv(result.pageInfo);
                    }
                    //### 結束脩改gun v5.1-final 原來的bootstrap-treetable.js ###

                    // 加載完數據先清空
                    tbody.html("");
                    if (!data || data.length <= 0) {
                        var _empty = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">沒有找到匹配的記錄</div></td></tr>'
                        tbody.html(_empty);
                        return;
                    }

                    target.formatData(data);

                    // 開始繪製
                    var rootNode = target.getRootNodes();
                    if (rootNode) {
                        $.each(rootNode, function (i, item) {
                            target.handleNode(item, 1, tbody);
                        });
                    }

                    // 下邊的操作主要是爲了查詢時讓一些沒有根節點的節點顯示
                    $.each(data, function (i, item) {
                        if (!item.isShow) {
                            var tr = target.renderRow(item, false, 1);
                            tbody.append(tr);
                        }
                    });

                    target.append(tbody);
                    //動態設置表頭寬度
                    thead.css("width", tbody.children(":first").css("width"));
                    // 行點擊選中事件
                    target.find("tbody").find("tr").click(function () {
                        if (hasSelectItem) {
                            var _ipt = $(this).find("input[name='select_item']");
                            if (_ipt.attr("type") == "radio") {
                                _ipt.prop('checked', true);
                                target.find("tbody").find("tr").removeClass("treetable-selected");
                                $(this).addClass("treetable-selected");
                            } else {
                                if (_ipt.prop('checked')) {
                                    _ipt.prop('checked', false);
                                    $(this).removeClass("treetable-selected");
                                } else {
                                    _ipt.prop('checked', true);
                                    $(this).addClass("treetable-selected");
                                }
                            }
                        }
                    });
                    // 小圖標點擊事件--展開縮起
                    target.find("tbody").find("tr").find(".treetable-expander").click(function () {
                        var tr = $(this).parent().parent();
                        var _code = tr.find("input[name='select_item']").val();
                        if (options.id == options.code) {
                            _code = tr.find("input[name='select_item']").val();
                        } else {
                            _code = tr.find("td[name='" + options.code + "']").text();
                        }
                        var _ls = target.find("tbody").find(".tg-" + _code);//下一級,改爲下所有級別
                        if (_ls && _ls.length > 0) {
                            var _flag = $(this).hasClass(options.expanderExpandedClass);
                            $.each(_ls, function (index, item) {

                                //查找當前這個節點的所有節點(包含子節點),如果是摺疊都顯示爲不顯示,如果是展開,則根據當前節點的狀態
                                extracted($, item, target, _flag, options);

                                $(item).css("display", _flag ? "none" : "table");
                            });
                            if (_flag) {
                                $(this).removeClass(options.expanderExpandedClass)
                                $(this).addClass(options.expanderCollapsedClass)
                            } else {
                                $(this).removeClass(options.expanderCollapsedClass)
                                $(this).addClass(options.expanderExpandedClass)
                            }
                        }
                    });


                },
                error: function (xhr, textStatus) {
                    var _errorMsg = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">' + xhr.responseText + '</div></td></tr>'
                    tbody.html(_errorMsg);
                    debugger;
                },
            });


        }
        if (options.url) {
            target.load();
        } else {
            // 也可以通過defaults裏面的data屬性通過傳遞一個數據集合進來對組件進行初始化....有興趣可以自己實現,思路和上述類似
        }


        return target;
    };

    // 組件方法封裝........
    $.fn.bootstrapTreeTable.methods = {
        // 返回選中記錄的id(返回的id由配置中的id屬性指定)
        // 爲了兼容bootstrap-table的寫法,統一返回數組,這裏只返回了指定的id
        getSelections: function (target, data) {
            // 所有被選中的記錄input
            var _ipt = target.find("tbody").find("tr").find("input[name='select_item']:checked");
            var chk_value = [];
            // 如果是radio
            if (_ipt.attr("type") == "radio") {
                var _data = {id: _ipt.val()};
                var _tds = _ipt.parent().parent().find("td");
                _tds.each(function (_i, _item) {
                    if (_i != 0) {
                        _data[$(_item).attr("name")] = $(_item).text();
                    }
                });
                chk_value.push(_data);
            } else {
                _ipt.each(function (_i, _item) {
                    var _data = {id: $(_item).val()};
                    var _tds = $(_item).parent().parent().find("td");
                    _tds.each(function (_ii, _iitem) {
                        if (_ii != 0) {
                            _data[$(_iitem).attr("name")] = $(_iitem).text();
                        }
                    });
                    chk_value.push(_data);
                });
            }
            return chk_value;
        },
        // 刷新記錄
        refresh: function (target, parms) {
            if (parms) {
                target.load(parms);
            } else {
                target.load();
            }
        },
        // 組件的其他方法也可以進行類似封裝........
    };

    $.fn.bootstrapTreeTable.defaults = {
        id: 'id',// 選取記錄返回的值
        code: 'id',// 用於設置父子關係
        parentCode: 'parentId',// 用於設置父子關係
        rootCodeValue: null,//設置根節點code值----可指定根節點,默認爲null,"",0,"0"
        data: [], // 構造table的數據集合
        type: "GET", // 請求數據的ajax類型
        url: null, // 請求數據的ajax的url
        ajaxParams: {}, // 請求數據的ajax的data屬性
        expandColumn: null,// 在哪一列上面顯示展開按鈕
        expandAll: true, // 是否全部展開
        expandFirst: false, // 是否默認第一級展開--expandAll爲false時生效
        striped: false, // 是否各行漸變色
        columns: [],
        toolbar: null,//頂部工具條
        height: 0,
        expanderExpandedClass: 'glyphicon glyphicon-chevron-down',// 展開的按鈕的圖標
        expanderCollapsedClass: 'glyphicon glyphicon-chevron-right'// 縮起的按鈕的圖標

    };
})(jQuery);

 

(11)category.js

/**
 * 分類管理管理初始化
 */
var Category = {
    id: "CategoryTable",	//表格id
    seItem: null,		//選中的條目
    table: null,
    layerIndex: -1,
    maxDepth: 3 //最大的深度
};



/**
 * 初始化表格的列
 */
Category.initColumn = function () {
    return [
        {field: 'selectItem', radio: true , hiddenField:"[version],"},
            {title: '分類名稱', field: 'name', visible: true, align: 'center', valign: 'middle'},
            {title: '分類編號', field: 'id', visible: true, align: 'center', valign: 'middle'},
            {title: '分類父編號', field: 'parentId', visible: true, align: 'center', valign: 'middle'},
            {title: '層級', field: 'depth', align: 'center', valign: 'middle', sortable: true},
            {title: '排序', field: 'sort', visible: true, align: 'center', valign: 'middle'},
            {title: '狀態', field: 'statusName', visible: true, align: 'center', valign: 'middle'},
            {title: '創建時間', field: 'createTime', visible: true, align: 'center', valign: 'middle',
                formatter: function (value) {
                    return typeof(value)=="undefined"?"":moment(+value).format('YYYY-MM-DD HH:mm:ss');
                }
            },
            {title: '更新時間', field: 'updateTime', visible: true, align: 'center', valign: 'middle',
                formatter: function (value) {
                    return typeof(value)=="undefined"?"":moment(+value).format('YYYY-MM-DD HH:mm:ss');
                }
            },
            {title: '版本', field: 'version', align: 'center', valign: 'middle'}
    ];
};

/**
 * 檢查是否選中
 */
Category.check = function () {

    var selected = $('#' + this.id).bootstrapTreeTable('getSelections');;
    if(selected.length == 0){
        Feng.info("請先選中表格中的某一記錄!");
        return false;
    }else{

        Category.seItem = selected[0];
        return true;
    }
};



/**
 * 點擊添加分類管理
 */
Category.openAddCategory = function () {
    //默認用戶是添加頂級分類
    var parentId = 0;
    var parentName = '頂級';
    var depth = 1;

    //嘗試獲取用戶選中的item標籤
    var selected = $('#' + this.id).bootstrapTreeTable('getSelections');

    //如果用戶選中了某個單選框,說明是在這個單選框下添加
    if(selected.length > 0){
        var item = selected[0];
        parentName = item.name;//分類名

        //如果當前選中的分類已經被廢棄了,那麼就不允許添加子分類
        if(item.statusName == "已廢棄"){
            Feng.info("分類【"+parentName+"】已被廢棄,無法添加子分類!");
            return;
        }
        parentId = item.id;//分類id
        depth = item.depth;//分類的深度

        //我項目設計了分類管理項目的最大層級是3級,如果超過3級就不能添加
        if(depth >= Category.maxDepth){
            Feng.error("當前節點已是葉子節點,無法繼續添加子分類");
            return ;
        }
    }

    //拼接url上需要的參數
    var urlParams = '?parentId='+parentId+'&parentName='+parentName
        +'&depth='+depth+"&currentPage="+$("#currentPage").val();

    var index = layer.open({
        type: 2,
        title: '添加分類管理',
        area: ['800px', '420px'], //寬高
        fix: false, //不固定
        maxmin: true,
        content: Feng.ctxPath + '/category/category_add'+urlParams
    });
    this.layerIndex = index;


};


/**
 * 打開查看分類管理詳情
 */
Category.openCategoryDetail = function () {
    if(!this.check()){
        return ;
    }
    //獲取瀏覽器的當前時間偏移
    var zoneHour = moment(new Date()).format('Z');

    var id = Category.seItem.id.trim();
    if(!id){
        Feng.error("沒有獲取到id!");
    }

    //拼接需要的參數
    var urlParams = "?id="+id+"&timeZone="+zoneHour
        +"&currentPage="+$("#currentPage").val();

    var index = layer.open({
        type: 2,
        title: '分類管理詳情',
        area: ['953px', '533px'], //寬高
        fix: false, //不固定
        maxmin: true,
        content: Feng.ctxPath + '/category/category_update' + urlParams
    });
    this.layerIndex = index;

};

/**
 * 重置查詢條件條件
 */
Category.reset = function () {
    $("#byStatus").find("option[text='正常']").attr("selected",true);
    $("#byStatus").find("option[text!='正常']").attr("selected",false);

    $("#searchChild").find("option[text='是']").attr("selected",true);
    $("#searchChild").find("option[text!='是']").attr("selected",false);

    $("#currentPage").val("1");//當前頁
    $("#limit").val("5");//每頁查詢條數
    $("#byName").val("");//分類名稱
    $("#byDepth").val("");//層級
    $("#searchChild").attr("disabled",false);
}

/**
 * 修改分類的狀態,停用或啓用
 */
Category.changeStatus = function () {

    if(!this.check()){
        return;
    }

    var item =this.seItem;
    var id = item.id;
    var version = item.version;
    var statusName = item.statusName;

    var status ;
    if(statusName =='正常'){//如果當前是正常,那麼接下來的操作就是要停用
        status =2;
        statusName = "停用";
    }else{//如果當前的狀態是已停用了,那麼接下來的操作就是要啓用
        status =1 ;
        statusName = "啓用";
    }

    var operation =function () {
        var ajax = new $ax(Feng.ctxPath + "/category/status", function () {

            Feng.success("修改成功!");

            //刪除成功之後,刷新當前頁
            var queryParams = Category.formParams();
            queryParams['currentPage'] = $("#currentPage").val();
            Category.table.refresh({query: queryParams});

        }, function (data) {
            Feng.error("操作失敗:" + data.responseJSON.message + "!");
        });
        ajax.set("id",id);
        ajax.set("version",version);
        ajax.set("status",status);
        ajax.start();
    }
    Feng.confirm("是否【"+statusName+"】分類【"+item.name+"】及其下的所有子分類?", operation);
}

/**
 * 刪除分類管理
 */
Category.delete = function () {

    if(!this.check()){
        return;
    }

    var id = this.seItem.id;
    var version = this.seItem.version;

    var operation =function () {
        var ajax = new $ax(Feng.ctxPath + "/category/delete", function (data) {

            Feng.success("刪除成功!");

            //刪除成功之後,刷新當前頁
            var queryParams = Category.formParams();
            queryParams['currentPage'] = $("#currentPage").val();
            Category.table.refresh({query: queryParams});

        }, function (data) {
            Feng.error("操作失敗:" + data.responseJSON.message + "!");
        });
        ajax.set("id",id);
        ajax.set("version",version);
        ajax.start();
    }
    Feng.confirm("是否刪除分類【"+this.seItem.name+"】及其所有子分類?", operation);
};



/**
 * 條件查詢分類管理列表
 */
Category.search = function () {

    //當前頁面刷新
    var queryParams = Category.formParams();
    queryParams['currentPage'] = $("#currentPage").val();

    Category.table.refresh({query: queryParams});

};


$(function () {

    var defaultColunms = Category.initColumn();
    var table = new BSTreeTable(Category.id, "/category/list", defaultColunms);
    table.setExpandColumn(1);//設置第一列展示下拉列表
    table.setIdField("id");//分類編號
    table.setCodeField("id");//分類父編號,用於設置父子關係
    table.setParentCodeField("parentId");//分類父編號,用於設置父子關係
    table.setExpandAll(true);

    //設置請求時的參數
    var queryData = Category.formParams();
    queryData['limit'] = 5;//
    table.setData(queryData);

    table.init();
    Category.table = table;

    $("#limit").val("5");//設置每頁的查詢的默認條數

    //設置當前對象的名稱,分頁時需要使用
    PageTool.callerName="Category";

});


/**
 * 查詢表單提交參數對象
 * @returns {{}}
 */
Category.formParams = function() {
    var queryData = {};

    queryData['name'] = $("#byName").val().trim();//名稱條件
    queryData['depth'] = $("#byDepth").val();//層級條件
    queryData['status'] = $("#byStatus").val();//狀態條件
    queryData['searchChild'] = $("#searchChild").val();//是否查詢子菜單
    queryData['limit'] = $("#limit").val();//設置每頁查詢條數

    return queryData;
}

/**
 * 每頁查詢的頁碼數修改之後觸發失去焦點事件,
 * 將當前頁碼重置爲 1 .
 * 主要是爲了解決以下情況:
 * 假設總共10條記錄,每頁查詢3條,那麼總共就有4頁,當用戶在第三頁的時候,
 * 修改成每頁查詢10條,修改後點擊查詢,會出現沒有數據顯示的情況。
 * 原因是,用戶的當前頁碼 currentPage 的值依舊是3,
 * 而每頁查詢10條後,總共只有1頁,查詢第三頁時肯定沒有數據啦
 */
$("#limit").on('blur',function(){
    $("#currentPage").val(1);
});


/**
 * 【分類名稱】輸入框失去焦點事件
 */
$("#byName").on('blur',function(){
    Category.setSearchChildSelected();
});

/**
 *【深度】輸入框失去焦點事件
 */
$("#byDepth").on('blur',function(){
    Category.setSearchChildSelected();
});

/**
 * 設置【是否查詢子菜單】選擇框是否可用
 */
Category.setSearchChildSelected = function () {
    var byName = $("#byName").val().trim();
    var byDepth = $("#byDepth").val().trim();


    if(byName && !byDepth){
        //當選擇分類名稱查詢,不選擇層級查詢時,默認無法查詢子類菜單,這樣是爲了防止查重和查出不必要的數據
        $("#searchChild").val("false");
        $("#searchChild").attr("disabled",true);
    }else{
        //其它情況,都可以自主覺得是否查詢子菜單
        $("#searchChild").val("true");
        $("#searchChild").attr("disabled",false);
    }
}

 

(12)category_info.js

/**
 * 初始化分類管理詳情對話框
 */
var CategoryInfoDlg = {
    ztreeInstance: null
};



/**
 * 關閉此對話框
 */
CategoryInfoDlg.close = function() {
    parent.layer.close(window.parent.Category.layerIndex);
}


/**
 * 收集數據
 */
CategoryInfoDlg.collectData = function(type) {

    var fieldArr;//定義一個數組,封裝所有需要收集的數據的字段
    var needArr;//定義一個數組,封裝字段是否是必傳字段,注意必須與 fieldArr數組中的容量保持一致
    var fieldNameArr;//定義一個數組,封裝對應字段的字段名稱,用於收集信息出錯時定位錯誤信息
    var typeArr;//定義數組,封裝需要收集的字段的值的類型
    var strMaxLengthArr;//定義數組,指定字符串類型的字段的值的最大長度。-1表示該字段不用比較

    if(type === 'add'){
        //設置添加需要的字段
        fieldArr = ['parentId','name','sort','status','parentName'];
        needArr = [true,true,true,true,false];
        fieldNameArr = ['父類編號','分類名稱','排序編號','狀態設置',''];
        typeArr = ['number','','number','number',''];
        strMaxLengthArr = [11,10,11,2,-1];

    }else if(type==='edit'){
        //設置修改需要的字段
        fieldArr = ['id','parentId','name','sort','version'];
        needArr = [true,true,true,true,true];
        fieldNameArr = ['分類編號','父類編號','分類名稱','排序編號','版本信息'];
        typeArr = ['number','number','','number','number'];
        strMaxLengthArr = [11,11,10,11,11];
    }else{
        return "收集信息出錯啦";
    }

    return CommonTool.collectData(fieldArr,needArr,fieldNameArr,typeArr,strMaxLengthArr);
}


/**
 * 提交添加
 */
CategoryInfoDlg.addSubmit = function() {

    //重新採集數據
    var result = this.collectData("add");

    //如果返回的字段是字符串類型的,說明出錯了,直接返回出錯信息
    if(typeof(result)=='string'){
        Feng.error(result);
        return ;
    }

    //提交信息
    var ajax = new $ax(Feng.ctxPath + "/category/add", function(data){
        Feng.success("添加成功!");
        //修改成功之後,刷新數據,依舊跳轉到原來的頁面
        var queryParams = window.parent.Category.formParams();
        queryParams['currentPage'] = $("#currentPage").val()
        window.parent.Category.table.refresh({query:queryParams });

        CategoryInfoDlg.close();
    },function(data){
        Feng.error("添加失敗:" + data.responseJSON.message + "!");
    });
    ajax.set(result);//設置請求參數
    ajax.start();

}

/**
 * 提交修改
 */
CategoryInfoDlg.editSubmit = function() {

    var result = this.collectData("edit");

    //如果返回的字段是字符串類型的,說明出錯了,直接返回出錯信息
    if(typeof(result)=='string'){
        Feng.error(result);
        return ;
    }

    // if(result["id"]===result["parentId"]){
    //     Feng.error("子分類與父分類不能相同");
    //     return ;
    // }

    //提交信息
    var ajax = new $ax(Feng.ctxPath + "/category/update", function(data){
        Feng.success("修改成功!");

        //修改成功之後,刷新數據,依舊跳轉到原來的頁面
        var queryParams = window.parent.Category.formParams();
        queryParams['currentPage'] = $("#currentPage").val();
        window.parent.Category.table.refresh({query: queryParams});

        CategoryInfoDlg.close();
    },function(data){
        Feng.error("修改失敗!" + data.responseJSON.message + "!");
    });
    ajax.set(result);//設置請求參數
    ajax.start();

}


$(function () {
    
});


 

 

(13)category_edit.html

@layout("/common/_container.html"){
<div class="ibox float-e-margins">

    <div class="ibox-content">
        <div class="form-horizontal">

            <div class="row">
                <div class="col-sm-6 b-r">
                    <h3 style="color:green;text-align:center;">父類信息</h3>


                    <#input id="parentName" disabled="disabled" value="${nvl(item.parentName,'無')}"
                            name="父類名稱" underline="false"/>
                    <br/>

                    <#input id="parentId" disabled="disabled" value="${nvl(item.parentId,'無')}"
                            name="父類編號" underline="false"/>
                    <br/>
                    <#input id="parentDepth" disabled="disabled" value="${nvl(item.parentDepth,'無')}"
                            name="父類層級" underline="false"/>
                    <br/><br><br><br><br><br>
                </div>

                <div  class="col-sm-6">

                    <h3 style="color:green;text-align:center;">當前分類信息</h3>

                    <input id="id" type="hidden" value="${item.id}"/>
                    <input id="currentPage" type="hidden" value="${item.currentPage}"/>
                    <input id="version" type="hidden" value="${item.version}">

                    <#input id="name" disabled="${item.status==1?'':'disabled'}"
                            name="分類名稱" value="${nvl(item.name,'無')}" />

                    <#input id="sort" disabled="${item.status==1?'':'disabled'}"
                            name="排序編號" value="${nvl(item.sort,'無')}" />

                    <#input id="status" disabled="disabled" name="分類狀態"
                            value="${nvl(item.statusName,'無')}" />

                    <#input id="depth" disabled="disabled" name="分類層級"
                            value="${nvl(item.depth,'無')}" />

                    <#input id="parentNames" disabled="disabled" name="層級詳情"
                            value="${nvl(item.parentNames,'無')}" />

                    <#input id="createTime" disabled="disabled" name="創建時間"
                            value="${nvl(item.createTime,'無')}" />

                    <#input id="updateTime" disabled="disabled" name="更新時間"
                            value="${nvl(item.updateTime,'無')}" />

                </div>
            </div>

            <div class="row btn-group-m-t">
                <div class="col-sm-10">
                    @if(item.status ==1){
                        <#button btnCss="info" name="提交" id="ensure" icon="fa-check"
                            clickFun="CategoryInfoDlg.editSubmit()"/>
                    @}

                    <#button btnCss="danger" name="取消" id="cancel" icon="fa-eraser"
                            clickFun="CategoryInfoDlg.close()"/>
                </div>
            </div>
        </div>

    </div>
</div>
<script src="${ctxPath}/static/modular/elephish/category/category_info.js?j=${date().time}"></script>
@}

 

(14)BeanConfig.java

package cn.stylefeng.guns.elephish.config;

import cn.stylefeng.guns.elephish.utils.TimeUtil;
import com.baomidou.mybatisplus.mapper.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Instant;


/**
 *
 * Created by hqq  
 */
@Configuration
public class BeanConfig {

    /**
     * mybatis-plus自動填充新增是修改時間的時間字段
     * @return
     */
    @Bean
    public MetaObjectHandler metaObjectHandler() {
        return new MetaObjectHandler() {
            @Override
            public void insertFill(MetaObject metaObject) {
                this.setFieldValByName("createTime", TimeUtil.currentTimeMillis(), metaObject);
//                this.setFieldValByName("update_time", LocalDateTime.now(), metaObject);
            }

            /**
             * 設置自動填充updateTime字段的值,爲13位的unix時間戳。
             * 這個方法必須設置,因爲我的所有表中,時間字段存的是時間戳,
             * 而mybatis-plus(也有可能是guns)這個字段默認填充的datetime類型的,
             * 所以調用mybatis-plus自帶的刪除、更新等方法,會報錯,爲了解決這個問題,
             * 所以使用了自己的配置覆蓋前面的配置
             * @param metaObject
             */
            @Override
            public void updateFill(MetaObject metaObject) {
                //定義所有的updateTime字段的值爲時間戳類型
                this.setFieldValByName("updateTime", TimeUtil.currentTimeMillis(), metaObject);
            }
        };
    }
}

 

(15)LimitationConstant.java

package cn.stylefeng.guns.elephish.constants;

/**
 * Created by hqq 
 */
public interface LimitationConstant {

    /**
     * 商品分類菜單樹形結構的最大層級,也就是葉子節點所在的層級
     */
    int MALL_CATEGORY_TREE_MAX_DEPTH = 3;
}

 

(16)WrapperDictNameConstant.java

package cn.stylefeng.guns.elephish.constants;

/**
 *
 * 字典包裝類的字典名稱常量
 *
 * Created by hqq  
 */
public interface WrapperDictNameConstant {

    /**
     * sys_user 表中 sex 字段對應的字典名稱 的ID
     */
    int SYS_USER_SEX_ID = 127;

    /**
     * mall_product_category 表中status字段對應的 字典ID
     */
    int MALL_CATEGORY_STATUS_ID = 130;
}

 

(17)category.html

 

@layout("/common/_container.html"){
<div class="row">
    <div class="col-sm-12">
        <div class="ibox float-e-margins">
            <div class="ibox-title">
                <h5 id="pageInfo">分類管理</h5>
            </div>
            <div class="ibox-content">
                <div class="row row-lg">
                    <div class="col-sm-12">
                        <div class="row">
                            <div class="col-sm-2">
                                <#NameCon id="byName" name="分類名稱" />
                            </div>
                            <div class="col-sm-2">
                                <#NameCon id="byDepth" name="層級" />
                            </div>
                            <div class="col-sm-2">
                                <!-- 默認查詢正常的,所以將<option value="1">正常</option>放在最前 -->
                                <#SelectCon id="byStatus" name="狀態" >
                                    <option value="1">正常</option>
                                    <option value="0">全部</option>
                                    <option value="2">停用中</option>
                                </#SelectCon>
                            </div>
                        </div>
                        <div class="row">
                            <div class="col-sm-2">
                                <#SelectCon id="searchChild" name="是否查詢子菜單" >
                                    <option value="true">是</option>
                                    <option value="false">否</option>
                                </#SelectCon>
                            </div>
                            <div class="col-sm-2">
                                <#NameCon id="limit" name="每頁查詢條數"/>
                            </div>

                            <div class="col-sm-2"></div>
                            <div class="col-sm-3">
                                <#button name="重置" icon="fa-repeat" clickFun="Category.reset()" space="true"/>
                                <#button id='searchBtn' name="搜索" icon="fa-search" clickFun="Category.search()"/>
                            </div>

                        </div>
                        <br/>
                        <div class="hidden-xs" id="CategoryTableToolbar" role="group">
                            @if(shiro.hasPermission("/category/add")){
                            <#button name="添加" icon="fa-plus" clickFun="Category.openAddCategory()"/>
                            @}
                            @if(shiro.hasPermission("/category/update")){
                            <#button name="修改" icon="fa-edit" clickFun="Category.openCategoryDetail()" space="true"/>
                            @}
                            @if(shiro.hasPermission("/category/delete")){
                            <#button name="刪除" icon="fa-remove" clickFun="Category.delete()" space="true"/>
                            @}
                            @if(shiro.hasPermission("/category/status")){
                            <#button name="啓用/停用" icon="fa-hourglass-start" clickFun="Category.changeStatus()" space="true"/>
                            @}

                        </div>
                        <#table id="CategoryTable"/>
                    </div>
                </div>

                <!--定義一個空的div標籤,用於分頁內容的位置-->
                <br>
                <div  id="pageDiv"></div>

            </div>
        </div>
    </div>
</div>

<!-- 自定義分頁引入 -->
<script src="${ctxPath}/static/modular/elephish/common/paging.js?j=${date().time}"></script>
<script src="${ctxPath}/static/modular/elephish/category/category.js?j=${date().time}"/>


@}

 

 

該系列更多文章請前往 Guns二次開發目錄

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章