项目小结(一):用电监控实现数据库查询竖向转横向
前言:
需求
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 }
展现
以上这就是这个需求前后端解决的完整思路,页面效果如下: