導讀: 主要從4個方面來闡述,1:背景;2:思路;3:代碼實現;4:使用 一:封裝背景 像easy ui 之類的純前端組件,也有下拉列表組件,但是使用的時候,每個下拉列表,要配一個URL ,以及設置URL反回來的值和 select 的text ,和value 的對應關係 ,這有2個問題:一使用者必須 ...
導讀:
主要從4個方面來闡述,1:背景;2:思路;3:代碼實現;4:使用
一:封裝背景
像easy ui 之類的純前端組件,也有下拉列表組件,但是使用的時候,每個下拉列表,要配一個URL ,以及設置URL反回來的值和 select 的text ,和value 的對應關係 ,這有2個問題:一使用者必須知道URL ,二,如果頁面有10個下拉表表,要請求後臺10次,肯定影響性能,而我想要的是使用者只要申明用哪個數據字典就行了,其他根本不用操心,另外加上在做itest開測試測試管理項目的時候,有幾個頁面,特別多下拉列表,且是動態數據,到處都有處理下拉表列表,後臺代碼還好,前端到處都要用JS處理,就算是用vue ,或理angular JS 一樣要處理,我這人又很懶, 最怕重覆的代碼,千女散花似的散落在各個角落中,一不做,二不休乾脆不如簡單的寫一個組件(前後端都有的),讓使用者前後端0行代碼。我們先來看看一下,itest 開源測試管理項目中這個界面,下拉列表,多得頭大,處理不好,會很慢。可以在這體驗這個多下拉列表頁面(點測試,然後選擇一個項目,然後點缺陷管理,再點增加),體驗地址:https://itest.work/rsf/site/itest/product/index.html 然後點線上體驗
二:封裝實現思路
(1) 後端,第1步,字典對像維護:項目中所有字典放一張表中,定義了一個完整的父類,子類只要通過@DiscriminatorValue 註解標明某個字典,在字典分類欄位上的值就行
(2) 後端,第2步,寫一個初始化字典的工具類,主要完成的功能,一是緩存字典數據,二提供,把某個list 的對像中的字典屬性轉換為他的名稱,如把性別中的0轉為“男”,1 轉為女,這個轉換主要是為前端 表格組件用,在後臺把轉換了,不用前臺再加format 之類的函數處理
(3) 後端,第3步,對前端實現rest 介面,返回下拉列表數據,參數:前端下拉表的元素ID用逗號拼成的串,,以及他對應的字典類型和逗號拼成的串,這麼做是實現,批量一次以MAP返回前端所有下拉列表的數據,Map<String,List<BaseDictionary>>,key 為字前端下拉表列元素的ID,value 是一個字典對像的list
(4) 寫一個公用JS ,描掃頁面中的下拉列表對像,獲取其ID,同時 獲取,下拉表中自定義的用於標識字典類型的html 屬性,把這些按對應的順序拼為(3)中描述的兩個以逗號隔開的字元串
三:代碼實現
(1) BaseDictionary 抽像類定義字典對像的通用的方法
(2) Dictionary 繼承 BaseDictionary ,Dictionary是所有字典類對像的實體類父類,採用子類和父類共一張表的策略 ,Dictionary 類上的註解如下
@Entity @Table(name = "t_dictionary") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn( name = "type_name", discriminatorType = DiscriminatorType.STRING ) @DiscriminatorValue(value = "all")
其他字典類,只要申明一下就行,如下麵的姓別,主要是要用 DiscriminatorValue註解申明t_dictionary表中的type_name 欄位的值為什麼時表示這個子類,下麵示例表示 type_name 為Gender 表示姓別的字典類
@Entity @DiscriminatorValue("Gender") public class Gender extends Dictionary{ }
(3) DictionaryCacheServiceImpl ,實現DictionaryCacheService 接中,同時定義一個init 方法,來載入字典到緩存 通過@PostConstruct 註解告訴spring ,構造這個對像完後成,就執行init 方法
(4) DictionaryHolderService ,實現public Map<String, String> getDictionaryHolder() ,方法,一個靜態的MAP, key 是字典類型,也就是具體的字典子類中,@DiscriminatorValue註解的值,value 就是 字典包名的類名,DictionaryCacheServiceImpl,通過這介面,知道有哪些字典類,然後載入到緩存中,後續版本我們通過spi 實現 DictionaryHolderService ,有老項目, 他們直接在 applicationContext.xml 中配置一個MAP ,
(5) DictionaryRest ,提供rest 介面供前端調用
(6) 前端公用JS ,只要引入該JS,他會自動掃描頁面上的下拉表組件,後來我們實現了jquery 版本,easy ui 版,angular 版本
另外,現在公司內部,我們字典,後端做成兩部分,上面描述的我稱作自定議欄位,是項目內部字典,還有一個公共字典,在前端,在自定義HTML 屬性中,除了字典屬性外,還有一個是自定議的,還是公用的;公用的做成一個微服務了,只要POM中引入相關包就行了
上面簡單回顧了一個實現思路,下麵就上代碼:
BaseDictionary
public abstract class BaseDictionary { public abstract String getDictId(); public abstract String getDesc(); public abstract String getValue(); }
Dictionary
/** * <p>標題: Dictionary.java</p> * <p>業務描述:字典公共父類</p> * <p>公司:itest.work</p> * <p>版權:itest 2018 </p> * @author itest andy * @date 2018年6月8日 * @version V1.0 */ @Entity @Table(name = "t_dictionary") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn( name = "type_name", discriminatorType = DiscriminatorType.STRING ) @DiscriminatorValue(value = "all") public class Dictionary extends BaseDictionary implements Serializable { private static final long serialVersionUID = 1L; private Integer dictId; private String desc; private String value; public Dictionary() { } public Dictionary(Integer dictId) { this.dictId = dictId; } public Dictionary(Integer dictId, String desc, String value) { this.dictId = dictId; this.desc = desc; this.value = value; } /** * @return dictId */ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name = "ID", unique=true, nullable=false, length=32) public Integer getDictId() { return dictId; } /** * @param dictId dictId */ public void setDictId(Integer dictId) { this.dictId = dictId; } /** * @return desc */ @Column(name = "lable_text", length = 100) public String getDesc() { return desc; } /** * @param desc desc */ public void setDesc(String desc) { this.desc = desc; } /** * @return value */ @Column(name = "value", length = 100) public String getValue() { return value; } /** * @param value value */ public void setValue(String value) { this.value = value; } }
DictionaryCacheServiceImpl
package cn.com.mypm.framework.app.service.dictionary.impl; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import cn.com.mypm.framework.app.dao.common.CommonDao; import cn.com.mypm.framework.app.entity.dictionary.BaseDictionary; import cn.com.mypm.framework.app.service.dictionary.DictionaryCacheService; import cn.com.mypm.framework.app.service.dictionary.DictionaryHolderService; import cn.com.mypm.framework.common.SpringContextHolder; import cn.com.mypm.framework.utils.ItestBeanUtils; @Service("dictionaryCacheService") @DependsOn("springContextHolder") public class DictionaryCacheServiceImpl implements DictionaryCacheService { private static Log log = LogFactory.getLog(DictionaryCacheServiceImpl.class); private static DictionaryHolderService dictionaryHolder; /** * */ private static Map<String, List<BaseDictionary>> direcListMap = new HashMap<String, List<BaseDictionary>>(); /** * key 為字典type value 為某類欄位的map 它的key為字典value ,value這字典的名稱 */ private static Map<String, BaseDictionary> dictionaryMap = new HashMap<String, BaseDictionary>(); public DictionaryCacheServiceImpl() { } @PostConstruct @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void init() { try { if (SpringContextHolder.getBean("dictionaryHolderService") != null) { dictionaryHolder = SpringContextHolder.getBean("dictionaryHolderService"); } Iterator<Map.Entry<String, String>> it = dictionaryHolder.getDictionaryHolder().entrySet().iterator(); CommonDao commonDao = SpringContextHolder.getBean("commonDao"); while (it.hasNext()) { Map.Entry<String, String> me = (Map.Entry<String, String>) it.next(); List<BaseDictionary> list = commonDao.findDictionary(me.getValue()); if (list != null) { String type = me.getKey(); direcListMap.put(type, list); for (BaseDictionary dc : list) { dictionaryMap.put(type + "_" + dc.getValue(), dc); } } } } catch (Exception e) { log.warn("======PLS confirm if or not configuration dictionaryHolder====="); log.warn(e.getMessage()); } } /** * * @param value * 字典值 * @param type * 字典類型 * @return 字典名稱 */ public static String getDictNameByValueType(String value, String type) { if (dictionaryMap.get(type + "_" + value) != null) { return dictionaryMap.get(type + "_" + value).getDesc(); } return ""; } /** * * @param type * 字典類型 * @return 字典列表 */ public static List<BaseDictionary> getDictListByType(String type) { return direcListMap.get(type) == null ? null : direcListMap.get(type); } public Map<String, BaseDictionary> getDictionaryMap() { return dictionaryMap; } public Map<String, List<BaseDictionary>> getDictionaryListMap() { return direcListMap; } /** * 把list中字典表中代碼值轉換為他的名稱 * * @param list * @param praAndValueMap * key為list中object的表示字典表的屬性 (支持通過點來多層次的屬性如 dto.user.id或是無層次的id), * value為他的類型,如學歷,性別 */ @Override public void dictionaryConvert(List<?> list, Map<String, String> dictMapDesc) { if (list == null || list.isEmpty()) { return; } if (dictMapDesc == null || dictMapDesc.isEmpty()) { return; } for (Object currObj : list) { this.dictionaryConvert(currObj, dictMapDesc); } } public void dictionaryConvert(Object dictObj, Map<String, String> dictMapDesc) { if (dictObj == null) { return; } if (dictMapDesc == null || dictMapDesc.isEmpty()) { return; } try { Iterator<Entry<String, String>> it = dictMapDesc.entrySet() .iterator(); String[] propertys = null; while (it.hasNext()) { Entry<String, String> me = it.next(); propertys = me.getKey().split("\\."); Object dictValue = ItestBeanUtils.forceGetProperty(dictObj, propertys[0]); if (dictValue == null) { continue; } if (propertys.length == 1) { ; ItestBeanUtils.forceSetProperty(dictObj, me.getKey(), DictionaryCacheServiceImpl.getDictNameByValueType( (String) dictValue, me.getValue())); } else { Object laseLayerObj = null; for (int i = 1; i < propertys.length; i++) { if (i != propertys.length - 1 || (propertys.length == 2 && i == 1)) { laseLayerObj = dictValue; } dictValue = ItestBeanUtils.forceGetProperty(dictValue, propertys[i]); if (dictValue == null) { break; } } if (dictValue != null && laseLayerObj != null) { ItestBeanUtils.forceSetProperty(laseLayerObj, propertys[propertys.length - 1], DictionaryCacheServiceImpl .getDictNameByValueType( (String) dictValue, me.getValue())); } } dictValue = null; } } catch (NoSuchFieldException e) { logger.error(e.getMessage(), e); } } }
DictionaryRest
package cn.com.mypm.framework.app.web.rest.dict; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import cn.com.mypm.framework.app.entity.dictionary.BaseDictionary; import cn.com.mypm.framework.app.entity.vo.dict.BatchLoad; import cn.com.mypm.framework.app.service.common.BaseDic; import cn.com.mypm.framework.app.service.dictionary.PubDicInterface; import cn.com.mypm.framework.app.service.dictionary.impl.DictionaryCacheServiceImpl; import cn.com.mypm.framework.common.SpringContextHolder; @RestController @RequestMapping("/itestAPi/dictService") public class DictionaryRest { private static Log logger = LogFactory.getLog(DictionaryRest.class); @GetMapping(value="/find/{type}",consumes="application/json") public List<BaseDictionary> find(@PathVariable("type") String type) { return DictionaryCacheServiceImpl.getDictListByType(type); } //項目內自定義字典 @PostMapping(value="batchLoad",consumes="application/json") public Map<String,List<BaseDictionary>> load(@RequestBody BatchLoad batchLoad){ if(batchLoad==null){ return null; } if(batchLoad.getIds()==null||batchLoad.getIds().trim().equals("")){ return null; } if(batchLoad.getDicts()==null||batchLoad.getDicts().trim().equals("")){ return null; } String[] idsArr = batchLoad.getIds().split(","); String[] dictsArr = batchLoad.getDicts().split(","); Map<String,List<BaseDictionary>> resultMap = new HashMap<String,List<BaseDictionary>>(idsArr.length); int i = 0; for(String id :idsArr){ List<BaseDictionary> currDict = DictionaryCacheServiceImpl.getDictListByType(dictsArr[i]); if(currDict!=null&&!currDict.isEmpty()){ resultMap.put(id, currDict); } i++; } return resultMap; }
//公共字典 @PostMapping(value="pubBatchLoad",consumes="application/json") public Map<String,List<BaseDic>> pubLoad(@RequestBody BatchLoad batchLoad){ if(batchLoad==null){ return null; } if(batchLoad.getIds()==null||batchLoad.getIds().trim().equals("")){ return null; } if(batchLoad.getDicts()==null||batchLoad.getDicts().trim().equals("")){ return null; } PubDicInterface pubDicInterface = null ; try { pubDicInterface = SpringContextHolder.getBean("pubDicInterface"); } catch (Exception e) { logger.error("pub dic no pubDicInterface implements "+e.getMessage(),e); return null; } return pubDicInterface.batchLoadDic(batchLoad); } }
列舉幾個字典類:
@Entity @DiscriminatorValue("AccessMode") public class AccessMode extends Dictionary{ }
@Entity @DiscriminatorValue("Gender") public class Gender extends Dictionary{ }
import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; @Entity @DiscriminatorValue("CertificateType") public class CertificateType extends Dictionary{ }
不一一列舉了,總之後端,只要定義,字典類就行了
載入字典的公共JS ,下麵是eas ui 版本,且作預設執行的JS中要執行的方法,
/** * 批量獲取頁面字典數據 */ function batDicts(opts) { // action var url = ''; // type 區分是公共不是項目內自定義字典 var type = 'public'; if(!!opts) { if(!!opts.type) { type = opts.type; } } var dicts = []; var pageCombos = $(document).data("bsui-combobox")||{}; $.each(pageCombos, function(i, n){ if(i && n) { var td = i.split('^'); if(2 === td.length) { if(td[0]===type && -1===$.inArray(td[1], dicts)) { dicts.push(td[1]); } } } }); if(!!url && dicts.length > 0) { // req params var params = '{"ids": "'+dicts.join(",")+'","dicts": "'+dicts.join(",")+'"}'; // post request ajaxReq(url, params, '', '',{ 'type' : 'POST', 'contentType':'application/json; charset=UTF-8' }).done(function(data){ $.each(dicts, function(i,n){ if(!!pageCombos[type+'^'+n] && !pageCombos[type+'^'+n]['getted'] && !!data[n]) { pageCombos[type+'^'+n]['getted'] = true; pageCombos[type+'^'+n]['data'] = data[n]; $.each(pageCombos[type+'^'+n]["list"], function(){ // 更新頁面combo $(this).combobox('loadData', data[n]); }); } }); }); } } /** * 一次設置頁面上所有下拉列表 */ function batCombo() { batDicts({ type: 'public', url: $CommonUI._PUBLIC_DICT_SERVICE_URL }); batDicts({ type: 'custom', url: $CommonUI._CUSTOM_DICT_SERVICE_URL }); }
四:使用
在前端,正常使用select 組件的基本上,增加一個自定義屬性 即,可,不用寫任何JS代碼,當然要引用公用JS
簡單吧,前端,什麼都不用了,只要定義了用什麼字典及是公共字典,還是自定義的,後端,是通用的代碼,只需要申明字類就 OK ,如 Gender ,有其他的,如學歷等,只要後臺定義一個 他的類,並用 @DiscriminatorValue 申明就行了 , 不再寫任何代碼 ,是不是很省事呀, easy ui ,預設的下拉表表組件,只要寫URL和兩個屬性,但是下拉多,一個下拉請求一次後臺,這很不友好,且需要使用者知道URL,或是實現 load 的JS函數,侵入性我認為太大。
另外,前面gird 的數據,通知會包含量字典數據,通知會在前端通過 grid 組年中,定義format 方法,時行轉行,這麻煩,轉換者,還要知道如來轉,所以後臺字典的service 實現中中增加了一個方法,用於把list 中的的對像里的字典屬性轉換為其名稱
/**
* 把list中字典表中代碼值轉換為他的名稱
*
* @param list
* @param praAndValueMap
* key為list中object的表示字典表的屬性 (支持通過點來多層次的屬性如 dto.user.id或是無層次的id),
* value為他的類型,如學歷,性別
*/
@Override
public void dictionaryConvert(List<?> list, Map<String, String> dictMapDesc)