itest 开源测试管理项目中封装的下拉列表小组件:实现下拉列表使用者前后端0行代码
导读:
主要从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)
itest 开源测试管理项目中封装的下拉列表小组件:实现下拉列表使用者前后端0行代码的更多相关文章
- spring JdbcTemplate 在itest 开源测试管理项目中的浅层(5个使用场景)封装
导读: 主要从4个方面来阐述,1:背景:2:思路:3:代码实现:4:使用 一:封装背景, 在做项目的时候,用的JPA ,有些复杂查询,比如报表用原生的JdbcTemplate ,很不方便;传参也不方便 ...
- Itest(爱测试),最懂测试人的开源测试管理软件隆重发布
测试人自己开发,汇聚10年沉淀,独创流程驱动测试.度量展现测试人价值的测试协同软件,开源免费 官网刚上线,近期发布源码:http://www.itest.work 在线体验 http://www. ...
- 软件测试自动化的最新趋势对开源测试管理软件ITEST的启示
https://www.infoq.cn/article/c-LHJS2ksuDxp1WkrGl4 理面提到几点,DevOps 的关键原则是开发团队.测试团队和运营团队协作,无缝发布软件.这意味着集中 ...
- 管理项目中的贴图-Texture overview插件
Texture overview插件管理项目中的贴图 1. Assetstore地址 2. 总览项目中所有贴图 3. 针对不同平台的贴图压缩设置 在插件的右上角 4. 支持多选批量修改 5. 点击表头 ...
- Android 项目中常用到的第三方组件
项目中常用到的第三方组件 1 社会化分享ShareSDK-Core-2.5.9.jarShareSDK-QQ-2.5.9.jarShareSDK-QZone-2.5.9.jarShareSDK-Sin ...
- 项目平台统一(前后端IDE、代码风格)
项目平台统一(前后端IDE.代码风格) 记录人:娄雨禛 前端:Webstorm(HTML+CSS+JavaScript) 后端:IntelliJ IDEA(Java) 代码风格:Java风格代码 代码 ...
- flask中的session cookie 测试 和 项目中的用户状态保持
# -*- coding:utf-8 -*- # Author: json_steve from flask import Flask, current_app, make_response, req ...
- 如何在项目中封装api
一般在项目中,会有很多的api请求,无论在vue,angular,还是react中都应该把接口封装起来,方便后期的维护. 1.新建一个api文件 我们可以在项目的分目录下创建一个api文件夹,在这里面 ...
- 使用iconfont管理项目中的字体图标
先来说说字体图标的好处: 很容易任意地缩放: 很容易地改变颜色: 很容易地产生阴影: 可以拥有透明效果: 一般来说,有先进的浏览器支持: 可以使用CSS来装饰(可以得到CSS很好支持): 可以快速转化 ...
随机推荐
- Node六-模块化
Node实现CommonJS规范 -因此node可以使用模块化的方式组织代码结构 简单命令行加减运算 -代码 命令行执行 V8对es6的支持 -直接支持:const.模版字符串等. -严格模式支持:l ...
- Vue.js与Jquery的比较 谁与争锋 js风暴
普遍认为jQuery是适合web初学者的起步工具.许多人甚至在学习jQuery之前,他们已经学习了一些轻量JavaScript知识.为什么?部分是因为jQuery的流行,但主要是源于经验开发人员的一个 ...
- Binary Search 的递归与迭代实现及STL中的搜索相关内容
与排序算法不同,搜索算法是比较统一的,常用的搜索除hash外仅有两种,包括不需要排序的线性搜索和需要排序的binary search. 首先介绍一下binary search,其原理很直接,不断地选取 ...
- python奇技淫巧——max/min函数的用法
本文以max()为例,对min/max内建函数进行说明 源码 def max(*args, key=None): # known special case of max ""&qu ...
- 计算机的Cache和Memory访问时Write-back,Write-through及write allocate的区别
计算机的存储系统采用Register,Cache,Memory和I/O的方式来构成存储系统,无疑是一个性能和经济性的妥协的产物.Cache和Memory机制是计算机硬件的基础内容,这里就不再啰嗦.下面 ...
- CDH简易离线部署文档
CDH 离线简易部署文档 文档说明 本文为开发部署文档,生产环境需做相应调整. 以下操作尽量在root用户下操作,避免权限问题. 目录 文档说明 2 文档修改历史记录 2 目录 3 ...
- spring的7个模块
Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架. Spring ...
- 向Oracle数据库插入中文乱码解决方法
解决方法: 第一步:sqlplus下执行:select userenv('language') from dual;//查看oracle字符集 注:如果oracle字符集与后台代码设置的 ...
- Tiny4412之按键驱动
一:按键驱动 按键驱动跟之前的LED,蜂鸣器的方法类似:通过底板,核心板我们可以看到按键的电路图: 通过电路图我们可以看出,当按键按下去为低电平,松开为高电平:所以我们要检测XEINT26的状态,通过 ...
- Java历程-初学篇 Day01初识java
HelloWorld!!!!! 一,第一个java程序的构成 1,外层框架 class 后面的类名必须与文件名相同 起名方法:1)构成只能有_ $ 字母 数字 2)数字不能开头 3)首字母必须大写 4 ...