需求确定

最近再搞一个需求时,需要在layui中实现表头字段的动态更新,无奈转战前端阵营
来一波官方文档先,个人感觉还是挺简单易懂的
https://www.layui.com/doc/
再来个在线演示网站
https://www.layui.com/demo/table/height.html

修改前

layui.render中默认调用的接口的返回值是集合类型,所以根据一一匹配的原则可以快速的生成表头和表数据。但如果我们的表头是动态生成的,那就有些麻烦了,这里要涉及到表头的数据以及表格的数据。
原来是这样的

layui.use('table', function() {
    var table = layui.table;

    table.render({
        elem: '#tableList'
        , url: '/web/aliyunOSSMetric/query?day=' + day + '&company=' + company
        , toolbar: true
        , height: 'full-330'
        , cols: [
            [
                 {field: 'bucket', width: 420, title: 'bucket' }
                ,{ title:'操作', toolbar: '#tableListAction', width:120}
                , {field: 'companyName', width: 150, title: '厂商', sort: true}
                , {field: 'value', width: 200, title: '流量(TB)', sort: true}
                , {field: 'percent', width: 200, title: '比例', sort: true}
            ]
        ]
    });
});

修改后

parseData方法

在table中,我们可以关注到一个异步回调函数parseData,能够对回调的数据进行一些处理,例如将数据格式化。本来我也打算用这个来渲染cols的,但是这个是在数据返回之后的,cols已经在该方法之前便生效了,所以只能另辟蹊径
在这里插入图片描述

done函数

同理,done函数也能做一些事情,但是cols也已经在该函数前便已生效
在这里插入图片描述

解决方案

在render方法之前,先请求接口获取数据,并可以处理成想要的格式,在render中直接对cols直接赋值就可以了

layui.use('table', function() {
    let table = layui.table;
    let cols = []
    let col = []
    $.ajax({
        type: 'POST',
        url: '/web/aliyunOSSMetric/getCols?day=' + end + '&buckets=' + '&company=' + company,
        dataType: "JSON",
        sync: false,
        contentType: "application/json",
        success: function (res) {
            col.push({field: 'bucket', width: 420, title: 'bucket',merge: true});
            col.push({title:'操作', toolbar: '#tableListAction', width:120});
            col.push({field: 'companyName', width: 150, title: '厂商', sort: true, rowspan: 1});
            $.each(res.data, function (index, obj) {
                col.push({field: obj, width: 200, title: obj, sort: true, rowspan: 1});
            });
            col.push({field: 'percent', width: 200, title: '比例', sort: true, rowspan: 1});
            cols.push(col);

            table.render({
                elem: '#tableList'
                ,
                url: '/web/aliyunOSSMetric/query?day=' + end + '&company=' + company
                ,
                toolbar: true
                ,
                height: 'full-330'
                ,
                cols: cols
            });
        }
    });
});

那么我们会想到,cols是不固定的,那么后端返回的集合类型的每个元素的属性数量也是不固定的
针对后端的返回类型,我们可以抽象为Object集合
针对每个Object,我们可以通过动态的改变业务对象的属性数量,再将业务对象转成json对象,再向上转型成Object对象
具体如下:

先创建工具类
import com.google.common.collect.Maps;
import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap;
import org.apache.commons.beanutils.PropertyUtilsBean;

import java.beans.PropertyDescriptor;
import java.util.Map;

/**
 * @author junfeng.lin
 * @date 2021/9/2
 */
public class ReflectUtil {
    public static Object getTarget(Object dest, Map<String, Object> addProperties) {
        PropertyUtilsBean propertyUtilsBean =new PropertyUtilsBean();
        PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(dest);
        Map<String, Class> propertyMap = Maps.newHashMap();
        for(PropertyDescriptor d : descriptors) {
            if(!"class".equalsIgnoreCase(d.getName())) {
                propertyMap.put(d.getName(), d.getPropertyType());
            }
        }
        // add extra properties
        addProperties.forEach((k, v) -> propertyMap.put(k, v.getClass()));
        // new dynamic bean
        DynamicBean dynamicBean =new DynamicBean(dest.getClass(), propertyMap);
        // add old value
        propertyMap.forEach((k, v) -> {
            try{
                // filter extra properties
                if(!addProperties.containsKey(k)) {
                    dynamicBean.setValue(k, propertyUtilsBean.getNestedProperty(dest, k));
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        });
        // add extra value
        addProperties.forEach((k, v) -> {
            try{
                dynamicBean.setValue(k, v);
            }catch (Exception e) {
                e.printStackTrace();
            }
        });
        Object target = dynamicBean.getTarget();
        return target;
    }

    public static class DynamicBean {
        /**
         * 目标对象
         */
        private Object target;

        /**
         * 属性集合
         */
        private BeanMap beanMap;

        public DynamicBean(Class superclass, Map<String, Class> propertyMap) {
            this.target = generateBean(superclass, propertyMap);
            this.beanMap = BeanMap.create(this.target);
        }


        /**
         * bean 添加属性和值
         *
         * @param property
         * @param value
         */
        public void setValue(String property, Object value) {
            beanMap.put(property, value);
        }

        /**
         * 获取属性值
         *
         * @param property
         * @return
         */
        public Object getValue(String property) {
            return beanMap.get(property);
        }

        /**
         * 获取对象
         *
         * @return
         */
        public Object getTarget() {
            return this.target;
        }


        /**
         * 根据属性生成对象
         *
         * @param superclass
         * @param propertyMap
         * @return
         */
        private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
            BeanGenerator generator =new BeanGenerator();
            if(null != superclass) {
                generator.setSuperclass(superclass);
            }
            BeanGenerator.addProperties(generator, propertyMap);
            return generator.create();
        }
    }
}

处理逻辑

service层进行处理

	Map<String,Object> properties = Maps.newHashMap();
	//动态添加属性,其中第一个参数是属性名,第二个参数是属性值
   	properties.put(ossMetric.getName(), entity.getValue());
    ObjectMapper mapper = new ObjectMapper();
    //先转成JSON对象
    String json = mapper.writeValueAsString(ReflectUtil.getTarget(vo,properties));	//其中vo是具体的业务对象
    //再将JSON对象转成实体类,由于不知道具体的属性值是怎么样的,所以向上转型用Object接收
    Object ossMetricVo = JSONObject.parseObject(json, Object.class);

最后在controller中直接将Object数组返回给前端就可以了
controller中的伪代码

@RequestMapping("/aliyunOSSMetric/query")
    public Map<String,Object> queryByDay(@RequestParam(name = "day") String day,
                                         @RequestParam(name = "company") String company
    ) throws InvocationTargetException, JsonProcessingException {
        Map<String,Object> res = new HashMap<>();
        List<Object> data = service.query(day, company);
        res.put("code", 0);
        res.put("msg", "");
        res.put("count", data .size());
        res.put("data", data);
        return res;
    }

如此,前端就能根据cols中的属性名自动匹配到data中对象的属性名了,大功告成!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