feat(menu): 添加菜单完整路径功能和待写病历管理
- 在SysMenu实体类中新增fullPath字段用于存储完整路径 - 实现buildMenuTreeWithFullPath方法构建带完整路径的菜单树 - 添加getMenuFullPath和generateFullPath服务方法获取和生成完整路径 - 在菜单控制器中增加获取完整路径的API接口 - 前端菜单组件显示完整路径并在新增修改时使用后端返回的路径 - 添加待写病历管理功能包括获取待写病历列表、数量统计和检查接口 - 在医生工作站界面集成待写病历选项卡和相关处理逻辑 - 更新首页统计数据接口路径并添加待写病历数量获取功能 - 重构首页快捷功能配置为动态从数据库获取用户自定义配置 - 优化菜单列表查询使用异步方式处理带完整路径的菜单数据 - 添加菜单完整路径的数据库映射配置和前端API调用支持
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -78,6 +77,14 @@ public interface ISysMenuService {
|
||||
*/
|
||||
public List<SysMenu> buildMenuTree(List<SysMenu> menus);
|
||||
|
||||
/**
|
||||
* 构建前端所需要树结构(包含完整路径)
|
||||
*
|
||||
* @param menus 菜单列表
|
||||
* @return 树结构列表
|
||||
*/
|
||||
public List<SysMenu> buildMenuTreeWithFullPath(List<SysMenu> menus);
|
||||
|
||||
/**
|
||||
* 构建前端所需要下拉树结构
|
||||
*
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建前端所需要下拉树结构
|
||||
*
|
||||
@@ -495,4 +525,132 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import request from '@/utils/request'
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 获取首页统计数据
|
||||
export function getHomeStatistics() {
|
||||
return request({
|
||||
url: '/home/statistics',
|
||||
url: '/system/home/statistics',
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// 获取待写病历数量
|
||||
export function getPendingEmrCount() {
|
||||
return request({
|
||||
url: '/doctor-station/pending-emr/pending-count',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
@@ -58,3 +58,23 @@ export function delMenu(menuId) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取菜单完整路径
|
||||
export function getMenuFullPath(menuId) {
|
||||
return request({
|
||||
url: '/system/menu/fullPath/' + menuId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 生成完整路径
|
||||
export function generateFullPath(parentId, currentPath) {
|
||||
return request({
|
||||
url: '/system/menu/generateFullPath',
|
||||
method: 'post',
|
||||
params: {
|
||||
parentId: parentId,
|
||||
currentPath: currentPath
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import {parseTime} from './openhis'
|
||||
import { parseTime } from './openhis'
|
||||
|
||||
// 导出 parseTime 函数以供其他模块使用
|
||||
export { parseTime }
|
||||
|
||||
/**
|
||||
* 表格时间格式化
|
||||
|
||||
@@ -129,6 +129,39 @@ export function saveEmrTemplate(data) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待写病历列表
|
||||
*/
|
||||
export function listPendingEmr(queryParams) {
|
||||
return request({
|
||||
url: '/doctor-station/pending-emr/pending-list',
|
||||
method: 'get',
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待写病历数量
|
||||
*/
|
||||
export function getPendingEmrCount(doctorId) {
|
||||
return request({
|
||||
url: '/doctor-station/pending-emr/pending-count',
|
||||
method: 'get',
|
||||
params: { doctorId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查患者是否需要写病历
|
||||
*/
|
||||
export function checkNeedWriteEmr(encounterId) {
|
||||
return request({
|
||||
url: '/doctor-station/pending-emr/need-write-emr',
|
||||
method: 'get',
|
||||
params: { encounterId },
|
||||
});
|
||||
}
|
||||
|
||||
// 诊断相关接口
|
||||
/**
|
||||
* 保存诊断
|
||||
|
||||
@@ -129,6 +129,9 @@
|
||||
<el-tab-pane label="门诊病历" name="hospitalizationEmr">
|
||||
<hospitalizationEmr :patientInfo="patientInfo" :activeTab="activeTab" @emrSaved="handleEmrSaved" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="待写病历" name="pendingEmr">
|
||||
<PendingEmr @writeEmr="handleWriteEmr" @viewPatient="handleViewPatient" />
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane label="病历" name="emr">
|
||||
<Emr
|
||||
:patientInfo="patientInfo"
|
||||
@@ -188,6 +191,7 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import hospitalizationEmr from './components/hospitalizationEmr/index.vue';
|
||||
import PendingEmr from './components/pendingEmr/index.vue';
|
||||
import {
|
||||
completeEncounter,
|
||||
getEncounterDiagnosis,
|
||||
@@ -214,6 +218,8 @@ import { nextTick } from 'vue';
|
||||
import { updatePatientInfo } from './components/store/patient.js';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
// // 监听路由离开事件
|
||||
// onBeforeRouteLeave((to, from, next) => {
|
||||
// // 弹出确认框
|
||||
@@ -228,6 +234,20 @@ defineOptions({
|
||||
name: 'PatientParentCard',
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
// 监听路由参数变化
|
||||
watch(
|
||||
() => route.query.tab,
|
||||
(newTab) => {
|
||||
if (newTab === 'pendingEmr') {
|
||||
console.log('Route tab changed to pendingEmr');
|
||||
activeTab.value = 'pendingEmr';
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const userStore = useUserStore();
|
||||
const bedfont = 'bed-font';
|
||||
const queryParams = ref({
|
||||
@@ -321,6 +341,21 @@ onMounted(() => {
|
||||
getWaitPatient();
|
||||
getWaitPatientList();
|
||||
getPatientList();
|
||||
|
||||
// 检查路由参数,如果指定了待写病历,则默认选中该选项卡
|
||||
console.log('Route query:', route.query); // 调试信息
|
||||
if (route.query.tab === 'pendingEmr') {
|
||||
console.log('Switching to pendingEmr tab'); // 调试信息
|
||||
activeTab.value = 'pendingEmr';
|
||||
}
|
||||
|
||||
// 确保DOM更新后激活正确的选项卡
|
||||
nextTick(() => {
|
||||
if (route.query.tab === 'pendingEmr') {
|
||||
// 强制触发选项卡切换
|
||||
handleClick('pendingEmr');
|
||||
}
|
||||
});
|
||||
});
|
||||
// 获取现诊患者列表
|
||||
function getPatientList() {
|
||||
@@ -622,6 +657,20 @@ function handleEmrSaved(isSaved) {
|
||||
outpatientEmrSaved.value = isSaved;
|
||||
}
|
||||
|
||||
// 处理写病历事件
|
||||
function handleWriteEmr(row) {
|
||||
console.log('处理写病历:', row);
|
||||
// 这里可以触发切换到病历页面并加载患者信息
|
||||
// 可以根据需要实现具体逻辑
|
||||
}
|
||||
|
||||
// 处理查看患者事件
|
||||
function handleViewPatient(row) {
|
||||
console.log('处理查看患者:', row);
|
||||
// 这里可以触发查看患者详细信息的逻辑
|
||||
// 可以根据需要实现具体逻辑
|
||||
}
|
||||
|
||||
function openDrawer() {
|
||||
drawer.value = true;
|
||||
}
|
||||
|
||||
@@ -46,27 +46,31 @@
|
||||
<div class="quick-access-section">
|
||||
<div class="section-header">
|
||||
<h3>快捷功能</h3>
|
||||
<el-button text type="primary" @click="showAllFunctions">查看全部</el-button>
|
||||
<div>
|
||||
<el-button text type="primary" @click="showConfig">配置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quick-access-grid">
|
||||
<div
|
||||
v-for="func in quickAccess"
|
||||
:key="func.key"
|
||||
class="quick-access-card"
|
||||
@click="handleQuickAccess(func)"
|
||||
>
|
||||
<div class="quick-icon">
|
||||
<el-icon :size="28" :color="func.iconColor">
|
||||
<component :is="func.icon" />
|
||||
</el-icon>
|
||||
<div v-loading="quickAccessLoading" element-loading-text="加载快捷功能...">
|
||||
<div class="quick-access-grid">
|
||||
<div
|
||||
v-for="func in quickAccess"
|
||||
:key="func.key"
|
||||
class="quick-access-card"
|
||||
@click="handleQuickAccess(func)"
|
||||
>
|
||||
<div class="quick-icon">
|
||||
<el-icon :size="28" :color="func.iconColor">
|
||||
<component :is="func.icon" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="quick-label">{{ func.label }}</div>
|
||||
</div>
|
||||
<div class="quick-label">{{ func.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 待办事项 -->
|
||||
<div class="todo-section" v-if="todoList.length > 0">
|
||||
<div class="todo-section">
|
||||
<div class="section-header">
|
||||
<h3>待办事项</h3>
|
||||
<el-badge :value="todoList.length" class="todo-badge">
|
||||
@@ -92,6 +96,9 @@
|
||||
</div>
|
||||
<div class="todo-time">{{ todo.time }}</div>
|
||||
</div>
|
||||
<div v-if="todoList.length === 0" class="empty-todo">
|
||||
<el-empty description="暂无待办事项" :image-size="60" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -123,11 +130,15 @@
|
||||
</template>
|
||||
|
||||
<script setup name="Home">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { markRaw } from 'vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { getHomeStatistics } from '@/api/home'
|
||||
import { getHomeStatistics, getPendingEmrCount } from '@/api/home'
|
||||
import { listTodo } from '@/api/workflow/task'
|
||||
import { getCurrentUserConfig } from '@/api/system/userConfig'
|
||||
import { listMenu, getMenuFullPath } from '@/api/system/menu'
|
||||
import { ElDivider } from 'element-plus'
|
||||
import {
|
||||
User,
|
||||
Document,
|
||||
@@ -149,9 +160,67 @@ import {
|
||||
Van,
|
||||
Bell,
|
||||
Setting,
|
||||
Search
|
||||
Search,
|
||||
Menu,
|
||||
Grid,
|
||||
Folder,
|
||||
Tickets,
|
||||
ChatDotSquare,
|
||||
Histogram,
|
||||
OfficeBuilding,
|
||||
Postcard,
|
||||
Collection,
|
||||
VideoPlay,
|
||||
Camera,
|
||||
Headset,
|
||||
Phone,
|
||||
Message,
|
||||
ChatLineSquare,
|
||||
ChatRound,
|
||||
Guide,
|
||||
Help,
|
||||
InfoFilled,
|
||||
CircleCheck,
|
||||
CircleClose,
|
||||
QuestionFilled,
|
||||
Star,
|
||||
Link,
|
||||
Position,
|
||||
Picture,
|
||||
Upload,
|
||||
Download,
|
||||
CaretLeft,
|
||||
CaretRight,
|
||||
More,
|
||||
Close,
|
||||
Check,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
Plus,
|
||||
Minus,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
Refresh,
|
||||
Edit,
|
||||
Delete,
|
||||
Share,
|
||||
View,
|
||||
SwitchButton,
|
||||
Hide,
|
||||
Finished,
|
||||
CirclePlus,
|
||||
Remove,
|
||||
CircleCheckFilled,
|
||||
CircleCloseFilled,
|
||||
WarningFilled,
|
||||
Goods
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
// 为别名单独导入
|
||||
import { InfoFilled as InfoFilledIcon, QuestionFilled as QuestionFilledIcon, SuccessFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -180,7 +249,7 @@ const roleStatsConfig = {
|
||||
doctor: [
|
||||
{ key: 'myPatients', label: '我的患者', icon: markRaw(User), type: 'primary', iconColor: '#409eff' },
|
||||
{ key: 'todayAppointments', label: '今日门诊', icon: markRaw(Calendar), type: 'success', iconColor: '#67c23a' },
|
||||
{ key: 'pendingRecords', label: '待写病历', icon: markRaw(Document), type: 'warning', iconColor: '#e6a23c' },
|
||||
{ key: 'pendingEmr', label: '待写病历', icon: markRaw(Document), type: 'warning', iconColor: '#e6a23c' },
|
||||
{ key: 'prescriptions', label: '今日处方', icon: markRaw(Box), type: 'info', iconColor: '#909399' }
|
||||
],
|
||||
nurse: [
|
||||
@@ -203,60 +272,371 @@ const roleStatsConfig = {
|
||||
]
|
||||
}
|
||||
|
||||
// 不同角色的快捷功能配置
|
||||
const roleQuickAccessConfig = {
|
||||
admin: [
|
||||
{ key: 'patient', label: '患者管理', icon: markRaw(User), iconColor: '#409eff', route: '/patientmanagement' },
|
||||
{ key: 'appointment', label: '预约管理', icon: markRaw(Calendar), iconColor: '#67c23a', route: '/appoinmentmanage' },
|
||||
{ key: 'doctor', label: '医生管理', icon: markRaw(User), iconColor: '#e6a23c', route: '/doctorstation' },
|
||||
{ key: 'surgery', label: '手术管理', icon: markRaw(Operation), iconColor: '#f56c6c', route: '/surgerymanage' },
|
||||
{ key: 'drug', label: '药品管理', icon: markRaw(Box), iconColor: '#909399', route: '/pharmacymanagement' },
|
||||
{ key: 'statistic', label: '数据统计', icon: markRaw(TrendCharts), iconColor: '#409eff', route: '/monitor' },
|
||||
{ key: 'invoice', label: '发票管理', icon: markRaw(Files), iconColor: '#67c23a', route: '/basicmanage/InvoiceManagement' },
|
||||
{ key: 'system', label: '系统设置', icon: markRaw(Setting), iconColor: '#909399', route: '/system' }
|
||||
],
|
||||
doctor: [
|
||||
{ key: 'outpatient', label: '门诊接诊', icon: markRaw(ChatDotRound), iconColor: '#409eff', route: '/doctorstation' },
|
||||
{ key: 'emr', label: '病历管理', icon: markRaw(Document), iconColor: '#67c23a', route: '/doctorstation/doctorphrase' },
|
||||
{ key: 'prescription', label: '开立处方', icon: markRaw(Box), iconColor: '#e6a23c', route: '/clinicmanagement/ePrescribing' },
|
||||
{ key: 'history', label: '历史处方', icon: markRaw(Clock), iconColor: '#f56c6c', route: '/clinicmanagement/historicalPrescription' },
|
||||
{ key: 'schedule', label: '排班管理', icon: markRaw(Calendar), iconColor: '#909399', route: '/appoinmentmanage/deptManage' },
|
||||
{ key: 'inquiry', label: '患者查询', icon: markRaw(Search), iconColor: '#409eff', route: '/patientmanagement' }
|
||||
],
|
||||
nurse: [
|
||||
{ key: 'ward', label: '病房管理', icon: markRaw(User), iconColor: '#409eff', route: '/inpatientNurse/inpatientNurseStation' },
|
||||
{ key: 'execution', label: '医嘱执行', icon: markRaw(Operation), iconColor: '#67c23a', route: '/inpatientNurse/medicalOrderExecution' },
|
||||
{ key: 'proofread', label: '医嘱核对', icon: markRaw(Document), iconColor: '#e6a23c', route: '/inpatientNurse/medicalOrderProofread' },
|
||||
{ key: 'drugCollect', label: '领药管理', icon: markRaw(Box), iconColor: '#f56c6c', route: '/inpatientNurse/medicineCollect' },
|
||||
{ key: 'tpr', label: '体温单', icon: markRaw(Monitor), iconColor: '#909399', route: '/inpatientNurse/tprsheet' },
|
||||
{ key: 'nursing', label: '护理记录', icon: markRaw(ChatDotRound), iconColor: '#409eff', route: '/inpatientNurse/nursingRecord' }
|
||||
],
|
||||
pharmacist: [
|
||||
{ key: 'dispensing', label: '发药管理', icon: markRaw(Box), iconColor: '#409eff', route: '/pharmacymanagement' },
|
||||
{ key: 'prescription', label: '处方审核', icon: markRaw(Document), iconColor: '#67c23a', route: '/pharmacymanagement' },
|
||||
{ key: 'inventory', label: '库存管理', icon: markRaw(Van), iconColor: '#e6a23c', route: '/medicineStorage' },
|
||||
{ key: 'purchase', label: '采购管理', icon: markRaw(ShoppingCart), iconColor: '#f56c6c', route: '/medicineStorage' },
|
||||
{ key: 'warning', label: '效期预警', icon: markRaw(Warning), iconColor: '#f56c6c', route: '/medicationmanagement/statisticalManagement/statisticalManagement' },
|
||||
{ key: 'statistics', label: '用药统计', icon: markRaw(DataLine), iconColor: '#909399', route: '/monitor' }
|
||||
],
|
||||
cashier: [
|
||||
{ key: 'registration', label: '挂号收费', icon: markRaw(Money), iconColor: '#409eff', route: '/charge/outpatientregistration' },
|
||||
{ key: 'clinicCharge', label: '门诊收费', icon: markRaw(Wallet), iconColor: '#67c23a', route: '/charge/cliniccharge' },
|
||||
{ key: 'refund', label: '退费管理', icon: markRaw(Document), iconColor: '#e6a23c', route: '/charge/clinicrefund' },
|
||||
{ key: 'invoice', label: '发票打印', icon: markRaw(Files), iconColor: '#f56c6c', route: '/basicmanage/InvoiceManagement' },
|
||||
{ key: 'record', label: '收费记录', icon: markRaw(Clock), iconColor: '#909399', route: '/charge/clinicRecord' },
|
||||
{ key: 'insurance', label: '医保结算', icon: markRaw(Bell), iconColor: '#409eff', route: '/ybmanagement' }
|
||||
]
|
||||
}
|
||||
// 从数据库获取用户配置的快捷功能ID列表
|
||||
const getUserQuickAccessConfig = async () => {
|
||||
try {
|
||||
// 优先从数据库获取配置
|
||||
const response = await getCurrentUserConfig('homeFeaturesConfig');
|
||||
console.log('从数据库获取的配置响应:', response);
|
||||
|
||||
// 检查响应结构,数据可能在 data 或 msg 字段中
|
||||
let configData = null;
|
||||
if (response.code === 200) {
|
||||
if (response.data) {
|
||||
configData = response.data;
|
||||
} else if (response.msg) {
|
||||
// 如果数据在 msg 字段中(可能是URL编码的)
|
||||
configData = response.msg;
|
||||
}
|
||||
}
|
||||
|
||||
if (configData) {
|
||||
// 解码配置值(如果是URL编码的)
|
||||
let decodedData = configData;
|
||||
try {
|
||||
decodedData = decodeURIComponent(configData);
|
||||
} catch (decodeError) {
|
||||
console.warn('解码配置数据失败,使用原始数据:', decodeError);
|
||||
decodedData = configData;
|
||||
}
|
||||
console.log('解码后的配置数据:', decodedData);
|
||||
const parsedData = JSON.parse(decodedData);
|
||||
console.log('解析后的配置数据:', parsedData);
|
||||
return parsedData;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从数据库获取用户配置失败:', error);
|
||||
}
|
||||
|
||||
// 如果数据库中没有配置,尝试从本地存储获取
|
||||
try {
|
||||
const savedConfig = localStorage.getItem('homeFeaturesConfig');
|
||||
console.log('从本地存储获取的配置:', savedConfig);
|
||||
if (savedConfig) {
|
||||
const parsedData = JSON.parse(savedConfig);
|
||||
console.log('从本地存储解析的配置数据:', parsedData);
|
||||
return parsedData;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从本地存储获取用户配置失败:', error);
|
||||
}
|
||||
|
||||
// 如果没有配置,返回空数组
|
||||
console.log('没有找到用户配置,返回空数组');
|
||||
return [];
|
||||
};
|
||||
|
||||
// 响应式数据存储快捷访问功能
|
||||
const quickAccessData = ref([]);
|
||||
|
||||
// 根据用户配置获取快捷功能
|
||||
const quickAccess = computed(() => {
|
||||
return quickAccessData.value;
|
||||
});
|
||||
|
||||
// 添加 loading 状态
|
||||
const quickAccessLoading = ref(false);
|
||||
|
||||
// 异步加载用户配置
|
||||
const loadUserQuickAccessConfig = async () => {
|
||||
quickAccessLoading.value = true;
|
||||
try {
|
||||
console.log('开始加载用户快捷访问配置...');
|
||||
const userConfig = await getUserQuickAccessConfig();
|
||||
console.log('获取到的用户配置:', userConfig);
|
||||
|
||||
// 如果用户没有配置任何功能,返回默认配置
|
||||
if (!userConfig || userConfig.length === 0) {
|
||||
console.log('用户没有配置任何功能,使用默认配置');
|
||||
quickAccessData.value = getDefaultQuickAccessConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果用户配置了功能ID列表,需要从菜单中获取详细信息
|
||||
console.log('开始转换菜单ID为快捷访问格式...');
|
||||
const convertedFeatures = await convertMenuIdsToQuickAccess(userConfig);
|
||||
console.log('转换后的功能:', convertedFeatures);
|
||||
|
||||
// 如果转换后没有功能(可能是因为配置的ID没有对应的菜单项),返回默认配置
|
||||
if (!convertedFeatures || convertedFeatures.length === 0) {
|
||||
console.log('转换后没有功能,使用默认配置');
|
||||
quickAccessData.value = getDefaultQuickAccessConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('设置最终的快捷访问数据:', convertedFeatures);
|
||||
quickAccessData.value = convertedFeatures;
|
||||
} catch (error) {
|
||||
console.error('加载用户快捷访问配置失败:', error);
|
||||
// 出错时使用默认配置
|
||||
quickAccessData.value = getDefaultQuickAccessConfig();
|
||||
} finally {
|
||||
quickAccessLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 将菜单ID转换为快捷访问格式
|
||||
const convertMenuIdsToQuickAccess = async (menuIds) => {
|
||||
if (!menuIds || menuIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查 menuIds 是否已经是包含完整路径的对象数组(新格式)
|
||||
if (menuIds.length > 0 && typeof menuIds[0] === 'object') {
|
||||
// 检查是否包含 fullPath 属性(新格式)或 menuId 属性(新格式但可能还没获取完整路径)
|
||||
if (menuIds[0].hasOwnProperty('fullPath') || menuIds[0].hasOwnProperty('menuId')) {
|
||||
// 如果是新格式,直接转换为快捷功能格式
|
||||
return menuIds.map(menuItem => {
|
||||
// 如果没有 fullPath,使用 path 作为备选
|
||||
let route = menuItem.fullPath || menuItem.path;
|
||||
|
||||
// 确保路径格式正确,去除多余的斜杠
|
||||
if (route && typeof route === 'string') {
|
||||
// 将多个连续的斜杠替换为单个斜杠,但保留协议部分的双斜杠(如 http://)
|
||||
route = route.replace(/([^:])\/{2,}/g, '$1/');
|
||||
}
|
||||
|
||||
return {
|
||||
key: menuItem.menuId,
|
||||
label: menuItem.menuName,
|
||||
icon: getIconComponent(menuItem.icon || 'Document'), // 使用菜单项的图标,如果没有则使用默认图标
|
||||
iconColor: getIconColorByMenuType(menuItem.menuType) || '#67C23A', // 使用菜单类型的颜色,如果没有则使用默认颜色
|
||||
route: route
|
||||
};
|
||||
}).filter(item => item.route); // 过滤掉 route 为空的项
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是旧格式(仅包含菜单ID的数组),则按原方式处理
|
||||
// 获取所有菜单数据
|
||||
const response = await listMenu({});
|
||||
|
||||
if (response.code === 200) {
|
||||
const allMenus = response.data;
|
||||
|
||||
// 扁平化菜单树结构,获取所有叶子节点(菜单项)
|
||||
const flatMenus = flattenMenuTree(allMenus);
|
||||
|
||||
// 根据用户选择的菜单ID过滤并返回对应的功能对象
|
||||
const menuPromises = menuIds.map(async (id) => {
|
||||
// 查找匹配的菜单项
|
||||
const matchedMenu = flatMenus.find(menu => menu.menuId == id);
|
||||
|
||||
if (matchedMenu) {
|
||||
// 获取完整路径
|
||||
try {
|
||||
const fullPathResponse = await getMenuFullPath(matchedMenu.menuId);
|
||||
// 确保返回的路径不为空
|
||||
const fullPath = (fullPathResponse.code === 200 && fullPathResponse.data)
|
||||
? fullPathResponse.data
|
||||
: (matchedMenu.path || matchedMenu.fullPath);
|
||||
|
||||
// 将菜单数据转换为快捷功能格式
|
||||
return {
|
||||
key: matchedMenu.perms || matchedMenu.path || `menu_${matchedMenu.menuId}`,
|
||||
label: matchedMenu.menuName,
|
||||
icon: getIconComponent(matchedMenu.icon),
|
||||
iconColor: getIconColorByMenuType(matchedMenu.menuType),
|
||||
route: fullPath || matchedMenu.path // 确保 route 不为空
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`获取菜单 ${matchedMenu.menuName} 的完整路径失败:`, error);
|
||||
// 如果获取完整路径失败,使用现有路径作为备选
|
||||
return {
|
||||
key: matchedMenu.perms || matchedMenu.path || `menu_${matchedMenu.menuId}`,
|
||||
label: matchedMenu.menuName,
|
||||
icon: getIconComponent(matchedMenu.icon),
|
||||
iconColor: getIconColorByMenuType(matchedMenu.menuType),
|
||||
route: matchedMenu.path || matchedMenu.fullPath || '/' // 确保 route 不为空
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// 等待所有完整路径获取完成
|
||||
const convertedMenus = await Promise.all(menuPromises);
|
||||
// 过滤掉 route 为空的项和未找到的菜单ID
|
||||
return convertedMenus.filter(item => item !== null && item.route);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取菜单数据失败:', error);
|
||||
}
|
||||
|
||||
// 如果获取失败,返回空数组
|
||||
return [];
|
||||
};
|
||||
|
||||
// 将菜单树结构扁平化
|
||||
const flattenMenuTree = (menuTree) => {
|
||||
const result = [];
|
||||
|
||||
const flatten = (items) => {
|
||||
items.forEach(item => {
|
||||
// 只处理菜单类型为'C'(菜单)的项目,忽略目录('M')和按钮('F')
|
||||
if (item.menuType === 'C') {
|
||||
result.push(item);
|
||||
}
|
||||
|
||||
// 递归处理子菜单
|
||||
if (item.children && item.children.length > 0) {
|
||||
flatten(item.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
flatten(menuTree);
|
||||
return result;
|
||||
};
|
||||
|
||||
// 获取图标组件
|
||||
const getIconComponent = (iconName) => {
|
||||
if (!iconName) return Document;
|
||||
// 移除前缀,如 fa-, el-icon-
|
||||
const cleanIconName = iconName.replace(/^(fa-|el-icon-)/, '').toLowerCase();
|
||||
|
||||
const iconMap = {
|
||||
'menu': markRaw(Menu),
|
||||
'grid': markRaw(Grid),
|
||||
'folder': markRaw(Folder),
|
||||
'tickets': markRaw(Tickets),
|
||||
'document': markRaw(Document),
|
||||
'setting': markRaw(Setting),
|
||||
'user': markRaw(User),
|
||||
'goods': markRaw(Goods),
|
||||
'chat-dot-square': markRaw(ChatDotSquare),
|
||||
'histogram': markRaw(Histogram),
|
||||
'wallet': markRaw(Wallet),
|
||||
'office-building': markRaw(OfficeBuilding),
|
||||
'postcard': markRaw(Postcard),
|
||||
'collection': markRaw(Collection),
|
||||
'video-play': markRaw(VideoPlay),
|
||||
'camera': markRaw(Camera),
|
||||
'headset': markRaw(Headset),
|
||||
'phone': markRaw(Phone),
|
||||
'message': markRaw(Message),
|
||||
'chat-line-square': markRaw(ChatLineSquare),
|
||||
'chat-round': markRaw(ChatRound),
|
||||
'guide': markRaw(Guide),
|
||||
'help': markRaw(Help),
|
||||
'info-filled': markRaw(InfoFilled),
|
||||
'circle-check': markRaw(CircleCheck),
|
||||
'circle-close': markRaw(CircleClose),
|
||||
'warning': markRaw(Warning),
|
||||
'question-filled': markRaw(QuestionFilled),
|
||||
'star': markRaw(Star),
|
||||
'link': markRaw(Link),
|
||||
'position': markRaw(Position),
|
||||
'picture': markRaw(Picture),
|
||||
'upload': markRaw(Upload),
|
||||
'download': markRaw(Download),
|
||||
'caret-left': markRaw(CaretLeft),
|
||||
'caret-right': markRaw(CaretRight),
|
||||
'more': markRaw(More),
|
||||
'close': markRaw(Close),
|
||||
'check': markRaw(Check),
|
||||
'arrow-up': markRaw(ArrowUp),
|
||||
'arrow-down': markRaw(ArrowDown),
|
||||
'arrow-left': markRaw(ArrowLeft),
|
||||
'arrow-right': markRaw(ArrowRight),
|
||||
'plus': markRaw(Plus),
|
||||
'minus': markRaw(Minus),
|
||||
'zoom-in': markRaw(ZoomIn),
|
||||
'zoom-out': markRaw(ZoomOut),
|
||||
'refresh': markRaw(Refresh),
|
||||
'search': markRaw(Search),
|
||||
'edit': markRaw(Edit),
|
||||
'delete': markRaw(Delete),
|
||||
'share': markRaw(Share),
|
||||
'view': markRaw(View),
|
||||
'switch-button': markRaw(SwitchButton),
|
||||
'hide': markRaw(Hide),
|
||||
'finished': markRaw(Finished),
|
||||
'circle-plus': markRaw(CirclePlus),
|
||||
'remove': markRaw(Remove),
|
||||
'circle-check-filled': markRaw(CircleCheckFilled),
|
||||
'circle-close-filled': markRaw(CircleCloseFilled),
|
||||
'warning-filled': markRaw(WarningFilled),
|
||||
'info-filled-icon': markRaw(InfoFilledIcon),
|
||||
'success-filled': markRaw(SuccessFilled),
|
||||
'question-filled-icon': markRaw(QuestionFilledIcon)
|
||||
};
|
||||
|
||||
return iconMap[cleanIconName] || markRaw(Document);
|
||||
};
|
||||
|
||||
// 根据菜单类型获取图标颜色
|
||||
const getIconColorByMenuType = (menuType) => {
|
||||
if (menuType === 'M') return '#409EFF'; // 目录蓝色
|
||||
if (menuType === 'C') return '#67C23A'; // 菜单绿色
|
||||
if (menuType === 'F') return '#E6A23C'; // 按钮橙色
|
||||
return '#909399'; // 默认灰色
|
||||
};
|
||||
|
||||
// 获取默认快捷功能配置
|
||||
const getDefaultQuickAccessConfig = () => {
|
||||
// 根据不同角色返回默认配置
|
||||
const role = userStore.roles[0] || 'admin';
|
||||
switch (role) {
|
||||
case 'doctor':
|
||||
return [
|
||||
{ key: 'outpatient', label: '门诊接诊', icon: markRaw(ChatDotRound), iconColor: '#409eff', route: '/doctorstation' },
|
||||
{ key: 'emr', label: '病历管理', icon: markRaw(Document), iconColor: '#67c23a', route: '/doctorstation/doctorphrase' },
|
||||
{ key: 'prescription', label: '开立处方', icon: markRaw(Box), iconColor: '#e6a23c', route: '/clinicmanagement/ePrescribing' },
|
||||
{ key: 'history', label: '历史处方', icon: markRaw(Clock), iconColor: '#f56c6c', route: '/clinicmanagement/historicalPrescription' },
|
||||
{ key: 'schedule', label: '排班管理', icon: markRaw(Calendar), iconColor: '#909399', route: '/appoinmentmanage/deptManage' },
|
||||
{ key: 'inquiry', label: '患者查询', icon: markRaw(Search), iconColor: '#409eff', route: '/patientmanagement' }
|
||||
];
|
||||
case 'nurse':
|
||||
return [
|
||||
{ key: 'ward', label: '病房管理', icon: markRaw(User), iconColor: '#409eff', route: '/inpatientNurse/inpatientNurseStation' },
|
||||
{ key: 'execution', label: '医嘱执行', icon: markRaw(Operation), iconColor: '#67c23a', route: '/inpatientNurse/medicalOrderExecution' },
|
||||
{ key: 'proofread', label: '医嘱核对', icon: markRaw(Document), iconColor: '#e6a23c', route: '/inpatientNurse/medicalOrderProofread' },
|
||||
{ key: 'drugCollect', label: '领药管理', icon: markRaw(Box), iconColor: '#f56c6c', route: '/inpatientNurse/medicineCollect' },
|
||||
{ key: 'tpr', label: '体温单', icon: markRaw(Monitor), iconColor: '#909399', route: '/inpatientNurse/tprsheet' },
|
||||
{ key: 'nursing', label: '护理记录', icon: markRaw(ChatDotRound), iconColor: '#409eff', route: '/inpatientNurse/nursingRecord' }
|
||||
];
|
||||
case 'pharmacist':
|
||||
return [
|
||||
{ key: 'dispensing', label: '发药管理', icon: markRaw(Box), iconColor: '#409eff', route: '/pharmacymanagement' },
|
||||
{ key: 'prescription', label: '处方审核', icon: markRaw(Document), iconColor: '#67c23a', route: '/pharmacymanagement' },
|
||||
{ key: 'inventory', label: '库存管理', icon: markRaw(Van), iconColor: '#e6a23c', route: '/medicineStorage' },
|
||||
{ key: 'purchase', label: '采购管理', icon: markRaw(ShoppingCart), iconColor: '#f56c6c', route: '/medicineStorage' },
|
||||
{ key: 'warning', label: '效期预警', icon: markRaw(Warning), iconColor: '#f56c6c', route: '/medicationmanagement/statisticalManagement/statisticalManagement' },
|
||||
{ key: 'statistics', label: '用药统计', icon: markRaw(DataLine), iconColor: '#909399', route: '/monitor' }
|
||||
];
|
||||
case 'cashier':
|
||||
return [
|
||||
{ key: 'registration', label: '挂号收费', icon: markRaw(Money), iconColor: '#409eff', route: '/charge/outpatientregistration' },
|
||||
{ key: 'clinicCharge', label: '门诊收费', icon: markRaw(Wallet), iconColor: '#67c23a', route: '/charge/cliniccharge' },
|
||||
{ key: 'refund', label: '退费管理', icon: markRaw(Document), iconColor: '#e6a23c', route: '/charge/clinicrefund' },
|
||||
{ key: 'invoice', label: '发票打印', icon: markRaw(Files), iconColor: '#f56c6c', route: '/basicmanage/InvoiceManagement' },
|
||||
{ key: 'record', label: '收费记录', icon: markRaw(Clock), iconColor: '#909399', route: '/charge/clinicRecord' },
|
||||
{ key: 'insurance', label: '医保结算', icon: markRaw(Bell), iconColor: '#409eff', route: '/ybmanagement' }
|
||||
];
|
||||
default: // admin
|
||||
return [
|
||||
{ key: 'patient', label: '患者管理', icon: markRaw(User), iconColor: '#409eff', route: '/patient/patientmgr' },
|
||||
{ key: 'appointment', label: '预约管理', icon: markRaw(Calendar), iconColor: '#67c23a', route: '/appoinmentmanage' },
|
||||
{ key: 'doctor', label: '医生管理', icon: markRaw(User), iconColor: '#e6a23c', route: '/doctorstation' },
|
||||
{ key: 'surgery', label: '手术管理', icon: markRaw(Operation), iconColor: '#f56c6c', route: '/surgerymanage' },
|
||||
{ key: 'drug', label: '药品管理', icon: markRaw(Box), iconColor: '#909399', route: '/pharmacymanagement' },
|
||||
{ key: 'statistic', label: '数据统计', icon: markRaw(TrendCharts), iconColor: '#409eff', route: '/monitor' }
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
// 待办事项
|
||||
const todoList = ref([
|
||||
{ id: 1, title: '审核处方申请', desc: '张医生提交的5条处方待审核', priority: 'high', icon: markRaw(Document), time: '10分钟前' },
|
||||
{ id: 2, title: '确认患者入院', desc: '李某某(男,45岁)已办理入院', priority: 'medium', icon: markRaw(User), time: '30分钟前' },
|
||||
{ id: 3, title: '处理投诉反馈', desc: '患者家属对服务态度的投诉', priority: 'high', icon: markRaw(ChatDotRound), time: '1小时前' },
|
||||
{ id: 4, title: '药品效期预警', desc: '阿莫西林等3种药品即将过期', priority: 'low', icon: markRaw(Warning), time: '2小时前' },
|
||||
{ id: 5, title: '月度报表审核', desc: '11月份门诊收入报表待审核', priority: 'medium', icon: markRaw(Files), time: '3小时前' }
|
||||
])
|
||||
const todoList = ref([])
|
||||
|
||||
// 更新待办事项中的待写病历数量
|
||||
const updatePendingEmrTodo = () => {
|
||||
const pendingEmrTodo = todoList.value.find(item => item.title === '待写病历' || item.desc?.includes('患者等待写病历'));
|
||||
if (pendingEmrTodo) {
|
||||
pendingEmrTodo.desc = `有${statisticsData.value.pendingEmr || 0}个患者等待写病历`;
|
||||
}
|
||||
}
|
||||
|
||||
// 今日日程
|
||||
const scheduleList = ref([
|
||||
@@ -314,91 +694,86 @@ const currentStats = computed(() => {
|
||||
// 根据不同的 key 获取对应的值
|
||||
switch (stat.key) {
|
||||
case 'totalPatients':
|
||||
statWith.value = statisticsData.value.totalPatients
|
||||
statWith.trend = statisticsData.value.patientTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.totalPatients;
|
||||
statWith.trend = statisticsData.value.patientTrend;
|
||||
break;
|
||||
case 'todayRevenue':
|
||||
statWith.value = statisticsData.value.todayRevenue
|
||||
statWith.trend = statisticsData.value.revenueTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.todayRevenue;
|
||||
statWith.trend = statisticsData.value.revenueTrend;
|
||||
break;
|
||||
case 'appointments':
|
||||
statWith.value = statisticsData.value.todayAppointments
|
||||
statWith.trend = statisticsData.value.appointmentTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.todayAppointments;
|
||||
statWith.trend = statisticsData.value.appointmentTrend;
|
||||
break;
|
||||
case 'pendingApprovals':
|
||||
statWith.value = statisticsData.value.pendingApprovals
|
||||
break
|
||||
statWith.value = statisticsData.value.pendingApprovals;
|
||||
break;
|
||||
case 'myPatients':
|
||||
statWith.value = statisticsData.value.totalPatients
|
||||
statWith.trend = statisticsData.value.patientTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.totalPatients;
|
||||
statWith.trend = statisticsData.value.patientTrend;
|
||||
break;
|
||||
case 'todayAppointments':
|
||||
statWith.value = statisticsData.value.todayAppointments
|
||||
statWith.trend = statisticsData.value.appointmentTrend
|
||||
break
|
||||
case 'pendingRecords':
|
||||
statWith.value = statisticsData.value.pendingApprovals
|
||||
break
|
||||
statWith.value = statisticsData.value.todayAppointments;
|
||||
statWith.trend = statisticsData.value.appointmentTrend;
|
||||
break;
|
||||
case 'pendingEmr':
|
||||
statWith.value = statisticsData.value.pendingEmr;
|
||||
break;
|
||||
case 'prescriptions':
|
||||
statWith.value = statisticsData.value.todayAppointments
|
||||
statWith.trend = statisticsData.value.appointmentTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.todayAppointments;
|
||||
statWith.trend = statisticsData.value.appointmentTrend;
|
||||
break;
|
||||
case 'wardPatients':
|
||||
statWith.value = statisticsData.value.totalPatients
|
||||
statWith.trend = statisticsData.value.patientTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.totalPatients;
|
||||
statWith.trend = statisticsData.value.patientTrend;
|
||||
break;
|
||||
case 'todayTreatments':
|
||||
statWith.value = statisticsData.value.todayAppointments
|
||||
statWith.trend = statisticsData.value.appointmentTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.todayAppointments;
|
||||
statWith.trend = statisticsData.value.appointmentTrend;
|
||||
break;
|
||||
case 'vitalSigns':
|
||||
statWith.value = statisticsData.value.pendingApprovals
|
||||
break
|
||||
statWith.value = statisticsData.value.pendingApprovals;
|
||||
break;
|
||||
case 'drugDistribution':
|
||||
statWith.value = statisticsData.value.todayAppointments
|
||||
statWith.trend = statisticsData.value.appointmentTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.todayAppointments;
|
||||
statWith.trend = statisticsData.value.appointmentTrend;
|
||||
break;
|
||||
case 'todayPrescriptions':
|
||||
statWith.value = statisticsData.value.todayAppointments
|
||||
statWith.trend = statisticsData.value.appointmentTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.todayAppointments;
|
||||
statWith.trend = statisticsData.value.appointmentTrend;
|
||||
break;
|
||||
case 'pendingReview':
|
||||
statWith.value = statisticsData.value.pendingApprovals
|
||||
break
|
||||
statWith.value = statisticsData.value.pendingApprovals;
|
||||
break;
|
||||
case 'outOfStock':
|
||||
statWith.value = statisticsData.value.pendingApprovals
|
||||
break
|
||||
statWith.value = statisticsData.value.pendingApprovals;
|
||||
break;
|
||||
case 'nearExpiry':
|
||||
statWith.value = statisticsData.value.pendingApprovals
|
||||
break
|
||||
statWith.value = statisticsData.value.pendingApprovals;
|
||||
break;
|
||||
case 'todayPayments':
|
||||
statWith.value = statisticsData.value.todayRevenue
|
||||
statWith.trend = statisticsData.value.revenueTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.todayRevenue;
|
||||
statWith.trend = statisticsData.value.revenueTrend;
|
||||
break;
|
||||
case 'refundRequests':
|
||||
statWith.value = statisticsData.value.pendingApprovals
|
||||
break
|
||||
statWith.value = statisticsData.value.pendingApprovals;
|
||||
break;
|
||||
case 'pendingInvoices':
|
||||
statWith.value = statisticsData.value.pendingApprovals
|
||||
break
|
||||
statWith.value = statisticsData.value.pendingApprovals;
|
||||
break;
|
||||
case 'insuranceClaims':
|
||||
statWith.value = statisticsData.value.todayAppointments
|
||||
statWith.trend = statisticsData.value.appointmentTrend
|
||||
break
|
||||
statWith.value = statisticsData.value.todayAppointments;
|
||||
statWith.trend = statisticsData.value.appointmentTrend;
|
||||
break;
|
||||
default:
|
||||
statWith.value = '0'
|
||||
statWith.trend = 0
|
||||
statWith.value = '0';
|
||||
statWith.trend = 0;
|
||||
}
|
||||
|
||||
return statWith
|
||||
})
|
||||
})
|
||||
|
||||
// 根据角色获取快捷功能
|
||||
const quickAccess = computed(() => {
|
||||
const role = userStore.roles[0] || 'admin'
|
||||
return roleQuickAccessConfig[role] || roleQuickAccessConfig.admin
|
||||
})
|
||||
|
||||
// 处理统计卡片点击
|
||||
const handleStatClick = (stat) => {
|
||||
@@ -419,13 +794,42 @@ const handleStatClick = (stat) => {
|
||||
} else if (stat.key === 'pendingApprovals' || stat.key === 'pendingReview') {
|
||||
// 跳转到待审核页面
|
||||
router.push('/clinicmanagement/ePrescribing')
|
||||
} else if (stat.key === 'pendingEmr') {
|
||||
// 跳转到待写病历页面
|
||||
router.push('/doctorstation/pending-emr')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理快捷功能点击
|
||||
const handleQuickAccess = (func) => {
|
||||
if (func.route) {
|
||||
router.push(func.route)
|
||||
// 检查是否为外部链接
|
||||
if (func.route.startsWith('http://') || func.route.startsWith('https://')) {
|
||||
// 如果是外部链接,使用 window.open 打开
|
||||
window.open(func.route, '_blank');
|
||||
} else {
|
||||
// 确保路径格式正确,去除多余的斜杠
|
||||
let normalizedPath = func.route;
|
||||
if (normalizedPath && typeof normalizedPath === 'string') {
|
||||
// 将多个连续的斜杠替换为单个斜杠,但保留协议部分的双斜杠(如 http://)
|
||||
normalizedPath = normalizedPath.replace(/([^:])\/{2,}/g, '$1/');
|
||||
}
|
||||
|
||||
// 确保内部路径以 / 开头,以保证正确的路由跳转
|
||||
if (!normalizedPath.startsWith('/')) {
|
||||
normalizedPath = '/' + normalizedPath;
|
||||
}
|
||||
|
||||
try {
|
||||
router.push(normalizedPath);
|
||||
} catch (error) {
|
||||
console.error('路由跳转失败:', error);
|
||||
// 如果路径跳转失败,尝试使用原始路径
|
||||
router.push(func.route);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('快捷功能没有配置路由路径:', func);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,16 +837,22 @@ const handleQuickAccess = (func) => {
|
||||
const handleTodoClick = (todo) => {
|
||||
console.log('Todo clicked:', todo)
|
||||
// 跳转到相应的处理页面
|
||||
if (todo.id === 6) { // 待写病历
|
||||
router.push('/doctorstation?tab=pendingEmr')
|
||||
}
|
||||
}
|
||||
|
||||
// 显示全部功能
|
||||
const showAllFunctions = () => {
|
||||
// 跳转到功能菜单页面
|
||||
|
||||
// 显示功能配置
|
||||
const showConfig = () => {
|
||||
// 跳转到功能配置页面
|
||||
router.push('/features/config')
|
||||
}
|
||||
|
||||
// 显示全部待办
|
||||
const showAllTodos = () => {
|
||||
// 跳转到待办事项页面
|
||||
router.push('/todo')
|
||||
}
|
||||
|
||||
// 管理日程
|
||||
@@ -461,12 +871,111 @@ const fetchStatsData = async () => {
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取待写病历数量
|
||||
const pendingEmrRes = await getPendingEmrCount();
|
||||
if (pendingEmrRes.code === 200) {
|
||||
// 确保统计数据对象中有pendingEmr字段
|
||||
if (!statisticsData.value) {
|
||||
statisticsData.value = {}
|
||||
}
|
||||
statisticsData.value.pendingEmr = pendingEmrRes.data || 0;
|
||||
} else {
|
||||
statisticsData.value.pendingEmr = 0;
|
||||
}
|
||||
// 更新待办事项中的待写病历数量
|
||||
updatePendingEmrTodo();
|
||||
} catch (error) {
|
||||
console.error('获取待写病历数量失败:', error)
|
||||
// 确保统计数据对象中有pendingEmr字段
|
||||
if (!statisticsData.value) {
|
||||
statisticsData.value = {}
|
||||
}
|
||||
statisticsData.value.pendingEmr = 0;
|
||||
// 更新待办事项中的待写病历数量
|
||||
updatePendingEmrTodo();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取待办事项(实际应用中应该从API获取)
|
||||
const fetchTodoList = async () => {
|
||||
// TODO: 调用API获取真实数据
|
||||
console.log('Fetching todo list...')
|
||||
try {
|
||||
const response = await listTodo({ pageNum: 1, pageSize: 5 })
|
||||
if (response.code === 200) {
|
||||
// 将工作流任务数据转换为待办事项格式
|
||||
const rows = response.rows || [];
|
||||
const todoData = rows.slice(0, 5).map((task, index) => ({
|
||||
id: task.id || index,
|
||||
title: task.taskName || task.name || '待办事项',
|
||||
desc: task.description || '暂无描述',
|
||||
priority: getPriorityFromTask(task),
|
||||
status: getTaskStatus(task.status || task.state),
|
||||
icon: getTaskIcon(task.category),
|
||||
time: parseTime(task.createTime || task.createTimeStr, '{y}-{m}-{d} {h}:{i}'),
|
||||
taskInfo: task // 保存原始任务信息,便于后续处理
|
||||
}))
|
||||
|
||||
// 检查是否已经有"待写病历"任务,如果没有则添加
|
||||
const hasPendingEmrTask = todoData.some(task => task.title === '待写病历' || task.desc?.includes('患者等待写病历'));
|
||||
if (!hasPendingEmrTask && statisticsData.value.pendingEmr > 0) {
|
||||
// 添加待写病历任务
|
||||
const pendingEmrTask = {
|
||||
id: Date.now(), // 使用时间戳作为唯一ID
|
||||
title: '待写病历',
|
||||
desc: `有${statisticsData.value.pendingEmr || 0}个患者等待写病历`,
|
||||
priority: 'high',
|
||||
icon: markRaw(Document),
|
||||
time: '刚刚',
|
||||
taskInfo: null
|
||||
};
|
||||
// 如果数组未满5个,添加到末尾;否则替换最后一个
|
||||
if (todoData.length < 5) {
|
||||
todoData.push(pendingEmrTask);
|
||||
} else {
|
||||
todoData[4] = pendingEmrTask;
|
||||
}
|
||||
}
|
||||
|
||||
todoList.value = todoData;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取待办事项失败:', error)
|
||||
// 如果获取真实数据失败,保留空数组,但模块框架仍会显示
|
||||
todoList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 根据任务信息确定优先级
|
||||
const getPriorityFromTask = (task) => {
|
||||
// 根据任务的某些属性来确定优先级,这里可以根据实际业务调整
|
||||
if (task.priority && task.priority > 50) return 'high'
|
||||
if (task.priority && task.priority > 20) return 'medium'
|
||||
return 'low'
|
||||
}
|
||||
|
||||
// 获取任务状态
|
||||
const getTaskStatus = (status) => {
|
||||
// 根据实际返回的状态值映射
|
||||
if (status === 'completed' || status === 'finish') return 'completed'
|
||||
if (status === 'processing' || status === 'active') return 'processing'
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
// 获取任务图标
|
||||
const getTaskIcon = (category) => {
|
||||
// 根据任务分类确定图标
|
||||
if (category && category.includes('approval')) return markRaw(Document)
|
||||
if (category && category.includes('patient')) return markRaw(User)
|
||||
if (category && category.includes('feedback')) return markRaw(ChatDotRound)
|
||||
if (category && category.includes('warning')) return markRaw(Warning)
|
||||
if (category && category.includes('report')) return markRaw(Files)
|
||||
if (category && category.includes('data')) return markRaw(DataLine)
|
||||
if (category && category.includes('operation')) return markRaw(Operation)
|
||||
if (category && category.includes('system')) return markRaw(Setting)
|
||||
return markRaw(Document) // 默认图标
|
||||
}
|
||||
|
||||
// 获取日程数据(实际应用中应该从API获取)
|
||||
@@ -475,11 +984,37 @@ const fetchScheduleList = async () => {
|
||||
console.log('Fetching schedule list...')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 监听本地存储变化,以便在其他标签页或窗口中修改配置后更新当前页面
|
||||
const handleStorageChange = (event) => {
|
||||
if (event.key === 'homeFeaturesConfig') {
|
||||
console.log('检测到快捷功能配置更新,正在重新加载...');
|
||||
loadUserQuickAccessConfig();
|
||||
}
|
||||
};
|
||||
|
||||
// 监听配置更新事件
|
||||
const handleConfigUpdate = () => {
|
||||
console.log('检测到快捷功能配置更新事件,正在重新加载...');
|
||||
loadUserQuickAccessConfig();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
fetchStatsData()
|
||||
fetchTodoList()
|
||||
await fetchTodoList()
|
||||
fetchScheduleList()
|
||||
await loadUserQuickAccessConfig()
|
||||
|
||||
// 添加本地存储变化监听器
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
// 添加配置更新事件监听器
|
||||
window.addEventListener('homeFeaturesConfigUpdated', handleConfigUpdate);
|
||||
})
|
||||
|
||||
// 在组件卸载前移除监听器
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
window.removeEventListener('homeFeaturesConfigUpdated', handleConfigUpdate);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -641,6 +1176,11 @@ onMounted(() => {
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
color: #d8d8d8;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-access-grid {
|
||||
@@ -702,6 +1242,11 @@ onMounted(() => {
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
color: #d8d8d8;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.todo-list {
|
||||
@@ -762,6 +1307,11 @@ onMounted(() => {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-todo {
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,6 +1334,11 @@ onMounted(() => {
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
color: #d8d8d8;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-list {
|
||||
|
||||
@@ -74,6 +74,12 @@
|
||||
<el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
|
||||
<el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="path" label="路由地址" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="fullPath" label="完整路径" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.fullPath">{{ scope.row.fullPath }}</span>
|
||||
<span v-else-if="scope.row.path">{{ scope.row.path }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="scope">
|
||||
@@ -186,6 +192,11 @@
|
||||
<el-input v-model="form.path" placeholder="请输入路由地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" v-if="form.menuType != 'F' && form.fullPath">
|
||||
<el-form-item label="完整路径">
|
||||
<el-input v-model="form.fullPath" readonly placeholder="完整路径" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="form.menuType == 'C'">
|
||||
<el-form-item prop="component">
|
||||
<template #label>
|
||||
@@ -325,12 +336,17 @@ const data = reactive({
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询菜单列表 */
|
||||
function getList() {
|
||||
async function getList() {
|
||||
loading.value = true;
|
||||
listMenu(queryParams.value).then(response => {
|
||||
menuList.value = proxy.handleTree(response.data, "menuId");
|
||||
try {
|
||||
const response = await listMenu(queryParams.value);
|
||||
// 后端已经返回了带完整路径的菜单树,直接使用即可
|
||||
menuList.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取菜单列表失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
/** 查询菜单下拉树结构 */
|
||||
function getTreeselect() {
|
||||
@@ -380,13 +396,16 @@ function resetQuery() {
|
||||
handleQuery();
|
||||
}
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd(row) {
|
||||
async function handleAdd(row) {
|
||||
reset();
|
||||
getTreeselect();
|
||||
await getTreeselect();
|
||||
if (row != null && row.menuId) {
|
||||
form.value.parentId = row.menuId;
|
||||
// 使用后端返回的完整路径
|
||||
form.value.parentFullPath = row.fullPath || row.path;
|
||||
} else {
|
||||
form.value.parentId = 0;
|
||||
form.value.parentFullPath = '';
|
||||
}
|
||||
open.value = true;
|
||||
title.value = "添加菜单";
|
||||
@@ -403,11 +422,16 @@ function toggleExpandAll() {
|
||||
async function handleUpdate(row) {
|
||||
reset();
|
||||
await getTreeselect();
|
||||
getMenu(row.menuId).then(response => {
|
||||
try {
|
||||
const response = await getMenu(row.menuId);
|
||||
form.value = response.data;
|
||||
// 使用后端返回的完整路径
|
||||
form.value.fullPath = row.fullPath || row.path;
|
||||
open.value = true;
|
||||
title.value = "修改菜单";
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取菜单信息失败:', error);
|
||||
}
|
||||
}
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
|
||||
Reference in New Issue
Block a user