feat(emr): 病历完整性检查
- 创建 IEmrCompletenessAppService + impl,实现 checkCompleteness() 和 getCheckResults()
- 创建 EmrCompletenessController,POST /emr/completeness/check 和 GET /emr/completeness/results/{emrId}
- 新建 EmrCompletenessCheck.vue 检查结果展示 + 不合格项提醒
- 添加前端 API 函数 checkCompleteness / getCompletenessResults
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.healthlink.his.web.emr.appservice;
|
||||
|
||||
import com.healthlink.his.emr.domain.EmrCompletenessCheck;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IEmrCompletenessAppService {
|
||||
|
||||
Map<String, Object> checkCompleteness(Long emrId, Long encounterId);
|
||||
|
||||
List<EmrCompletenessCheck> getCheckResults(Long emrId);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.healthlink.his.web.emr.appservice.impl;
|
||||
|
||||
import com.healthlink.his.document.domain.Emr;
|
||||
import com.healthlink.his.document.service.IEmrService;
|
||||
import com.healthlink.his.emr.domain.EmrCompletenessCheck;
|
||||
import com.healthlink.his.emr.service.IEmrCompletenessCheckService;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrCompletenessAppService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class EmrCompletenessAppServiceImpl implements IEmrCompletenessAppService {
|
||||
|
||||
@Resource
|
||||
private IEmrCompletenessCheckService emrCompletenessCheckService;
|
||||
|
||||
@Resource
|
||||
private IEmrService emrService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> checkCompleteness(Long emrId, Long encounterId) {
|
||||
Emr emr = emrService.getById(emrId);
|
||||
if (emr == null) {
|
||||
throw new IllegalArgumentException("病历不存在: " + emrId);
|
||||
}
|
||||
|
||||
List<EmrCompletenessCheck> checks = new ArrayList<>();
|
||||
int total = 0;
|
||||
int requiredPassed = 0;
|
||||
int requiredTotal = 0;
|
||||
|
||||
String[][] checkDefs = {
|
||||
{"chief_complaint", "basic", "true", "主诉"},
|
||||
{"medical_history", "basic", "true", "现病史"},
|
||||
{"past_history", "basic", "false", "既往史"},
|
||||
{"physical_exam", "basic", "true", "体格检查"},
|
||||
{"auxiliary_exam", "examination", "false", "辅助检查"},
|
||||
{"diagnosis", "diagnosis", "true", "诊断"},
|
||||
{"treatment_plan", "treatment", "true", "治疗计划"},
|
||||
{"signature", "signature", "false", "签名"}
|
||||
};
|
||||
|
||||
Map<String, Object> contentMap = parseContent(emr.getContextJson());
|
||||
|
||||
for (String[] def : checkDefs) {
|
||||
total++;
|
||||
boolean isRequired = Boolean.parseBoolean(def[2]);
|
||||
if (isRequired) requiredTotal++;
|
||||
|
||||
boolean hasValue = contentMap.containsKey(def[0])
|
||||
&& contentMap.get(def[0]) != null
|
||||
&& !contentMap.get(def[0]).toString().trim().isEmpty();
|
||||
|
||||
String result = hasValue ? "PASS" : "FAIL";
|
||||
String detail = def[3] + (hasValue ? " - 已填写" : " - 未填写");
|
||||
if (!isRequired && !hasValue) {
|
||||
detail = def[3] + " - 未填写(选填项)";
|
||||
}
|
||||
|
||||
if (isRequired && hasValue) requiredPassed++;
|
||||
|
||||
EmrCompletenessCheck check = new EmrCompletenessCheck()
|
||||
.setEmrId(emrId)
|
||||
.setEncounterId(encounterId)
|
||||
.setCheckItem(def[0])
|
||||
.setCheckCategory(def[1])
|
||||
.setIsRequired(isRequired)
|
||||
.setCheckResult(result)
|
||||
.setCheckDetail(detail)
|
||||
.setCheckTime(new Date());
|
||||
|
||||
emrCompletenessCheckService.save(check);
|
||||
checks.add(check);
|
||||
}
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("emrId", emrId);
|
||||
result.put("encounterId", encounterId);
|
||||
result.put("totalItems", total);
|
||||
result.put("requiredTotal", requiredTotal);
|
||||
result.put("requiredPassed", requiredPassed);
|
||||
result.put("requiredFailed", requiredTotal - requiredPassed);
|
||||
result.put("isComplete", requiredPassed == requiredTotal);
|
||||
result.put("checks", checks);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EmrCompletenessCheck> getCheckResults(Long emrId) {
|
||||
return emrCompletenessCheckService.selectByEmrId(emrId);
|
||||
}
|
||||
|
||||
private Map<String, Object> parseContent(String contextJson) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
if (contextJson == null || contextJson.isEmpty()) {
|
||||
return map;
|
||||
}
|
||||
try {
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> parsed = mapper.readValue(contextJson, Map.class);
|
||||
map.putAll(parsed);
|
||||
} catch (Exception e) {
|
||||
map.put("raw", contextJson);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.healthlink.his.web.emr.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrCompletenessAppService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/emr/completeness")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@Tag(name = "病历完整性检查")
|
||||
public class EmrCompletenessController {
|
||||
|
||||
private final IEmrCompletenessAppService emrCompletenessAppService;
|
||||
|
||||
@PostMapping("/check")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:edit')")
|
||||
@Operation(summary = "执行病历完整性检查")
|
||||
public R<Map<String, Object>> checkCompleteness(
|
||||
@RequestParam("emrId") Long emrId,
|
||||
@RequestParam("encounterId") Long encounterId) {
|
||||
return R.ok(emrCompletenessAppService.checkCompleteness(emrId, encounterId));
|
||||
}
|
||||
|
||||
@GetMapping("/results/{emrId}")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:list')")
|
||||
@Operation(summary = "获取完整性检查结果")
|
||||
public R<?> getCheckResults(@PathVariable Long emrId) {
|
||||
return R.ok(emrCompletenessAppService.getCheckResults(emrId));
|
||||
}
|
||||
}
|
||||
@@ -13,3 +13,6 @@ export function compareEmrRevisions(id1, id2) { return request({ url: "/emr/revi
|
||||
export function saveEmrVersion(data) { return request({ url: "/emr/version/save", method: "post", data }) }
|
||||
export function getEmrVersionList(emrId) { return request({ url: "/emr/version/list/" + emrId, method: "get" }) }
|
||||
export function compareEmrVersions(id1, id2) { return request({ url: "/emr/version/compare", method: "get", params: { versionId1: id1, versionId2: id2 } }) }
|
||||
|
||||
export function checkCompleteness(emrId, encounterId) { return request({ url: "/emr/completeness/check", method: "post", params: { emrId, encounterId } }) }
|
||||
export function getCompletenessResults(emrId) { return request({ url: "/emr/completeness/results/" + emrId, method: "get" }) }
|
||||
|
||||
156
healthlink-his-ui/src/views/emr/completeness-check/index.vue
Normal file
156
healthlink-his-ui/src/views/emr/completeness-check/index.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card shadow="hover" class="mb8">
|
||||
<template #header>
|
||||
<span>执行完整性检查</span>
|
||||
</template>
|
||||
<el-form :inline="true" :model="checkForm">
|
||||
<el-form-item label="病历ID">
|
||||
<el-input v-model="checkForm.emrId" placeholder="请输入病历ID" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="就诊ID">
|
||||
<el-input v-model="checkForm.encounterId" placeholder="请输入就诊ID" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" :loading="checkLoading" @click="handleCheck">
|
||||
执行检查
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-row v-if="checkResult" :gutter="20" class="mb8">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="检查项总数" :value="checkResult.totalItems" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="必填项总数" :value="checkResult.requiredTotal" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="必填项通过" :value="checkResult.requiredPassed" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="是否完整" :value="checkResult.isComplete ? '是' : '否'" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card v-if="checkResult && checkResult.requiredFailed > 0" shadow="hover" class="mb8">
|
||||
<template #header>
|
||||
<span style="color: #e6a23c;">不合格项提醒</span>
|
||||
</template>
|
||||
<el-alert
|
||||
v-for="item in failedItems"
|
||||
:key="item.id"
|
||||
:title="item.checkItem"
|
||||
:description="item.checkDetail"
|
||||
type="warning"
|
||||
show-icon
|
||||
class="mb4"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<span>检查结果明细</span>
|
||||
</template>
|
||||
<el-table v-loading="resultLoading" :data="resultList">
|
||||
<el-table-column label="检查项" prop="checkItem" width="150" />
|
||||
<el-table-column label="分类" prop="checkCategory" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag>{{ categoryMap[scope.row.checkCategory] || scope.row.checkCategory }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否必填" prop="isRequired" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.isRequired ? 'danger' : 'info'">
|
||||
{{ scope.row.isRequired ? '必填' : '选填' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="检查结果" prop="checkResult" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.checkResult === 'PASS' ? 'success' : 'danger'">
|
||||
{{ scope.row.checkResult === 'PASS' ? '通过' : '不合格' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="详情" prop="checkDetail" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="检查时间" prop="checkTime" width="180" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { checkCompleteness, getCompletenessResults } from '@/api/emr'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const checkLoading = ref(false)
|
||||
const resultLoading = ref(false)
|
||||
const checkResult = ref(null)
|
||||
const resultList = ref([])
|
||||
|
||||
const checkForm = reactive({ emrId: '', encounterId: '' })
|
||||
|
||||
const categoryMap = {
|
||||
basic: '基本信息',
|
||||
examination: '检查',
|
||||
diagnosis: '诊断',
|
||||
treatment: '治疗',
|
||||
signature: '签名'
|
||||
}
|
||||
|
||||
const failedItems = computed(() => {
|
||||
if (!checkResult.value || !checkResult.value.checks) return []
|
||||
return checkResult.value.checks.filter(c => c.checkResult === 'FAIL')
|
||||
})
|
||||
|
||||
const handleCheck = async () => {
|
||||
if (!checkForm.emrId) {
|
||||
ElMessage.warning('请输入病历ID')
|
||||
return
|
||||
}
|
||||
if (!checkForm.encounterId) {
|
||||
ElMessage.warning('请输入就诊ID')
|
||||
return
|
||||
}
|
||||
checkLoading.value = true
|
||||
try {
|
||||
const res = await checkCompleteness(checkForm.emrId, checkForm.encounterId)
|
||||
checkResult.value = res.data || res
|
||||
resultList.value = checkResult.value.checks || []
|
||||
ElMessage.success(checkResult.value.isComplete ? '病历完整性检查通过' : '病历完整性检查未通过,存在不合格项')
|
||||
} catch (e) {
|
||||
ElMessage.error('执行检查失败')
|
||||
} finally {
|
||||
checkLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadResults = async () => {
|
||||
if (!checkForm.emrId) return
|
||||
resultLoading.value = true
|
||||
try {
|
||||
const res = await getCompletenessResults(checkForm.emrId)
|
||||
resultList.value = res.data || res || []
|
||||
} catch (e) {
|
||||
ElMessage.error('获取检查结果失败')
|
||||
} finally {
|
||||
resultLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
.mb4 { margin-bottom: 4px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user