欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

itest 开源测试管理项目中封装的下拉列表小组件:实现下拉列表使用者前后端0行代码

程序员文章站 2022-03-30 21:13:09
导读: 主要从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 开源测试管理项目中这个界面,下拉列表,多得头大,处理不好,会很慢。可以在这体验这个多下拉列表页面(点测试,然后选择一个项目,然后点缺陷管理,再点增加),体验地址: 然后点在线体验 

itest 开源测试管理项目中封装的下拉列表小组件:实现下拉列表使用者前后端0行代码

 

 二:封装实现思路

     (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

 itest 开源测试管理项目中封装的下拉列表小组件:实现下拉列表使用者前后端0行代码

简单吧,前端,什么都不用了,只要定义了用什么字典及是公共字典,还是自定义的,后端,是通用的代码,只需要申明字类就 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)