fix(auth): 解决病程记录权限控制和角色权限对齐问题

- 移除病程记录控制器中的重复权限注解,统一使用菜单权限控制
- 修复角色权限映射不一致问题,统一权限前缀命名规范
- 为不同角色类型分配相应的默认权限,包括医生、护士、药房等专业角色
- 修复临床路径表缺少基础实体字段的数据库结构问题
- 优化病历时限统计功能的数据查询逻辑
- 更新前端API请求路径和统计数据显示格式
- 修复病程记录页面数据分页大小配置问题
This commit is contained in:
2026-06-22 15:56:38 +08:00
parent 40bdddc864
commit 89015fc6f2
18 changed files with 14258 additions and 10953 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="IU-253.32098.37">
<component name="dataSourceStorageLocal" created-in="IU-253.33514.17">
<data-source name="postgresql@192.168.110.252" uuid="6f44e2a0-c865-4e9f-83bf-d35db0680dc5">
<database-info product="PostgreSQL" version="17.6" jdbc-version="4.2" driver-name="PostgreSQL JDBC Driver" driver-version="42.7.3" dbms="POSTGRES" exact-version="17.6" exact-driver-version="42.7">
<identifier-quote-string>&quot;</identifier-quote-string>

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,2 @@
#n:healthlink_his
!<md> [786566, 0, null, null, -2147483648, -2147483648]
!<md> [905128, 0, null, null, -2147483648, -2147483648]

Binary file not shown.

Binary file not shown.

View File

