Merge remote-tracking branch 'origin/develop' into guanyu
This commit is contained in:
Binary file not shown.
@@ -1,113 +1,130 @@
|
||||
package com.healthlink.his.web.anesthesia.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.anesthesia.domain.*;
|
||||
import com.healthlink.his.anesthesia.service.*;
|
||||
import com.healthlink.his.anesthesia.domain.AnesthesiaAldreteScore;
|
||||
import com.healthlink.his.anesthesia.domain.AnesthesiaIntraopEvent;
|
||||
import com.healthlink.his.anesthesia.domain.AnesthesiaPreopVisit;
|
||||
import com.healthlink.his.anesthesia.service.IAnesthesiaAldreteScoreService;
|
||||
import com.healthlink.his.anesthesia.service.IAnesthesiaIntraopEventService;
|
||||
import com.healthlink.his.anesthesia.service.IAnesthesiaPreopVisitService;
|
||||
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.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/anesthesia-enhanced")
|
||||
@RequestMapping("/api/v1/anesthesia")
|
||||
@Tag(name = "麻醉扩展功能")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class AnesthesiaEnhancedController {
|
||||
|
||||
private final IAnesthesiaSpecimenService specimenService;
|
||||
private final IAnesthesiaPostopFollowupService followupService;
|
||||
private final IAnesthesiaQualityControlService qcService;
|
||||
private final IAnesthesiaPreopVisitService preopVisitService;
|
||||
private final IAnesthesiaIntraopEventService intraopEventService;
|
||||
private final IAnesthesiaAldreteScoreService aldreteScoreService;
|
||||
|
||||
// ==================== 标本管理 ====================
|
||||
@GetMapping("/specimen/page")
|
||||
public R<?> getSpecimenPage(
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "pathologyStatus", required = false) String status,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<AnesthesiaSpecimen> w = new LambdaQueryWrapper<>();
|
||||
w.like(StringUtils.hasText(patientName), AnesthesiaSpecimen::getPatientName, patientName)
|
||||
.eq(StringUtils.hasText(status), AnesthesiaSpecimen::getPathologyStatus, status)
|
||||
.orderByDesc(AnesthesiaSpecimen::getCreateTime);
|
||||
return R.ok(specimenService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@PostMapping("/specimen/add")
|
||||
@PostMapping("/preop-visit")
|
||||
@Operation(summary = "保存麻醉前评估(术前访视)")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addSpecimen(@RequestBody AnesthesiaSpecimen s) {
|
||||
s.setPathologyStatus("PENDING");
|
||||
s.setCreateTime(new Date());
|
||||
specimenService.save(s);
|
||||
return R.ok(s);
|
||||
public R<AnesthesiaPreopVisit> savePreopVisit(@RequestBody AnesthesiaPreopVisit visit) {
|
||||
if (visit.getId() != null) {
|
||||
preopVisitService.updateById(visit);
|
||||
} else {
|
||||
visit.setCreateTime(new Date());
|
||||
preopVisitService.save(visit);
|
||||
}
|
||||
return R.ok(visit);
|
||||
}
|
||||
|
||||
@PostMapping("/specimen/report")
|
||||
@GetMapping("/preop-visit/record/{recordId}")
|
||||
@Operation(summary = "查询麻醉前评估列表")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
|
||||
public R<List<AnesthesiaPreopVisit>> getPreopVisits(@PathVariable Long recordId) {
|
||||
return R.ok(preopVisitService.selectByRecordId(recordId));
|
||||
}
|
||||
|
||||
@GetMapping("/preop-visit/encounter/{encounterId}")
|
||||
@Operation(summary = "按就诊查询麻醉前评估")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
|
||||
public R<List<AnesthesiaPreopVisit>> getPreopVisitsByEncounter(@PathVariable Long encounterId) {
|
||||
return R.ok(preopVisitService.selectByEncounterId(encounterId));
|
||||
}
|
||||
|
||||
@PostMapping("/intraop-event")
|
||||
@Operation(summary = "记录术中事件(插管/拔管/体位)")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> reportSpecimen(@RequestBody Map<String, Object> params) {
|
||||
Long id = Long.valueOf(params.get("id").toString());
|
||||
AnesthesiaSpecimen s = specimenService.getById(id);
|
||||
if (s == null) return R.fail("标本不存在");
|
||||
s.setPathologyStatus("REPORTED");
|
||||
s.setPathologyResult((String) params.get("pathologyResult"));
|
||||
s.setReportTime(new Date());
|
||||
s.setUpdateTime(new Date());
|
||||
specimenService.updateById(s);
|
||||
return R.ok();
|
||||
public R<AnesthesiaIntraopEvent> saveIntraopEvent(@RequestBody AnesthesiaIntraopEvent event) {
|
||||
if (event.getId() != null) {
|
||||
intraopEventService.updateById(event);
|
||||
} else {
|
||||
event.setCreateTime(new Date());
|
||||
intraopEventService.save(event);
|
||||
}
|
||||
return R.ok(event);
|
||||
}
|
||||
|
||||
// ==================== 术后随访 ====================
|
||||
@GetMapping("/followup/page")
|
||||
public R<?> getFollowupPage(
|
||||
@RequestParam(value = "followupType", required = false) String type,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<AnesthesiaPostopFollowup> w = new LambdaQueryWrapper<>();
|
||||
w.eq(StringUtils.hasText(type), AnesthesiaPostopFollowup::getFollowupType, type)
|
||||
.orderByDesc(AnesthesiaPostopFollowup::getFollowupTime);
|
||||
return R.ok(followupService.page(new Page<>(pageNo, pageSize), w));
|
||||
@GetMapping("/intraop-event/{recordId}")
|
||||
@Operation(summary = "查询术中事件列表")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
|
||||
public R<List<AnesthesiaIntraopEvent>> getIntraopEvents(@PathVariable Long recordId) {
|
||||
return R.ok(intraopEventService.selectByRecordId(recordId));
|
||||
}
|
||||
|
||||
@PostMapping("/followup/add")
|
||||
@PostMapping("/aldrete-score")
|
||||
@Operation(summary = "保存麻醉复苏评分(PACU Aldrete)")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addFollowup(@RequestBody AnesthesiaPostopFollowup f) {
|
||||
f.setStatus(0);
|
||||
f.setCreateTime(new Date());
|
||||
followupService.save(f);
|
||||
return R.ok(f);
|
||||
public R<AnesthesiaAldreteScore> saveAldreteScore(@RequestBody AnesthesiaAldreteScore score) {
|
||||
int total = (score.getActivityScore() != null ? score.getActivityScore() : 0)
|
||||
+ (score.getRespirationScore() != null ? score.getRespirationScore() : 0)
|
||||
+ (score.getCirculationScore() != null ? score.getCirculationScore() : 0)
|
||||
+ (score.getConsciousnessScore() != null ? score.getConsciousnessScore() : 0)
|
||||
+ (score.getSpo2Score() != null ? score.getSpo2Score() : 0);
|
||||
score.setTotalScore(total);
|
||||
if (total >= 9) {
|
||||
score.setRiskLevel("NORMAL");
|
||||
} else if (total >= 7) {
|
||||
score.setRiskLevel("WARNING");
|
||||
} else {
|
||||
score.setRiskLevel("CRITICAL");
|
||||
}
|
||||
if (score.getId() != null) {
|
||||
aldreteScoreService.updateById(score);
|
||||
} else {
|
||||
score.setCreateTime(new Date());
|
||||
aldreteScoreService.save(score);
|
||||
}
|
||||
return R.ok(score);
|
||||
}
|
||||
|
||||
// ==================== 麻醉质控 ====================
|
||||
@GetMapping("/qc/page")
|
||||
public R<?> getQcPage(
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<AnesthesiaQualityControl> w = new LambdaQueryWrapper<>();
|
||||
w.orderByDesc(AnesthesiaQualityControl::getCreateTime);
|
||||
return R.ok(qcService.page(new Page<>(pageNo, pageSize), w));
|
||||
@GetMapping("/aldrete-score/{recordId}")
|
||||
@Operation(summary = "查询复苏评分列表")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
|
||||
public R<List<AnesthesiaAldreteScore>> getAldreteScores(@PathVariable Long recordId) {
|
||||
return R.ok(aldreteScoreService.selectByRecordId(recordId));
|
||||
}
|
||||
|
||||
@PostMapping("/qc/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addQc(@RequestBody AnesthesiaQualityControl qc) {
|
||||
qc.setStatus(0);
|
||||
qc.setCreateTime(new Date());
|
||||
qcService.save(qc);
|
||||
return R.ok(qc);
|
||||
}
|
||||
|
||||
@GetMapping("/qc/stats")
|
||||
public R<?> getQcStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("total", qcService.count());
|
||||
LambdaQueryWrapper<AnesthesiaQualityControl> w = new LambdaQueryWrapper<>();
|
||||
w.isNotNull(AnesthesiaQualityControl::getComplications);
|
||||
w.ne(AnesthesiaQualityControl::getComplications, "");
|
||||
stats.put("withComplications", qcService.count(w));
|
||||
return R.ok(stats);
|
||||
@GetMapping("/aldrete-score/summary/{recordId}")
|
||||
@Operation(summary = "复苏评分趋势汇总")
|
||||
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
|
||||
public R<List<Map<String, Object>>> getAldreteTrend(@PathVariable Long recordId) {
|
||||
List<AnesthesiaAldreteScore> scores = aldreteScoreService.selectByRecordId(recordId);
|
||||
return R.ok(scores.stream().map(s -> {
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("assessTime", s.getAssessTime());
|
||||
m.put("totalScore", s.getTotalScore());
|
||||
m.put("riskLevel", s.getRiskLevel());
|
||||
return m;
|
||||
}).toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.check.domain.ExamAppointment;
|
||||
import com.healthlink.his.check.service.IExamAppointmentService;
|
||||
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.*;
|
||||
import java.util.*;
|
||||
@@ -12,6 +13,7 @@ import java.util.*;
|
||||
public class ExamAppointmentController {
|
||||
private final IExamAppointmentService appointmentService;
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:list')")
|
||||
public R<?> getPage(@RequestParam(value="status",required=false) String status,
|
||||
@RequestParam(value="patientName",required=false) String patientName,
|
||||
@RequestParam(value="appointDate",required=false) String appointDate,
|
||||
@@ -23,7 +25,9 @@ public class ExamAppointmentController {
|
||||
.orderByAsc(ExamAppointment::getQueueNumber);
|
||||
return R.ok(appointmentService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
@PostMapping("/appoint") @Transactional(rollbackFor=Exception.class)
|
||||
@PostMapping("/appoint")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> appoint(@RequestBody ExamAppointment a) {
|
||||
a.setStatus("APPOINTED"); a.setCreateTime(new Date());
|
||||
LambdaQueryWrapper<ExamAppointment> w = new LambdaQueryWrapper<>();
|
||||
@@ -32,27 +36,36 @@ public class ExamAppointmentController {
|
||||
a.setQueueNumber(last == null ? 1 : last.getQueueNumber() + 1);
|
||||
appointmentService.save(a); return R.ok(a);
|
||||
}
|
||||
@PutMapping("/checkin/{id}") @Transactional(rollbackFor=Exception.class)
|
||||
@PutMapping("/checkin/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> checkin(@PathVariable Long id) {
|
||||
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在");
|
||||
a.setStatus("CHECKED_IN"); appointmentService.updateById(a); return R.ok();
|
||||
}
|
||||
@PutMapping("/start/{id}") @Transactional(rollbackFor=Exception.class)
|
||||
@PutMapping("/start/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> startExam(@PathVariable Long id) {
|
||||
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在");
|
||||
a.setStatus("EXAMINING"); appointmentService.updateById(a); return R.ok();
|
||||
}
|
||||
@PutMapping("/complete/{id}") @Transactional(rollbackFor=Exception.class)
|
||||
@PutMapping("/complete/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> complete(@PathVariable Long id) {
|
||||
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在");
|
||||
a.setStatus("COMPLETED"); appointmentService.updateById(a); return R.ok();
|
||||
}
|
||||
@PutMapping("/cancel/{id}") @Transactional(rollbackFor=Exception.class)
|
||||
@PutMapping("/cancel/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> cancel(@PathVariable Long id) {
|
||||
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在");
|
||||
a.setStatus("CANCELLED"); appointmentService.updateById(a); return R.ok();
|
||||
}
|
||||
@GetMapping("/queue")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:list')")
|
||||
public R<?> getQueue(@RequestParam("appointDate") String date) {
|
||||
LambdaQueryWrapper<ExamAppointment> w = new LambdaQueryWrapper<>();
|
||||
w.eq(ExamAppointment::getAppointDate, date).orderByAsc(ExamAppointment::getQueueNumber);
|
||||
|
||||
@@ -74,6 +74,7 @@ public class RadiologyEnhancedController {
|
||||
}
|
||||
|
||||
@PostMapping("/statistics/add")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addStatistics(@RequestBody RadiologyStatistics s) {
|
||||
s.setCreateTime(new Date());
|
||||
@@ -82,6 +83,7 @@ public class RadiologyEnhancedController {
|
||||
}
|
||||
|
||||
@GetMapping("/statistics/summary")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:list')")
|
||||
public R<?> getStatisticsSummary() {
|
||||
Map<String, Object> summary = new HashMap<>();
|
||||
summary.put("totalRecords", statisticsService.count());
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.*;
|
||||
@@ -51,6 +52,7 @@ public class ProgressNoteController {
|
||||
* 分页查询病程记录列表
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:list')")
|
||||
public R<?> getPage(
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "noteType", required = false) Integer noteType,
|
||||
@@ -73,6 +75,7 @@ public class ProgressNoteController {
|
||||
* 查询病程记录详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:list')")
|
||||
public R<?> getDetail(@RequestParam Long id) {
|
||||
ProgressNote note = progressNoteService.getById(id);
|
||||
if (note == null) return R.fail("病程记录不存在");
|
||||
@@ -83,6 +86,7 @@ public class ProgressNoteController {
|
||||
* 新增病程记录
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:add')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> add(@RequestBody ProgressNote note) {
|
||||
note.setSignStatus(0);
|
||||
@@ -104,6 +108,7 @@ public class ProgressNoteController {
|
||||
* 修改病程记录(仅未签名可修改)
|
||||
*/
|
||||
@PutMapping("/update")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> update(@RequestBody ProgressNote note) {
|
||||
ProgressNote existing = progressNoteService.getById(note.getId());
|
||||
@@ -119,6 +124,7 @@ public class ProgressNoteController {
|
||||
* 删除病程记录(仅未签名可删除)
|
||||
*/
|
||||
@DeleteMapping("/delete")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:remove')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> delete(@RequestParam Long id) {
|
||||
ProgressNote note = progressNoteService.getById(id);
|
||||
@@ -132,6 +138,7 @@ public class ProgressNoteController {
|
||||
* 签名病程记录
|
||||
*/
|
||||
@PostMapping("/sign")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> sign(@RequestBody Map<String, Object> params) {
|
||||
Long id = Long.valueOf(params.get("id").toString());
|
||||
@@ -151,6 +158,7 @@ public class ProgressNoteController {
|
||||
* 审核病程记录(上级医师)
|
||||
*/
|
||||
@PostMapping("/review")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> review(@RequestBody Map<String, Object> params) {
|
||||
Long id = Long.valueOf(params.get("id").toString());
|
||||
@@ -169,6 +177,7 @@ public class ProgressNoteController {
|
||||
* 获取时限监控面板
|
||||
*/
|
||||
@GetMapping("/monitor")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:list')")
|
||||
public R<?> getMonitor(@RequestParam(required = false) Long encounterId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
Date now = new Date();
|
||||
@@ -216,6 +225,7 @@ public class ProgressNoteController {
|
||||
* 获取提醒列表
|
||||
*/
|
||||
@GetMapping("/reminders")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:list')")
|
||||
public R<?> getReminders(
|
||||
@RequestParam(value = "status", required = false) Integer status,
|
||||
@RequestParam(value = "encounterId", required = false) Long encounterId) {
|
||||
@@ -230,6 +240,7 @@ public class ProgressNoteController {
|
||||
* 获取病程记录统计
|
||||
*/
|
||||
@GetMapping("/stats")
|
||||
@PreAuthorize("hasAuthority('document:progressnote:list')")
|
||||
public R<?> getStats(@RequestParam Long encounterId) {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
LambdaQueryWrapper<ProgressNote> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.healthlink.his.web.emr.appservice;
|
||||
|
||||
import com.healthlink.his.emr.domain.EmrQualityScore;
|
||||
import com.healthlink.his.emr.domain.EmrStructuredData;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IEmrDataWarehouseAppService {
|
||||
|
||||
List<EmrStructuredData> extractStructuredData(Long emrId);
|
||||
|
||||
List<EmrStructuredData> getStructuredData(Long encounterId);
|
||||
|
||||
EmrQualityScore calculateQualityScore(Long encounterId);
|
||||
|
||||
List<EmrQualityScore> getQualityScores(Long encounterId);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.healthlink.his.web.emr.appservice.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.healthlink.his.document.domain.Emr;
|
||||
import com.healthlink.his.document.service.IEmrService;
|
||||
import com.healthlink.his.emr.domain.EmrQualityScore;
|
||||
import com.healthlink.his.emr.domain.EmrStructuredData;
|
||||
import com.healthlink.his.emr.service.IEmrQualityScoreService;
|
||||
import com.healthlink.his.emr.service.IEmrStructuredDataService;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrDataWarehouseAppService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class EmrDataWarehouseAppServiceImpl implements IEmrDataWarehouseAppService {
|
||||
|
||||
@Resource
|
||||
private IEmrStructuredDataService emrStructuredDataService;
|
||||
|
||||
@Resource
|
||||
private IEmrQualityScoreService emrQualityScoreService;
|
||||
|
||||
@Resource
|
||||
private IEmrService emrService;
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private static final Map<String, String[]> STRUCTURED_KEYS = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
STRUCTURED_KEYS.put("vital_signs", new String[]{"temperature", "pulse", "respiration", "blood_pressure_systolic", "blood_pressure_diastolic", "spo2"});
|
||||
STRUCTURED_KEYS.put("lab_results", new String[]{"wbc", "rbc", "hemoglobin", "platelet", "ALT", "AST", "creatinine", "BUN", "glucose"});
|
||||
STRUCTURED_KEYS.put("diagnosis", new String[]{"primary_diagnosis", "secondary_diagnosis"});
|
||||
STRUCTURED_KEYS.put("medication", new String[]{"medication_name", "dosage", "frequency", "route"});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<EmrStructuredData> extractStructuredData(Long emrId) {
|
||||
Emr emr = emrService.getById(emrId);
|
||||
if (emr == null) {
|
||||
throw new IllegalArgumentException("病历不存在: " + emrId);
|
||||
}
|
||||
|
||||
List<EmrStructuredData> result = new ArrayList<>();
|
||||
Map<String, Object> contentMap = parseContent(emr.getContextJson());
|
||||
|
||||
for (Map.Entry<String, String[]> entry : STRUCTURED_KEYS.entrySet()) {
|
||||
String dataType = entry.getKey();
|
||||
for (String key : entry.getValue()) {
|
||||
Object value = contentMap.get(key);
|
||||
if (value != null && !value.toString().trim().isEmpty()) {
|
||||
String dataValue = value.toString().trim();
|
||||
String dataUnit = inferUnit(key);
|
||||
EmrStructuredData data = new EmrStructuredData()
|
||||
.setEmrId(emrId)
|
||||
.setEncounterId(emr.getEncounterId())
|
||||
.setPatientId(emr.getPatientId())
|
||||
.setDataType(dataType)
|
||||
.setDataKey(key)
|
||||
.setDataValue(dataValue)
|
||||
.setDataUnit(dataUnit)
|
||||
.setRecordTime(new Date());
|
||||
emrStructuredDataService.save(data);
|
||||
result.add(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EmrStructuredData> getStructuredData(Long encounterId) {
|
||||
return emrStructuredDataService.selectByEncounterId(encounterId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public EmrQualityScore calculateQualityScore(Long encounterId) {
|
||||
List<EmrStructuredData> dataList = emrStructuredDataService.selectByEncounterId(encounterId);
|
||||
|
||||
BigDecimal completeness = calculateCompleteness(dataList);
|
||||
BigDecimal timeliness = calculateTimeliness(dataList);
|
||||
BigDecimal accuracy = calculateAccuracy(dataList);
|
||||
BigDecimal total = completeness.add(timeliness).add(accuracy).divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP);
|
||||
|
||||
EmrQualityScore score = new EmrQualityScore()
|
||||
.setEncounterId(encounterId)
|
||||
.setEmrType("STANDARD")
|
||||
.setTotalScore(total)
|
||||
.setCompletenessScore(completeness)
|
||||
.setTimelinessScore(timeliness)
|
||||
.setAccuracyScore(accuracy)
|
||||
.setCheckTime(new Date());
|
||||
emrQualityScoreService.save(score);
|
||||
return score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EmrQualityScore> getQualityScores(Long encounterId) {
|
||||
return emrQualityScoreService.selectByEncounterId(encounterId);
|
||||
}
|
||||
|
||||
private BigDecimal calculateCompleteness(List<EmrStructuredData> dataList) {
|
||||
if (dataList.isEmpty()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
Set<String> expectedKeys = new HashSet<>();
|
||||
for (String[] keys : STRUCTURED_KEYS.values()) {
|
||||
expectedKeys.addAll(Arrays.asList(keys));
|
||||
}
|
||||
Set<String> actualKeys = new HashSet<>();
|
||||
for (EmrStructuredData data : dataList) {
|
||||
actualKeys.add(data.getDataKey());
|
||||
}
|
||||
actualKeys.retainAll(expectedKeys);
|
||||
if (expectedKeys.isEmpty()) return BigDecimal.ZERO;
|
||||
return BigDecimal.valueOf(actualKeys.size())
|
||||
.divide(BigDecimal.valueOf(expectedKeys.size()), 2, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100));
|
||||
}
|
||||
|
||||
private BigDecimal calculateTimeliness(List<EmrStructuredData> dataList) {
|
||||
if (dataList.isEmpty()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
int timely = 0;
|
||||
for (EmrStructuredData data : dataList) {
|
||||
if (data.getRecordTime() != null) {
|
||||
timely++;
|
||||
}
|
||||
}
|
||||
return BigDecimal.valueOf(timely)
|
||||
.divide(BigDecimal.valueOf(dataList.size()), 2, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100));
|
||||
}
|
||||
|
||||
private BigDecimal calculateAccuracy(List<EmrStructuredData> dataList) {
|
||||
if (dataList.isEmpty()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
int accurate = 0;
|
||||
for (EmrStructuredData data : dataList) {
|
||||
if (data.getDataValue() != null && !data.getDataValue().trim().isEmpty()) {
|
||||
accurate++;
|
||||
}
|
||||
}
|
||||
return BigDecimal.valueOf(accurate)
|
||||
.divide(BigDecimal.valueOf(dataList.size()), 2, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100));
|
||||
}
|
||||
|
||||
private String inferUnit(String key) {
|
||||
return switch (key) {
|
||||
case "temperature" -> "°C";
|
||||
case "pulse", "respiration" -> "次/分";
|
||||
case "blood_pressure_systolic", "blood_pressure_diastolic" -> "mmHg";
|
||||
case "spo2" -> "%";
|
||||
case "wbc" -> "10^9/L";
|
||||
case "rbc" -> "10^12/L";
|
||||
case "hemoglobin" -> "g/L";
|
||||
case "platelet" -> "10^9/L";
|
||||
case "ALT", "AST" -> "U/L";
|
||||
case "creatinine", "BUN" -> "mmol/L";
|
||||
case "glucose" -> "mmol/L";
|
||||
case "dosage" -> "mg";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, Object> parseContent(String contextJson) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
if (contextJson == null || contextJson.isEmpty()) {
|
||||
return map;
|
||||
}
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> parsed = objectMapper.readValue(contextJson, Map.class);
|
||||
map.putAll(parsed);
|
||||
} catch (Exception e) {
|
||||
map.put("raw", contextJson);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.healthlink.his.web.emr.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.emr.domain.EmrQualityScore;
|
||||
import com.healthlink.his.emr.domain.EmrStructuredData;
|
||||
import com.healthlink.his.web.emr.appservice.IEmrDataWarehouseAppService;
|
||||
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.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/emr/warehouse")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@Tag(name = "电子病历数据仓库")
|
||||
public class EmrDataWarehouseController {
|
||||
|
||||
private final IEmrDataWarehouseAppService emrDataWarehouseAppService;
|
||||
|
||||
@PostMapping("/extract")
|
||||
@PreAuthorize("@ss.hasPermi('infection:emr:edit')")
|
||||
@Operation(summary = "提取结构化数据")
|
||||
public R<List<EmrStructuredData>> extractStructuredData(@RequestParam("emrId") Long emrId) {
|
||||
return R.ok(emrDataWarehouseAppService.extractStructuredData(emrId));
|
||||
}
|
||||
|
||||
@GetMapping("/data/{encounterId}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:emr:list')")
|
||||
@Operation(summary = "查询结构化数据")
|
||||
public R<List<EmrStructuredData>> getStructuredData(@PathVariable Long encounterId) {
|
||||
return R.ok(emrDataWarehouseAppService.getStructuredData(encounterId));
|
||||
}
|
||||
|
||||
@PostMapping("/quality-score")
|
||||
@PreAuthorize("@ss.hasPermi('infection:emr:edit')")
|
||||
@Operation(summary = "计算质控评分")
|
||||
public R<EmrQualityScore> calculateQualityScore(@RequestParam("encounterId") Long encounterId) {
|
||||
return R.ok(emrDataWarehouseAppService.calculateQualityScore(encounterId));
|
||||
}
|
||||
|
||||
@GetMapping("/quality-scores")
|
||||
@PreAuthorize("@ss.hasPermi('infection:emr:list')")
|
||||
@Operation(summary = "查询质控评分列表")
|
||||
public R<List<EmrQualityScore>> getQualityScores(@RequestParam("encounterId") Long encounterId) {
|
||||
return R.ok(emrDataWarehouseAppService.getQualityScores(encounterId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.healthlink.his.web.esbmanage.appservice;
|
||||
|
||||
import com.healthlink.his.esb.domain.RegionalShareRecord;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IRegionalShareAppService {
|
||||
RegionalShareRecord sharePatientData(Long encounterId, String targetSystem);
|
||||
List<RegionalShareRecord> getShareRecords(Long encounterId);
|
||||
Map<String, Object> getShareStats();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.healthlink.his.web.esbmanage.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.esb.domain.EsbServiceRegistry;
|
||||
import com.healthlink.his.esb.domain.RegionalShareRecord;
|
||||
import com.healthlink.his.esb.service.IEsbServiceRegistryService;
|
||||
import com.healthlink.his.esb.service.IRegionalShareRecordService;
|
||||
import com.healthlink.his.web.esbmanage.appservice.IRegionalShareAppService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RegionalShareAppServiceImpl implements IRegionalShareAppService {
|
||||
|
||||
private final IRegionalShareRecordService shareRecordService;
|
||||
private final IEsbServiceRegistryService registryService;
|
||||
|
||||
@Override
|
||||
public RegionalShareRecord sharePatientData(Long encounterId, String targetSystem) {
|
||||
LambdaQueryWrapper<EsbServiceRegistry> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(EsbServiceRegistry::getServiceName, targetSystem)
|
||||
.eq(EsbServiceRegistry::getServiceStatus, "启用");
|
||||
boolean registered = registryService.count(wrapper) > 0;
|
||||
if (!registered) {
|
||||
throw new IllegalArgumentException("目标系统 '" + targetSystem + "' 未注册或已停用");
|
||||
}
|
||||
|
||||
RegionalShareRecord record = new RegionalShareRecord();
|
||||
record.setEncounterId(encounterId);
|
||||
record.setPatientId(0L);
|
||||
record.setShareType("PATIENT_DATA");
|
||||
record.setTargetSystem(targetSystem);
|
||||
record.setShareStatus("PENDING");
|
||||
record.setRetryCount(0);
|
||||
record.setRequestData("{\"encounterId\":" + encounterId + ",\"targetSystem\":\"" + targetSystem + "\"}");
|
||||
shareRecordService.save(record);
|
||||
|
||||
log.info("区域共享请求已提交: encounterId={}, targetSystem={}", encounterId, targetSystem);
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RegionalShareRecord> getShareRecords(Long encounterId) {
|
||||
LambdaQueryWrapper<RegionalShareRecord> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(RegionalShareRecord::getEncounterId, encounterId)
|
||||
.orderByDesc(RegionalShareRecord::getCreateTime);
|
||||
return shareRecordService.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getShareStats() {
|
||||
Map<String, Object> stats = new LinkedHashMap<>();
|
||||
long total = shareRecordService.count();
|
||||
stats.put("total", total);
|
||||
|
||||
long pending = shareRecordService.count(
|
||||
new LambdaQueryWrapper<RegionalShareRecord>().eq(RegionalShareRecord::getShareStatus, "PENDING"));
|
||||
long success = shareRecordService.count(
|
||||
new LambdaQueryWrapper<RegionalShareRecord>().eq(RegionalShareRecord::getShareStatus, "SUCCESS"));
|
||||
long failed = shareRecordService.count(
|
||||
new LambdaQueryWrapper<RegionalShareRecord>().eq(RegionalShareRecord::getShareStatus, "FAILED"));
|
||||
|
||||
stats.put("pending", pending);
|
||||
stats.put("success", success);
|
||||
stats.put("failed", failed);
|
||||
stats.put("successRate", total > 0 ? Math.round(success * 100.0 / total) : 100);
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.healthlink.his.web.esbmanage.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.esb.domain.RegionalShareRecord;
|
||||
import com.healthlink.his.web.esbmanage.appservice.IRegionalShareAppService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 区域医疗信息共享 Controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/regional/share")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RegionalShareController {
|
||||
|
||||
private final IRegionalShareAppService regionalShareAppService;
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('infection:regional:edit')")
|
||||
public R<?> sharePatientData(@RequestParam Long encounterId, @RequestParam String targetSystem) {
|
||||
try {
|
||||
RegionalShareRecord record = regionalShareAppService.sharePatientData(encounterId, targetSystem);
|
||||
return R.ok(record);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/records/{encounterId}")
|
||||
@PreAuthorize("hasAuthority('infection:regional:list')")
|
||||
public R<?> getShareRecords(@PathVariable Long encounterId) {
|
||||
List<RegionalShareRecord> records = regionalShareAppService.getShareRecords(encounterId);
|
||||
return R.ok(records);
|
||||
}
|
||||
|
||||
@GetMapping("/stats")
|
||||
@PreAuthorize("hasAuthority('infection:regional:list')")
|
||||
public R<?> getShareStats() {
|
||||
Map<String, Object> stats = regionalShareAppService.getShareStats();
|
||||
return R.ok(stats);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.healthlink.his.web.infection.appservice;
|
||||
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ICdssAppService {
|
||||
Map<String, Object> evaluateRules(Long encounterId);
|
||||
List<CdssAlert> getAlerts(Long encounterId);
|
||||
boolean acknowledgeAlert(Long alertId);
|
||||
List<CdssRule> getRules(Map<String, Object> params);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthlink.his.web.infection.appservice;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IInfectionDetailAppService {
|
||||
|
||||
Map<String, Object> getInfectionRateByDept(Long deptId);
|
||||
|
||||
List<Map<String, Object>> getInfectionTrend(String startDate, String endDate);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.healthlink.his.web.infection.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
import com.healthlink.his.infection.service.ICdssAlertService;
|
||||
import com.healthlink.his.infection.service.ICdssRuleService;
|
||||
import com.healthlink.his.web.infection.appservice.ICdssAppService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class CdssAppServiceImpl implements ICdssAppService {
|
||||
|
||||
private final ICdssRuleService cdssRuleService;
|
||||
private final ICdssAlertService cdssAlertService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> evaluateRules(Long encounterId) {
|
||||
log.info("CDSS规则评估开始, encounterId={}", encounterId);
|
||||
|
||||
List<CdssRule> enabledRules = cdssRuleService.list(
|
||||
new LambdaQueryWrapper<CdssRule>()
|
||||
.eq(CdssRule::getEnabled, true)
|
||||
);
|
||||
|
||||
List<CdssAlert> newAlerts = new ArrayList<>();
|
||||
for (CdssRule rule : enabledRules) {
|
||||
CdssAlert alert = new CdssAlert();
|
||||
alert.setEncounterId(encounterId);
|
||||
alert.setPatientId(0L);
|
||||
alert.setRuleId(rule.getId());
|
||||
alert.setAlertType(rule.getRuleType());
|
||||
alert.setAlertMessage("[" + rule.getRuleName() + "] " + rule.getSuggestion());
|
||||
alert.setSeverity(rule.getSeverity());
|
||||
alert.setAcknowledged(false);
|
||||
cdssAlertService.save(alert);
|
||||
newAlerts.add(alert);
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalRules", enabledRules.size());
|
||||
result.put("newAlertCount", newAlerts.size());
|
||||
result.put("newAlerts", newAlerts);
|
||||
result.put("evaluateTime", new Date());
|
||||
|
||||
log.info("CDSS规则评估完成: {}条规则, 生成{}条告警", enabledRules.size(), newAlerts.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CdssAlert> getAlerts(Long encounterId) {
|
||||
LambdaQueryWrapper<CdssAlert> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CdssAlert::getEncounterId, encounterId);
|
||||
wrapper.orderByDesc(CdssAlert::getCreateTime);
|
||||
return cdssAlertService.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean acknowledgeAlert(Long alertId) {
|
||||
CdssAlert alert = cdssAlertService.getById(alertId);
|
||||
if (alert == null) {
|
||||
return false;
|
||||
}
|
||||
alert.setAcknowledged(true);
|
||||
alert.setAcknowledgedTime(new Date());
|
||||
return cdssAlertService.updateById(alert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CdssRule> getRules(Map<String, Object> params) {
|
||||
LambdaQueryWrapper<CdssRule> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
String ruleType = getStr(params, "ruleType");
|
||||
if (ruleType != null && !ruleType.isEmpty()) {
|
||||
wrapper.eq(CdssRule::getRuleType, ruleType);
|
||||
}
|
||||
String severity = getStr(params, "severity");
|
||||
if (severity != null && !severity.isEmpty()) {
|
||||
wrapper.eq(CdssRule::getSeverity, severity);
|
||||
}
|
||||
String keyword = getStr(params, "keyword");
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
wrapper.and(w -> w.like(CdssRule::getRuleName, keyword)
|
||||
.or().like(CdssRule::getRuleCode, keyword));
|
||||
}
|
||||
wrapper.orderByDesc(CdssRule::getCreateTime);
|
||||
return cdssRuleService.list(wrapper);
|
||||
}
|
||||
|
||||
private String getStr(Map<String, Object> params, String key) {
|
||||
Object v = params.get(key);
|
||||
return v != null ? v.toString() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.healthlink.his.web.infection.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.infection.domain.HirInfectionCase;
|
||||
import com.healthlink.his.infection.service.IHirInfectionCaseService;
|
||||
import com.healthlink.his.web.infection.appservice.IInfectionDetailAppService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class InfectionDetailAppServiceImpl implements IInfectionDetailAppService {
|
||||
|
||||
private final IHirInfectionCaseService infectionCaseService;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getInfectionRateByDept(Long deptId) {
|
||||
LambdaQueryWrapper<HirInfectionCase> wrapper = new LambdaQueryWrapper<>();
|
||||
if (deptId != null) {
|
||||
wrapper.eq(HirInfectionCase::getEncounterId, deptId);
|
||||
}
|
||||
List<HirInfectionCase> cases = infectionCaseService.list(wrapper);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalCases", cases.size());
|
||||
|
||||
long confirmed = cases.stream()
|
||||
.filter(c -> "CONFIRMED".equals(c.getStatus()))
|
||||
.count();
|
||||
result.put("confirmedCases", confirmed);
|
||||
|
||||
long reported = cases.stream()
|
||||
.filter(c -> "REPORTED".equals(c.getStatus()))
|
||||
.count();
|
||||
result.put("reportedCases", reported);
|
||||
|
||||
result.put("infectionRate", cases.isEmpty() ? 0 :
|
||||
Math.round(confirmed * 1000.0 / cases.size()) / 10.0);
|
||||
|
||||
Map<String, Long> byType = cases.stream()
|
||||
.filter(c -> c.getInfectionType() != null)
|
||||
.collect(Collectors.groupingBy(HirInfectionCase::getInfectionType, Collectors.counting()));
|
||||
result.put("byType", byType);
|
||||
|
||||
Map<String, Long> bySite = cases.stream()
|
||||
.filter(c -> c.getInfectionSite() != null)
|
||||
.collect(Collectors.groupingBy(HirInfectionCase::getInfectionSite, Collectors.counting()));
|
||||
result.put("bySite", bySite);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getInfectionTrend(String startDate, String endDate) {
|
||||
LambdaQueryWrapper<HirInfectionCase> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.ge(StringUtils.hasText(startDate), HirInfectionCase::getReportTime, startDate)
|
||||
.le(StringUtils.hasText(endDate), HirInfectionCase::getReportTime, endDate)
|
||||
.orderByAsc(HirInfectionCase::getReportTime);
|
||||
List<HirInfectionCase> cases = infectionCaseService.list(wrapper);
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Map<String, Map<String, Long>> dailyTrend = cases.stream()
|
||||
.filter(c -> c.getReportTime() != null)
|
||||
.collect(Collectors.groupingBy(
|
||||
c -> sdf.format(c.getReportTime()),
|
||||
LinkedHashMap::new,
|
||||
Collectors.groupingBy(
|
||||
c -> c.getStatus() != null ? c.getStatus() : "UNKNOWN",
|
||||
Collectors.counting()
|
||||
)
|
||||
));
|
||||
|
||||
List<Map<String, Object>> trend = new ArrayList<>();
|
||||
dailyTrend.forEach((date, statusMap) -> {
|
||||
Map<String, Object> entry = new HashMap<>();
|
||||
entry.put("date", date);
|
||||
entry.putAll(statusMap);
|
||||
long total = statusMap.values().stream().mapToLong(Long::longValue).sum();
|
||||
entry.put("total", total);
|
||||
trend.add(entry);
|
||||
});
|
||||
|
||||
return trend;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.healthlink.his.web.infection.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
import com.healthlink.his.web.infection.appservice.ICdssAppService;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "CDSS临床决策支持")
|
||||
@RestController
|
||||
@RequestMapping("/infection/cdss")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class CdssController {
|
||||
|
||||
private final ICdssAppService cdssAppService;
|
||||
|
||||
@Operation(summary = "评估规则生成告警")
|
||||
@PreAuthorize("@ss.hasPermi('infection:cdss:edit')")
|
||||
@PostMapping("/evaluate")
|
||||
public R<?> evaluateRules(@RequestParam Long encounterId) {
|
||||
log.info("CDSS规则评估, encounterId={}", encounterId);
|
||||
return R.ok(cdssAppService.evaluateRules(encounterId));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取告警列表")
|
||||
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
|
||||
@GetMapping("/alerts/{encounterId}")
|
||||
public R<?> getAlerts(@PathVariable Long encounterId) {
|
||||
return R.ok(cdssAppService.getAlerts(encounterId));
|
||||
}
|
||||
|
||||
@Operation(summary = "确认告警")
|
||||
@PreAuthorize("@ss.hasPermi('infection:cdss:edit')")
|
||||
@PostMapping("/alerts/{id}/acknowledge")
|
||||
public R<?> acknowledgeAlert(@PathVariable Long id) {
|
||||
return R.ok(cdssAppService.acknowledgeAlert(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询规则列表")
|
||||
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
|
||||
@GetMapping("/rules")
|
||||
public R<?> getRules(
|
||||
@RequestParam(value = "ruleType", required = false) String ruleType,
|
||||
@RequestParam(value = "severity", required = false) String severity,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
Map<String, Object> params = new java.util.HashMap<>();
|
||||
if (ruleType != null) params.put("ruleType", ruleType);
|
||||
if (severity != null) params.put("severity", severity);
|
||||
if (keyword != null) params.put("keyword", keyword);
|
||||
return R.ok(cdssAppService.getRules(params));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.healthlink.his.web.infection.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.web.infection.appservice.IInfectionDetailAppService;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "院感监测统计")
|
||||
@RestController
|
||||
@RequestMapping("/infection-detail")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class InfectionDetailController {
|
||||
|
||||
private final IInfectionDetailAppService infectionDetailAppService;
|
||||
|
||||
@Operation(summary = "科室感染率统计")
|
||||
@PreAuthorize("@ss.hasPermi('infection:infection:list')")
|
||||
@GetMapping("/rate-by-dept")
|
||||
public R<Map<String, Object>> getInfectionRateByDept(
|
||||
@RequestParam(value = "deptId", required = false) Long deptId) {
|
||||
return R.ok(infectionDetailAppService.getInfectionRateByDept(deptId));
|
||||
}
|
||||
|
||||
@Operation(summary = "感染趋势统计")
|
||||
@PreAuthorize("@ss.hasPermi('infection:infection:list')")
|
||||
@GetMapping("/trend")
|
||||
public R<List<Map<String, Object>>> getInfectionTrend(
|
||||
@RequestParam(value = "startDate", required = false) String startDate,
|
||||
@RequestParam(value = "endDate", required = false) String endDate) {
|
||||
return R.ok(infectionDetailAppService.getInfectionTrend(startDate, endDate));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.healthlink.his.web.inpatientmanage.dto.NursingRecordDto;
|
||||
import com.healthlink.his.web.inpatientmanage.dto.NursingSearchParam;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -40,6 +41,7 @@ public class NursingRecordController {
|
||||
* @return 患者信息
|
||||
*/
|
||||
@GetMapping("/patient-page")
|
||||
@PreAuthorize("hasAuthority('nursing:record:list')")
|
||||
public R<?> getPatientInfoPage(NursingSearchParam nursingSearchParam,
|
||||
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@@ -58,6 +60,7 @@ public class NursingRecordController {
|
||||
* @return 患者护理记录单信息
|
||||
*/
|
||||
@GetMapping("/nursing-patient-page")
|
||||
@PreAuthorize("hasAuthority('nursing:record:list')")
|
||||
public R<?> getNursingPatientPage(NursingSearchParam nursingSearchParam,
|
||||
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@@ -72,6 +75,7 @@ public class NursingRecordController {
|
||||
* @param nursingRecordDto 护理记录实体
|
||||
*/
|
||||
@PostMapping("/save-nursing")
|
||||
@PreAuthorize("hasAuthority('nursing:record:add')")
|
||||
public R<?> saveRecord(@Validated @RequestBody NursingRecordDto nursingRecordDto) {
|
||||
return nursingRecordAppService.saveRecord(nursingRecordDto);
|
||||
}
|
||||
@@ -82,6 +86,7 @@ public class NursingRecordController {
|
||||
* @param nursingRecordDto 护理记录实体
|
||||
*/
|
||||
@PostMapping("/update-nursing")
|
||||
@PreAuthorize("hasAuthority('nursing:record:edit')")
|
||||
public R<?> updateRecord(@Validated @RequestBody NursingRecordDto nursingRecordDto) {
|
||||
return nursingRecordAppService.updateRecord(nursingRecordDto);
|
||||
}
|
||||
@@ -92,6 +97,7 @@ public class NursingRecordController {
|
||||
* @param recordList 记录单List
|
||||
*/
|
||||
@PostMapping("/delete-nursing")
|
||||
@PreAuthorize("hasAuthority('nursing:record:remove')")
|
||||
public R<?> delRecord(@Validated @RequestBody List<NursingRecordDto> recordList) {
|
||||
return nursingRecordAppService.delRecord(recordList);
|
||||
}
|
||||
@@ -106,6 +112,7 @@ public class NursingRecordController {
|
||||
* @return 患者护理记录单信息
|
||||
*/
|
||||
@GetMapping("/emr-template-page")
|
||||
@PreAuthorize("hasAuthority('nursing:record:list')")
|
||||
public R<?> getEmrTemplate(NursingSearchParam nursingSearchParam,
|
||||
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@@ -120,6 +127,7 @@ public class NursingRecordController {
|
||||
* @param emrTemplateDto 病历模板信息
|
||||
*/
|
||||
@PostMapping("/emr-template-save")
|
||||
@PreAuthorize("hasAuthority('nursing:record:add')")
|
||||
public R<?> saveEmrTemplate(@Validated @RequestBody NursingEmrTemplateDto emrTemplateDto) {
|
||||
return nursingRecordAppService.saveEmrTemplate(emrTemplateDto);
|
||||
}
|
||||
@@ -131,6 +139,7 @@ public class NursingRecordController {
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/emr-template-del")
|
||||
@PreAuthorize("hasAuthority('nursing:record:remove')")
|
||||
public R<?> deleteEmrTemplate(@Validated @RequestBody List<Long> idList) {
|
||||
return nursingRecordAppService.deleteEmrTemplate(idList);
|
||||
}
|
||||
@@ -142,6 +151,7 @@ public class NursingRecordController {
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/emr-template-update")
|
||||
@PreAuthorize("hasAuthority('nursing:record:edit')")
|
||||
public R<?> updateEmrTemplate(@Validated @RequestBody NursingEmrTemplateDto emrTemplateDto) {
|
||||
return nursingRecordAppService.updateEmrTemplate(emrTemplateDto);
|
||||
}
|
||||
@@ -153,6 +163,7 @@ public class NursingRecordController {
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/batch-save")
|
||||
@PreAuthorize("hasAuthority('nursing:record:edit')")
|
||||
public R<?> batchSaveRecord(@Validated @RequestBody BatchNursingRecordDto batchDto) {
|
||||
return nursingRecordAppService.batchSaveRecord(batchDto);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthlink.his.web.mrhomepage.appservice;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IMrStatsDetailAppService {
|
||||
|
||||
Map<String, Object> getMrStatsByDept(Long deptId);
|
||||
|
||||
Map<String, Object> getMrStatsByDoctor(Long doctorId);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.healthlink.his.web.mrhomepage.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.mrhomepage.domain.MrHomepage;
|
||||
import com.healthlink.his.mrhomepage.service.IMrHomepageService;
|
||||
import com.healthlink.his.web.mrhomepage.appservice.IMrStatsDetailAppService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class MrStatsDetailAppServiceImpl implements IMrStatsDetailAppService {
|
||||
|
||||
private final IMrHomepageService mrHomepageService;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getMrStatsByDept(Long deptId) {
|
||||
LambdaQueryWrapper<MrHomepage> wrapper = new LambdaQueryWrapper<>();
|
||||
if (deptId != null) {
|
||||
wrapper.eq(MrHomepage::getEncounterId, deptId);
|
||||
}
|
||||
List<MrHomepage> list = mrHomepageService.list(wrapper);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalCount", list.size());
|
||||
|
||||
BigDecimal totalCost = list.stream()
|
||||
.map(MrHomepage::getTotalCost)
|
||||
.filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
result.put("totalCost", totalCost);
|
||||
|
||||
result.put("avgCost", list.isEmpty() ? BigDecimal.ZERO :
|
||||
totalCost.divide(BigDecimal.valueOf(list.size()), 2, RoundingMode.HALF_UP));
|
||||
|
||||
Map<String, Long> byStatus = list.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
h -> h.getQualityStatus() != null ? h.getQualityStatus() : "UNKNOWN",
|
||||
Collectors.counting()));
|
||||
result.put("byStatus", byStatus);
|
||||
|
||||
Map<String, Long> byDrg = list.stream()
|
||||
.filter(h -> h.getDrgGroup() != null)
|
||||
.collect(Collectors.groupingBy(MrHomepage::getDrgGroup, Collectors.counting()));
|
||||
result.put("byDrg", byDrg);
|
||||
|
||||
long totalLos = list.stream()
|
||||
.mapToInt(h -> h.getLosDays() != null ? h.getLosDays() : 0)
|
||||
.sum();
|
||||
result.put("totalLosDays", totalLos);
|
||||
result.put("avgLosDays", list.isEmpty() ? 0 :
|
||||
Math.round(totalLos * 10.0 / list.size()) / 10.0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getMrStatsByDoctor(Long doctorId) {
|
||||
LambdaQueryWrapper<MrHomepage> wrapper = new LambdaQueryWrapper<>();
|
||||
if (doctorId != null) {
|
||||
wrapper.eq(MrHomepage::getPatientId, doctorId);
|
||||
}
|
||||
List<MrHomepage> list = mrHomepageService.list(wrapper);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalCount", list.size());
|
||||
|
||||
BigDecimal totalCost = list.stream()
|
||||
.map(MrHomepage::getTotalCost)
|
||||
.filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
result.put("totalCost", totalCost);
|
||||
|
||||
result.put("avgCost", list.isEmpty() ? BigDecimal.ZERO :
|
||||
totalCost.divide(BigDecimal.valueOf(list.size()), 2, RoundingMode.HALF_UP));
|
||||
|
||||
Map<String, Long> byStatus = list.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
h -> h.getQualityStatus() != null ? h.getQualityStatus() : "UNKNOWN",
|
||||
Collectors.counting()));
|
||||
result.put("byStatus", byStatus);
|
||||
|
||||
Map<String, Long> byDiagnosis = list.stream()
|
||||
.filter(h -> h.getPrimaryDiagnosisName() != null)
|
||||
.collect(Collectors.groupingBy(MrHomepage::getPrimaryDiagnosisName, Collectors.counting()));
|
||||
result.put("byDiagnosis", byDiagnosis);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.healthlink.his.web.mrhomepage.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.web.mrhomepage.appservice.IMrStatsDetailAppService;
|
||||
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;
|
||||
|
||||
@Tag(name = "病案统计明细")
|
||||
@RestController
|
||||
@RequestMapping("/mr-stats-detail")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class MrStatsDetailController {
|
||||
|
||||
private final IMrStatsDetailAppService mrStatsDetailAppService;
|
||||
|
||||
@Operation(summary = "科室病案统计")
|
||||
@PreAuthorize("@ss.hasPermi('mrhomepage:mrhomepage:list')")
|
||||
@GetMapping("/by-dept")
|
||||
public R<Map<String, Object>> getMrStatsByDept(
|
||||
@RequestParam(value = "deptId", required = false) Long deptId) {
|
||||
return R.ok(mrStatsDetailAppService.getMrStatsByDept(deptId));
|
||||
}
|
||||
|
||||
@Operation(summary = "医生病案统计")
|
||||
@PreAuthorize("@ss.hasPermi('mrhomepage:mrhomepage:list')")
|
||||
@GetMapping("/by-doctor")
|
||||
public R<Map<String, Object>> getMrStatsByDoctor(
|
||||
@RequestParam(value = "doctorId", required = false) Long doctorId) {
|
||||
return R.ok(mrStatsDetailAppService.getMrStatsByDoctor(doctorId));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.healthlink.his.nursing.domain.*;
|
||||
import com.healthlink.his.nursing.service.*;
|
||||
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.*;
|
||||
@@ -25,6 +26,7 @@ public class NursingExecutionController {
|
||||
|
||||
// ==================== 执行扫码 ====================
|
||||
@GetMapping("/scan/page")
|
||||
@PreAuthorize("hasAuthority('nursing:execution:list')")
|
||||
public R<?> getScanPage(
|
||||
@RequestParam(value = "scanType", required = false) String scanType,
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@@ -38,6 +40,7 @@ public class NursingExecutionController {
|
||||
}
|
||||
|
||||
@PostMapping("/scan/add")
|
||||
@PreAuthorize("hasAuthority('nursing:execution:add')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addScan(@RequestBody NursingExecutionScan scan) {
|
||||
scan.setScanTime(new Date());
|
||||
@@ -48,6 +51,7 @@ public class NursingExecutionController {
|
||||
|
||||
// ==================== 交接班 ====================
|
||||
@GetMapping("/handoff/page")
|
||||
@PreAuthorize("hasAuthority('nursing:execution:list')")
|
||||
public R<?> getHandoffPage(
|
||||
@RequestParam(value = "ward", required = false) String ward,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@@ -59,6 +63,7 @@ public class NursingExecutionController {
|
||||
}
|
||||
|
||||
@PostMapping("/handoff/add")
|
||||
@PreAuthorize("hasAuthority('nursing:execution:add')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addHandoff(@RequestBody NursingHandoffRecord record) {
|
||||
record.setStatus(0);
|
||||
@@ -68,6 +73,7 @@ public class NursingExecutionController {
|
||||
}
|
||||
|
||||
@PostMapping("/handoff/confirm")
|
||||
@PreAuthorize("hasAuthority('nursing:execution:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> confirmHandoff(@RequestParam Long id) {
|
||||
NursingHandoffRecord record = handoffService.getById(id);
|
||||
@@ -79,6 +85,7 @@ public class NursingExecutionController {
|
||||
}
|
||||
|
||||
@GetMapping("/handoff/key-patients")
|
||||
@PreAuthorize("hasAuthority('nursing:execution:list')")
|
||||
public R<?> getKeyPatients(
|
||||
@RequestParam(value = "ward", required = false) String ward) {
|
||||
LambdaQueryWrapper<NursingHandoffRecord> w = new LambdaQueryWrapper<>();
|
||||
@@ -105,6 +112,7 @@ public class NursingExecutionController {
|
||||
|
||||
// ==================== 输液巡视 ====================
|
||||
@GetMapping("/infusion/page")
|
||||
@PreAuthorize("hasAuthority('nursing:execution:list')")
|
||||
public R<?> getInfusionPage(
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "patencyStatus", required = false) String status,
|
||||
@@ -118,6 +126,7 @@ public class NursingExecutionController {
|
||||
}
|
||||
|
||||
@PostMapping("/infusion/add")
|
||||
@PreAuthorize("hasAuthority('nursing:execution:add')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addInfusion(@RequestBody NursingInfusionPatrol patrol) {
|
||||
patrol.setPatrolTime(new Date());
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.core.system.service.ISysRoleService;
|
||||
import com.core.common.core.domain.entity.SysRole;
|
||||
import com.core.common.core.domain.entity.SysUser;
|
||||
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.*;
|
||||
import java.util.*;
|
||||
@@ -22,6 +23,7 @@ public class Reconstruction3DController {
|
||||
|
||||
// ==================== 重建任务 ====================
|
||||
@GetMapping("/task/page")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:list')")
|
||||
public R<?> getTaskPage(@RequestParam(value="taskStatus",required=false) String taskStatus,
|
||||
@RequestParam(value="patientName",required=false) String patientName,
|
||||
@RequestParam(value="modality",required=false) String modality,
|
||||
@@ -34,7 +36,9 @@ public class Reconstruction3DController {
|
||||
.orderByDesc(ReconstructionTask::getCreateTime);
|
||||
return R.ok(taskService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
@PostMapping("/task/add") @Transactional(rollbackFor=Exception.class)
|
||||
@PostMapping("/task/add")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> addTask(@RequestBody ReconstructionTask t) {
|
||||
t.setTaskStatus("PENDING"); t.setCreateTime(new Date());
|
||||
taskService.save(t);
|
||||
@@ -44,8 +48,11 @@ public class Reconstruction3DController {
|
||||
return R.ok(t);
|
||||
}
|
||||
@GetMapping("/task/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:list')")
|
||||
public R<?> getTask(@PathVariable Long id) { return R.ok(taskService.getById(id)); }
|
||||
@PutMapping("/task/cancel/{id}") @Transactional(rollbackFor=Exception.class)
|
||||
@PutMapping("/task/cancel/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> cancelTask(@PathVariable Long id) {
|
||||
ReconstructionTask t = taskService.getById(id); if (t == null) return R.fail("任务不存在");
|
||||
t.setTaskStatus("CANCELLED"); taskService.updateById(t); return R.ok();
|
||||
@@ -53,16 +60,20 @@ public class Reconstruction3DController {
|
||||
|
||||
// ==================== 重建结果 ====================
|
||||
@GetMapping("/result/list/{taskId}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:list')")
|
||||
public R<?> getResults(@PathVariable Long taskId) {
|
||||
LambdaQueryWrapper<ReconstructionResult> w = new LambdaQueryWrapper<>();
|
||||
w.eq(ReconstructionResult::getTaskId, taskId);
|
||||
return R.ok(resultService.list(w));
|
||||
}
|
||||
@PostMapping("/result/add") @Transactional(rollbackFor=Exception.class)
|
||||
@PostMapping("/result/add")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> addResult(@RequestBody ReconstructionResult r) { r.setCreateTime(new Date()); resultService.save(r); return R.ok(r); }
|
||||
|
||||
// ==================== 重建报告 ====================
|
||||
@GetMapping("/report/page")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:list')")
|
||||
public R<?> getReportPage(@RequestParam(value="status",required=false) String status,
|
||||
@RequestParam(value="patientName",required=false) String patientName,
|
||||
@RequestParam(value="pageNo",defaultValue="1") Integer pageNo,
|
||||
@@ -72,14 +83,20 @@ public class Reconstruction3DController {
|
||||
.orderByDesc(ReconstructionReport::getCreateTime);
|
||||
return R.ok(reportService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
@PostMapping("/report/add") @Transactional(rollbackFor=Exception.class)
|
||||
@PostMapping("/report/add")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> addReport(@RequestBody ReconstructionReport r) { r.setStatus("DRAFT"); r.setCreateTime(new Date()); reportService.save(r); return R.ok(r); }
|
||||
@PutMapping("/report/submit/{id}") @Transactional(rollbackFor=Exception.class)
|
||||
@PutMapping("/report/submit/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> submitReport(@PathVariable Long id) {
|
||||
ReconstructionReport r = reportService.getById(id); if (r == null) return R.fail("报告不存在");
|
||||
r.setStatus("REPORTED"); r.setReportTime(new Date()); reportService.updateById(r); return R.ok();
|
||||
}
|
||||
@PutMapping("/report/verify/{id}") @Transactional(rollbackFor=Exception.class)
|
||||
@PutMapping("/report/verify/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
|
||||
@Transactional(rollbackFor=Exception.class)
|
||||
public R<?> verifyReport(@PathVariable Long id, @RequestParam("doctor") String doctor) {
|
||||
ReconstructionReport r = reportService.getById(id); if (r == null) return R.fail("报告不存在");
|
||||
r.setStatus("VERIFIED"); r.setVerifyDoctor(doctor); r.setVerifyTime(new Date()); reportService.updateById(r); return R.ok();
|
||||
@@ -87,6 +104,7 @@ public class Reconstruction3DController {
|
||||
|
||||
// ==================== 统计 ====================
|
||||
@GetMapping("/stats")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:list')")
|
||||
public R<?> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalTasks", taskService.count());
|
||||
@@ -109,6 +127,7 @@ public class Reconstruction3DController {
|
||||
|
||||
// ==================== 医生列表 ====================
|
||||
@GetMapping("/doctors")
|
||||
@PreAuthorize("@ss.hasPermi('infection:check:list')")
|
||||
public R<?> getDoctors() {
|
||||
SysUser query = new SysUser();
|
||||
query.setStatus("0");
|
||||
|
||||
@@ -5,19 +5,26 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.surgicalschedule.domain.SurgerySafetyCheck;
|
||||
import com.healthlink.his.surgicalschedule.service.ISurgerySafetyCheckService;
|
||||
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.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 术前安全核查 Controller (WS/T 313)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/surgery-safety-check")
|
||||
@Tag(name = "手术安全核查")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class SurgerySafetyCheckController {
|
||||
@@ -25,6 +32,8 @@ public class SurgerySafetyCheckController {
|
||||
private final ISurgerySafetyCheckService safetyCheckService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询安全核查")
|
||||
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
|
||||
public R<?> getPage(
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "encounterId", required = false) Long encounterId,
|
||||
@@ -40,6 +49,8 @@ public class SurgerySafetyCheckController {
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "查询安全核查列表")
|
||||
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
|
||||
public R<?> getList(@RequestParam("encounterId") Long encounterId) {
|
||||
LambdaQueryWrapper<SurgerySafetyCheck> w = new LambdaQueryWrapper<>();
|
||||
w.eq(SurgerySafetyCheck::getEncounterId, encounterId)
|
||||
@@ -48,11 +59,15 @@ public class SurgerySafetyCheckController {
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取安全核查详情")
|
||||
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
|
||||
public R<?> getById(@PathVariable Long id) {
|
||||
return R.ok(safetyCheckService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/add")
|
||||
@Operation(summary = "新增安全核查")
|
||||
@PreAuthorize("@ss.hasPermi('surgery:schedule:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> add(@RequestBody SurgerySafetyCheck check) {
|
||||
check.setCreateTime(new Date());
|
||||
@@ -61,6 +76,8 @@ public class SurgerySafetyCheckController {
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新安全核查")
|
||||
@PreAuthorize("@ss.hasPermi('surgery:schedule:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> update(@RequestBody SurgerySafetyCheck check) {
|
||||
safetyCheckService.updateById(check);
|
||||
@@ -68,9 +85,45 @@ public class SurgerySafetyCheckController {
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete/{id}")
|
||||
@Operation(summary = "删除安全核查")
|
||||
@PreAuthorize("@ss.hasPermi('surgery:schedule:remove')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> delete(@PathVariable Long id) {
|
||||
safetyCheckService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/phase-status/{encounterId}")
|
||||
@Operation(summary = "查询各阶段核查完成状态")
|
||||
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
|
||||
public R<?> getPhaseStatus(@PathVariable Long encounterId) {
|
||||
LambdaQueryWrapper<SurgerySafetyCheck> w = new LambdaQueryWrapper<>();
|
||||
w.eq(SurgerySafetyCheck::getEncounterId, encounterId);
|
||||
List<SurgerySafetyCheck> checks = safetyCheckService.list(w);
|
||||
Map<String, Boolean> phaseMap = new HashMap<>();
|
||||
phaseMap.put("sign_in", false);
|
||||
phaseMap.put("time_out", false);
|
||||
phaseMap.put("sign_out", false);
|
||||
for (SurgerySafetyCheck c : checks) {
|
||||
if (Boolean.TRUE.equals(c.getChecklistCompleted()) && c.getCheckPhase() != null) {
|
||||
phaseMap.put(c.getCheckPhase(), true);
|
||||
}
|
||||
}
|
||||
return R.ok(phaseMap);
|
||||
}
|
||||
|
||||
@GetMapping("/compliance-stats")
|
||||
@Operation(summary = "安全核查合规统计")
|
||||
@PreAuthorize("@ss.hasPermi('surgery:schedule:list')")
|
||||
public R<?> getComplianceStats() {
|
||||
LambdaQueryWrapper<SurgerySafetyCheck> w = new LambdaQueryWrapper<>();
|
||||
long total = safetyCheckService.count(w);
|
||||
w.eq(SurgerySafetyCheck::getChecklistCompleted, true);
|
||||
long completed = safetyCheckService.count(w);
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("total", total);
|
||||
stats.put("completed", completed);
|
||||
stats.put("complianceRate", total > 0 ? (completed * 100.0 / total) : 0);
|
||||
return R.ok(stats);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- V66__update_menu_icons.sql
|
||||
-- V66__update_menu_icons.sql
|
||||
-- 更新菜单图标 - 根据菜单功能名称匹配合适的图标
|
||||
-- 仅使用 src/assets/icons/svg/ 目录下实际存在的图标
|
||||
|
||||
@@ -30,7 +30,7 @@ UPDATE sys_menu SET icon = 'surgery' WHERE menu_id = 2119; -- 手术管理
|
||||
UPDATE sys_menu SET icon = 'user' WHERE menu_id = 2140; -- 患者管理
|
||||
UPDATE sys_menu SET icon = 'consultation' WHERE menu_id = 2147; -- 会诊管理
|
||||
UPDATE sys_menu SET icon = 'report' WHERE menu_id = 2159; -- 疾病报告管理
|
||||
UPDATE sys_MENU SET icon = 'infection' WHERE menu_id = 10001; -- 院感管理
|
||||
UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10001; -- 院感管理
|
||||
UPDATE sys_menu SET icon = 'log' WHERE menu_id = 10011; -- 药品追溯管理
|
||||
UPDATE sys_menu SET icon = 'edit' WHERE menu_id = 10021; -- 电子签名管理
|
||||
UPDATE sys_menu SET icon = 'alert' WHERE menu_id = 10031; -- 危急值管理
|
||||
@@ -109,7 +109,7 @@ UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 277; -- 医技工作
|
||||
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 282; -- 门诊收费工作站
|
||||
|
||||
-- 门诊工作站子菜单
|
||||
UPDATE sys_MENU SET icon = 'drug' WHERE menu_id = 272; -- 门诊退药
|
||||
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 272; -- 门诊退药
|
||||
UPDATE sys_menu SET icon = 'registration' WHERE menu_id = 274; -- 门诊退号
|
||||
|
||||
-- 门诊收费工作站子菜单
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
-- V70: 麻醉扩展功能表(术前访视、术中事件、复苏评分)
|
||||
|
||||
-- 麻醉前评估(术前访视)
|
||||
CREATE TABLE IF NOT EXISTS anes_preop_visit (
|
||||
id BIGINT PRIMARY KEY,
|
||||
record_id BIGINT,
|
||||
encounter_id BIGINT,
|
||||
patient_id BIGINT,
|
||||
patient_name VARCHAR(64),
|
||||
visit_doctor_id BIGINT,
|
||||
visit_doctor_name VARCHAR(64),
|
||||
visit_time TIMESTAMP,
|
||||
chief_complaint TEXT,
|
||||
present_illness TEXT,
|
||||
past_history TEXT,
|
||||
allergy_history TEXT,
|
||||
airway_assessment VARCHAR(255),
|
||||
asa_grade VARCHAR(16),
|
||||
cardiovascular_status VARCHAR(255),
|
||||
respiratory_status VARCHAR(255),
|
||||
neurological_status VARCHAR(255),
|
||||
hepatorenal_function VARCHAR(255),
|
||||
coagulation_status VARCHAR(255),
|
||||
fasting_status VARCHAR(32),
|
||||
npo_hours VARCHAR(16),
|
||||
difficult_airway_risk VARCHAR(255),
|
||||
anesthesia_risk VARCHAR(255),
|
||||
risk_factors TEXT,
|
||||
recommendation TEXT,
|
||||
status VARCHAR(16) DEFAULT 'DRAFT',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP,
|
||||
tenant_id INT DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0'
|
||||
);
|
||||
|
||||
-- 术中事件记录(插管/拔管/体位)
|
||||
CREATE TABLE IF NOT EXISTS anes_intraop_event (
|
||||
id BIGINT PRIMARY KEY,
|
||||
record_id BIGINT,
|
||||
encounter_id BIGINT,
|
||||
patient_id BIGINT,
|
||||
event_type VARCHAR(32),
|
||||
event_detail TEXT,
|
||||
event_time TIMESTAMP,
|
||||
operator_name VARCHAR(64),
|
||||
position VARCHAR(64),
|
||||
intubation_type VARCHAR(64),
|
||||
intubation_result VARCHAR(64),
|
||||
extubation_reason VARCHAR(255),
|
||||
extubation_result VARCHAR(64),
|
||||
complication TEXT,
|
||||
action TEXT,
|
||||
remark TEXT,
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP,
|
||||
tenant_id INT DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0'
|
||||
);
|
||||
|
||||
-- 麻醉复苏评分(PACU Aldrete)
|
||||
CREATE TABLE IF NOT EXISTS anes_alldrete_score (
|
||||
id BIGINT PRIMARY KEY,
|
||||
record_id BIGINT,
|
||||
encounter_id BIGINT,
|
||||
patient_id BIGINT,
|
||||
patient_name VARCHAR(64),
|
||||
assess_time TIMESTAMP,
|
||||
activity_score INT DEFAULT 0,
|
||||
respiration_score INT DEFAULT 0,
|
||||
circulation_score INT DEFAULT 0,
|
||||
consciousness_score INT DEFAULT 0,
|
||||
spo2_score INT DEFAULT 0,
|
||||
total_score INT DEFAULT 0,
|
||||
risk_level VARCHAR(16),
|
||||
assessor_id BIGINT,
|
||||
assessor_name VARCHAR(64),
|
||||
remark TEXT,
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP,
|
||||
tenant_id INT DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0'
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
CREATE TABLE cdss_rule (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
rule_code VARCHAR(32) NOT NULL,
|
||||
rule_name VARCHAR(100) NOT NULL,
|
||||
rule_type VARCHAR(20) NOT NULL,
|
||||
condition_expr TEXT NOT NULL,
|
||||
suggestion TEXT NOT NULL,
|
||||
severity VARCHAR(16) DEFAULT 'INFO',
|
||||
enabled BOOLEAN DEFAULT TRUE,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(64),
|
||||
update_time TIMESTAMP,
|
||||
update_by VARCHAR(64)
|
||||
);
|
||||
|
||||
CREATE TABLE cdss_alert (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
encounter_id BIGINT NOT NULL,
|
||||
patient_id BIGINT NOT NULL,
|
||||
rule_id BIGINT NOT NULL,
|
||||
alert_type VARCHAR(20) NOT NULL,
|
||||
alert_message TEXT NOT NULL,
|
||||
severity VARCHAR(16) NOT NULL,
|
||||
acknowledged BOOLEAN DEFAULT FALSE,
|
||||
acknowledged_by BIGINT,
|
||||
acknowledged_time TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(64)
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE regional_share_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
encounter_id BIGINT NOT NULL,
|
||||
patient_id BIGINT NOT NULL,
|
||||
share_type VARCHAR(32) NOT NULL,
|
||||
target_system VARCHAR(64) NOT NULL,
|
||||
share_status VARCHAR(20) DEFAULT 'PENDING',
|
||||
request_data TEXT,
|
||||
response_data TEXT,
|
||||
error_message TEXT,
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(64),
|
||||
update_time TIMESTAMP,
|
||||
update_by VARCHAR(64)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_regional_share_encounter ON regional_share_record(encounter_id);
|
||||
CREATE INDEX idx_regional_share_patient ON regional_share_record(patient_id);
|
||||
CREATE INDEX idx_regional_share_status ON regional_share_record(share_status);
|
||||
@@ -0,0 +1,40 @@
|
||||
-- V73: 电子病历5级 - 结构化数据仓库+质控评分
|
||||
|
||||
-- 结构化数据表
|
||||
CREATE TABLE IF NOT EXISTS emr_structured_data (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
emr_id BIGINT NOT NULL,
|
||||
encounter_id BIGINT NOT NULL,
|
||||
patient_id BIGINT NOT NULL,
|
||||
data_type VARCHAR(32) NOT NULL,
|
||||
data_key VARCHAR(64) NOT NULL,
|
||||
data_value TEXT,
|
||||
data_unit VARCHAR(32),
|
||||
record_time TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(64)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_structured_data_emr ON emr_structured_data(emr_id);
|
||||
CREATE INDEX idx_structured_data_encounter ON emr_structured_data(encounter_id);
|
||||
CREATE INDEX idx_structured_data_patient ON emr_structured_data(patient_id);
|
||||
|
||||
-- 质控评分表
|
||||
CREATE TABLE IF NOT EXISTS emr_quality_score (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
encounter_id BIGINT NOT NULL,
|
||||
emr_type VARCHAR(32) NOT NULL,
|
||||
total_score DECIMAL(5,2),
|
||||
completeness_score DECIMAL(5,2),
|
||||
timeliness_score DECIMAL(5,2),
|
||||
accuracy_score DECIMAL(5,2),
|
||||
check_time TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(64)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_quality_score_encounter ON emr_quality_score(encounter_id);
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE hir_infection_case ADD COLUMN IF NOT EXISTS department_id BIGINT;
|
||||
COMMENT ON COLUMN hir_infection_case.department_id IS '科室ID';
|
||||
CREATE INDEX IF NOT EXISTS idx_hir_infection_case_dept ON hir_infection_case(department_id);
|
||||
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE mr_homepage ADD COLUMN IF NOT EXISTS department_id BIGINT;
|
||||
ALTER TABLE mr_homepage ADD COLUMN IF NOT EXISTS doctor_id BIGINT;
|
||||
COMMENT ON COLUMN mr_homepage.department_id IS '科室ID';
|
||||
COMMENT ON COLUMN mr_homepage.doctor_id IS '主治医生ID';
|
||||
CREATE INDEX IF NOT EXISTS idx_mr_homepage_dept ON mr_homepage(department_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_mr_homepage_doctor ON mr_homepage(doctor_id);
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.healthlink.his.emr.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@TableName("emr_quality_score")
|
||||
@Accessors(chain = true)
|
||||
public class EmrQualityScore implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long encounterId;
|
||||
|
||||
private String emrType;
|
||||
|
||||
private BigDecimal totalScore;
|
||||
|
||||
private BigDecimal completenessScore;
|
||||
|
||||
private BigDecimal timelinessScore;
|
||||
|
||||
private BigDecimal accuracyScore;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date checkTime;
|
||||
|
||||
private Integer tenantId;
|
||||
|
||||
private String deleteFlag;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private String createBy;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.healthlink.his.emr.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@TableName("emr_structured_data")
|
||||
@Accessors(chain = true)
|
||||
public class EmrStructuredData implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long emrId;
|
||||
|
||||
private Long encounterId;
|
||||
|
||||
private Long patientId;
|
||||
|
||||
private String dataType;
|
||||
|
||||
private String dataKey;
|
||||
|
||||
private String dataValue;
|
||||
|
||||
private String dataUnit;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date recordTime;
|
||||
|
||||
private Integer tenantId;
|
||||
|
||||
private String deleteFlag;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private String createBy;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.healthlink.his.emr.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.emr.domain.EmrQualityScore;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface EmrQualityScoreMapper extends BaseMapper<EmrQualityScore> {
|
||||
|
||||
List<EmrQualityScore> selectByEncounterId(@Param("encounterId") Long encounterId);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.healthlink.his.emr.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.emr.domain.EmrStructuredData;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface EmrStructuredDataMapper extends BaseMapper<EmrStructuredData> {
|
||||
|
||||
List<EmrStructuredData> selectByEncounterId(@Param("encounterId") Long encounterId);
|
||||
|
||||
List<EmrStructuredData> selectByEmrId(@Param("emrId") Long emrId);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthlink.his.emr.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.emr.domain.EmrQualityScore;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IEmrQualityScoreService extends IService<EmrQualityScore> {
|
||||
|
||||
List<EmrQualityScore> selectByEncounterId(Long encounterId);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.healthlink.his.emr.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.emr.domain.EmrStructuredData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IEmrStructuredDataService extends IService<EmrStructuredData> {
|
||||
|
||||
List<EmrStructuredData> selectByEncounterId(Long encounterId);
|
||||
|
||||
List<EmrStructuredData> selectByEmrId(Long emrId);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.healthlink.his.emr.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.emr.domain.EmrQualityScore;
|
||||
import com.healthlink.his.emr.mapper.EmrQualityScoreMapper;
|
||||
import com.healthlink.his.emr.service.IEmrQualityScoreService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class EmrQualityScoreServiceImpl
|
||||
extends ServiceImpl<EmrQualityScoreMapper, EmrQualityScore>
|
||||
implements IEmrQualityScoreService {
|
||||
|
||||
@Override
|
||||
public List<EmrQualityScore> selectByEncounterId(Long encounterId) {
|
||||
return baseMapper.selectByEncounterId(encounterId);
|
||||
}
|
||||
}
|
||||
@@ -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.EmrStructuredData;
|
||||
import com.healthlink.his.emr.mapper.EmrStructuredDataMapper;
|
||||
import com.healthlink.his.emr.service.IEmrStructuredDataService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class EmrStructuredDataServiceImpl
|
||||
extends ServiceImpl<EmrStructuredDataMapper, EmrStructuredData>
|
||||
implements IEmrStructuredDataService {
|
||||
|
||||
@Override
|
||||
public List<EmrStructuredData> selectByEncounterId(Long encounterId) {
|
||||
return baseMapper.selectByEncounterId(encounterId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EmrStructuredData> selectByEmrId(Long emrId) {
|
||||
return baseMapper.selectByEmrId(emrId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.healthlink.his.esb.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 区域医疗信息共享记录
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("regional_share_record")
|
||||
public class RegionalShareRecord extends HisBaseEntity {
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long encounterId;
|
||||
private Long patientId;
|
||||
private String shareType;
|
||||
private String targetSystem;
|
||||
private String shareStatus;
|
||||
private String requestData;
|
||||
private String responseData;
|
||||
private String errorMessage;
|
||||
private Integer retryCount;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.healthlink.his.esb.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.esb.domain.RegionalShareRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface RegionalShareRecordMapper extends BaseMapper<RegionalShareRecord> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.healthlink.his.esb.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.esb.domain.RegionalShareRecord;
|
||||
|
||||
public interface IRegionalShareRecordService extends IService<RegionalShareRecord> {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthlink.his.esb.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.esb.domain.RegionalShareRecord;
|
||||
import com.healthlink.his.esb.mapper.RegionalShareRecordMapper;
|
||||
import com.healthlink.his.esb.service.IRegionalShareRecordService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class RegionalShareRecordServiceImpl extends ServiceImpl<RegionalShareRecordMapper, RegionalShareRecord> implements IRegionalShareRecordService {
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.healthlink.his.infection.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("cdss_alert")
|
||||
public class CdssAlert extends HisBaseEntity {
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@TableField("encounter_id")
|
||||
private Long encounterId;
|
||||
@TableField("patient_id")
|
||||
private Long patientId;
|
||||
@TableField("rule_id")
|
||||
private Long ruleId;
|
||||
@TableField("alert_type")
|
||||
private String alertType;
|
||||
@TableField("alert_message")
|
||||
private String alertMessage;
|
||||
@TableField("severity")
|
||||
private String severity;
|
||||
@TableField("acknowledged")
|
||||
private Boolean acknowledged;
|
||||
@TableField("acknowledged_by")
|
||||
private Long acknowledgedBy;
|
||||
@TableField("acknowledged_time")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date acknowledgedTime;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.healthlink.his.infection.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("cdss_rule")
|
||||
public class CdssRule extends HisBaseEntity {
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@TableField("rule_code")
|
||||
private String ruleCode;
|
||||
@TableField("rule_name")
|
||||
private String ruleName;
|
||||
@TableField("rule_type")
|
||||
private String ruleType;
|
||||
@TableField("condition_expr")
|
||||
private String conditionExpr;
|
||||
@TableField("suggestion")
|
||||
private String suggestion;
|
||||
@TableField("severity")
|
||||
private String severity;
|
||||
@TableField("enabled")
|
||||
private Boolean enabled;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.healthlink.his.infection.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CdssAlertMapper extends BaseMapper<CdssAlert> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.healthlink.his.infection.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CdssRuleMapper extends BaseMapper<CdssRule> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.healthlink.his.infection.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
|
||||
public interface ICdssAlertService extends IService<CdssAlert> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.healthlink.his.infection.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
|
||||
public interface ICdssRuleService extends IService<CdssRule> {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthlink.his.infection.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import com.healthlink.his.infection.mapper.CdssAlertMapper;
|
||||
import com.healthlink.his.infection.service.ICdssAlertService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CdssAlertServiceImpl extends ServiceImpl<CdssAlertMapper, CdssAlert> implements ICdssAlertService {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthlink.his.infection.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
import com.healthlink.his.infection.mapper.CdssRuleMapper;
|
||||
import com.healthlink.his.infection.service.ICdssRuleService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CdssRuleServiceImpl extends ServiceImpl<CdssRuleMapper, CdssRule> implements ICdssRuleService {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?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.EmrQualityScoreMapper">
|
||||
|
||||
<select id="selectByEncounterId" resultType="com.healthlink.his.emr.domain.EmrQualityScore">
|
||||
SELECT * FROM emr_quality_score
|
||||
WHERE encounter_id = #{encounterId}
|
||||
ORDER BY check_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?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.EmrStructuredDataMapper">
|
||||
|
||||
<select id="selectByEncounterId" resultType="com.healthlink.his.emr.domain.EmrStructuredData">
|
||||
SELECT * FROM emr_structured_data
|
||||
WHERE encounter_id = #{encounterId}
|
||||
ORDER BY record_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectByEmrId" resultType="com.healthlink.his.emr.domain.EmrStructuredData">
|
||||
SELECT * FROM emr_structured_data
|
||||
WHERE emr_id = #{emrId}
|
||||
ORDER BY record_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -14,3 +14,8 @@ export function getEmrRevisionList(emrId) { return request({ url: '/emr/revision
|
||||
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 extractStructuredData(emrId) { return request({ url: '/emr/warehouse/extract', method: 'post', params: { emrId } }) }
|
||||
export function getStructuredData(encounterId) { return request({ url: '/emr/warehouse/data/' + encounterId, method: 'get' }) }
|
||||
export function calculateQualityScore(encounterId) { return request({ url: '/emr/warehouse/quality-score', method: 'post', params: { encounterId } }) }
|
||||
export function getQualityScores(encounterId) { return request({ url: '/emr/warehouse/quality-scores', method: 'get', params: { encounterId } }) }
|
||||
|
||||
9
healthlink-his-ui/src/api/infection/detail.js
Normal file
9
healthlink-his-ui/src/api/infection/detail.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getInfectionRateByDept(params) {
|
||||
return request({ url: '/infection-detail/rate-by-dept', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getInfectionTrend(params) {
|
||||
return request({ url: '/infection-detail/trend', method: 'get', params })
|
||||
}
|
||||
9
healthlink-his-ui/src/api/mrhomepage/statsDetail.js
Normal file
9
healthlink-his-ui/src/api/mrhomepage/statsDetail.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getMrStatsByDept(params) {
|
||||
return request({ url: '/mr-stats-detail/by-dept', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getMrStatsByDoctor(params) {
|
||||
return request({ url: '/mr-stats-detail/by-doctor', method: 'get', params })
|
||||
}
|
||||
193
healthlink-his-ui/src/views/emr/data-warehouse/index.vue
Normal file
193
healthlink-his-ui/src/views/emr/data-warehouse/index.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card shadow="hover" class="mb8">
|
||||
<template #header>
|
||||
<span>结构化数据提取</span>
|
||||
</template>
|
||||
<el-form :inline="true" :model="extractForm">
|
||||
<el-form-item label="病历ID">
|
||||
<el-input v-model="extractForm.emrId" placeholder="请输入病历ID" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" :loading="extractLoading" @click="handleExtract">
|
||||
提取数据
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="hover" class="mb8">
|
||||
<template #header>
|
||||
<span>结构化数据查看</span>
|
||||
</template>
|
||||
<el-form :inline="true" :model="queryForm">
|
||||
<el-form-item label="就诊ID">
|
||||
<el-input v-model="queryForm.encounterId" placeholder="请输入就诊ID" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" :loading="queryLoading" @click="handleQuery">
|
||||
查询
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="queryLoading" :data="structuredDataList" border stripe>
|
||||
<el-table-column label="数据类型" prop="dataType" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag>{{ dataTypeMap[scope.row.dataType] || scope.row.dataType }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数据键" prop="dataKey" width="180" />
|
||||
<el-table-column label="数据值" prop="dataValue" min-width="150" />
|
||||
<el-table-column label="单位" prop="dataUnit" width="100" />
|
||||
<el-table-column label="记录时间" prop="recordTime" width="180" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="hover" class="mb8">
|
||||
<template #header>
|
||||
<span>质控评分</span>
|
||||
</template>
|
||||
<el-form :inline="true" :model="qualityForm">
|
||||
<el-form-item label="就诊ID">
|
||||
<el-input v-model="qualityForm.encounterId" placeholder="请输入就诊ID" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Check" :loading="qualityLoading" @click="handleCalculate">
|
||||
计算评分
|
||||
</el-button>
|
||||
<el-button type="success" icon="Search" :loading="scoreLoading" @click="handleLoadScores">
|
||||
查询历史
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row v-if="currentScore" :gutter="20" class="mb8">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="总分" :value="currentScore.totalScore" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="完整性" :value="currentScore.completenessScore" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="及时性" :value="currentScore.timelinessScore" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="准确性" :value="currentScore.accuracyScore" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table v-if="scoreList.length > 0" v-loading="scoreLoading" :data="scoreList" border stripe>
|
||||
<el-table-column label="评分时间" prop="checkTime" width="180" />
|
||||
<el-table-column label="总分" prop="totalScore" width="100" />
|
||||
<el-table-column label="完整性" prop="completenessScore" width="100" />
|
||||
<el-table-column label="及时性" prop="timelinessScore" width="100" />
|
||||
<el-table-column label="准确性" prop="accuracyScore" width="100" />
|
||||
<el-table-column label="病历类型" prop="emrType" width="120" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { extractStructuredData, getStructuredData, calculateQualityScore, getQualityScores } from '@/api/emr'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const extractLoading = ref(false)
|
||||
const queryLoading = ref(false)
|
||||
const qualityLoading = ref(false)
|
||||
const scoreLoading = ref(false)
|
||||
|
||||
const extractForm = reactive({ emrId: '' })
|
||||
const queryForm = reactive({ encounterId: '' })
|
||||
const qualityForm = reactive({ encounterId: '' })
|
||||
|
||||
const structuredDataList = ref([])
|
||||
const currentScore = ref(null)
|
||||
const scoreList = ref([])
|
||||
|
||||
const dataTypeMap = {
|
||||
vital_signs: '生命体征',
|
||||
lab_results: '检验结果',
|
||||
diagnosis: '诊断',
|
||||
medication: '用药'
|
||||
}
|
||||
|
||||
const handleExtract = async () => {
|
||||
if (!extractForm.emrId) {
|
||||
ElMessage.warning('请输入病历ID')
|
||||
return
|
||||
}
|
||||
extractLoading.value = true
|
||||
try {
|
||||
const res = await extractStructuredData(extractForm.emrId)
|
||||
const data = res.data || res
|
||||
ElMessage.success('提取完成,共 ' + (Array.isArray(data) ? data.length : 0) + ' 条数据')
|
||||
} catch (e) {
|
||||
ElMessage.error('提取失败')
|
||||
} finally {
|
||||
extractLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuery = async () => {
|
||||
if (!queryForm.encounterId) {
|
||||
ElMessage.warning('请输入就诊ID')
|
||||
return
|
||||
}
|
||||
queryLoading.value = true
|
||||
try {
|
||||
const res = await getStructuredData(queryForm.encounterId)
|
||||
structuredDataList.value = res.data || res || []
|
||||
} catch (e) {
|
||||
ElMessage.error('查询失败')
|
||||
} finally {
|
||||
queryLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleCalculate = async () => {
|
||||
if (!qualityForm.encounterId) {
|
||||
ElMessage.warning('请输入就诊ID')
|
||||
return
|
||||
}
|
||||
qualityLoading.value = true
|
||||
try {
|
||||
const res = await calculateQualityScore(qualityForm.encounterId)
|
||||
currentScore.value = res.data || res
|
||||
ElMessage.success('评分计算完成')
|
||||
} catch (e) {
|
||||
ElMessage.error('计算失败')
|
||||
} finally {
|
||||
qualityLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleLoadScores = async () => {
|
||||
if (!qualityForm.encounterId) {
|
||||
ElMessage.warning('请输入就诊ID')
|
||||
return
|
||||
}
|
||||
scoreLoading.value = true
|
||||
try {
|
||||
const res = await getQualityScores(qualityForm.encounterId)
|
||||
scoreList.value = res.data || res || []
|
||||
} catch (e) {
|
||||
ElMessage.error('查询失败')
|
||||
} finally {
|
||||
scoreLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
</style>
|
||||
13
healthlink-his-ui/src/views/esbmanage/regionalshare/api.js
Normal file
13
healthlink-his-ui/src/views/esbmanage/regionalshare/api.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function sharePatientData(params) {
|
||||
return request({ url: '/regional/share', method: 'post', params })
|
||||
}
|
||||
|
||||
export function getShareRecords(encounterId) {
|
||||
return request({ url: '/regional/share/records/' + encounterId, method: 'get' })
|
||||
}
|
||||
|
||||
export function getShareStats() {
|
||||
return request({ url: '/regional/share/stats', method: 'get' })
|
||||
}
|
||||
240
healthlink-his-ui/src/views/esbmanage/regionalshare/index.vue
Normal file
240
healthlink-his-ui/src/views/esbmanage/regionalshare/index.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row
|
||||
:gutter="16"
|
||||
class="stat-row"
|
||||
>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card blue">
|
||||
<div class="stat-icon">
|
||||
<el-icon :size="28">
|
||||
<Share />
|
||||
</el-icon>
|
||||
</div><div class="stat-info">
|
||||
<div class="stat-value">
|
||||
{{ stats.total || 0 }}
|
||||
</div><div class="stat-label">
|
||||
总共享数
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card orange">
|
||||
<div class="stat-icon">
|
||||
<el-icon :size="28">
|
||||
<Clock />
|
||||
</el-icon>
|
||||
</div><div class="stat-info">
|
||||
<div class="stat-value">
|
||||
{{ stats.pending || 0 }}
|
||||
</div><div class="stat-label">
|
||||
待处理
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card green">
|
||||
<div class="stat-icon">
|
||||
<el-icon :size="28">
|
||||
<CircleCheck />
|
||||
</el-icon>
|
||||
</div><div class="stat-info">
|
||||
<div class="stat-value">
|
||||
{{ stats.success || 0 }}
|
||||
</div><div class="stat-label">
|
||||
成功
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card red">
|
||||
<div class="stat-icon">
|
||||
<el-icon :size="28">
|
||||
<Warning />
|
||||
</el-icon>
|
||||
</div><div class="stat-info">
|
||||
<div class="stat-value">
|
||||
{{ stats.failed || 0 }}
|
||||
</div><div class="stat-label">
|
||||
失败
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span class="card-title">发起共享</span>
|
||||
</template>
|
||||
<el-form
|
||||
:model="shareForm"
|
||||
inline
|
||||
>
|
||||
<el-form-item label="就诊ID">
|
||||
<el-input
|
||||
v-model="shareForm.encounterId"
|
||||
placeholder="请输入就诊ID"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标系统">
|
||||
<el-input
|
||||
v-model="shareForm.targetSystem"
|
||||
placeholder="如: 区域卫生平台"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
:loading="shareLoading"
|
||||
type="primary"
|
||||
@click="handleShare"
|
||||
>
|
||||
共享
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card
|
||||
shadow="never"
|
||||
style="margin-top: 16px"
|
||||
>
|
||||
<template #header>
|
||||
<span class="card-title">共享记录</span>
|
||||
</template>
|
||||
<vxe-table
|
||||
:data="records"
|
||||
border
|
||||
height="400"
|
||||
>
|
||||
<vxe-column
|
||||
field="id"
|
||||
title="ID"
|
||||
width="80"
|
||||
/>
|
||||
<vxe-column
|
||||
field="encounterId"
|
||||
title="就诊ID"
|
||||
width="100"
|
||||
/>
|
||||
<vxe-column
|
||||
field="targetSystem"
|
||||
title="目标系统"
|
||||
width="150"
|
||||
/>
|
||||
<vxe-column
|
||||
field="shareType"
|
||||
title="共享类型"
|
||||
width="120"
|
||||
/>
|
||||
<vxe-column
|
||||
field="shareStatus"
|
||||
title="状态"
|
||||
width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.shareStatus === 'SUCCESS' ? 'success' : row.shareStatus === 'FAILED' ? 'danger' : 'warning'"
|
||||
size="small"
|
||||
>
|
||||
{{ row.shareStatus }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="retryCount"
|
||||
title="重试次数"
|
||||
width="90"
|
||||
align="center"
|
||||
/>
|
||||
<vxe-column
|
||||
field="errorMessage"
|
||||
title="错误信息"
|
||||
min-width="150"
|
||||
show-overflow
|
||||
/>
|
||||
<vxe-column
|
||||
field="createTime"
|
||||
title="创建时间"
|
||||
width="160"
|
||||
/>
|
||||
</vxe-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { sharePatientData, getShareRecords, getShareStats } from './api'
|
||||
|
||||
const stats = ref({})
|
||||
const records = ref([])
|
||||
const shareLoading = ref(false)
|
||||
const shareForm = ref({ encounterId: '', targetSystem: '' })
|
||||
|
||||
const loadStats = () => {
|
||||
getShareStats().then(res => { stats.value = res.data || {} })
|
||||
}
|
||||
|
||||
const loadRecords = () => {
|
||||
if (shareForm.value.encounterId) {
|
||||
getShareRecords(shareForm.value.encounterId).then(res => { records.value = res.data || [] })
|
||||
}
|
||||
}
|
||||
|
||||
const handleShare = () => {
|
||||
if (!shareForm.value.encounterId) {
|
||||
ElMessage.warning('请输入就诊ID')
|
||||
return
|
||||
}
|
||||
if (!shareForm.value.targetSystem) {
|
||||
ElMessage.warning('请输入目标系统')
|
||||
return
|
||||
}
|
||||
shareLoading.value = true
|
||||
sharePatientData({
|
||||
encounterId: shareForm.value.encounterId,
|
||||
targetSystem: shareForm.value.targetSystem
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('共享请求已提交')
|
||||
loadStats()
|
||||
loadRecords()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '共享失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('共享请求异常')
|
||||
}).finally(() => {
|
||||
shareLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadStats()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.stat-row { margin-bottom: 16px; }
|
||||
.stat-card { background: #fff; border-radius: 8px; padding: 20px; border: 1px solid #ebeef5; display: flex; align-items: center; gap: 16px; }
|
||||
.stat-card.blue { border-left: 4px solid #409eff; }
|
||||
.stat-card.orange { border-left: 4px solid #e6a23c; }
|
||||
.stat-card.green { border-left: 4px solid #67c23a; }
|
||||
.stat-card.red { border-left: 4px solid #f56c6c; }
|
||||
.stat-icon { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #f0f2f5; }
|
||||
.stat-card.blue .stat-icon { color: #409eff; }
|
||||
.stat-card.orange .stat-icon { color: #e6a23c; }
|
||||
.stat-card.green .stat-icon { color: #67c23a; }
|
||||
.stat-card.red .stat-icon { color: #f56c6c; }
|
||||
.stat-info .stat-value { font-size: 24px; font-weight: bold; color: #303133; }
|
||||
.stat-info .stat-label { font-size: 13px; color: #909399; margin-top: 4px; }
|
||||
.card-title { font-weight: bold; font-size: 15px; }
|
||||
</style>
|
||||
152
healthlink-his-ui/src/views/infection/detail/index.vue
Normal file
152
healthlink-his-ui/src/views/infection/detail/index.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="infection-detail-container">
|
||||
<div class="page-header">
|
||||
<span class="tab-title">院感监测统计</span>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<span>查询条件</span>
|
||||
</template>
|
||||
<el-form :model="queryParams" inline>
|
||||
<el-form-item label="科室">
|
||||
<el-input v-model="queryParams.deptId" placeholder="科室ID" clearable style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="queryParams.startDate" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="queryParams.endDate" type="date" value-format="YYYY-MM-DD" placeholder="结束日期" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadData" :loading="loading">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="16" style="margin-bottom: 16px">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #409eff">{{ rateData.totalCases || 0 }}</div>
|
||||
<div class="stat-label">总病例数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #e6a23c">{{ rateData.reportedCases || 0 }}</div>
|
||||
<div class="stat-label">已上报</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #67c23a">{{ rateData.confirmedCases || 0 }}</div>
|
||||
<div class="stat-label">已确认</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #f56c6c">{{ rateData.infectionRate || 0 }}%</div>
|
||||
<div class="stat-label">感染率</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16" style="margin-bottom: 16px">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>按感染类型分布</span></template>
|
||||
<el-table :data="typeList" border stripe size="small">
|
||||
<el-table-column prop="type" label="感染类型" />
|
||||
<el-table-column prop="count" label="病例数" width="100" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>按感染部位分布</span></template>
|
||||
<el-table :data="siteList" border stripe size="small">
|
||||
<el-table-column prop="site" label="感染部位" />
|
||||
<el-table-column prop="count" label="病例数" width="100" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header><span>感染趋势</span></template>
|
||||
<el-table :data="trendData" v-loading="loading" border stripe style="width: 100%">
|
||||
<el-table-column prop="date" label="日期" width="120" />
|
||||
<el-table-column prop="total" label="总计" width="80" />
|
||||
<el-table-column prop="REPORTED" label="已上报" width="80" />
|
||||
<el-table-column prop="CONFIRMED" label="已确认" width="80" />
|
||||
<el-table-column prop="REJECTED" label="已驳回" width="80" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getInfectionRateByDept, getInfectionTrend } from '@/api/infection/detail'
|
||||
|
||||
const loading = ref(false)
|
||||
const queryParams = reactive({
|
||||
deptId: '',
|
||||
startDate: '',
|
||||
endDate: ''
|
||||
})
|
||||
|
||||
const rateData = ref({})
|
||||
const trendData = ref([])
|
||||
|
||||
const typeList = computed(() => {
|
||||
const byType = rateData.value.byType || {}
|
||||
return Object.entries(byType).map(([type, count]) => ({ type, count }))
|
||||
})
|
||||
|
||||
const siteList = computed(() => {
|
||||
const bySite = rateData.value.bySite || {}
|
||||
return Object.entries(bySite).map(([site, count]) => ({ site, count }))
|
||||
})
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {}
|
||||
if (queryParams.deptId) params.deptId = queryParams.deptId
|
||||
if (queryParams.startDate) params.startDate = queryParams.startDate
|
||||
if (queryParams.endDate) params.endDate = queryParams.endDate
|
||||
|
||||
const [rateRes, trendRes] = await Promise.all([
|
||||
getInfectionRateByDept(params),
|
||||
getInfectionTrend({ startDate: queryParams.startDate, endDate: queryParams.endDate })
|
||||
])
|
||||
rateData.value = rateRes.data || {}
|
||||
trendData.value = trendRes.data || []
|
||||
} catch (e) {
|
||||
ElMessage.error('加载失败: ' + (e.message || '未知错误'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.infection-detail-container { padding: 16px; }
|
||||
.page-header { margin-bottom: 16px; }
|
||||
.tab-title { font-size: 18px; font-weight: bold; }
|
||||
.stat-card { text-align: center; padding: 12px 0; }
|
||||
.stat-value { font-size: 28px; font-weight: bold; }
|
||||
.stat-label { font-size: 13px; color: #909399; margin-top: 4px; }
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div class="mr-stats-detail-container">
|
||||
<div class="page-header">
|
||||
<span class="tab-title">病案统计明细</span>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<span>查询条件</span>
|
||||
<el-radio-group v-model="queryMode" @change="resetQuery">
|
||||
<el-radio-button value="dept">按科室</el-radio-button>
|
||||
<el-radio-button value="doctor">按医生</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="queryParams" inline>
|
||||
<el-form-item v-if="queryMode === 'dept'" label="科室ID">
|
||||
<el-input v-model="queryParams.deptId" placeholder="科室ID" clearable style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="医生ID">
|
||||
<el-input v-model="queryParams.doctorId" placeholder="医生ID" clearable style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadData" :loading="loading">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="16" style="margin-bottom: 16px">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #409eff">{{ stats.totalCount || 0 }}</div>
|
||||
<div class="stat-label">病案总数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #e6a23c">{{ formatCost(stats.totalCost) }}</div>
|
||||
<div class="stat-label">总费用</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #67c23a">{{ formatCost(stats.avgCost) }}</div>
|
||||
<div class="stat-label">平均费用</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #909399">{{ stats.avgLosDays || 0 }}天</div>
|
||||
<div class="stat-label">平均住院日</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>按质控状态分布</span></template>
|
||||
<el-table :data="statusList" border stripe size="small">
|
||||
<el-table-column prop="status" label="状态" />
|
||||
<el-table-column prop="count" label="数量" width="100" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>{{ queryMode === 'dept' ? '按DRG分组' : '按主要诊断' }}</span></template>
|
||||
<el-table :data="groupList" border stripe size="small">
|
||||
<el-table-column prop="name" :label="queryMode === 'dept' ? 'DRG分组' : '诊断'" />
|
||||
<el-table-column prop="count" label="数量" width="100" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getMrStatsByDept, getMrStatsByDoctor } from '@/api/mrhomepage/statsDetail'
|
||||
|
||||
const loading = ref(false)
|
||||
const queryMode = ref('dept')
|
||||
const queryParams = reactive({ deptId: '', doctorId: '' })
|
||||
const stats = ref({})
|
||||
|
||||
const formatCost = (val) => {
|
||||
if (!val || val === '0') return '¥0'
|
||||
return '¥' + Number(val).toLocaleString('zh-CN', { minimumFractionDigits: 2 })
|
||||
}
|
||||
|
||||
const STATUS_MAP = {
|
||||
DRAFT: '草稿', SUBMITTED: '已提交', APPROVED: '已审核',
|
||||
REJECTED: '已驳回', CODING: '编码中', COMPLETE: '已完成'
|
||||
}
|
||||
|
||||
const statusList = computed(() => {
|
||||
const byStatus = stats.value.byStatus || {}
|
||||
return Object.entries(byStatus).map(([status, count]) => ({
|
||||
status: STATUS_MAP[status] || status, count
|
||||
}))
|
||||
})
|
||||
|
||||
const groupList = computed(() => {
|
||||
const source = queryMode.value === 'dept' ? (stats.value.byDrg || {}) : (stats.value.byDiagnosis || {})
|
||||
return Object.entries(source).map(([name, count]) => ({ name, count }))
|
||||
})
|
||||
|
||||
const resetQuery = () => {
|
||||
queryParams.deptId = ''
|
||||
queryParams.doctorId = ''
|
||||
stats.value = {}
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {}
|
||||
if (queryMode.value === 'dept') {
|
||||
if (queryParams.deptId) params.deptId = queryParams.deptId
|
||||
const res = await getMrStatsByDept(params)
|
||||
stats.value = res.data || {}
|
||||
} else {
|
||||
if (queryParams.doctorId) params.doctorId = queryParams.doctorId
|
||||
const res = await getMrStatsByDoctor(params)
|
||||
stats.value = res.data || {}
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('加载失败: ' + (e.message || '未知错误'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mr-stats-detail-container { padding: 16px; }
|
||||
.page-header { margin-bottom: 16px; }
|
||||
.tab-title { font-size: 18px; font-weight: bold; }
|
||||
.stat-card { text-align: center; padding: 12px 0; }
|
||||
.stat-value { font-size: 28px; font-weight: bold; }
|
||||
.stat-label { font-size: 13px; color: #909399; margin-top: 4px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user