vlambda博客
学习文章列表

项目小结(一):用电监控实现数据库查询竖向转横向

前言:

需求

1、实现对电能采集设备各类数据的汇总展示,数据库中的点为每隔15分钟一个值,界面设计如下:


而数据库中的内容:
项目小结(一):用电监控实现数据库查询竖向转横向

其中的DataPoint的分钟点,对应的每15分钟一个点。

2、电量的实现要用柱状图来表示:后面一个点的数据减去前面一个点的数据算后面那个点的值,还要考虑空值的存在。第一个点的值减去前天最后一个点的值,暂时需求定为如果一个点没有数据则后面那个点就不去相减。

3、最后计算出来的值都要乘个系数。

需求难点分析

1、正如上面数据库中的数据所展示的,每个分钟点一个数据,而到展示端变成每个分钟点的数据横向展示,而且会出现断点数据的存在。那么这时候的实现就比较麻烦了,这就是数据库竖向转横向问题

2、电量相减的点考虑循环判断去做。

3、前端用Vue+elementui+echarts来实现。

功能实现

1、前端展示:

1.1、单选框和多选框

 1<template>
2  <el-radio v-model="radio" label="1">备选项</el-radio>
3  <el-radio v-model="radio" label="2">备选项</el-radio>
4</template>
5
6<script>
7  export default {
8    data () {
9      return {
10        radio'1'
11      };
12    }
13  }
14
</script>

这是elementui的案例。
关于radio单选框问题需要注意的点:

  • 注意外层包个div,写样式

  • lable的值必须和v-model相等才能显示值

  • 鼠标点击某个值,v-model的值则会与当前label的值相等。

实现思路:页面初始化时查询所有设备(并网点),传参的时候传入并网点id,单选电压、电流、功率、电量的时候,每个label的值不同,后面多选框的展示与这个label值相等则展示出来。
例如:

 1<!--电压-->
2              <div v-if="radio2==100" style="padding-top: 20px;margin-left: 50px">
3                 <el-checkbox-group v-model="checkList">
4                    <el-checkbox  label="101" >Ua</el-checkbox>
5                    <el-checkbox  label="102" >Ub</el-checkbox>
6                    <el-checkbox  label="103" >Uc</el-checkbox>
7                    <el-checkbox  label="111" >Uab</el-checkbox>
8                    <el-checkbox  label="112" >Ubc</el-checkbox>
9                    <el-checkbox  label="113" >Uca</el-checkbox>
10                </el-checkbox-group>
11              </div>

1.2 前端的这个表格需要自己来画,每隔15分钟作为一列的话共有96个,用代码生成:

 1time() {
2              let h1 = "",
3                  m1 = "",
4                  h = "",
5                  m = "",
6                  timestr = "",
7                  arr = [],
8                  seg = 15
9              for (let i = 15; i < 1455; i++) {
10                  if (seg % 15 == 0) {
11                      h1 = parseInt(i / 60)
12                      h = h1 < 10 ? "0" + h1 : h1
13                      m1 = parseInt(i % 60)
14                      m = m1 < 10 ? "0" + m1 : m1
15                      timestr = h + ":" + m
16                      arr.push(timestr)
17                  }
18                  seg++;
19              }
20              this.$data.initArr = arr
21          },

1.3 结合echarts使用

echarts常用于大数据可视化展示,必须要准备dom节点来创建,每个echarts实例独占一个Dom节点。

  • echarts的legend(显示框)和series(x轴)的data必须按顺序一一对应,因此如果有丢失数据的话,就用null来占位。

  • div外部dom的大小定义后,grid元素相当于padding,控制内部间距。

  • 点击效果:点击legend后隐藏该条曲线。代码如下:

 1/点击效果:点击legend后隐藏
