一、页面结构 overview
该页面主要由三部分组成:顶部操作栏、左侧树形区域选择器、右侧标签页式穿梭框组件,用于管理不同区域的水表设备关联关系。
java
<el-tree
:data="areaOptions"
:props="{ label: 'label', children: 'children' }"
:expand-on-click-node="false"
node-key="id"
highlight-current
default-expand-all
@node-click="handleNodeClick"
/>
java
/** 查询区域下拉树结构 */
function getAreaTree() {
areaTreeSelect().then(response => {
areaOptions.value = response.data
})
}
// 查询区域下拉树结构
export function areaTreeSelect() {
return request({
url: '/dma/area/areaTree',
method: 'get'
})
}
java
/**
* 获取区域树列表
*/
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/areaTree")
public AjaxResult areaTree(DmaArea area)
{
return success(dmaAreaService.selectDeptTreeList(area));
}
java
// 入口方法:获取树形选择器列表
public List<TreeSelect> selectDeptTreeList(DmaArea area) {
// 1. 获取AOP代理对象并查询区域列表(解决自调用AOP失效问题)
List<DmaArea> areaList = SpringUtils.getAopProxy(this).selectDmaAreaList(area);
// 2. 构建树形结构并转换为TreeSelect
return buildDeptTreeSelect(areaList);
}
-
SpringUtils.getAopProxy(this):获取当前对象的AOP代理,确保selectDmaAreaList方法的事务等AOP增强生效
SpringUtils.java
-
selectDmaAreaList(area):查询满足条件的区域列表(具体实现依赖MyBatis映射器)
-
buildDeptTreeSelect(areaList):核心转换方法,将平面列表转为树形结构
java
public List<TreeSelect> buildDeptTreeSelect(List<DmaArea> areaList) {
// 1. 构建部门树形结构
List<DmaArea> deptTrees = buildDeptTree(areaList);
// 2. 转换为前端需要的TreeSelect对象
return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
}
TreeSelect构造函数 :将 DmaArea 对象转换为前端组件所需的树形结构模型,包含 id / label / children 等标准树形属性`TreeSelect`
java
package com.ruoyi.project.dma.domain;
import com.ruoyi.project.system.domain.SysDept;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.framework.web.domain.BaseEntity;
import java.util.ArrayList;
import java.util.List;
/**
* 区域管理对象 dma_area
*
* @author smart
* @date 2025-08-28
*/
public class DmaArea extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** ID */
private Long id;
/** pid */
@Excel(name = "pid")
private Long pid;
/** 名称 */
@Excel(name = "名称")
private String name;
/** 分区类型 */
@Excel(name = "分区类型")
private String type;
/** 区域数据 */
private DmaData dmaData;
/** 子区域 */
private List<DmaArea> children = new ArrayList<DmaArea>();
public void setId(Long id)
{
this.id = id;
}
public List<DmaArea> getChildren() {
return children;
}
public void setChildren(List<DmaArea> children) {
this.children = children;
}
public Long getId()
{
return id;
}
public void setPid(Long pid)
{
this.pid = pid;
}
public Long getPid()
{
return pid;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setType(String type)
{
this.type = type;
}
public String getType()
{
return type;
}
public DmaData getDmaData() {
return dmaData;
}
public void setDmaData(DmaData dmaData) {
this.dmaData = dmaData;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("pid", getPid())
.append("name", getName())
.append("type", getType())
.toString();
}
}
java
public List<DmaArea> buildDeptTree(List<DmaArea> depts) {
if (depts == null || depts.isEmpty()) return Collections.emptyList();
// 步骤1:构建父ID到子节点的映射表(O(n)时间复杂度)
Map<Long, List<DmaArea>> pidMap = depts.stream()
.filter(d -> d.getPid() != null)
.collect(Collectors.groupingBy(DmaArea::getPid));
// 步骤2:获取所有区域ID集合,用于判断顶级节点
Set<Long> idSet = depts.stream()
.map(DmaArea::getId)
.collect(Collectors.toSet());
// 步骤3:筛选顶级节点(PID不存在于ID集合中的节点)
List<DmaArea> topNodes = depts.stream()
.filter(d -> d.getPid() == null || !idSet.contains(d.getPid()))
.collect(Collectors.toList());
// 步骤4:递归构建整棵树
for (DmaArea top : topNodes) {
buildTreeRecursive(top, pidMap);
}
return topNodes.isEmpty() ? depts : topNodes;
}
**- pidMap : Map<父ID, 子节点列表> ,实现O(1)时间复杂度查找子节点
- idSet :快速判断某个PID是否为有效区域ID(排除外部引用的无效PID)
- topNodes :树形结构的根节点集合(可能存在多个根节点)**
java
private DmaData buildTreeRecursive(DmaArea node, Map<Long, List<DmaArea>> pidMap) {
// 1. 获取当前节点的子节点列表
List<DmaArea> children = pidMap.getOrDefault(node.getId(), Collections.emptyList());
node.setChildren(children);
// 2. 递归处理所有子节点
for (DmaArea child : children) {
buildTreeRecursive(child, pidMap);
}
// 3. 数据聚合逻辑(核心业务计算)
// ... 子节点数据汇总 ...
}
**- 递归终止条件 :当节点ID在pidMap中无对应子节点时,children为空列表
- 树结构存储 :通过 node.setChildren(children) 将子节点挂载到当前节点,形成层级关系**
java
// 统计子节点数据
boolean hasChildData = false;
double supplyTotal = 0.0;
double salesTotal = 0.0;
for (DmaArea child : children) {
DmaData childData = child.getDmaData();
if (childData != null &&
(childData.getSupply() != null || childData.getSales() != null
|| childData.getLoss() != null || childData.getNrw() != null)) {
hasChildData = true;
supplyTotal += childData.getSupply() == null ? 0.0 : childData.getSupply();
salesTotal += childData.getSales() == null ? 0.0 : childData.getSales();
}
}
// 计算聚合数据
DmaData dmaData = new DmaData();
dmaData.setName(node.getName());
if (hasChildData) {
dmaData.setSupply(supplyTotal);
dmaData.setSales(salesTotal);
dmaData.setLoss(supplyTotal - salesTotal);
// 计算产销差率(NRW)
if (dmaData.getLoss() != null && dmaData.getSupply() != null && dmaData.getSupply() != 0.0) {
dmaData.setNrw(dmaData.getLoss() / dmaData.getSupply());
}
}
node.setDmaData(dmaData);
return dmaData;
聚合规则 :
-
仅当子节点存在有效数据时才进行聚合
-
供应总量=所有子节点供应量之和
-
损失量=供应量-销售量
-
产销差率(NRW)=损失量/供应量(避免除以零)


java
package com.ruoyi.system.service.impl;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.mapper.SysRoleMapper;
import com.ruoyi.system.service.ISysDeptService;
/**
* 部门管理 服务实现
*
* @author ruoyi
*/
@Service
public class SysDeptServiceImpl implements ISysDeptService
{
@Autowired
private SysDeptMapper deptMapper;
@Autowired
private SysRoleMapper roleMapper;
/**
* 查询部门管理数据
*
* @param dept 部门信息
* @return 部门信息集合
*/
@Override
@DataScope(deptAlias = "d")
public List<SysDept> selectDeptList(SysDept dept)
{
return deptMapper.selectDeptList(dept);
}
/**
* 查询部门树结构信息
*
* @param dept 部门信息
* @return 部门树信息集合
*/
@Override
public List<TreeSelect> selectDeptTreeList(SysDept dept)
{
List<SysDept> depts = SpringUtils.getAopProxy(this).selectDeptList(dept);
return buildDeptTreeSelect(depts);
}
/**
* 构建前端所需要树结构
*
* @param depts 部门列表
* @return 树结构列表
*/
@Override
public List<SysDept> buildDeptTree(List<SysDept> depts)
{
List<SysDept> returnList = new ArrayList<SysDept>();
List<Long> tempList = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList());
for (SysDept dept : depts)
{
// 如果是顶级节点, 遍历该父节点的所有子节点
if (!tempList.contains(dept.getParentId()))
{
recursionFn(depts, dept);
returnList.add(dept);
}
}
if (returnList.isEmpty())
{
returnList = depts;
}
return returnList;
}
/**
* 构建前端所需要下拉树结构
*
* @param depts 部门列表
* @return 下拉树结构列表
*/
@Override
public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts)
{
List<SysDept> deptTrees = buildDeptTree(depts);
return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
}
/**
* 根据角色ID查询部门树信息
*
* @param roleId 角色ID
* @return 选中部门列表
*/
@Override
public List<Long> selectDeptListByRoleId(Long roleId)
{
SysRole role = roleMapper.selectRoleById(roleId);
return deptMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly());
}
/**
* 根据部门ID查询信息
*
* @param deptId 部门ID
* @return 部门信息
*/
@Override
public SysDept selectDeptById(Long deptId)
{
return deptMapper.selectDeptById(deptId);
}
/**
* 根据ID查询所有子部门(正常状态)
*
* @param deptId 部门ID
* @return 子部门数
*/
@Override
public int selectNormalChildrenDeptById(Long deptId)
{
return deptMapper.selectNormalChildrenDeptById(deptId);
}
/**
* 是否存在子节点
*
* @param deptId 部门ID
* @return 结果
*/
@Override
public boolean hasChildByDeptId(Long deptId)
{
int result = deptMapper.hasChildByDeptId(deptId);
return result > 0;
}
/**
* 查询部门是否存在用户
*
* @param deptId 部门ID
* @return 结果 true 存在 false 不存在
*/
@Override
public boolean checkDeptExistUser(Long deptId)
{
int result = deptMapper.checkDeptExistUser(deptId);
return result > 0;
}
/**
* 校验部门名称是否唯一
*
* @param dept 部门信息
* @return 结果
*/
@Override
public boolean checkDeptNameUnique(SysDept dept)
{
Long deptId = StringUtils.isNull(dept.getDeptId()) ? -1L : dept.getDeptId();
SysDept info = deptMapper.checkDeptNameUnique(dept.getDeptName(), dept.getParentId());
if (StringUtils.isNotNull(info) && info.getDeptId().longValue() != deptId.longValue())
{
return UserConstants.NOT_UNIQUE;
}
return UserConstants.UNIQUE;
}
/**
* 校验部门是否有数据权限
*
* @param deptId 部门id
*/
@Override
public void checkDeptDataScope(Long deptId)
{
if (!SysUser.isAdmin(SecurityUtils.getUserId()) && StringUtils.isNotNull(deptId))
{
SysDept dept = new SysDept();
dept.setDeptId(deptId);
List<SysDept> depts = SpringUtils.getAopProxy(this).selectDeptList(dept);
if (StringUtils.isEmpty(depts))
{
throw new ServiceException("没有权限访问部门数据!");
}
}
}
/**
* 新增保存部门信息
*
* @param dept 部门信息
* @return 结果
*/
@Override
public int insertDept(SysDept dept)
{
SysDept info = deptMapper.selectDeptById(dept.getParentId());
// 如果父节点不为正常状态,则不允许新增子节点
if (!UserConstants.DEPT_NORMAL.equals(info.getStatus()))
{
throw new ServiceException("部门停用,不允许新增");
}
dept.setAncestors(info.getAncestors() + "," + dept.getParentId());
return deptMapper.insertDept(dept);
}
/**
* 修改保存部门信息
*
* @param dept 部门信息
* @return 结果
*/
@Override
public int updateDept(SysDept dept)
{
SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId());
SysDept oldDept = deptMapper.selectDeptById(dept.getDeptId());
if (StringUtils.isNotNull(newParentDept) && StringUtils.isNotNull(oldDept))
{
String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId();
String oldAncestors = oldDept.getAncestors();
dept.setAncestors(newAncestors);
updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors);
}
int result = deptMapper.updateDept(dept);
if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors())
&& !StringUtils.equals("0", dept.getAncestors()))
{
// 如果该部门是启用状态,则启用该部门的所有上级部门
updateParentDeptStatusNormal(dept);
}
return result;
}
/**
* 修改该部门的父级部门状态
*
* @param dept 当前部门
*/
private void updateParentDeptStatusNormal(SysDept dept)
{
String ancestors = dept.getAncestors();
Long[] deptIds = Convert.toLongArray(ancestors);
deptMapper.updateDeptStatusNormal(deptIds);
}
/**
* 修改子元素关系
*
* @param deptId 被修改的部门ID
* @param newAncestors 新的父ID集合
* @param oldAncestors 旧的父ID集合
*/
public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors)
{
List<SysDept> children = deptMapper.selectChildrenDeptById(deptId);
for (SysDept child : children)
{
child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
}
if (children.size() > 0)
{
deptMapper.updateDeptChildren(children);
}
}
/**
* 删除部门管理信息
*
* @param deptId 部门ID
* @return 结果
*/
@Override
public int deleteDeptById(Long deptId)
{
return deptMapper.deleteDeptById(deptId);
}
/**
* 递归列表
*/
private void recursionFn(List<SysDept> list, SysDept t)
{
// 得到子节点列表
List<SysDept> childList = getChildList(list, t);
t.setChildren(childList);
for (SysDept tChild : childList)
{
if (hasChild(list, tChild))
{
recursionFn(list, tChild);
}
}
}
/**
* 得到子节点列表
*/
private List<SysDept> getChildList(List<SysDept> list, SysDept t)
{
List<SysDept> tlist = new ArrayList<SysDept>();
Iterator<SysDept> it = list.iterator();
while (it.hasNext())
{
SysDept n = (SysDept) it.next();
if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue())
{
tlist.add(n);
}
}
return tlist;
}
/**
* 判断是否有子节点
*/
private boolean hasChild(List<SysDept> list, SysDept t)
{
return getChildList(list, t).size() > 0;
}
}