zoukankan      html  css  js  c++  java
  • 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)
  • 相关阅读:
    创建vue项目的时候报错,spawn yarn ENOENT
    理解比特币(4)——实现原理
    比特币(3)——比特币的其他优势
    比特币(2)——最大优势是价值存储
    如何在K8S中优雅的使用私有镜像库 (Docker版)
    [Go] godoc 打开本地文档, windows 同样适用
    [FAQ] Golang error strings should not be capitalized or end with punctuation
    [Go] gorm 错误处理 与 链式/Finisher方法
    [Go] golang 替换组件包 更新 go.mod, go.sum 的方式
    [FAQ] golang-migrate/migrate error: migration failed in line 0: (details: Error 1065: Query was empty)
  • 原文地址:https://www.cnblogs.com/mypm/p/10723039.html
Copyright © 2011-2022 走看看