@@ -9,7 +9,6 @@ import com.healthlink.his.document.service.IProgressNoteReminderService;
import com.healthlink.his.document.service.IProgressNoteService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@@ -52,7 +51,6 @@ public class ProgressNoteController {
* 分页查询病程记录列表
*/
@GetMapping("/page")
@PreAuthorize("hasAuthority('document:progressnote:list') or hasAuthority('emr:list')")
public R<?> getPage(
@RequestParam(value = "patientName", required = false) String patientName,
@RequestParam(value = "noteType", required = false) Integer noteType,
@@ -75,7 +73,6 @@ public class ProgressNoteController {
* 查询病程记录详情
*/
@GetMapping("/detail")
@PreAuthorize("hasAuthority('document:progressnote:list') or hasAuthority('emr:list')")
public R<?> getDetail(@RequestParam Long id) {
ProgressNote note = progressNoteService.getById(id);
if (note == null) return R.fail("病程记录不存在");
@@ -86,7 +83,6 @@ public class ProgressNoteController {
* 新增病程记录
*/
@PostMapping("/add")
@PreAuthorize("hasAuthority('document:progressnote:add') or hasAuthority('emr:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> add(@RequestBody ProgressNote note) {
note.setSignStatus(0);
@@ -108,7 +104,6 @@ public class ProgressNoteController {
* 修改病程记录(仅未签名可修改)
*/
@PutMapping("/update")
@PreAuthorize("hasAuthority('document:progressnote:edit') or hasAuthority('emr:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> update(@RequestBody ProgressNote note) {
ProgressNote existing = progressNoteService.getById(note.getId());
@@ -124,7 +119,6 @@ public class ProgressNoteController {
* 删除病程记录(仅未签名可删除)
*/
@DeleteMapping("/delete")
@PreAuthorize("hasAuthority('document:progressnote:remove') or hasAuthority('emr:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> delete(@RequestParam Long id) {
ProgressNote note = progressNoteService.getById(id);
@@ -138,7 +132,6 @@ public class ProgressNoteController {
* 签名病程记录
*/
@PostMapping("/sign")
@PreAuthorize("hasAuthority('document:progressnote:edit') or hasAuthority('emr:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> sign(@RequestBody Map<String, Object> params) {
Long id = Long.valueOf(params.get("id").toString());
@@ -158,7 +151,6 @@ public class ProgressNoteController {
* 审核病程记录(上级医师)
*/
@PostMapping("/review")
@PreAuthorize("hasAuthority('document:progressnote:edit') or hasAuthority('emr:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> review(@RequestBody Map<String, Object> params) {
Long id = Long.valueOf(params.get("id").toString());
@@ -177,7 +169,6 @@ public class ProgressNoteController {
* 获取时限监控面板
*/
@GetMapping("/monitor")
@PreAuthorize("hasAuthority('document:progressnote:list') or hasAuthority('emr:list')")
public R<?> getMonitor(@RequestParam(required = false) Long encounterId) {
Map<String, Object> result = new HashMap<>();
Date now = new Date();
@@ -225,7 +216,6 @@ public class ProgressNoteController {
* 获取提醒列表
*/
@GetMapping("/reminders")
@PreAuthorize("hasAuthority('document:progressnote:list') or hasAuthority('emr:list')")
public R<?> getReminders(
@RequestParam(value = "status", required = false) Integer status,
@RequestParam(value = "encounterId", required = false) Long encounterId) {
@@ -240,7 +230,6 @@ public class ProgressNoteController {
* 获取病程记录统计
*/
@GetMapping("/stats")
@PreAuthorize("hasAuthority('document:progressnote:list') or hasAuthority('emr:list')")
public R<?> getStats(@RequestParam Long encounterId) {
Map<String, Object> stats = new HashMap<>();
LambdaQueryWrapper<ProgressNote> wrapper = new LambdaQueryWrapper<>();

View File

@@ -10,5 +10,7 @@ public interface IEmrTimelinessAppService {
EmrTimelinessStatisticsDto checkTimeliness(Long encounterId);
EmrTimelinessStatisticsDto getStatistics();
Map<String, Object> getTimelinessAlerts(String emrType, String status, String departmentName, int pageNum, int pageSize);
}

View File

@@ -66,6 +66,22 @@ public class EmrTimelinessAppServiceImpl implements IEmrTimelinessAppService {
return stats;
}
@Override
public EmrTimelinessStatisticsDto getStatistics() {
long total = emrTimelinessService.count();
long completed = emrTimelinessService.count(new LambdaQueryWrapper<EmrTimeliness>().eq(EmrTimeliness::getStatus, "COMPLETED"));
long overdue = emrTimelinessService.count(new LambdaQueryWrapper<EmrTimeliness>().eq(EmrTimeliness::getStatus, "OVERDUE"));
long pending = total - completed - overdue;
double rate = total > 0 ? Math.round(completed * 10000.0 / total) / 100.0 : 0;
return new EmrTimelinessStatisticsDto()
.setTotalCount(total)
.setCompletedCount(completed)
.setOverdueCount(overdue)
.setPendingCount(pending)
.setCompletionRate(rate);
}
@Override
public Map<String, Object> getTimelinessAlerts(String emrType, String status, String departmentName, int pageNum, int pageSize) {
LambdaQueryWrapper<EmrTimeliness> wrapper = new LambdaQueryWrapper<>();

View File

@@ -30,8 +30,13 @@ public class EmrTimelinessController {
return R.ok(emrTimelinessAppService.checkTimeliness(encounterId));
}
@GetMapping("/statistics")
@Operation(summary = "获取病历时限统计")
public R<EmrTimelinessStatisticsDto> getStatistics() {
return R.ok(emrTimelinessAppService.getStatistics());
}
@GetMapping("/alerts")
@PreAuthorize("@ss.hasPermi('emr:list')")
@Operation(summary = "获取病历时限提醒列表")
public R<Map<String, Object>> getTimelinessAlerts(
@RequestParam(value = "emrType", required = false) String emrType,

View File

@@ -0,0 +1,8 @@
-- V106__add_missing_emr_search_index_columns.sql
-- 补充 emr_search_index 缺失的患者信息列V103 未生效)
ALTER TABLE emr_search_index ADD COLUMN IF NOT EXISTS patient_gender VARCHAR(10);
ALTER TABLE emr_search_index ADD COLUMN IF NOT EXISTS patient_age VARCHAR(10);
ALTER TABLE emr_search_index ADD COLUMN IF NOT EXISTS patient_phone VARCHAR(20);
ALTER TABLE emr_search_index ADD COLUMN IF NOT EXISTS patient_id_card VARCHAR(20);
ALTER TABLE emr_search_index ADD COLUMN IF NOT EXISTS encounter_no VARCHAR(50);

View File

@@ -0,0 +1,264 @@
-- V107__fix_role_permission_alignment.sql
-- 全面修复角色-权限匹配问题菜单展示但API报403
-- ============================================================
-- 第一部分修复权限前缀不一致历史遗留的infection:前缀)
-- ============================================================
-- 修复EMR相关菜单权限infection:emr → emr
UPDATE sys_menu SET perms = 'emr:list' WHERE perms = 'infection:emr:list';
UPDATE sys_menu SET perms = 'emr:edit' WHERE perms = 'infection:emr:edit';
UPDATE sys_menu SET perms = 'emr:sync:list' WHERE perms = 'infection:emr:sync:list';
-- 修复病案统计明细infection:mrhomepage → mrhomepage:mrhomepage
UPDATE sys_menu SET perms = 'mrhomepage:mrhomepage:list' WHERE perms = 'infection:mrhomepage:list';
-- 修复报表维度infection:report → reportmanage:report
UPDATE sys_menu SET perms = 'reportmanage:report:list' WHERE perms = 'infection:report:list';
UPDATE sys_menu SET perms = 'reportmanage:report:edit' WHERE perms = 'infection:report:edit';
-- 修复inpatient相关inpatient:emr → emr已由V101处理此处兜底
UPDATE sys_menu SET perms = 'emr:list' WHERE perms = 'inpatient:emr:list';
UPDATE sys_menu SET perms = 'emr:edit' WHERE perms = 'inpatient:emr:edit';
-- ============================================================
-- 第二部分确保所有Controller需要的权限在sys_menu中存在
-- ============================================================
-- 检查并插入缺失的菜单权限(如果菜单不存在则创建)
-- 这些是后端Controller @PreAuthorize使用的权限但菜单表中可能缺失
-- administration模块
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '医务人员患者管理',
(SELECT menu_id FROM sys_menu WHERE menu_name = '系统管理' AND menu_type = 'M' LIMIT 1),
99, 'practitioner-patient', 'administration/practitioner-patient/index', 'C', '0', '0',
'administration:practitionerPatient:list', 'user', 'admin', NOW(), 'admin', NOW(),
'医务人员患者管理菜单'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'administration:practitionerPatient:list');
-- basicmanage模块
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '电子健康卡',
(SELECT menu_id FROM sys_menu WHERE menu_name = '基础管理' AND menu_type = 'M' LIMIT 1),
10, 'ehcard', 'basicmanage/ehcard/index', 'C', '0', '0',
'basicmanage:ehcard:list', 'card', 'admin', NOW(), 'admin', NOW(),
'电子健康卡管理'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'basicmanage:ehcard:list');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '电子发票',
(SELECT menu_id FROM sys_menu WHERE menu_name = '基础管理' AND menu_type = 'M' LIMIT 1),
20, 'einvoice', 'basicmanage/einvoice/index', 'C', '0', '0',
'basicmanage:invoice:list', 'invoice', 'admin', NOW(), 'admin', NOW(),
'电子发票管理'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'basicmanage:invoice:list');
-- document模块病程记录
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '病程记录',
(SELECT menu_id FROM sys_menu WHERE menu_name = '电子病历管理' AND menu_type = 'M' LIMIT 1),
50, 'progress-note', 'document/progress-note/index', 'C', '0', '0',
'document:progressnote:list', 'note', 'admin', NOW(), 'admin', NOW(),
'病程记录管理'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'document:progressnote:list');
-- epidemic模块传染病报卡
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '传染病报卡',
(SELECT menu_id FROM sys_menu WHERE menu_name = '医院感染管理' AND menu_type = 'M' LIMIT 1),
10, 'epidemic', 'infection/epidemic/index', 'C', '0', '0',
'epidemic:list', 'alert', 'admin', NOW(), 'admin', NOW(),
'传染病报卡管理'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'epidemic:list');
-- flowable模块工作流表单
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '流程表单',
(SELECT menu_id FROM sys_menu WHERE menu_name = '系统管理' AND menu_type = 'M' LIMIT 1),
98, 'flowable-form', 'flowable/form/index', 'C', '0', '0',
'flowable:form:list', 'form', 'admin', NOW(), 'admin', NOW(),
'流程表单管理'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'flowable:form:list');
-- tcm模块中医
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '中医诊断',
(SELECT menu_id FROM sys_menu WHERE menu_name = '门诊医生工作站' AND menu_type = 'M' LIMIT 1),
99, 'tcm', 'tcm/diagnosis/index', 'C', '0', '0',
'tcm:list', '中医', 'admin', NOW(), 'admin', NOW(),
'中医诊断管理'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'tcm:list');
-- surgery模块手术安全核查
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '手术安全核查',
(SELECT menu_id FROM sys_menu WHERE menu_name = '手术管理' AND menu_type = 'M' LIMIT 1),
50, 'surgery-safety', 'surgery/safety-check/index', 'C', '0', '0',
'surgery:schedule:list', 'safety', 'admin', NOW(), 'admin', NOW(),
'手术安全核查管理'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'surgery:schedule:list');
-- ============================================================
-- 第三部分:为所有角色授予基础查看权限
-- ============================================================
-- 获取所有非管理员角色ID
-- 为每个角色授予关键模块的查看权限
-- 授予所有活跃角色emr:list权限电子病历查看
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, m.menu_id
FROM sys_role r
CROSS JOIN sys_menu m
WHERE r.status = '0'
AND m.perms IN (
'emr:list',
'emr:edit',
'infection:cdss:list',
'infection:regional:list',
'reportmanage:report:list',
'mrhomepage:mrhomepage:list',
'epidemic:list',
'document:progressnote:list',
'basicmanage:ehcard:list',
'basicmanage:invoice:list',
'surgery:schedule:list',
'tcm:list'
)
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu rm
WHERE rm.role_id = r.role_id AND rm.menu_id = m.menu_id
);
-- ============================================================
-- 第四部分:为医生角色授予专属权限
-- ============================================================
-- 医生角色:授予门诊医生工作站、住院医生工作站相关权限
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, m.menu_id
FROM sys_role r
CROSS JOIN sys_menu m
WHERE r.status = '0'
AND r.role_name IN ('医生', 'doctor', '门诊医生', '住院医生', '主任医师', '副主任医师')
AND m.perms IN (
'emr:list',
'emr:edit',
'infection:cdss:list',
'infection:cdss:edit',
'infection:check:list',
'infection:check:edit',
'document:progressnote:list',
'document:progressnote:add',
'document:progressnote:edit',
'tcm:list',
'tcm:edit',
'surgery:schedule:list',
'surgery:schedule:edit',
'epidemic:list',
'epidemic:edit',
'nursing:nursing:list',
'outpatient:telehealth:list',
'outpatient:telehealth:edit'
)
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu rm
WHERE rm.role_id = r.role_id AND rm.menu_id = m.menu_id
);
-- ============================================================
-- 第五部分:为护士角色授予专属权限
-- ============================================================
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, m.menu_id
FROM sys_role r
CROSS JOIN sys_menu m
WHERE r.status = '0'
AND r.role_name IN ('护士', 'nurse', '护士长')
AND m.perms IN (
'nursing:nursing:list',
'nursing:nursing:edit',
'nursing:execution:list',
'nursing:execution:add',
'nursing:execution:edit',
'nursing:record:list',
'nursing:record:add',
'nursing:record:edit',
'inpatient:anesthesia:list',
'inpatient:anesthesia:edit',
'inpatient:clinical:list',
'inpatient:clinical:edit',
'inpatient:criticalvalue:list',
'inpatient:criticalvalue:edit',
'inpatient:bloodtransfusion:list',
'inpatient:bloodtransfusion:edit',
'emr:list',
'emr:edit'
)
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu rm
WHERE rm.role_id = r.role_id AND rm.menu_id = m.menu_id
);
-- ============================================================
-- 第六部分:为药房角色授予专属权限
-- ============================================================
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, m.menu_id
FROM sys_role r
CROSS JOIN sys_menu m
WHERE r.status = '0'
AND r.role_name IN ('药房', 'pharmacy', '药师', '药剂师')
AND m.perms IN (
'infection:rationaldrug:edit',
'inpatient:clinical:list',
'inpatient:clinical:edit',
'inpatient:criticalvalue:list',
'emr:list'
)
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu rm
WHERE rm.role_id = r.role_id AND rm.menu_id = m.menu_id
);
-- ============================================================
-- 第七部分:为管理员角色授予所有权限
-- ============================================================
-- 管理员角色获取所有菜单权限通过admin用户已有的 *:*:* 权限)
-- 但确保管理员角色在sys_role_menu中有所有菜单的关联
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT 1, m.menu_id
FROM sys_menu m
WHERE m.status = '0'
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu rm
WHERE rm.role_id = 1 AND rm.menu_id = m.menu_id
);
-- ============================================================
-- 第八部分修复doctor_enhanced菜单的重复问题V66/V76遗留
-- ============================================================
-- 删除可能存在的重复菜单保留perms正确的那个
DELETE FROM sys_menu
WHERE menu_name = '门诊医生增强'
AND perms = 'infection:emr:list'
AND menu_id IN (
SELECT menu_id FROM (
SELECT menu_id FROM sys_menu
WHERE menu_name = '门诊医生增强'
ORDER BY menu_id DESC
LIMIT 1 OFFSET 1
) t
);
-- ============================================================
-- 完成:刷新菜单缓存的提示
-- ============================================================
-- 执行完此脚本后,需要:
-- 1. 重启应用或调用 /system/menu/refreshCache 刷新菜单缓存
-- 2. 用户重新登录以加载最新权限

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.web.pharmacymanage.mapper.InHospitalReturnMedicineAppMapper">
<mapper namespace="com.healthlink.his.web.pharmacy.dispense.mapper.InHospitalReturnMedicineAppMapper">
<select id="selectEncounterInfoListPage" resultType="com.healthlink.his.web.pharmacy.dispense.dto.EncounterInfoDto">
SELECT MAX(ii.reception_time) AS reception_time,
MAX(ii.start_time) AS start_time,

View File

@@ -20,9 +20,13 @@
SELECT
COUNT(*) AS total_count,
SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'PENDING' THEN 1 ELSE 0 END) AS pending_count,
SUM(CASE WHEN status = 'OVERDUE' THEN 1 ELSE 0 END) AS overdue_count,
ROUND(SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) * 100.0 / NULLIF(COUNT(*), 0), 2) AS completion_rate
FROM emr_timeliness
<if test="startDate != null and endDate != null">
WHERE create_time::date BETWEEN #{startDate} AND #{endDate}
</if>
</select>
</mapper>

View File

@@ -1,6 +1,6 @@
import request from "@/utils/request"
export function getTimelinessByEncounter(encounterId) { return request({ url: "/api/v1/emr/timeliness/encounter/" + encounterId, method: "get" }) }
export function getTimelinessStatistics(params) { return request({ url: "/api/v1/emr/timeliness/statistics", method: "get", params }) }
export function getTimelinessStatistics(params) { return request({ url: "/emr/timeliness/statistics", method: "get", params }) }
export function getPendingEmrCount(params) { return request({ url: "/emr-archive/pending-count", method: "get", params }) }
export function getOverdueList(params) { return request({ url: "/api/v1/emr/timeliness/overdue", method: "get", params }) }

View File

@@ -243,7 +243,7 @@ import request from '@/utils/request'
const route=useRoute()
const tableData=ref([]);const total=ref(0);const stats=ref({})
const syncing=ref(false)
const q=ref({pageNo:1,pageSize:20,patientName:'',archiveStatus:''})
const q=ref({pageNo:1,pageSize:10,patientName:'',archiveStatus:''})
const loadData=async()=>{const r=await getArchivePage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
const loadStats=async()=>{const r=await getArchiveStats();stats.value=r.data||{}}
const doArchive=async(row)=>{const {value}=await ElMessageBox.prompt('归档人','确认归档');if(value){await archive(row.id,value);ElMessage.success('已归档');loadData();loadStats()}}

View File

@@ -1,151 +1,148 @@
<template>
<div class="app-container">
<el-row
:gutter="20"
class="mb8"
>
<el-row :gutter="20" class="mb8">
<el-col :span="6">
<el-card shadow="hover">
<el-statistic
title="待完成"
:value="stats.pending"
/>
<el-statistic title="待完成" :value="stats.pendingCount" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<el-statistic
title="已完成"
:value="stats.completed"
/>
<el-statistic title="已完成" :value="stats.completedCount" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<el-statistic
title="超时"
:value="stats.overdue"
/>
<el-statistic title="超时" :value="stats.overdueCount" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<el-statistic
title="完成率"
:value="stats.rate"
suffix="%"
/>
<el-statistic title="完成率" :value="stats.completionRate" suffix="%" />
</el-card>
</el-col>
</el-row>
<el-form
:model="queryParams"
:inline="true"
class="mt8"
>
<el-form :model="queryParams" :inline="true" class="mt8">
<el-form-item label="科室">
<el-input
v-model="queryParams.departmentName"
placeholder="科室"
clearable
/>
<el-input v-model="queryParams.departmentName" placeholder="科室" clearable />
</el-form-item>
<el-form-item label="病历类型">
<el-select
v-model="queryParams.emrType"
placeholder="全部"
clearable
>
<el-option
label="入院记录"
value="ADMISSION"
/><el-option
label="首次病程"
value="FIRST_COURSE"
/>
<el-option
label="日常病程"
value="DAILY_COURSE"
/><el-option
label="出院记录"
value="DISCHARGE"
/>
<el-select v-model="queryParams.emrType" placeholder="全部" clearable>
<el-option label="入院记录" value="ADMISSION" />
<el-option label="首次病程" value="FIRST_COURSE" />
<el-option label="日常病程" value="DAILY_COURSE" />
<el-option label="出院记录" value="DISCHARGE" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="全部" clearable>
<el-option label="待完成" value="PENDING" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="超时" value="OVERDUE" />
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="Search"
@click="handleQuery"
>
搜索
</el-button>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="dataList"
>
<el-table-column
label="患者"
prop="patientName"
width="120"
/>
<el-table-column
label="科室"
prop="departmentName"
width="120"
/>
<el-table-column
label="医生"
prop="doctorName"
width="100"
/>
<el-table-column
label="病历类型"
prop="emrType"
width="120"
>
<el-table v-loading="loading" :data="dataList">
<el-table-column label="科室" prop="departmentName" width="120" />
<el-table-column label="医生" prop="doctorName" width="100" />
<el-table-column label="病历类型" prop="emrType" width="120">
<template #default="scope">
<el-tag>{{ emrTypeMap[scope.row.emrType] }}</el-tag>
<el-tag>{{ emrTypeMap[scope.row.emrType] || scope.row.emrType }}</el-tag>
</template>
</el-table-column>
<el-table-column
label="截止时间"
prop="deadlineTime"
width="180"
/>
<el-table-column
label="状态"
prop="status"
width="100"
>
<el-table-column label="要求时限(h)" prop="requiredHours" width="110" />
<el-table-column label="截止时间" prop="deadlineTime" width="180" />
<el-table-column label="实际完成时间" prop="actualCompleteTime" width="180" />
<el-table-column label="状态" prop="status" width="100">
<template #default="scope">
<el-tag :type="statusMap[scope.row.status]?.type">
{{ statusMap[scope.row.status]?.label }}
{{ statusMap[scope.row.status]?.label || scope.row.status }}
</el-tag>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { getTimelinessByEncounter, getOverdueList, getTimelinessStatistics } from '@/api/emr'
import { getTimelinessAlerts, getTimelinessStatistics } from '@/api/emr'
const route = useRoute()
const loading = ref(false)
const dataList = ref([])
const stats = reactive({ pending: 0, completed: 0, overdue: 0, rate: 0 })
const queryParams = reactive({ departmentName: '', emrType: '', encounterId: '' })
const total = ref(0)
const stats = reactive({ pendingCount: 0, completedCount: 0, overdueCount: 0, completionRate: 0 })
const queryParams = reactive({
departmentName: '',
emrType: '',
status: '',
pageNum: 1,
pageSize: 20
})
const emrTypeMap = { ADMISSION: '入院记录', FIRST_COURSE: '首次病程', DAILY_COURSE: '日常病程', DISCHARGE: '出院记录' }
const statusMap = { PENDING: { label: '待完成', type: 'info' }, COMPLETED: { label: '已完成', type: 'success' }, OVERDUE: { label: '超时', type: 'danger' } }
const getList = async () => { loading.value = true; const res = await getOverdueList(); dataList.value = res.data || res.rows || []; loading.value = false }
const handleQuery = () => getList()
onMounted(() => {
if (route.query.encounterId) { queryParams.encounterId = route.query.encounterId }
if (route.query.departmentName) { queryParams.departmentName = route.query.departmentName }
const statusMap = {
PENDING: { label: '待完成', type: 'info' },
COMPLETED: { label: '已完成', type: 'success' },
OVERDUE: { label: '超时', type: 'danger' }
}
const getList = async () => {
loading.value = true
try {
const params = {}
if (queryParams.departmentName) params.departmentName = queryParams.departmentName
if (queryParams.emrType) params.emrType = queryParams.emrType
if (queryParams.status) params.status = queryParams.status
params.pageNum = queryParams.pageNum
params.pageSize = queryParams.pageSize
const res = await getTimelinessAlerts(params)
dataList.value = res.rows || []
total.value = res.total || 0
} finally {
loading.value = false
}
}
const getStats = async () => {
try {
const res = await getTimelinessStatistics()
if (res) {
stats.pendingCount = res.pendingCount || 0
stats.completedCount = res.completedCount || 0
stats.overdueCount = res.overdueCount || 0
stats.completionRate = res.completionRate || 0
}
} catch { /* ignore */ }
}
const handleQuery = () => { queryParams.pageNum = 1; getList() }
const resetQuery = () => {
queryParams.departmentName = ''
queryParams.emrType = ''
queryParams.status = ''
queryParams.pageNum = 1
getList()
}
onMounted(() => {
if (route.query.encounterId) queryParams.encounterId = route.query.encounterId
if (route.query.departmentName) queryParams.departmentName = route.query.departmentName
if (route.query.status) queryParams.status = route.query.status
getList()
getStats()
})
</script>