【Java】在树结构中给节点追加数据
一、功能需求
有个树状组件,展示区域层级,每个区域节点需要展示该地区下的统计信息
从来没做过,给我整不会了属实是
二、功能分析
原型有功能和老系统代码,查看源码后发现的结构框架
1、树组件是自己用ul + li 封装的,牛逼
2、数据加载逻辑是: 先加载区域树接口,然后加载区域所有统计数据的接口,
再对树或者统计数据遍历,对节点进行赋值,完成渲染
3、统计数据的接口只提供有数据的区域
三、我的实现:
我菜逼写不来,想了想还是拿el-tree改改样式来实现
1、前端实现样式
<el-tree v-show="treeToggleFlag" ref="treeRef2" class="tree-expanded" node-key="regionCode" :default-expanded-keys="['360000']" highlight-current :expand-on-click-node="false" :data="treeData" :props="treeProps" @node-click="handleTreeNodeClick" > <span slot-scope="{ node, data }" class="custom-tree-node2"> <div class="region-bar"><span class="region-label">{{ node.label }}</span> <span class="link-color">数据统计</span></div> <div class="statistic-panel"> <el-row :gutter="10"> <el-col :span="12"> <el-statistic group-separator="," :precision="2" :value="data.licenseCount" title="车牌号码" /> </el-col> <el-col :span="12"> <el-statistic group-separator="," :precision="2" :value="data.obuCount" title="ETC/MTC" /> </el-col> </el-row> <el-row :gutter="10" style="margin-top: 10px;"> <el-col :span="12"> <el-statistic group-separator="," :precision="2" :value="data.apCount" title="行车记录仪" /> </el-col> <el-col :span="12"> <el-statistic group-separator="," :precision="2" :value="data.bleCount" title="车载蓝牙" /> </el-col> </el-row> <el-row :gutter="10" style="margin-top: 10px;"> <el-col :span="12"> <el-statistic group-separator="," :precision="2" :value="data.allCount" title="设备总数" /> </el-col> <el-col :span="12"> <el-statistic title="设备在线率"> <template slot="formatter"> {{ data.onlineRatio }} </template> </el-statistic> </el-col> </el-row> </div> </span> </el-tree>
组件需要的变量:
treeToggleFlag: false, treeData: [], treeProps: { label: 'regionName', children: 'subRegions' },
点击事件:
handleTreeNodeClick(region, val2) { console.log(region, val2) this.currentRegion = { code: region.regionCode, level: region.levelNo } this.initializeOverviewData() this.trIdx = 0 const option = this.tendencyRangeOption[this.trIdx] this.tendencyRangeChoose(option, this.trIdx) },
修改的样式:
/* - - - - - - - - - - - 展开的树节点样式 - - - - - - - - - - - */ .custom-tree-node2 { display: block; width: 100%; /*flex: 1;*/ /*display: flex;*/ /*align-items: center;*/ /*justify-content: space-between;*/ /*font-size: 14px;*/ /*color: white;*/ /*padding-right: 8px;*/ /*border: 1px dashed black;*/ } .custom-tree-node2 .region-bar { width: 100%; flex: 1; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px; } .custom-tree-node2 .statistic-panel { width: 100%; height: 100%; margin-top: 10px; } .custom-tree-node2 .region-bar .region-label { color: white; font-size: 14px; } /*.custom-tree-node2 .region-bar .region-statistic {*/ /*color: rgb(102, 177, 255);*/ /*}*/ /* 节点内容高度 */ /deep/ .tree-expanded .el-tree-node__content { height: 180px; width: 100%; border-bottom: #b8b9c226 1px dashed; background-color: #1A1F3E; } /* 点击选中的节点 */ /deep/ .el-tree-node:focus > .el-tree-node__content { background-color: #024588; } /* 高亮节点颜色 */ /deep/ .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content { background-color: #024588; } /* - - - - - - - - - - - 展开的树节点样式 - - - - - - - - - - - */ /* 没有展开且有子节点 */ /deep/ .el-tree .el-icon-caret-right:before { background: url("./../../../assets/image/draw-out.png") no-repeat 0 0; content: ""; display: inline-block; width: 16px; height: 16px; margin-left: 3px; background-size: 16px 16px; } /* 已经展开且有子节点 */ /deep/ .el-tree .el-tree-node__expand-icon.expanded.el-icon-caret-right:before { background: url("./../../../assets/image/draw-back.png") no-repeat 0 0; content: ""; display: inline-block; width: 16px; height: 16px; transform: rotate(-90deg); background-size: 16px 16px; } /* 没有子节点 */ /deep/ .el-tree .el-tree-node__expand-icon.is-leaf::before { background: none; } /* 统计数据样式 */ /deep/ .custom-tree-node2 .statistic-panel .el-statistic .con .number { color: white; font-size: 18px; } /deep/ .custom-tree-node2 .statistic-panel .el-statistic .head { font-size: 13px; color: #989AA8; }
实现效果:
做不到完全一样,但是想表达的东西到位了,我尽力了
2、数据来源解决
Java接口:
/** * @author OnCloud9 * @date 2023/9/25 15:18 * @description * @params [] * @return java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.Long>> */ @GetMapping("/region-count-data") public Map<String, List<Map<String, Object>>> getStatisticDataRegionCountData() { return statisticDataService.getStatisticDataRegionCountData(); }
Service:
@Override public Map<String, List<Map<String, Object>>> getStatisticDataRegionCountData() { /* distinct 获取存在数据的区域,无数据区域不查询 */ Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_MONTH, -1); Date previousDate = calendar.getTime(); String formatDate = DateUtil.format(previousDate, "yyyy-MM-dd"); /* 查找当日统计的区域 */ List<Map<String, Object>> regionData1 = obuStatisticService.selectAllRegionData(formatDate); List<Map<String, Object>> regionData2 = obuTDeviceService.selectAllRegionData(); Map<String, List<Map<String, Object>>> result = new HashMap<>(); result.put("list1", regionData1); result.put("list2", regionData2); return result; }
统计表的SQL,三次分组查询Union结合成一张表结果:
SELECT region_code AS regionCode, SUM(license_count) AS licenseCount, SUM(obu_count) AS obuCount, SUM(ap_count) AS apCount, SUM(ble_count) AS bleCount FROM obu_statistic WHERE statistic_day = #{formatDate} GROUP BY region_code UNION SELECT CONCAT(LEFT(region_code, 4), '00') AS regionCode, SUM(license_count) AS licenseCount, SUM(obu_count) AS obuCount, SUM(ap_count) AS apCount, SUM(ble_count) AS bleCount FROM obu_statistic WHERE statistic_day = #{formatDate} GROUP BY LEFT(region_code, 4) UNION SELECT CONCAT(LEFT(region_code, 2), '0000') AS regionCode, SUM(license_count) AS licenseCount, SUM(obu_count) AS obuCount, SUM(ap_count) AS apCount, SUM(ble_count) AS bleCount FROM obu_statistic WHERE statistic_day = #{formatDate} GROUP BY LEFT(region_code, 2)"
查询结果:
+------------+--------------+----------+---------+----------+ | regionCode | licenseCount | obuCount | apCount | bleCount | +------------+--------------+----------+---------+----------+ | 360111 | 106777 | 104264 | 161533 | 246934 | | 360100 | 106777 | 104264 | 161533 | 246934 | | 360000 | 106777 | 104264 | 161533 | 246934 | +------------+--------------+----------+---------+----------+
设备表的SQL需要统计设备在线率,这一步要把分组的表联表JOIN合并:
SELECT a.county_code, a.allCount, IFNULL(b.onlineCount, 0) AS onlineCount, ROUND(IFNULL(b.onlineCount, 0) / a.allCount, 4) AS onlineRatio FROM (SELECT county_code, COUNT(county_code) AS allCount FROM obu_t_device GROUP BY county_code) AS a LEFT JOIN (SELECT county_code, COUNT(county_code) AS onlineCount FROM obu_t_device WHERE run_status = 1 GROUP BY county_code) AS b ON a.county_code = b.county_code UNION SELECT a.county_code, a.allCount, IFNULL(b.onlineCount, 0) AS onlineCount, ROUND(IFNULL(b.onlineCount, 0) / a.allCount, 4) AS onlineRatio FROM (SELECT CONCAT( LEFT(county_code, 4), '00') AS county_code, COUNT(1) AS allCount FROM obu_t_device GROUP BY CONCAT(LEFT(county_code, 4), '00')) AS a LEFT JOIN (SELECT CONCAT( LEFT(county_code, 4), '00') AS county_code, COUNT(1) AS onlineCount FROM obu_t_device WHERE run_status = 1 GROUP BY CONCAT(LEFT(county_code, 4), '00')) AS b ON a.county_code = b.county_code UNION SELECT a.county_code, a.allCount, IFNULL(b.onlineCount, 0) AS onlineCount, ROUND(IFNULL(b.onlineCount, 0) / a.allCount, 4) AS onlineRatio FROM (SELECT CONCAT( LEFT(county_code, 2), '0000') AS county_code, COUNT(1) AS allCount FROM obu_t_device GROUP BY CONCAT( LEFT(county_code, 2), '0000')) AS a LEFT JOIN (SELECT CONCAT( LEFT(county_code, 2), '0000') AS county_code, COUNT(1) AS onlineCount FROM obu_t_device WHERE run_status = 1 GROUP BY CONCAT( LEFT(county_code, 2), '0000')) AS b ON a.county_code = b.county_code
查询结果:
+-------------+----------+-------------+-------------+ | county_code | allCount | onlineCount | onlineRatio | +-------------+----------+-------------+-------------+ | 360111 | 4 | 1 | 0.2500 | | 360100 | 4 | 1 | 0.2500 | | 360000 | 4 | 1 | 0.2500 | +-------------+----------+-------------+-------------+
3、数据结果联动
async initializeTreeData() { const { data: treeData } = await getSysRegionTreeData('36', {}) const { data: result } = await getStatisticDataRegionCountData() const list1 = result.list1 const list2 = result.list2 console.log(treeData) this.treeForeach(treeData, 'subRegions', node => { const d1 = list1.find(x => x.regionCode === node.regionCode) const d2 = list2.find(x => x.regionCode === node.regionCode) if (!d1 && !d2) { node.licenseCount = 0 node.obuCount = 0 node.apCount = 0 node.bleCount = 0 node.allCount = 0 node.onlineCount = 0 node.onlineRatio = `0%` return } if (d1) { const { licenseCount, obuCount, apCount, bleCount } = d1 node.licenseCount = licenseCount node.obuCount = obuCount node.apCount = apCount node.bleCount = bleCount } if (d2) { const { allCount, onlineCount, onlineRatio } = d2 node.allCount = allCount node.onlineCount = onlineCount node.onlineRatio = `${onlineRatio * 100}%` } }) this.treeData = treeData },
递归方法:
treeForeach(tree, childrenField, func) { tree.forEach(data => { func(data) data[childrenField] && this.treeForeach(data[childrenField], childrenField, func) // 遍历子树 }) },
四、参考资料:
El-tree组件:
https://element.eleme.io/#/zh-CN/component/tree#scoped-slot
树结构的操作方法:
https://blog.csdn.net/OUalen/article/details/131438154
样式优化修改参考:
https://blog.csdn.net/Sabrina_cc/article/details/125319257
节点的操作图标:
https://www.iconfont.cn/