Merge remote-tracking branch 'origin/develop' into guanyu
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,14 @@
|
||||
package com.healthlink.his.web.emr.appservice;
|
||||
|
||||
import com.healthlink.his.emr.domain.EmrRevision;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IEmrRevisionAppService {
|
||||
|
||||
EmrRevision recordRevision(EmrRevision revision);
|
||||
|
||||
List<EmrRevision> getRevisions(Long emrId);
|
||||
|
||||
EmrRevision getRevisionDetail(Long id);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.healthlink.his.web.emr.appservice;
|
||||
|
||||
import com.healthlink.his.emr.domain.EmrTimeliness;
|
||||
import com.healthlink.his.emr.dto.EmrTimelinessStatisticsDto;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IEmrTimelinessAppService {
|
||||
|
||||
EmrTimelinessStatisticsDto checkTimeliness(Long encounterId);
|
||||
|
||||
Map<String, Object> getTimelinessAlerts(String emrType, String status, String departmentName, int pageNum, int pageSize);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.healthlink.his.web.emr.appservice;
|
||||
|
||||
import com.healthlink.his.emr.domain.EmrVersion;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IEmrVersionAppService {
|
||||
|
||||
EmrVersion saveVersion(EmrVersion version);
|
||||
|
||||
List<EmrVersion> getVersions(Long emrId);
|
||||
|
||||
Map<String, Object> compareVersions(Long versionId1, Long versionId2);
|
||||
}
|
||||
@@ -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,43 @@
|
||||
package com.healthlink.his.web.emr.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.emr.domain.EmrRevision;
|
||||
import com.healthlink.his.emr.service.IEmrRevisionService;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrRevisionAppService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class EmrRevisionAppServiceImpl implements IEmrRevisionAppService {
|
||||
|
||||
@Resource
|
||||
private IEmrRevisionService emrRevisionService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public EmrRevision recordRevision(EmrRevision revision) {
|
||||
EmrRevision latest = emrRevisionService.selectLatest(revision.getEmrId());
|
||||
int nextNumber = (latest != null) ? latest.getRevisionNumber() + 1 : 1;
|
||||
revision.setRevisionNumber(nextNumber);
|
||||
if (revision.getEncounterId() == null && latest != null) {
|
||||
revision.setEncounterId(latest.getEncounterId());
|
||||
}
|
||||
revision.setCreateTime(new Date());
|
||||
emrRevisionService.save(revision);
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EmrRevision> getRevisions(Long emrId) {
|
||||
return emrRevisionService.selectByEmrId(emrId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmrRevision getRevisionDetail(Long id) {
|
||||
return emrRevisionService.getById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.healthlink.his.web.emr.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.healthlink.his.emr.domain.EmrTimeliness;
|
||||
import com.healthlink.his.emr.dto.EmrTimelinessStatisticsDto;
|
||||
import com.healthlink.his.emr.service.IEmrTimelinessService;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrTimelinessAppService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class EmrTimelinessAppServiceImpl implements IEmrTimelinessAppService {
|
||||
|
||||
@Resource
|
||||
private IEmrTimelinessService emrTimelinessService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public EmrTimelinessStatisticsDto checkTimeliness(Long encounterId) {
|
||||
LambdaQueryWrapper<EmrTimeliness> wrapper = new LambdaQueryWrapper<>();
|
||||
if (encounterId != null) {
|
||||
wrapper.eq(EmrTimeliness::getEncounterId, encounterId);
|
||||
}
|
||||
wrapper.eq(EmrTimeliness::getStatus, "PENDING");
|
||||
List<EmrTimeliness> pendingList = emrTimelinessService.list(wrapper);
|
||||
|
||||
Date now = new Date();
|
||||
int overdueCount = 0;
|
||||
for (EmrTimeliness record : pendingList) {
|
||||
if (record.getDeadlineTime() != null && now.after(record.getDeadlineTime())) {
|
||||
record.setStatus("OVERDUE");
|
||||
emrTimelinessService.updateById(record);
|
||||
overdueCount++;
|
||||
}
|
||||
}
|
||||
|
||||
EmrTimelinessStatisticsDto stats = new EmrTimelinessStatisticsDto();
|
||||
LambdaQueryWrapper<EmrTimeliness> countWrapper = new LambdaQueryWrapper<>();
|
||||
if (encounterId != null) {
|
||||
countWrapper.eq(EmrTimeliness::getEncounterId, encounterId);
|
||||
}
|
||||
long total = emrTimelinessService.count(countWrapper);
|
||||
|
||||
countWrapper.eq(EmrTimeliness::getStatus, "COMPLETED");
|
||||
long completed = emrTimelinessService.count(countWrapper);
|
||||
|
||||
countWrapper.eq(EmrTimeliness::getStatus, "OVERDUE");
|
||||
long overdue = emrTimelinessService.count(countWrapper);
|
||||
|
||||
long pending = total - completed - overdue;
|
||||
double rate = total > 0 ? Math.round(completed * 10000.0 / total) / 100.0 : 0;
|
||||
|
||||
stats.setTotalCount(total);
|
||||
stats.setCompletedCount(completed);
|
||||
stats.setOverdueCount(overdue);
|
||||
stats.setPendingCount(pending);
|
||||
stats.setCompletionRate(rate);
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getTimelinessAlerts(String emrType, String status, String departmentName, int pageNum, int pageSize) {
|
||||
LambdaQueryWrapper<EmrTimeliness> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(StringUtils.hasText(status), EmrTimeliness::getStatus, status);
|
||||
wrapper.eq(StringUtils.hasText(emrType), EmrTimeliness::getEmrType, emrType);
|
||||
wrapper.eq(StringUtils.hasText(departmentName), EmrTimeliness::getDepartmentName, departmentName);
|
||||
wrapper.orderByAsc(EmrTimeliness::getDeadlineTime);
|
||||
|
||||
Page<EmrTimeliness> page = emrTimelinessService.page(new Page<>(pageNum, pageSize), wrapper);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("total", page.getTotal());
|
||||
result.put("rows", page.getRecords());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.healthlink.his.web.emr.appservice.impl;
|
||||
|
||||
import com.healthlink.his.emr.domain.EmrVersion;
|
||||
import com.healthlink.his.emr.service.IEmrVersionService;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrVersionAppService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class EmrVersionAppServiceImpl implements IEmrVersionAppService {
|
||||
|
||||
@Resource
|
||||
private IEmrVersionService emrVersionService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public EmrVersion saveVersion(EmrVersion version) {
|
||||
EmrVersion latest = emrVersionService.selectLatest(version.getEmrId());
|
||||
int nextNumber = (latest != null) ? latest.getVersionNumber() + 1 : 1;
|
||||
version.setVersionNumber(nextNumber);
|
||||
|
||||
if (latest != null) {
|
||||
version.setContentDiff(computeDiff(latest.getContentSnapshot(), version.getContentSnapshot()));
|
||||
if (version.getEncounterId() == null) {
|
||||
version.setEncounterId(latest.getEncounterId());
|
||||
}
|
||||
}
|
||||
version.setCreateTime(new Date());
|
||||
emrVersionService.save(version);
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EmrVersion> getVersions(Long emrId) {
|
||||
return emrVersionService.selectByEmrId(emrId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> compareVersions(Long versionId1, Long versionId2) {
|
||||
EmrVersion v1 = emrVersionService.getById(versionId1);
|
||||
EmrVersion v2 = emrVersionService.getById(versionId2);
|
||||
if (v1 == null || v2 == null) {
|
||||
throw new IllegalArgumentException("版本记录不存在");
|
||||
}
|
||||
|
||||
String content1 = v1.getContentSnapshot() != null ? v1.getContentSnapshot() : "";
|
||||
String content2 = v2.getContentSnapshot() != null ? v2.getContentSnapshot() : "";
|
||||
|
||||
String diff = computeDiff(content1, content2);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("version1", v1);
|
||||
result.put("version2", v2);
|
||||
result.put("diff", diff);
|
||||
return result;
|
||||
}
|
||||
|
||||
private String computeDiff(String oldContent, String newContent) {
|
||||
if (oldContent == null) oldContent = "";
|
||||
if (newContent == null) newContent = "";
|
||||
|
||||
if (oldContent.equals(newContent)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder diff = new StringBuilder();
|
||||
String[] oldLines = oldContent.split("\n");
|
||||
String[] newLines = newContent.split("\n");
|
||||
int maxLen = Math.max(oldLines.length, newLines.length);
|
||||
|
||||
for (int i = 0; i < maxLen; i++) {
|
||||
String oldLine = i < oldLines.length ? oldLines[i] : "";
|
||||
String newLine = i < newLines.length ? newLines[i] : "";
|
||||
|
||||
if (!oldLine.equals(newLine)) {
|
||||
if (!oldLine.isEmpty()) {
|
||||
diff.append("- ").append(oldLine).append("\n");
|
||||
}
|
||||
if (!newLine.isEmpty()) {
|
||||
diff.append("+ ").append(newLine).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
return diff.toString();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -5,26 +5,47 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.emr.domain.EmrRevision;
|
||||
import com.healthlink.his.emr.service.IEmrRevisionService;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrRevisionAppService;
|
||||
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.transaction.annotation.Transactional;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 病历修改留痕 Controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/emr-revision")
|
||||
@RequestMapping("/emr/revision")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@Tag(name = "病历修改留痕")
|
||||
public class EmrRevisionController {
|
||||
|
||||
private final IEmrRevisionService revisionService;
|
||||
|
||||
private final IEmrRevisionAppService emrRevisionAppService;
|
||||
|
||||
@PostMapping("/record")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:edit')")
|
||||
@Operation(summary = "记录修改留痕")
|
||||
public R<EmrRevision> recordRevision(@RequestBody EmrRevision revision) {
|
||||
return R.ok(emrRevisionAppService.recordRevision(revision));
|
||||
}
|
||||
|
||||
@GetMapping("/list/{emrId}")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:list')")
|
||||
@Operation(summary = "获取修改历史列表")
|
||||
public R<?> getRevisions(@PathVariable Long emrId) {
|
||||
return R.ok(emrRevisionAppService.getRevisions(emrId));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:list')")
|
||||
@Operation(summary = "分页查询修改留痕")
|
||||
public R<?> getPage(
|
||||
@RequestParam(value = "emrId", required = false) Long emrId,
|
||||
@RequestParam(value = "encounterId", required = false) Long encounterId,
|
||||
@@ -39,35 +60,16 @@ public class EmrRevisionController {
|
||||
return R.ok(revisionService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public R<?> getList(@RequestParam("emrId") Long emrId) {
|
||||
LambdaQueryWrapper<EmrRevision> w = new LambdaQueryWrapper<>();
|
||||
w.eq(EmrRevision::getEmrId, emrId)
|
||||
.orderByAsc(EmrRevision::getRevisionNumber);
|
||||
return R.ok(revisionService.list(w));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:list')")
|
||||
@Operation(summary = "获取修订详情")
|
||||
public R<?> getById(@PathVariable Long id) {
|
||||
return R.ok(revisionService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/record")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> recordRevision(@RequestBody EmrRevision revision) {
|
||||
// 自动计算版本号
|
||||
LambdaQueryWrapper<EmrRevision> w = new LambdaQueryWrapper<>();
|
||||
w.eq(EmrRevision::getEmrId, revision.getEmrId())
|
||||
.orderByDesc(EmrRevision::getRevisionNumber)
|
||||
.last("LIMIT 1");
|
||||
EmrRevision last = revisionService.getOne(w);
|
||||
revision.setRevisionNumber(last == null ? 1 : last.getRevisionNumber() + 1);
|
||||
revision.setCreateTime(new Date());
|
||||
revisionService.save(revision);
|
||||
return R.ok(revision);
|
||||
return R.ok(emrRevisionAppService.getRevisionDetail(id));
|
||||
}
|
||||
|
||||
@GetMapping("/compare")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:list')")
|
||||
@Operation(summary = "对比两个修订版本")
|
||||
public R<?> compareRevisions(
|
||||
@RequestParam("revisionId1") Long id1,
|
||||
@RequestParam("revisionId2") Long id2) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.healthlink.his.web.emr.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.emr.domain.EmrTimeliness;
|
||||
import com.healthlink.his.emr.dto.EmrTimelinessStatisticsDto;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrTimelinessAppService;
|
||||
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/timeliness")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@Tag(name = "病历时限监控")
|
||||
public class EmrTimelinessController {
|
||||
|
||||
private final IEmrTimelinessAppService emrTimelinessAppService;
|
||||
|
||||
@PostMapping("/check")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:edit')")
|
||||
@Operation(summary = "执行病历时限检查")
|
||||
public R<EmrTimelinessStatisticsDto> checkTimeliness(
|
||||
@RequestParam(value = "encounterId", required = false) Long encounterId) {
|
||||
return R.ok(emrTimelinessAppService.checkTimeliness(encounterId));
|
||||
}
|
||||
|
||||
@GetMapping("/alerts")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:list')")
|
||||
@Operation(summary = "获取病历时限提醒列表")
|
||||
public R<Map<String, Object>> getTimelinessAlerts(
|
||||
@RequestParam(value = "emrType", required = false) String emrType,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "departmentName", required = false) String departmentName,
|
||||
@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
|
||||
return R.ok(emrTimelinessAppService.getTimelinessAlerts(emrType, status, departmentName, pageNum, pageSize));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.healthlink.his.web.emr.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.emr.domain.EmrVersion;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrVersionAppService;
|
||||
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.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/emr/version")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@Tag(name = "病历版本管理")
|
||||
public class EmrVersionController {
|
||||
|
||||
private final IEmrVersionAppService emrVersionAppService;
|
||||
|
||||
@PostMapping("/save")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:edit')")
|
||||
@Operation(summary = "保存病历版本")
|
||||
public R<EmrVersion> saveVersion(@RequestBody EmrVersion version) {
|
||||
return R.ok(emrVersionAppService.saveVersion(version));
|
||||
}
|
||||
|
||||
@GetMapping("/list/{emrId}")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:list')")
|
||||
@Operation(summary = "获取病历版本列表")
|
||||
public R<?> getVersions(@PathVariable Long emrId) {
|
||||
return R.ok(emrVersionAppService.getVersions(emrId));
|
||||
}
|
||||
|
||||
@GetMapping("/compare")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:emr:list')")
|
||||
@Operation(summary = "对比两个版本")
|
||||
public R<?> compareVersions(
|
||||
@RequestParam("versionId1") Long versionId1,
|
||||
@RequestParam("versionId2") Long versionId2) {
|
||||
return R.ok(emrVersionAppService.compareVersions(versionId1, versionId2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
-- V64: 病历版本管理
|
||||
|
||||
CREATE TABLE IF NOT EXISTS emr_version (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
emr_id BIGINT NOT NULL,
|
||||
encounter_id BIGINT NOT NULL,
|
||||
version_number INT NOT NULL DEFAULT 1,
|
||||
content_snapshot TEXT,
|
||||
content_diff TEXT,
|
||||
emr_type VARCHAR(50),
|
||||
emr_title VARCHAR(200),
|
||||
operator_id BIGINT,
|
||||
operator_name VARCHAR(64),
|
||||
save_reason VARCHAR(200),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE emr_version IS '病历版本管理(不可删除,三甲评审要求)';
|
||||
COMMENT ON COLUMN emr_version.version_number IS '版本号,递增';
|
||||
COMMENT ON COLUMN emr_version.content_snapshot IS '完整内容快照';
|
||||
COMMENT ON COLUMN emr_version.content_diff IS '与上一版本的差异';
|
||||
COMMENT ON COLUMN emr_version.save_reason IS '保存原因';
|
||||
CREATE INDEX IF NOT EXISTS idx_ev_emr ON emr_version(emr_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ev_encounter ON emr_version(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ev_version ON emr_version(emr_id, version_number);
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.healthlink.his.emr.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("emr_version")
|
||||
@Accessors(chain = true)
|
||||
public class EmrVersion extends HisBaseEntity {
|
||||
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long emrId;
|
||||
|
||||
private Long encounterId;
|
||||
|
||||
private Integer versionNumber;
|
||||
|
||||
private String contentSnapshot;
|
||||
|
||||
private String contentDiff;
|
||||
|
||||
private String emrType;
|
||||
|
||||
private String emrTitle;
|
||||
|
||||
private Long operatorId;
|
||||
|
||||
private String operatorName;
|
||||
|
||||
private String saveReason;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.healthlink.his.emr.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.emr.domain.EmrVersion;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface EmrVersionMapper extends BaseMapper<EmrVersion> {
|
||||
|
||||
List<EmrVersion> selectByEmrId(@Param("emrId") Long emrId);
|
||||
|
||||
EmrVersion selectLatest(@Param("emrId") Long emrId);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.healthlink.his.emr.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.emr.domain.EmrVersion;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IEmrVersionService extends IService<EmrVersion> {
|
||||
|
||||
List<EmrVersion> selectByEmrId(Long emrId);
|
||||
|
||||
EmrVersion selectLatest(Long emrId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.healthlink.his.emr.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.emr.domain.EmrVersion;
|
||||
import com.healthlink.his.emr.mapper.EmrVersionMapper;
|
||||
import com.healthlink.his.emr.service.IEmrVersionService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class EmrVersionServiceImpl
|
||||
extends ServiceImpl<EmrVersionMapper, EmrVersion>
|
||||
implements IEmrVersionService {
|
||||
|
||||
@Override
|
||||
public List<EmrVersion> selectByEmrId(Long emrId) {
|
||||
return baseMapper.selectByEmrId(emrId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmrVersion selectLatest(Long emrId) {
|
||||
return baseMapper.selectLatest(emrId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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.emr.mapper.EmrVersionMapper">
|
||||
|
||||
<select id="selectByEmrId" resultType="com.healthlink.his.emr.domain.EmrVersion">
|
||||
SELECT * FROM emr_version
|
||||
WHERE emr_id = #{emrId} AND delete_flag = '0'
|
||||
ORDER BY version_number DESC
|
||||
</select>
|
||||
|
||||
<select id="selectLatest" resultType="com.healthlink.his.emr.domain.EmrVersion">
|
||||
SELECT * FROM emr_version
|
||||
WHERE emr_id = #{emrId} AND delete_flag = '0'
|
||||
ORDER BY version_number DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -93,3 +93,11 @@ export function saveAnesSummary(data) {
|
||||
export function getAnesSummary(recordId) {
|
||||
return request({ url: '/api/v1/anesthesia/summary/' + recordId, method: 'get' })
|
||||
}
|
||||
|
||||
export function recordPostopFollowup(data) {
|
||||
return request({ url: '/api/v1/anesthesia/postop-followup', method: 'post', data })
|
||||
}
|
||||
|
||||
export function getPostopFollowups(encounterId) {
|
||||
return request({ url: '/api/v1/anesthesia/postop-followup/' + encounterId, method: 'get' })
|
||||
}
|
||||
|
||||
@@ -2,6 +2,20 @@ import request from "@/utils/request"
|
||||
export function getTimelinessByEncounter(encounterId) { return request({ url: "/emr-revision/timeliness/" + encounterId, method: "get" }) }
|
||||
export function getTimelinessStatistics(params) { return request({ url: "/emr-revision/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: "/emr-archive/overdue/list", method: "get", params }) }
|
||||
|
||||
export function recordEmrRevision(data) { return request({ url: "/emr/revision/record", method: "post", data }) }
|
||||
export function getEmrRevisionList(emrId) { return request({ url: "/emr/revision/list/" + emrId, method: "get" }) }
|
||||
export function getEmrRevisionPage(params) { return request({ url: "/emr/revision/page", method: "get", params }) }
|
||||
export function getEmrRevisionDetail(id) { return request({ url: "/emr/revision/" + id, method: "get" }) }
|
||||
export function compareEmrRevisions(id1, id2) { return request({ url: "/emr/revision/compare", method: "get", params: { revisionId1: id1, revisionId2: id2 } }) }
|
||||
|
||||
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" }) }
|
||||
|
||||
export function checkTimeliness(encounterId) { return request({ url: "/emr/timeliness/check", method: "post", params: { encounterId } }) }
|
||||
export function getTimelinessAlerts(params) { return request({ url: "/emr/timeliness/alerts", method: "get", params }) }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function createRevision(data) { return request({ url: '/api/v1/emr/revision', method: 'post', data }) }
|
||||
export function getRevisionHistory(emrId) { return request({ url: '/api/v1/emr/revision/' + emrId, method: 'get' }) }
|
||||
export function executeCompletenessCheck(emrId) { return request({ url: '/api/v1/emr/completeness-check/' + emrId, method: 'post' }) }
|
||||
@@ -7,3 +8,9 @@ export function getTimelinessByEncounter(encounterId) { return request({ url: '/
|
||||
export function getOverdueList() { return request({ url: '/api/v1/emr/timeliness/overdue', method: 'get' }) }
|
||||
export function getTimelinessStatistics(params) { return request({ url: '/api/v1/emr/timeliness/statistics', method: 'get', params }) }
|
||||
export function checkTimeliness(data) { return request({ url: '/api/v1/emr/timeliness/check', method: 'post', data }) }
|
||||
|
||||
export function recordEmrRevision(data) { return request({ url: '/emr/revision/record', method: 'post', data }) }
|
||||
export function getEmrRevisionList(emrId) { return request({ url: '/emr/revision/list/' + emrId, method: 'get' }) }
|
||||
export function getEmrRevisionPage(params) { return request({ url: '/emr/revision/page', method: 'get', params }) }
|
||||
export function getEmrRevisionDetail(id) { return request({ url: '/emr/revision/' + id, method: 'get' }) }
|
||||
export function compareEmrRevisions(id1, id2) { return request({ url: '/emr/revision/compare', method: 'get', params: { revisionId1: id1, revisionId2: id2 } }) }
|
||||
|
||||
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>
|
||||
277
healthlink-his-ui/src/views/inpatientDoctor/EmrRevisionTrack.vue
Normal file
277
healthlink-his-ui/src/views/inpatientDoctor/EmrRevisionTrack.vue
Normal file
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<div class="emr-revision-track">
|
||||
<el-card v-loading="loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>修改留痕记录</span>
|
||||
<div>
|
||||
<el-button v-if="compareIds.length === 2" type="primary" size="small" @click="handleCompare">对比选中</el-button>
|
||||
<el-button type="info" size="small" @click="handleRefresh">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-timeline v-if="revisions.length > 0">
|
||||
<el-timeline-item
|
||||
v-for="item in revisions"
|
||||
:key="item.id"
|
||||
:timestamp="item.createTime"
|
||||
placement="top"
|
||||
:type="getTimelineType(item.operationType)"
|
||||
size="large"
|
||||
>
|
||||
<el-card shadow="never" class="revision-card">
|
||||
<div class="revision-header">
|
||||
<div class="revision-meta">
|
||||
<el-tag size="small" :type="getOpTagType(item.operationType)">{{ getOpLabel(item.operationType) }}</el-tag>
|
||||
<span class="revision-number">版本 #{{ item.revisionNumber }}</span>
|
||||
<span class="revision-operator">{{ item.operatorName }}</span>
|
||||
</div>
|
||||
<div class="revision-actions">
|
||||
<el-checkbox
|
||||
:model-value="compareIds.includes(item.id)"
|
||||
:disabled="!compareIds.includes(item.id) && compareIds.length >= 2"
|
||||
@change="(val) => toggleCompare(item.id, val)"
|
||||
/>
|
||||
<el-button link type="primary" size="small" @click="handleViewDetail(item)">查看详情</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.diffContent" class="revision-diff">
|
||||
<div class="diff-label">修改内容:</div>
|
||||
<pre class="diff-content">{{ item.diffContent }}</pre>
|
||||
</div>
|
||||
<div v-if="item.snapshotContent" class="revision-snapshot">
|
||||
<div class="diff-label">快照内容:</div>
|
||||
<pre class="diff-content">{{ truncateContent(item.snapshotContent) }}</pre>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<el-empty v-else description="暂无修改记录" />
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="detailVisible" title="修订详情" width="700px" destroy-on-close top="5vh">
|
||||
<div v-if="currentRevision" class="detail-content">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="版本号">{{ currentRevision.revisionNumber }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作类型">
|
||||
<el-tag size="small" :type="getOpTagType(currentRevision.operationType)">{{ getOpLabel(currentRevision.operationType) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人">{{ currentRevision.operatorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作时间">{{ currentRevision.createTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="病历ID">{{ currentRevision.emrId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊ID">{{ currentRevision.encounterId }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div v-if="currentRevision.diffContent" style="margin-top: 16px">
|
||||
<div class="diff-label">修改内容:</div>
|
||||
<pre class="detail-pre">{{ currentRevision.diffContent }}</pre>
|
||||
</div>
|
||||
<div v-if="currentRevision.snapshotContent" style="margin-top: 16px">
|
||||
<div class="diff-label">快照内容:</div>
|
||||
<pre class="detail-pre">{{ currentRevision.snapshotContent }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="compareVisible" title="修订对比" width="900px" destroy-on-close top="5vh">
|
||||
<div v-if="compareData" class="compare-content">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>版本 #{{ compareData.revision1?.revisionNumber }} — {{ compareData.revision1?.operatorName }}</span>
|
||||
</template>
|
||||
<pre class="compare-pre">{{ compareData.revision1?.snapshotContent || '无快照' }}</pre>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>版本 #{{ compareData.revision2?.revisionNumber }} — {{ compareData.revision2?.operatorName }}</span>
|
||||
</template>
|
||||
<pre class="compare-pre">{{ compareData.revision2?.snapshotContent || '无快照' }}</pre>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="compareVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getEmrRevisionList, getEmrRevisionDetail, compareEmrRevisions } from '@/api/emr'
|
||||
|
||||
const props = defineProps({
|
||||
emrId: { type: [Number, String], default: null }
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const revisions = ref([])
|
||||
const compareIds = ref([])
|
||||
const detailVisible = ref(false)
|
||||
const compareVisible = ref(false)
|
||||
const currentRevision = ref(null)
|
||||
const compareData = ref(null)
|
||||
|
||||
const OP_LABEL_MAP = { CREATE: '创建', EDIT: '编辑', APPROVE: '审批', SIGN: '签名' }
|
||||
const OP_TAG_MAP = { CREATE: 'success', EDIT: '', APPROVE: 'warning', SIGN: 'info' }
|
||||
const TIMELINE_MAP = { CREATE: 'primary', EDIT: 'success', APPROVE: 'warning', SIGN: 'info' }
|
||||
|
||||
function getOpLabel(type) { return OP_LABEL_MAP[type] || type }
|
||||
function getOpTagType(type) { return OP_TAG_MAP[type] || '' }
|
||||
function getTimelineType(type) { return TIMELINE_MAP[type] || '' }
|
||||
|
||||
function truncateContent(content) {
|
||||
if (!content) return ''
|
||||
return content.length > 200 ? content.substring(0, 200) + '...' : content
|
||||
}
|
||||
|
||||
function loadRevisions() {
|
||||
if (!props.emrId) return
|
||||
loading.value = true
|
||||
getEmrRevisionList(props.emrId).then(res => {
|
||||
revisions.value = res.data || []
|
||||
}).catch(() => {
|
||||
ElMessage.error('查询修改记录失败')
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleRefresh() {
|
||||
compareIds.value = []
|
||||
loadRevisions()
|
||||
}
|
||||
|
||||
function toggleCompare(id, checked) {
|
||||
if (checked) {
|
||||
compareIds.value.push(id)
|
||||
} else {
|
||||
compareIds.value = compareIds.value.filter(i => i !== id)
|
||||
}
|
||||
}
|
||||
|
||||
function handleViewDetail(item) {
|
||||
loading.value = true
|
||||
getEmrRevisionDetail(item.id).then(res => {
|
||||
currentRevision.value = res.data
|
||||
detailVisible.value = true
|
||||
}).catch(() => {
|
||||
ElMessage.error('查询详情失败')
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleCompare() {
|
||||
if (compareIds.value.length !== 2) {
|
||||
ElMessage.warning('请选择两个版本进行对比')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
compareEmrRevisions(compareIds.value[0], compareIds.value[1]).then(res => {
|
||||
compareData.value = res.data
|
||||
compareVisible.value = true
|
||||
}).catch(() => {
|
||||
ElMessage.error('对比查询失败')
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => props.emrId, () => { loadRevisions() })
|
||||
|
||||
onMounted(() => { loadRevisions() })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.emr-revision-track {
|
||||
padding: 12px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.revision-card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.revision-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.revision-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.revision-number {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.revision-operator {
|
||||
color: #606266;
|
||||
}
|
||||
.revision-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.diff-label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.diff-content {
|
||||
background: #f5f7fa;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
margin: 0;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.detail-content {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.detail-pre {
|
||||
background: #f5f7fa;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
margin: 0;
|
||||
}
|
||||
.compare-content {
|
||||
max-height: 65vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.compare-pre {
|
||||
background: #f5f7fa;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
margin: 0;
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<div class="emr-timeliness-monitor">
|
||||
<el-row :gutter="16" class="stats-row">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-value total">{{ stats.totalCount || 0 }}</div>
|
||||
<div class="stat-label">病历总数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-value completed">{{ stats.completedCount || 0 }}</div>
|
||||
<div class="stat-label">已完成</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-value pending">{{ stats.pendingCount || 0 }}</div>
|
||||
<div class="stat-label">待完成</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-value overdue">{{ stats.overdueCount || 0 }}</div>
|
||||
<div class="stat-label">已超时</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card shadow="never" class="filter-card">
|
||||
<el-form :inline="true" :model="queryParams" class="filter-form">
|
||||
<el-form-item label="病历类型">
|
||||
<el-select v-model="queryParams.emrType" clearable placeholder="全部" style="width: 160px">
|
||||
<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" clearable placeholder="全部" style="width: 140px">
|
||||
<el-option label="待完成" value="PENDING" />
|
||||
<el-option label="已完成" value="COMPLETED" />
|
||||
<el-option label="已超时" value="OVERDUE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="科室">
|
||||
<el-input v-model="queryParams.departmentName" clearable placeholder="输入科室名" style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="handleReset">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="table-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>时限提醒列表</span>
|
||||
<el-button type="primary" size="small" @click="handleCheckAll">
|
||||
执行检查
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="alertList" stripe border size="default">
|
||||
<el-table-column prop="emrType" label="病历类型" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="getEmrTypeTag(row.emrType)">
|
||||
{{ getEmrTypeLabel(row.emrType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="patientId" label="患者ID" width="100" align="center" />
|
||||
<el-table-column prop="doctorName" label="主治医生" width="110" align="center" />
|
||||
<el-table-column prop="departmentName" label="科室" width="130" align="center" />
|
||||
<el-table-column prop="requiredHours" label="时限(小时)" width="100" align="center" />
|
||||
<el-table-column prop="deadlineTime" label="截止时间" width="170" align="center" />
|
||||
<el-table-column prop="actualCompleteTime" label="完成时间" width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.actualCompleteTime || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="getStatusTag(row.status)">
|
||||
{{ getStatusLabel(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
background
|
||||
@size-change="loadAlerts"
|
||||
@current-change="loadAlerts"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { checkTimeliness, getTimelinessAlerts } from '@/api/emr'
|
||||
|
||||
const loading = ref(false)
|
||||
const stats = ref({})
|
||||
const alertList = ref([])
|
||||
const total = ref(0)
|
||||
|
||||
const queryParams = reactive({
|
||||
emrType: '',
|
||||
status: '',
|
||||
departmentName: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20
|
||||
})
|
||||
|
||||
const EMR_TYPE_MAP = {
|
||||
ADMISSION: '入院记录',
|
||||
FIRST_COURSE: '首次病程',
|
||||
DAILY_COURSE: '日常病程',
|
||||
DISCHARGE: '出院记录'
|
||||
}
|
||||
const EMR_TYPE_TAG = {
|
||||
ADMISSION: 'primary',
|
||||
FIRST_COURSE: 'success',
|
||||
DAILY_COURSE: 'warning',
|
||||
DISCHARGE: 'info'
|
||||
}
|
||||
const STATUS_MAP = {
|
||||
PENDING: '待完成',
|
||||
COMPLETED: '已完成',
|
||||
OVERDUE: '已超时'
|
||||
}
|
||||
const STATUS_TAG = {
|
||||
PENDING: 'warning',
|
||||
COMPLETED: 'success',
|
||||
OVERDUE: 'danger'
|
||||
}
|
||||
|
||||
function getEmrTypeLabel(type) { return EMR_TYPE_MAP[type] || type }
|
||||
function getEmrTypeTag(type) { return EMR_TYPE_TAG[type] || '' }
|
||||
function getStatusLabel(s) { return STATUS_MAP[s] || s }
|
||||
function getStatusTag(s) { return STATUS_TAG[s] || '' }
|
||||
|
||||
function loadStats() {
|
||||
checkTimeliness(null).then(res => {
|
||||
stats.value = res.data || {}
|
||||
})
|
||||
}
|
||||
|
||||
function loadAlerts() {
|
||||
loading.value = true
|
||||
const params = { ...queryParams }
|
||||
if (!params.emrType) delete params.emrType
|
||||
if (!params.status) delete params.status
|
||||
if (!params.departmentName) delete params.departmentName
|
||||
getTimelinessAlerts(params).then(res => {
|
||||
alertList.value = res.data?.rows || []
|
||||
total.value = res.data?.total || 0
|
||||
}).catch(() => {
|
||||
ElMessage.error('查询时限提醒失败')
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
queryParams.pageNum = 1
|
||||
loadAlerts()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
queryParams.emrType = ''
|
||||
queryParams.status = ''
|
||||
queryParams.departmentName = ''
|
||||
queryParams.pageNum = 1
|
||||
loadAlerts()
|
||||
}
|
||||
|
||||
function handleCheckAll() {
|
||||
loading.value = true
|
||||
checkTimeliness(null).then(res => {
|
||||
stats.value = res.data || {}
|
||||
ElMessage.success('时限检查完成')
|
||||
loadAlerts()
|
||||
}).catch(() => {
|
||||
ElMessage.error('执行检查失败')
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadStats()
|
||||
loadAlerts()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.emr-timeliness-monitor {
|
||||
padding: 16px;
|
||||
}
|
||||
.stats-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.stat-card {
|
||||
text-align: center;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.stat-value.total { color: #409eff; }
|
||||
.stat-value.completed { color: #67c23a; }
|
||||
.stat-value.pending { color: #e6a23c; }
|
||||
.stat-value.overdue { color: #f56c6c; }
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.filter-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.filter-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.table-card .el-pagination {
|
||||
margin-top: 16px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="emr-version-compare">
|
||||
<el-card v-loading="loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>历史版本管理</span>
|
||||
<div>
|
||||
<el-button
|
||||
v-if="compareIds.length === 2"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleCompare"
|
||||
>
|
||||
对比选中版本
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
size="small"
|
||||
@click="handleRefresh"
|
||||
>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-if="versions.length > 0"
|
||||
:data="versions"
|
||||
border
|
||||
size="small"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="40" />
|
||||
<el-table-column label="版本号" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">v{{ row.versionNumber }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="emrTitle" label="病历标题" show-overflow-tooltip />
|
||||
<el-table-column prop="operatorName" label="操作人" width="100" />
|
||||
<el-table-column prop="saveReason" label="保存原因" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" label="保存时间" width="170" />
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="handleViewDetail(row)">
|
||||
查看
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-empty v-else description="暂无历史版本" />
|
||||
</el-card>
|
||||
|
||||
<el-dialog
|
||||
v-model="detailVisible"
|
||||
title="版本详情"
|
||||
width="700px"
|
||||
destroy-on-close
|
||||
top="5vh"
|
||||
>
|
||||
<div v-if="currentVersion" class="detail-content">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="版本号">
|
||||
v{{ currentVersion.versionNumber }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人">
|
||||
{{ currentVersion.operatorName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="保存时间">
|
||||
{{ currentVersion.createTime }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="保存原因">
|
||||
{{ currentVersion.saveReason || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="病历ID">
|
||||
{{ currentVersion.emrId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊ID">
|
||||
{{ currentVersion.encounterId }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div v-if="currentVersion.contentSnapshot" style="margin-top: 16px">
|
||||
<div class="diff-label">内容快照:</div>
|
||||
<pre class="detail-pre">{{ currentVersion.contentSnapshot }}</pre>
|
||||
</div>
|
||||
<div v-if="currentVersion.contentDiff" style="margin-top: 16px">
|
||||
<div class="diff-label">与上一版本差异:</div>
|
||||
<pre class="detail-pre diff-text">{{ currentVersion.contentDiff }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="compareVisible"
|
||||
title="版本对比"
|
||||
width="900px"
|
||||
destroy-on-close
|
||||
top="5vh"
|
||||
>
|
||||
<div v-if="compareData" class="compare-content">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>版本 #{{ compareData.version1?.versionNumber }} — {{ compareData.version1?.operatorName }}</span>
|
||||
</template>
|
||||
<pre class="compare-pre">{{ compareData.version1?.contentSnapshot || '无快照' }}</pre>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>版本 #{{ compareData.version2?.versionNumber }} — {{ compareData.version2?.operatorName }}</span>
|
||||
</template>
|
||||
<pre class="compare-pre">{{ compareData.version2?.contentSnapshot || '无快照' }}</pre>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-if="compareData.diff" style="margin-top: 16px">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>差异详情</span>
|
||||
</template>
|
||||
<pre class="compare-pre diff-text">{{ compareData.diff }}</pre>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="compareVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getEmrVersionList, compareEmrVersions } from '@/api/emr'
|
||||
|
||||
const props = defineProps({
|
||||
emrId: { type: [Number, String], default: null }
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const versions = ref([])
|
||||
const compareIds = ref([])
|
||||
const detailVisible = ref(false)
|
||||
const compareVisible = ref(false)
|
||||
const currentVersion = ref(null)
|
||||
const compareData = ref(null)
|
||||
|
||||
function loadVersions() {
|
||||
if (!props.emrId) return
|
||||
loading.value = true
|
||||
getEmrVersionList(props.emrId).then(res => {
|
||||
versions.value = res.data || []
|
||||
}).catch(() => {
|
||||
ElMessage.error('查询版本记录失败')
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleRefresh() {
|
||||
compareIds.value = []
|
||||
loadVersions()
|
||||
}
|
||||
|
||||
function handleSelectionChange(selection) {
|
||||
compareIds.value = selection.map(item => item.id)
|
||||
}
|
||||
|
||||
function handleViewDetail(item) {
|
||||
currentVersion.value = item
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
function handleCompare() {
|
||||
if (compareIds.value.length !== 2) {
|
||||
ElMessage.warning('请选择两个版本进行对比')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
compareEmrVersions(compareIds.value[0], compareIds.value[1]).then(res => {
|
||||
compareData.value = res.data
|
||||
compareVisible.value = true
|
||||
}).catch(() => {
|
||||
ElMessage.error('对比查询失败')
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => props.emrId, () => { loadVersions() })
|
||||
|
||||
onMounted(() => { loadVersions() })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.emr-version-compare {
|
||||
padding: 12px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.diff-label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.detail-content {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.detail-pre {
|
||||
background: #f5f7fa;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
margin: 0;
|
||||
}
|
||||
.diff-text {
|
||||
color: #e6a23c;
|
||||
}
|
||||
.compare-content {
|
||||
max-height: 65vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.compare-pre {
|
||||
background: #f5f7fa;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
margin: 0;
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user