2              this.$data.chart.on('legendselectchanged'function(params{
3                  var option = this.getOption();
4                  var select_key = Object.keys(params.selected);
5                  var select_value = Object.values(params.selected);
6                  var n = 0;
7                  select_value.map(res => {
8                      if (!res) {
9                          n++;
10                      }
11                  });
12                  console.log('n', n)
13                  if (n == select_value.length) {
14                      option.legend[0].selected[params.name] = true;
15                  }
16                  this.setOption(option)
17
18              });
  • 遇到的一个问题:legend标题显示返回后总是显示在同一个位置,那是因为返回多条数据的时候返回的都是同个位置,因此,返回第一条数据后,后面的数据的位置都应该有变化。(解决经验)

 1 var x=200;
2              this.$data.option.legend=tableData.map(i=>{
3                  x=x+100;
4                  var arr=[];
5                  for(var key in i){
6                      arr.push(i[key])
7                  }
8                  return{
9                      data:arr,
10                      orient: "horizontal",
11                      icon:"circle",
12                      paddingLeft:50,
13                      left:x
14                  }
15              })

1.4 一个新颖点

这个点是我对于搞后端的人来说做前端可能真的有点难,就是在前端生成一个时间数组后,如何在table表格里面把这些值给遍历出来。我试了几种都不能表示出来这个时间数组,最后问我师傅,他成功的试了出来。因此记录下来:在prop里面加个单引号,然后通过$获取,至于原因我还不知道。

1       <el-table-column
2                        :prop="`timeArr.${item}`"
3                        v-for="item in initArr"
4                        :key="item"
5                        :label="item">
6              </el-table-column>

2、后端及数据库实现

难点分析:

2.1、解决数据库查询数据竖向转横向

思路:

可以在sql通过case when来实现,但是查询后96个点的查询时间为7s左右,因此不考虑,后面觉得在代码角度来转变更快。这块之前并没有做过,因此思路还是很重要的。虚拟一个基准数组,这样就有这些坐标点的key,保证了这些key与前端的key相等,就不会说断点问题,另外遍历这个查询结果,如果数据库的时间点与基准数组的key相等,则把这个key的value更新为当前时间点的数据。用linkedhashMap来表现,因为它有序,map相同key会不断重写,没有被重写的key的value为null,最后返回一个map的key-value列,封装成实体给前端,前端就可以对这个类中的map进行解析遍历。这就是实现思路。

代码实现:

获取基准数组:

 1 /**
2     * 按15分钟分割datapoint 输出为: [0:15 0:30 0:45 1:00...24:00]
3    **/

4    public static ArrayList getSegArr(){
5        String h="",m="",timestr="";
6        int seg=15;
7        int h1,m1;
8        ArrayList<Object> list = new ArrayList<>();
9        for (int i=15;i<1455;i++){
10            if(seg%15==0){
11                h1=(i/60);
12                h = h1<10?"0"+h1:h1+"";
13                m1=(i%60);
14                m = m1<10?"0"+m1:m1+"";
15                timestr = h+":"+m;
16                list.add(timestr);
17            }
18            seg++;
19        }
20        return list;
21    }

思路实现:

 1                if (cdElectrictyPowerVO.getDataType().intValue()==CdMonitorConst.MONITOR_CONST_POWER_HAVE_A.getStatus()){
2                    if(cdElectrictyDataVO.getDataName()==null){
3                        cdElectrictyDataVO.setDataName(CdMonitorConst.MONITOR_CONST_POWER_HAVE_A.getName());
4                    }
5                    if(cdElectrictyDataVO.getDevName()==null){
6                        cdElectrictyDataVO.setDevName(cdElectrictyPowerVO.getMpntName());
7                    }
8                    if(cdElectrictyDataVO.getDateTime()==null){
9                        cdElectrictyDataVO.setDateTime(cdElectrictyPowerVO.getDataDate());
10                    }
11                    //如果当前的datapoint和基准数组的datapoint相等,则将key和value重写put进map,相同key自动重写,空值的value默认为null
12                    if(DataPointSegUtil.dataPointToTime(cdElectrictyPowerVO.getDataPoint()).equals(segArr.get(i))){
13                        timeMap.put(DataPointSegUtil.dataPointToTime(cdElectrictyPowerVO.getDataPoint()),cdElectrictyPowerVO.getPower().multiply(new BigDecimal(cdElectrictyPtCtVO.getPt())).multiply(new BigDecimal(cdElectrictyPtCtVO.getCt())));
14                    }
15                }

2、计算两点的差值。

这块的比较需要用到bigDecimal,商业计算一般都用这个,因为它本质是用String类转的,不存在精确度丢失问题。第一点的数据提前塞进去。后面的从第二个点开始塞。

 1if(!ObjectUtil.isEmpty(cdElectrictyDataVO2)) {
2            for (int i = 1; i < segArr.size() - 1; i++) {
3                BigDecimal bigDecimal = new BigDecimal(timeMap2.get(segArr.get(i)).toString());
4                BigDecimal bigDecimal2 = new BigDecimal(timeMap2.get(segArr.get(i - 1)).toString());
5                BigDecimal bigDecimal3 = new BigDecimal(-1);
6                //只有两个值都不相等才能进来
7                if (!bigDecimal.equals(bigDecimal3) && !bigDecimal2.equals(bigDecimal3)) {
8                    BigDecimal subtract = bigDecimal.subtract(bigDecimal2);
9                    timeMap2New.put(segArr.get(i) + "", subtract.multiply(new BigDecimal(cdElectrictyPtCtVO.getPt())).multiply(new BigDecimal(cdElectrictyPtCtVO.getCt())));
10                }else{
11                    timeMap2New.put(segArr.get(i)+"",null);
12                }
13            }
14        }

展现

以上这就是这个需求前后端解决的完整思路,页面效果如下: