feat(menu): 添加菜单完整路径功能和待写病历管理

- 在SysMenu实体类中新增fullPath字段用于存储完整路径
- 实现buildMenuTreeWithFullPath方法构建带完整路径的菜单树
- 添加getMenuFullPath和generateFullPath服务方法获取和生成完整路径
- 在菜单控制器中增加获取完整路径的API接口
- 前端菜单组件显示完整路径并在新增修改时使用后端返回的路径
- 添加待写病历管理功能包括获取待写病历列表、数量统计和检查接口
- 在医生工作站界面集成待写病历选项卡和相关处理逻辑
- 更新首页统计数据接口路径并添加待写病历数量获取功能
- 重构首页快捷功能配置为动态从数据库获取用户自定义配置
- 优化菜单列表查询使用异步方式处理带完整路径的菜单数据
- 添加菜单完整路径的数据库映射配置和前端API调用支持
This commit is contained in:
2026-02-01 14:50:22 +08:00
parent 29ecfd90f2
commit 0a08088ada
14 changed files with 1240 additions and 163 deletions

View File

@@ -33,7 +33,9 @@ public class SysMenuController extends BaseController {
@GetMapping("/list")
public AjaxResult list(SysMenu menu) {
List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
return success(menus);
// 构建带完整路径的菜单树
List<SysMenu> menuTreeWithFullPath = menuService.buildMenuTreeWithFullPath(menus);
return success(menuTreeWithFullPath);
}
/**
@@ -115,4 +117,25 @@ public class SysMenuController extends BaseController {
}
return toAjax(menuService.deleteMenuById(menuId));
}
/**
* 获取菜单完整路径
*/
@PreAuthorize("@ss.hasPermi('system:menu:query')")
@GetMapping("/fullPath/{menuId}")
public AjaxResult getFullPath(@PathVariable("menuId") Long menuId) {
String fullPath = menuService.getMenuFullPath(menuId);
return success(fullPath);
}
/**
* 生成完整路径
*/
@PreAuthorize("@ss.hasPermi('system:menu:query')")
@PostMapping("/generateFullPath")
public AjaxResult generateFullPath(@RequestParam(required = false) Long parentId,
@RequestParam String currentPath) {
String fullPath = menuService.generateFullPath(parentId, currentPath);
return success(fullPath);
}
}

View File

@@ -69,6 +69,9 @@ public class SysMenu extends BaseEntity {
/** 子菜单 */
private List<SysMenu> children = new ArrayList<SysMenu>();
/** 完整路径 */
private String fullPath;
public Long getMenuId() {
return menuId;
}
@@ -212,6 +215,14 @@ public class SysMenu extends BaseEntity {
this.children = children;
}
public String getFullPath() {
return fullPath;
}
public void setFullPath(String fullPath) {
this.fullPath = fullPath;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("menuId", getMenuId())
@@ -219,8 +230,8 @@ public class SysMenu extends BaseEntity {
.append("path", getPath()).append("component", getComponent()).append("query", getQuery())
.append("routeName", getRouteName()).append("isFrame", getIsFrame()).append("IsCache", getIsCache())
.append("menuType", getMenuType()).append("visible", getVisible()).append("status ", getStatus())
.append("perms", getPerms()).append("icon", getIcon()).append("createBy", getCreateBy())
.append("createTime", getCreateTime()).append("updateBy", getUpdateBy())
.append("perms", getPerms()).append("icon", getIcon()).append("fullPath", getFullPath())
.append("createBy", getCreateBy()).append("createTime", getCreateTime()).append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime()).append("remark", getRemark()).toString();
}
}

View File

@@ -3,7 +3,6 @@ package com.core.system.service;
import com.core.common.core.domain.TreeSelect;
import com.core.common.core.domain.entity.SysMenu;
import com.core.system.domain.vo.RouterVo;
import java.util.List;
import java.util.Set;
@@ -22,7 +21,7 @@ public interface ISysMenuService {
public List<SysMenu> selectMenuList(Long userId);
/**
* 根据用户查询系统菜单列表
* 查询系统菜单列表
*
* @param menu 菜单信息
* @param userId 用户ID
@@ -50,7 +49,7 @@ public interface ISysMenuService {
* 根据用户ID查询菜单树信息
*
* @param userId 用户ID
* @return 菜单列表
* @return 菜单树信息
*/
public List<SysMenu> selectMenuTreeByUserId(Long userId);
@@ -72,15 +71,23 @@ public interface ISysMenuService {
/**
* 构建前端所需要树结构
*
*
* @param menus 菜单列表
* @return 树结构列表
*/
public List<SysMenu> buildMenuTree(List<SysMenu> menus);
/**
* 构建前端所需要树结构(包含完整路径)
*
* @param menus 菜单列表
* @return 树结构列表
*/
public List<SysMenu> buildMenuTreeWithFullPath(List<SysMenu> menus);
/**
* 构建前端所需要下拉树结构
*
*
* @param menus 菜单列表
* @return 下拉树结构列表
*/
@@ -98,15 +105,15 @@ public interface ISysMenuService {
* 是否存在菜单子节点
*
* @param menuId 菜单ID
* @return 结果 true 存在 false 不存在
* @return 结果
*/
public boolean hasChildByMenuId(Long menuId);
/**
* 查询菜单是否存在角色
* 查询菜单使用数量
*
* @param menuId 菜单ID
* @return 结果 true 存在 false 不存在
* @return 结果
*/
public boolean checkMenuExistRole(Long menuId);
@@ -141,4 +148,21 @@ public interface ISysMenuService {
* @return 结果
*/
public boolean checkMenuNameUnique(SysMenu menu);
}
/**
* 根据菜单ID获取完整路径
*
* @param menuId 菜单ID
* @return 完整路径
*/
public String getMenuFullPath(Long menuId);
/**
* 根据路径参数生成完整路径
*
* @param parentId 父级菜单ID
* @param currentPath 当前路径
* @return 完整路径
*/
public String generateFullPath(Long parentId, String currentPath);
}

View File

@@ -193,7 +193,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
/**
* 构建前端所需要树结构
*
*
* @param menus 菜单列表
* @return 树结构列表
*/
@@ -215,6 +215,36 @@ public class SysMenuServiceImpl implements ISysMenuService {
return returnList;
}
/**
* 构建前端所需要树结构(包含完整路径)
*
* @param menus 菜单列表
* @return 树结构列表
*/
@Override
public List<SysMenu> buildMenuTreeWithFullPath(List<SysMenu> menus) {
List<SysMenu> menuTree = buildMenuTree(menus);
// 为每个菜单项添加完整路径
addFullPathToMenuTree(menuTree);
return menuTree;
}
/**
* 为菜单树添加完整路径
*
* @param menus 菜单树
*/
private void addFullPathToMenuTree(List<SysMenu> menus) {
for (SysMenu menu : menus) {
// 计算当前菜单的完整路径
menu.setFullPath(getMenuFullPath(menu.getMenuId()));
// 递归处理子菜单
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
addFullPathToMenuTree(menu.getChildren());
}
}
}
/**
* 构建前端所需要下拉树结构
*
@@ -488,11 +518,139 @@ public class SysMenuServiceImpl implements ISysMenuService {
/**
* 内链域名特殊字符替换
*
*
* @return 替换后的内链域名
*/
public String innerLinkReplaceEach(String path) {
return StringUtils.replaceEach(path, new String[] {Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":"},
new String[] {"", "", "", "/", "/"});
}
/**
* 根据菜单ID获取完整路径
*
* @param menuId 菜单ID
* @return 完整路径
*/
@Override
public String getMenuFullPath(Long menuId) {
SysMenu menu = menuMapper.selectMenuById(menuId);
if (menu == null) {
return "";
}
StringBuilder fullPath = new StringBuilder();
buildMenuPath(menu, fullPath);
// 标准化完整路径,确保没有多余的斜杠
return normalizePath(fullPath.toString());
}
/**
* 递归构建菜单路径
*
* @param menu 菜单信息
* @param path 路径构建器
*/
private void buildMenuPath(SysMenu menu, StringBuilder path) {
if (menu == null || menu.getMenuId() == null) {
return;
}
// 如果不是根节点,则递归查找父节点
if (menu.getParentId() != null && menu.getParentId() > 0) {
SysMenu parentMenu = menuMapper.selectMenuById(menu.getParentId());
if (parentMenu != null) {
buildMenuPath(parentMenu, path);
}
}
// 添加当前菜单的路径,避免双斜杠
String currentPath = normalizePathSegment(menu.getPath());
if (currentPath != null && !currentPath.isEmpty()) {
if (path.length() > 0) {
// 确保路径之间只有一个斜杠分隔符
// 如果当前路径不为空,且当前路径不以斜杠结尾,则添加斜杠并追加路径
if (path.charAt(path.length() - 1) != '/') {
path.append("/").append(currentPath);
} else {
path.append(currentPath);
}
} else {
// 对于第一个路径,直接追加
path.append(currentPath);
}
}
}
/**
* 标准化路径片段,移除开头的斜杠
*
* @param path 原始路径
* @return 标准化后的路径片段
*/
private String normalizePathSegment(String path) {
if (path == null) {
return null;
}
// 移除开头的斜杠
if (path.startsWith("/")) {
path = path.substring(1);
}
return path;
}
/**
* 标准化完整路径,移除多余的斜杠
*
* @param path 原始路径
* @return 标准化后的完整路径
*/
private String normalizePath(String path) {
if (path == null) {
return null;
}
// 处理多个连续斜杠,将其替换为单个斜杠
while (path.contains("//")) {
path = path.replace("//", "/");
}
return path;
}
/**
* 根据路径参数生成完整路径
*
* @param parentId 父级菜单ID
* @param currentPath 当前路径
* @return 完整路径
*/
@Override
public String generateFullPath(Long parentId, String currentPath) {
StringBuilder fullPath = new StringBuilder();
// 如果有父级菜单,则先获取父级菜单的完整路径
if (parentId != null && parentId > 0) {
SysMenu parentMenu = menuMapper.selectMenuById(parentId);
if (parentMenu != null) {
String parentFullPath = getMenuFullPath(parentId);
if (StringUtils.isNotEmpty(parentFullPath)) {
fullPath.append(parentFullPath);
}
}
}
// 添加当前路径
if (StringUtils.isNotEmpty(currentPath)) {
if (fullPath.length() > 0) {
fullPath.append("/").append(currentPath);
} else {
fullPath.append(currentPath);
}
}
return normalizePath(fullPath.toString());
}
}

View File

@@ -21,6 +21,7 @@
<result property="status" column="status"/>
<result property="perms" column="perms"/>
<result property="icon" column="icon"/>
<result property="fullPath" column="full_path"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>

View File

@@ -58,4 +58,28 @@ public interface IDoctorStationEmrAppService {
* @return 病历详情
*/
R<?> getEmrDetail(Long encounterId);
/**
* 获取待写病历列表
*
* @param doctorId 医生ID
* @return 待写病历列表
*/
R<?> getPendingEmrList(Long doctorId);
/**
* 获取待写病历数量
*
* @param doctorId 医生ID
* @return 待写病历数量
*/
R<?> getPendingEmrCount(Long doctorId);
/**
* 检查患者是否需要写病历
*
* @param encounterId 就诊ID
* @return 患者是否需要写病历
*/
R<?> checkNeedWriteEmr(Long encounterId);
}

View File

@@ -7,7 +7,15 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.core.common.utils.SecurityUtils;
import com.openhis.administration.domain.Encounter;
import com.openhis.administration.domain.Patient;
import com.openhis.administration.mapper.EncounterMapper;
import com.openhis.administration.mapper.PatientMapper;
import java.util.Date;
import java.sql.Timestamp;
import com.openhis.common.enums.BindingType;
import com.openhis.common.enums.EncounterStatus;
import com.openhis.document.domain.Emr;
import com.openhis.document.domain.EmrDetail;
import com.openhis.document.domain.EmrDict;
@@ -46,6 +54,15 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
@Resource
IEmrDictService emrDictService;
@Resource
private EncounterMapper encounterMapper;
@Resource
private PatientMapper patientMapper;
@Resource
private com.openhis.administration.mapper.EncounterParticipantMapper encounterParticipantMapper;
/**
* 添加病人病历信息
*
@@ -175,4 +192,131 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
return emrTemplateService.removeById(id) ? R.ok() : R.fail();
}
/**
* 获取待写病历列表
*
* @param doctorId 医生ID
* @return 待写病历列表
*/
@Override
public R<?> getPendingEmrList(Long doctorId) {
// 由于Encounter实体中没有jzPractitionerUserId字段我们需要通过关联查询来获取相关信息
// 使用医生工作站的mapper来查询相关数据
// 这里我们直接使用医生工作站的查询逻辑
// 查询当前医生负责的、状态为"就诊中"但还没有写病历的患者
// 需要通过EncounterParticipant表来关联医生信息
List<Encounter> encounters = encounterMapper.selectList(
new LambdaQueryWrapper<Encounter>()
.eq(Encounter::getStatusEnum, EncounterStatus.IN_PROGRESS.getValue())
);
// 过滤出由指定医生负责且还没有写病历的就诊记录
List<Map<String, Object>> pendingEmrs = new ArrayList<>();
for (Encounter encounter : encounters) {
// 检查该就诊记录是否已经有病历
Emr existingEmr = emrService.getOne(
new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounter.getId())
);
// 检查该就诊是否由指定医生负责
boolean isAssignedToDoctor = isEncounterAssignedToDoctor(encounter.getId(), doctorId);
if (existingEmr == null && isAssignedToDoctor) {
// 如果没有病历且由该医生负责,则添加到待写病历列表
Map<String, Object> pendingEmr = new java.util.HashMap<>();
// 获取患者信息
Patient patient = patientMapper.selectById(encounter.getPatientId());
pendingEmr.put("encounterId", encounter.getId());
pendingEmr.put("patientId", encounter.getPatientId());
pendingEmr.put("patientName", patient != null ? patient.getName() : "未知");
pendingEmr.put("gender", patient != null ? patient.getGenderEnum() : null);
// 使用出生日期计算年龄
pendingEmr.put("age", patient != null && patient.getBirthDate() != null ?
calculateAge(patient.getBirthDate()) : null);
// 使用创建时间作为挂号时间
pendingEmr.put("registerTime", encounter.getCreateTime());
pendingEmr.put("busNo", encounter.getBusNo()); // 病历号
pendingEmrs.add(pendingEmr);
}
}
return R.ok(pendingEmrs);
}
/**
* 获取待写病历数量
*
* @param doctorId 医生ID
* @return 待写病历数量
*/
@Override
public R<?> getPendingEmrCount(Long doctorId) {
// 获取待写病历列表,然后返回数量
R<?> result = getPendingEmrList(doctorId);
if (result.getCode() == 200) {
List<?> pendingEmrs = (List<?>) result.getData();
return R.ok(pendingEmrs.size());
}
return R.ok(0);
}
/**
* 检查患者是否需要写病历
*
* @param encounterId 就诊ID
* @return 患者是否需要写病历
*/
@Override
public R<?> checkNeedWriteEmr(Long encounterId) {
// 检查该就诊记录是否已经有病历
Emr existingEmr = emrService.getOne(
new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounterId)
);
// 如果没有病历,则需要写病历
boolean needWrite = existingEmr == null;
return R.ok(needWrite);
}
/**
* 检查就诊是否分配给指定医生
*
* @param encounterId 就诊ID
* @param doctorId 医生ID
* @return 是否分配给指定医生
*/
private boolean isEncounterAssignedToDoctor(Long encounterId, Long doctorId) {
// 查询就诊参与者表,检查是否有指定医生的接诊记录
com.openhis.administration.domain.EncounterParticipant participant =
encounterParticipantMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.openhis.administration.domain.EncounterParticipant>()
.eq(com.openhis.administration.domain.EncounterParticipant::getEncounterId, encounterId)
.eq(com.openhis.administration.domain.EncounterParticipant::getPractitionerId, doctorId)
);
return participant != null;
}
/**
* 根据出生日期计算年龄
*
* @param birthDate 出生日期
* @return 年龄
*/
private String calculateAge(Date birthDate) {
if (birthDate == null) {
return null;
}
// 将java.util.Date转换为java.time.LocalDate
java.time.LocalDate birthLocalDate = new java.sql.Timestamp(birthDate.getTime()).toLocalDateTime().toLocalDate();
java.time.LocalDate currentDate = java.time.LocalDate.now();
int age = java.time.Period.between(birthLocalDate, currentDate).getYears();
return String.valueOf(age);
}
}