Compare commits
16 Commits
4bcbeef52f
...
aec389998d
| Author | SHA1 | Date | |
|---|---|---|---|
| aec389998d | |||
| 8b099d94df | |||
| bd90c40c49 | |||
| c682fbb7c8 | |||
| 50a73cc626 | |||
| 5afeece809 | |||
| 4dd5bfeb4f | |||
| 51b3728600 | |||
| d7d7f2a752 | |||
| d5a75083fd | |||
| a1e77e0962 | |||
| 650ebac32c | |||
| 5ad22c3af6 | |||
| 9cef0ac4a7 | |||
| 931a13d05d | |||
| 74d4beeeef |
@@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/healthlink-his/api/v1/anesthesia")
|
||||
@RequestMapping("/api/v1/anesthesia")
|
||||
@Tag(name = "麻醉记录管理")
|
||||
public class AnesthesiaController {
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@Tag(name = "抗菌药物管控")
|
||||
@RestController @RequestMapping("/healthlink-his/api/v1/antibiotic")
|
||||
@RestController @RequestMapping("/api/v1/antibiotic")
|
||||
public class AntibioticController {
|
||||
@Autowired private IAntibioticAppService antibioticAppService;
|
||||
@Operation(summary = "查询药品限制规则") @GetMapping("/rules/{drugCode}")
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/healthlink-his/api/v1/ca-signature")
|
||||
@RequestMapping("/api/v1/ca-signature")
|
||||
@Tag(name = "电子签名管理")
|
||||
public class CaSignatureController {
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@Tag(name = "危急值管理")
|
||||
@RestController
|
||||
@RequestMapping("/healthlink-his/api/v1/critical-value")
|
||||
@RequestMapping("/api/v1/critical-value")
|
||||
public class CriticalValueController {
|
||||
@Autowired private ICriticalValueAppService criticalValueAppService;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
@Tag(name = "患者主索引(EMPI)") @RestController @RequestMapping("/healthlink-his/api/v1/empi")
|
||||
@Tag(name = "患者主索引(EMPI)") @RestController @RequestMapping("/api/v1/empi")
|
||||
public class EmpiController {
|
||||
@Autowired private IEmpiAppService empiAppService;
|
||||
@Operation(summary = "注册患者") @PostMapping("/person")
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/healthlink-his/api/v1/emr")
|
||||
@RequestMapping("/api/v1/emr")
|
||||
@Tag(name = "电子病历结构化")
|
||||
public class StructuredEmrController {
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@Tag(name = "传染病直报") @RestController @RequestMapping("/healthlink-his/api/v1/epidemic")
|
||||
@Tag(name = "传染病直报") @RestController @RequestMapping("/api/v1/epidemic")
|
||||
public class EpidemicController {
|
||||
@Autowired private IEpidemicAppService epidemicAppService;
|
||||
@Operation(summary = "上报") @PostMapping("/report")
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/healthlink-his/api/v1/mr-homepage")
|
||||
@RequestMapping("/api/v1/mr-homepage")
|
||||
@Tag(name = "病案首页管理")
|
||||
public class MrHomepageController {
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ public class NursingAppServiceImpl implements INursingAppService {
|
||||
@Override
|
||||
public NursingAssessment createAssessment(NursingAssessment a) {
|
||||
a.setRiskLevel(calculateRiskLevel(a));
|
||||
a.setDelFlag("0");
|
||||
a.setDeleteFlag("0");
|
||||
assessmentService.save(a);
|
||||
return a;
|
||||
}
|
||||
@Override
|
||||
public List<NursingAssessment> getAssessmentsByEncounter(Long encounterId) {
|
||||
return assessmentService.list(new LambdaQueryWrapper<NursingAssessment>()
|
||||
.eq(NursingAssessment::getEncounterId, encounterId).eq(NursingAssessment::getDelFlag, "0")
|
||||
.eq(NursingAssessment::getEncounterId, encounterId).eq(NursingAssessment::getDeleteFlag, "0")
|
||||
.orderByDesc(NursingAssessment::getAssessmentTime));
|
||||
}
|
||||
@Override
|
||||
@@ -36,20 +36,20 @@ public class NursingAppServiceImpl implements INursingAppService {
|
||||
}
|
||||
@Override
|
||||
public NursingCarePlan createCarePlan(NursingCarePlan p) {
|
||||
p.setDelFlag("0"); carePlanService.save(p); return p;
|
||||
p.setDeleteFlag("0"); carePlanService.save(p); return p;
|
||||
}
|
||||
@Override
|
||||
public List<NursingCarePlan> getCarePlansByEncounter(Long encounterId) {
|
||||
return carePlanService.list(new LambdaQueryWrapper<NursingCarePlan>()
|
||||
.eq(NursingCarePlan::getEncounterId, encounterId).eq(NursingCarePlan::getDelFlag, "0"));
|
||||
.eq(NursingCarePlan::getEncounterId, encounterId).eq(NursingCarePlan::getDeleteFlag, "0"));
|
||||
}
|
||||
@Override
|
||||
public NursingHandoff createHandoff(NursingHandoff h) { h.setDelFlag("0"); handoffService.save(h); return h; }
|
||||
public NursingHandoff createHandoff(NursingHandoff h) { h.setDeleteFlag("0"); handoffService.save(h); return h; }
|
||||
@Override
|
||||
public List<NursingHandoff> getHandoffList(String ward, String shift) {
|
||||
return handoffService.list(new LambdaQueryWrapper<NursingHandoff>()
|
||||
.eq(ward != null, NursingHandoff::getWard, ward)
|
||||
.eq(shift != null, NursingHandoff::getShift, shift)
|
||||
.eq(NursingHandoff::getDelFlag, "0").orderByDesc(NursingHandoff::getHandoffTime));
|
||||
.eq(NursingHandoff::getDeleteFlag, "0").orderByDesc(NursingHandoff::getHandoffTime));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class NursingAssessmentEnhancedController {
|
||||
.eq(StringUtils.hasText(riskLevel), NursingAssessment::getRiskLevel, riskLevel)
|
||||
.eq(encounterId != null, NursingAssessment::getEncounterId, encounterId)
|
||||
.like(StringUtils.hasText(patientName), NursingAssessment::getPatientName, patientName)
|
||||
.eq(NursingAssessment::getDelFlag, "0")
|
||||
.eq(NursingAssessment::getDeleteFlag, "0")
|
||||
.orderByDesc(NursingAssessment::getAssessmentTime);
|
||||
return R.ok(assessmentService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class NursingAssessmentEnhancedController {
|
||||
LambdaQueryWrapper<NursingAssessment> w = new LambdaQueryWrapper<>();
|
||||
w.eq(NursingAssessment::getEncounterId, encounterId)
|
||||
.eq(StringUtils.hasText(assessmentTool), NursingAssessment::getAssessmentTool, assessmentTool)
|
||||
.eq(NursingAssessment::getDelFlag, "0")
|
||||
.eq(NursingAssessment::getDeleteFlag, "0")
|
||||
.orderByDesc(NursingAssessment::getAssessmentTime);
|
||||
return R.ok(assessmentService.list(w));
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public class NursingAssessmentEnhancedController {
|
||||
assessment.setAssessmentTool("BRADEN");
|
||||
assessment.setTotalScore(total);
|
||||
assessment.setRiskLevel(riskLevel);
|
||||
assessment.setDelFlag("0");
|
||||
assessment.setDeleteFlag("0");
|
||||
assessment.setAssessmentTime(new Date());
|
||||
assessmentService.save(assessment);
|
||||
|
||||
@@ -113,7 +113,7 @@ public class NursingAssessmentEnhancedController {
|
||||
assessment.setAssessmentTool("MORSE");
|
||||
assessment.setTotalScore(total);
|
||||
assessment.setRiskLevel(riskLevel);
|
||||
assessment.setDelFlag("0");
|
||||
assessment.setDeleteFlag("0");
|
||||
assessment.setAssessmentTime(new Date());
|
||||
assessmentService.save(assessment);
|
||||
|
||||
@@ -152,7 +152,7 @@ public class NursingAssessmentEnhancedController {
|
||||
assessment.setAssessmentTool("NRS2002");
|
||||
assessment.setTotalScore(total);
|
||||
assessment.setRiskLevel(riskLevel);
|
||||
assessment.setDelFlag("0");
|
||||
assessment.setDeleteFlag("0");
|
||||
assessment.setAssessmentTime(new Date());
|
||||
assessmentService.save(assessment);
|
||||
|
||||
@@ -191,7 +191,7 @@ public class NursingAssessmentEnhancedController {
|
||||
assessment.setAssessmentTool("NRS_PAIN");
|
||||
assessment.setTotalScore(total);
|
||||
assessment.setRiskLevel(riskLevel);
|
||||
assessment.setDelFlag("0");
|
||||
assessment.setDeleteFlag("0");
|
||||
assessment.setAssessmentTime(new Date());
|
||||
assessmentService.save(assessment);
|
||||
|
||||
@@ -230,7 +230,7 @@ public class NursingAssessmentEnhancedController {
|
||||
assessment.setAssessmentTool("TUBE");
|
||||
assessment.setTotalScore(total);
|
||||
assessment.setRiskLevel(riskLevel);
|
||||
assessment.setDelFlag("0");
|
||||
assessment.setDeleteFlag("0");
|
||||
assessment.setAssessmentTime(new Date());
|
||||
assessmentService.save(assessment);
|
||||
|
||||
@@ -295,13 +295,13 @@ public class NursingAssessmentEnhancedController {
|
||||
for (String tool : tools) {
|
||||
LambdaQueryWrapper<NursingAssessment> w = new LambdaQueryWrapper<>();
|
||||
w.eq(NursingAssessment::getAssessmentTool, tool)
|
||||
.eq(NursingAssessment::getDelFlag, "0");
|
||||
.eq(NursingAssessment::getDeleteFlag, "0");
|
||||
long total = assessmentService.count(w);
|
||||
|
||||
LambdaQueryWrapper<NursingAssessment> hw = new LambdaQueryWrapper<>();
|
||||
hw.eq(NursingAssessment::getAssessmentTool, tool)
|
||||
.eq(NursingAssessment::getRiskLevel, "HIGH")
|
||||
.eq(NursingAssessment::getDelFlag, "0");
|
||||
.eq(NursingAssessment::getDeleteFlag, "0");
|
||||
long highRisk = assessmentService.count(hw);
|
||||
|
||||
stats.put(tool + "_total", total);
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
@Tag(name = "护理评估管理")
|
||||
@RestController
|
||||
@RequestMapping("/healthlink-his/api/v1/nursing")
|
||||
@RequestMapping("/api/v1/nursing")
|
||||
public class NursingController {
|
||||
@Autowired private INursingAppService nursingAppService;
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.healthlink.his.web.orderclosedloop.appservice;
|
||||
import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord;
|
||||
import com.healthlink.his.orderclosedloop.domain.OrderExecuteStep;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
public interface IOrderClosedLoopAppService {
|
||||
IPage<OrderExecuteRecord> listRecords(String orderNo, String orderType, String executeStatus, Integer pageNum, Integer pageSize);
|
||||
Map<String, Object> getOrderStatus(Long orderId);
|
||||
void executeOrder(OrderExecuteRecord record);
|
||||
void completeOrder(OrderExecuteRecord record);
|
||||
void cancelOrder(OrderExecuteRecord record);
|
||||
Map<String, Object> getStatistics();
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.healthlink.his.web.orderclosedloop.appservice.impl;
|
||||
import com.healthlink.his.orderclosedloop.domain.*;
|
||||
import com.healthlink.his.orderclosedloop.service.*;
|
||||
import com.healthlink.his.web.orderclosedloop.appservice.IOrderClosedLoopAppService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
@Service
|
||||
public class OrderClosedLoopAppServiceImpl implements IOrderClosedLoopAppService {
|
||||
@Autowired private IOrderExecuteRecordService recordService;
|
||||
@Autowired private IOrderExecuteStepService stepService;
|
||||
|
||||
@Override
|
||||
public IPage<OrderExecuteRecord> listRecords(String orderNo, String orderType, String executeStatus, Integer pageNum, Integer pageSize) {
|
||||
LambdaQueryWrapper<OrderExecuteRecord> w = new LambdaQueryWrapper<>();
|
||||
if (orderNo != null && !orderNo.isEmpty()) w.like(OrderExecuteRecord::getOrderNo, orderNo);
|
||||
if (orderType != null && !orderType.isEmpty()) w.eq(OrderExecuteRecord::getOrderType, orderType);
|
||||
if (executeStatus != null && !executeStatus.isEmpty()) w.eq(OrderExecuteRecord::getExecuteStatus, executeStatus);
|
||||
w.orderByDesc(OrderExecuteRecord::getCreateTime);
|
||||
return recordService.page(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getOrderStatus(Long orderId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
OrderExecuteRecord record = recordService.getById(orderId);
|
||||
if (record == null) {
|
||||
result.put("error", "记录不存在");
|
||||
return result;
|
||||
}
|
||||
result.put("record", record);
|
||||
List<OrderExecuteStep> steps = stepService.list(new LambdaQueryWrapper<OrderExecuteStep>().eq(OrderExecuteStep::getOrderNo, record.getOrderNo()).orderByAsc(OrderExecuteStep::getStepOrder));
|
||||
result.put("steps", steps);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeOrder(OrderExecuteRecord record) {
|
||||
record.setExecuteStatus("executing");
|
||||
record.setExecuteTime(new Date());
|
||||
record.setCurrentStep("1");
|
||||
recordService.save(record);
|
||||
// Create default steps
|
||||
String[] stepNames = {"医师开立", "药师审核", "护士执行", "执行确认"};
|
||||
for (int i = 0; i < stepNames.length; i++) {
|
||||
OrderExecuteStep step = new OrderExecuteStep();
|
||||
step.setOrderNo(record.getOrderNo());
|
||||
step.setStepName(stepNames[i]);
|
||||
step.setStepOrder(i + 1);
|
||||
step.setCompleted(false);
|
||||
step.setTenantId(record.getTenantId() != null ? record.getTenantId() : 0);
|
||||
stepService.save(step);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeOrder(OrderExecuteRecord record) {
|
||||
OrderExecuteRecord existing = recordService.getById(record.getId());
|
||||
if (existing != null) {
|
||||
existing.setExecuteStatus("completed");
|
||||
existing.setCurrentStep("4");
|
||||
existing.setExecutorId(record.getExecutorId());
|
||||
existing.setExecutorName(record.getExecutorName());
|
||||
existing.setExecuteTime(new Date());
|
||||
recordService.updateById(existing);
|
||||
// Complete all steps
|
||||
List<OrderExecuteStep> steps = stepService.list(new LambdaQueryWrapper<OrderExecuteStep>().eq(OrderExecuteStep::getOrderNo, existing.getOrderNo()));
|
||||
for (OrderExecuteStep step : steps) {
|
||||
step.setCompleted(true);
|
||||
step.setExecuteTime(new Date());
|
||||
step.setExecutorId(record.getExecutorId());
|
||||
step.setExecutorName(record.getExecutorName());
|
||||
}
|
||||
stepService.updateBatchById(steps);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelOrder(OrderExecuteRecord record) {
|
||||
OrderExecuteRecord existing = recordService.getById(record.getId());
|
||||
if (existing != null) {
|
||||
existing.setExecuteStatus("cancelled");
|
||||
existing.setCancelReason(record.getCancelReason());
|
||||
existing.setExecutorId(record.getExecutorId());
|
||||
existing.setExecutorName(record.getExecutorName());
|
||||
existing.setExecuteTime(new Date());
|
||||
recordService.updateById(existing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getStatistics() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
long total = recordService.count();
|
||||
long pending = recordService.count(new LambdaQueryWrapper<OrderExecuteRecord>().eq(OrderExecuteRecord::getExecuteStatus, "pending"));
|
||||
long executing = recordService.count(new LambdaQueryWrapper<OrderExecuteRecord>().eq(OrderExecuteRecord::getExecuteStatus, "executing"));
|
||||
long completed = recordService.count(new LambdaQueryWrapper<OrderExecuteRecord>().eq(OrderExecuteRecord::getExecuteStatus, "completed"));
|
||||
long cancelled = recordService.count(new LambdaQueryWrapper<OrderExecuteRecord>().eq(OrderExecuteRecord::getExecuteStatus, "cancelled"));
|
||||
stats.put("total", total);
|
||||
stats.put("pending", pending);
|
||||
stats.put("executing", executing);
|
||||
stats.put("completed", completed);
|
||||
stats.put("cancelled", cancelled);
|
||||
stats.put("completionRate", total > 0 ? Math.round(completed * 100.0 / total) : 0);
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.healthlink.his.web.orderclosedloop.controller;
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord;
|
||||
import com.healthlink.his.web.orderclosedloop.appservice.IOrderClosedLoopAppService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.Map;
|
||||
@Tag(name = "医嘱闭环") @RestController @RequestMapping("/api/v1/order-closed-loop")
|
||||
public class OrderClosedLoopController {
|
||||
@Autowired private IOrderClosedLoopAppService appService;
|
||||
|
||||
@Operation(summary = "医嘱执行记录列表")
|
||||
@GetMapping("/list")
|
||||
public AjaxResult list(@RequestParam(required = false) String orderNo,
|
||||
@RequestParam(required = false) String orderType,
|
||||
@RequestParam(required = false) String executeStatus,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
return AjaxResult.success(appService.listRecords(orderNo, orderType, executeStatus, pageNum, pageSize));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取医嘱执行状态")
|
||||
@GetMapping("/status/{orderId}")
|
||||
public AjaxResult status(@PathVariable Long orderId) {
|
||||
return AjaxResult.success(appService.getOrderStatus(orderId));
|
||||
}
|
||||
|
||||
@Operation(summary = "执行医嘱")
|
||||
@PostMapping("/execute")
|
||||
public AjaxResult execute(@RequestBody OrderExecuteRecord record) {
|
||||
appService.executeOrder(record);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "完成医嘱")
|
||||
@PostMapping("/complete")
|
||||
public AjaxResult complete(@RequestBody OrderExecuteRecord record) {
|
||||
appService.completeOrder(record);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "取消医嘱")
|
||||
@PostMapping("/cancel")
|
||||
public AjaxResult cancel(@RequestBody OrderExecuteRecord record) {
|
||||
appService.cancelOrder(record);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "统计")
|
||||
@GetMapping("/statistics")
|
||||
public AjaxResult statistics() {
|
||||
return AjaxResult.success(appService.getStatistics());
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@Tag(name = "病历质控管理")
|
||||
@RestController
|
||||
@RequestMapping("/healthlink-his/api/v1/emr-quality")
|
||||
@RequestMapping("/api/v1/emr-quality")
|
||||
public class EmrQualityController {
|
||||
@Autowired private IEmrQualityAppService emrQualityAppService;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.Map;
|
||||
* @author system
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/healthlink-his/api/v1/rational-drug")
|
||||
@RequestMapping("/api/v1/rational-drug")
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
@Tag(name = "合理用药管理")
|
||||
|
||||
@@ -79,4 +79,12 @@ public interface IRequestFormManageAppService {
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> withdrawRequestForm(Long requestFormId);
|
||||
|
||||
/**
|
||||
* 根据ID获取申请单详情
|
||||
*
|
||||
* @param id 申请单ID
|
||||
* @return 申请单详情
|
||||
*/
|
||||
RequestFormQueryDto getRequestFormById(Long id);
|
||||
}
|
||||
|
||||
@@ -828,4 +828,22 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
return R.ok("撤回成功");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RequestFormQueryDto getRequestFormById(Long id) {
|
||||
RequestForm form = iRequestFormService.getById(id);
|
||||
if (form == null) {
|
||||
return null;
|
||||
}
|
||||
RequestFormQueryDto dto = new RequestFormQueryDto();
|
||||
dto.setRequestFormId(form.getId());
|
||||
dto.setPrescriptionNo(form.getPrescriptionNo());
|
||||
dto.setName(form.getName());
|
||||
dto.setDescJson(form.getDescJson());
|
||||
dto.setRequesterId(form.getRequesterId());
|
||||
dto.setEncounterId(form.getEncounterId());
|
||||
dto.setStatus(form.getStatus());
|
||||
dto.setCreateTime(form.getCreateTime());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ public class RequestFormManageController {
|
||||
* 分页查询申请单
|
||||
* @return 申请单
|
||||
*/
|
||||
@RequestMapping(value = "/get-page")
|
||||
@RequestMapping(value = "/page")
|
||||
public R<IPage<RequestFormPageDto>> getRequestFormPage(@RequestBody RequestFormDto requestFormDto) {
|
||||
return R.ok(iRequestFormManageAppService.getRequestFormPage(requestFormDto));
|
||||
}
|
||||
@@ -234,4 +234,30 @@ public class RequestFormManageController {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 门诊申请单管理兼容接口 ====================
|
||||
|
||||
/**
|
||||
* 根据ID获取申请单详情
|
||||
*/
|
||||
@GetMapping(value = "/{id}")
|
||||
public R<?> getDetail(@PathVariable Long id) {
|
||||
return R.ok(iRequestFormManageAppService.getRequestFormById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回申请单(路径参数版本,兼容门诊申请单管理页面)
|
||||
*/
|
||||
@PutMapping(value = "/withdraw/{id}")
|
||||
public R<?> withdrawById(@PathVariable Long id) {
|
||||
return iRequestFormManageAppService.withdrawRequestForm(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除申请单(路径参数版本,兼容门诊申请单管理页面)
|
||||
*/
|
||||
@DeleteMapping(value = "/delete/{id}")
|
||||
public R<?> deleteById(@PathVariable Long id) {
|
||||
return iRequestFormManageAppService.deleteRequestForm(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,11 @@ public interface IReviewAppService {
|
||||
List<ReviewRecord> getRecordsByPlan(Long planId);
|
||||
Map<String, Object> getStatistics(String startDate, String endDate);
|
||||
List<Map<String, Object>> getDoctorRanking(String startDate, String endDate);
|
||||
|
||||
com.baomidou.mybatisplus.core.metadata.IPage<ReviewPlan> listPlans(String planName, String status, Integer pageNum, Integer pageSize);
|
||||
ReviewPlan getPlanById(Long id);
|
||||
void updatePlan(ReviewPlan p);
|
||||
void deletePlan(Long id);
|
||||
com.baomidou.mybatisplus.core.metadata.IPage<ReviewRecord> listRecords(String prescriptionNo, String reviewResult, Integer pageNum, Integer pageSize);
|
||||
List<Map<String, Object>> autoScreen(Map<String, Object> params);
|
||||
}
|
||||
|
||||
@@ -12,23 +12,23 @@ public class ReviewAppServiceImpl implements IReviewAppService {
|
||||
@Autowired private IReviewRecordService recordService;
|
||||
|
||||
@Override
|
||||
public ReviewPlan createPlan(ReviewPlan p) { p.setStatus("ACTIVE"); p.setDelFlag("0"); planService.save(p); return p; }
|
||||
public ReviewPlan createPlan(ReviewPlan p) { p.setStatus("ACTIVE"); planService.save(p); return p; }
|
||||
@Override
|
||||
public void submitReview(ReviewRecord r) {
|
||||
r.setDelFlag("0"); r.setReviewTime(new Date()); recordService.save(r);
|
||||
r.setReviewTime(new Date()); recordService.save(r);
|
||||
ReviewPlan plan = planService.getById(r.getPlanId());
|
||||
if (plan != null) { plan.setReviewedCount(plan.getReviewedCount() == null ? 1 : plan.getReviewedCount() + 1); planService.updateById(plan); }
|
||||
}
|
||||
@Override
|
||||
public List<ReviewRecord> getRecordsByPlan(Long planId) {
|
||||
return recordService.list(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getPlanId, planId).eq(ReviewRecord::getDelFlag, "0"));
|
||||
return recordService.list(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getPlanId, planId));
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> getStatistics(String startDate, String endDate) {
|
||||
Map<String, Object> r = new HashMap<>();
|
||||
r.put("totalPlans", planService.count(new LambdaQueryWrapper<ReviewPlan>().eq(ReviewPlan::getDelFlag, "0")));
|
||||
r.put("totalRecords", recordService.count(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getDelFlag, "0")));
|
||||
long unreasonable = recordService.count(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getReviewResult, "UNREASONABLE").eq(ReviewRecord::getDelFlag, "0"));
|
||||
r.put("totalPlans", planService.count(new LambdaQueryWrapper<ReviewPlan>()));
|
||||
r.put("totalRecords", recordService.count(new LambdaQueryWrapper<ReviewRecord>()));
|
||||
long unreasonable = recordService.count(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getReviewResult, "UNREASONABLE"));
|
||||
r.put("unreasonableCount", unreasonable);
|
||||
long total = r.get("totalRecords") != null ? (long) r.get("totalRecords") : 0;
|
||||
r.put("reasonableRate", total > 0 ? Math.round((total - unreasonable) * 100.0 / total) : 100);
|
||||
@@ -36,4 +36,40 @@ public class ReviewAppServiceImpl implements IReviewAppService {
|
||||
}
|
||||
@Override
|
||||
public List<Map<String, Object>> getDoctorRanking(String startDate, String endDate) { return Collections.emptyList(); }
|
||||
|
||||
@Override
|
||||
public com.baomidou.mybatisplus.core.metadata.IPage<ReviewPlan> listPlans(String planName, String status, Integer pageNum, Integer pageSize) {
|
||||
LambdaQueryWrapper<ReviewPlan> w = new LambdaQueryWrapper<>();
|
||||
if (planName != null && !planName.isEmpty()) w.like(ReviewPlan::getPlanName, planName);
|
||||
if (status != null && !status.isEmpty()) w.eq(ReviewPlan::getStatus, status);
|
||||
w.orderByDesc(ReviewPlan::getCreateTime);
|
||||
return planService.page(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewPlan getPlanById(Long id) { return planService.getById(id); }
|
||||
|
||||
@Override
|
||||
public void updatePlan(ReviewPlan p) { planService.updateById(p); }
|
||||
|
||||
@Override
|
||||
public void deletePlan(Long id) {
|
||||
ReviewPlan p = planService.getById(id);
|
||||
if (p != null) { planService.removeById(id); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.baomidou.mybatisplus.core.metadata.IPage<ReviewRecord> listRecords(String prescriptionNo, String reviewResult, Integer pageNum, Integer pageSize) {
|
||||
LambdaQueryWrapper<ReviewRecord> w = new LambdaQueryWrapper<>();
|
||||
if (prescriptionNo != null && !prescriptionNo.isEmpty()) w.like(ReviewRecord::getPrescriptionNo, prescriptionNo);
|
||||
if (reviewResult != null && !reviewResult.isEmpty()) w.eq(ReviewRecord::getReviewResult, reviewResult);
|
||||
w.orderByDesc(ReviewRecord::getReviewTime);
|
||||
return recordService.page(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> autoScreen(Map<String, Object> params) {
|
||||
// TODO: 自动筛查逻辑 - 基于规则库筛查不合理处方
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@Tag(name = "处方点评") @RestController @RequestMapping("/healthlink-his/api/v1/review")
|
||||
@Tag(name = "处方点评") @RestController @RequestMapping("/api/v1/review")
|
||||
public class ReviewController {
|
||||
@Autowired private IReviewAppService reviewAppService;
|
||||
@Operation(summary = "创建点评计划") @PostMapping("/plan")
|
||||
@@ -17,4 +17,55 @@ public class ReviewController {
|
||||
public AjaxResult records(@PathVariable Long planId) { return AjaxResult.success(reviewAppService.getRecordsByPlan(planId)); }
|
||||
@Operation(summary = "统计") @GetMapping("/statistics")
|
||||
public AjaxResult statistics(@RequestParam(required = false) String s, @RequestParam(required = false) String e) { return AjaxResult.success(reviewAppService.getStatistics(s, e)); }
|
||||
|
||||
@Operation(summary = "查询计划列表")
|
||||
@GetMapping("/plans")
|
||||
public AjaxResult listPlans(@RequestParam(required = false) String planName,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
return AjaxResult.success(reviewAppService.listPlans(planName, status, pageNum, pageSize));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询计划详情")
|
||||
@GetMapping("/plan/{id}")
|
||||
public AjaxResult getPlan(@PathVariable Long id) {
|
||||
return AjaxResult.success(reviewAppService.getPlanById(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "修改计划")
|
||||
@PutMapping("/plan")
|
||||
public AjaxResult updatePlan(@RequestBody ReviewPlan p) {
|
||||
reviewAppService.updatePlan(p);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除计划")
|
||||
@DeleteMapping("/plan/{id}")
|
||||
public AjaxResult deletePlan(@PathVariable Long id) {
|
||||
reviewAppService.deletePlan(id);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "查询点评记录列表")
|
||||
@GetMapping("/records")
|
||||
public AjaxResult listRecords(@RequestParam(required = false) String prescriptionNo,
|
||||
@RequestParam(required = false) String reviewResult,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
return AjaxResult.success(reviewAppService.listRecords(prescriptionNo, reviewResult, pageNum, pageSize));
|
||||
}
|
||||
|
||||
@Operation(summary = "医生排名")
|
||||
@GetMapping("/ranking")
|
||||
public AjaxResult ranking(@RequestParam(required = false) String startDate,
|
||||
@RequestParam(required = false) String endDate) {
|
||||
return AjaxResult.success(reviewAppService.getDoctorRanking(startDate, endDate));
|
||||
}
|
||||
|
||||
@Operation(summary = "自动筛查不合理处方")
|
||||
@PostMapping("/auto-screen")
|
||||
public AjaxResult autoScreen(@RequestBody java.util.Map<String, Object> params) {
|
||||
return AjaxResult.success(reviewAppService.autoScreen(params));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,22 +15,22 @@ public class TcmAppServiceImpl implements ITcmAppService {
|
||||
public List<TcmPrescription> getPrescriptions(String type) {
|
||||
return prescriptionService.list(new LambdaQueryWrapper<TcmPrescription>()
|
||||
.eq(type != null, TcmPrescription::getPrescriptionType, type)
|
||||
.eq(TcmPrescription::getEnabled, "1").eq(TcmPrescription::getDelFlag, "0"));
|
||||
.eq(TcmPrescription::getEnabled, "1").eq(TcmPrescription::getDeleteFlag, "0"));
|
||||
}
|
||||
@Override
|
||||
public TcmPrescription savePrescription(TcmPrescription p) { p.setDelFlag("0"); p.setEnabled("1"); prescriptionService.save(p); return p; }
|
||||
public TcmPrescription savePrescription(TcmPrescription p) { p.setDeleteFlag("0"); p.setEnabled("1"); prescriptionService.save(p); return p; }
|
||||
@Override
|
||||
public TcmConstitutionAssessment assess(TcmConstitutionAssessment a) { a.setDelFlag("0"); assessmentService.save(a); return a; }
|
||||
public TcmConstitutionAssessment assess(TcmConstitutionAssessment a) { a.setDeleteFlag("0"); assessmentService.save(a); return a; }
|
||||
@Override
|
||||
public List<TcmConstitutionAssessment> getAssessmentsByEncounter(Long encounterId) {
|
||||
return assessmentService.list(new LambdaQueryWrapper<TcmConstitutionAssessment>()
|
||||
.eq(TcmConstitutionAssessment::getEncounterId, encounterId).eq(TcmConstitutionAssessment::getDelFlag, "0"));
|
||||
.eq(TcmConstitutionAssessment::getEncounterId, encounterId).eq(TcmConstitutionAssessment::getDeleteFlag, "0"));
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> getStatistics() {
|
||||
Map<String, Object> r = new HashMap<>();
|
||||
r.put("totalPrescriptions", prescriptionService.count(new LambdaQueryWrapper<TcmPrescription>().eq(TcmPrescription::getDelFlag, "0")));
|
||||
r.put("totalAssessments", assessmentService.count(new LambdaQueryWrapper<TcmConstitutionAssessment>().eq(TcmConstitutionAssessment::getDelFlag, "0")));
|
||||
r.put("totalPrescriptions", prescriptionService.count(new LambdaQueryWrapper<TcmPrescription>().eq(TcmPrescription::getDeleteFlag, "0")));
|
||||
r.put("totalAssessments", assessmentService.count(new LambdaQueryWrapper<TcmConstitutionAssessment>().eq(TcmConstitutionAssessment::getDeleteFlag, "0")));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
@Tag(name = "壮医中医特色") @RestController @RequestMapping("/healthlink-his/api/v1/tcm")
|
||||
@Tag(name = "壮医中医特色") @RestController @RequestMapping("/api/v1/tcm")
|
||||
public class TcmController {
|
||||
@Autowired private ITcmAppService tcmAppService;
|
||||
@Operation(summary = "中医方剂列表") @GetMapping("/prescriptions")
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
-- 处方点评计划表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.review_plan (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
plan_name VARCHAR(200) NOT NULL,
|
||||
review_type VARCHAR(50),
|
||||
department_ids TEXT,
|
||||
doctor_ids TEXT,
|
||||
dept_name VARCHAR(100),
|
||||
target_count INTEGER DEFAULT 50,
|
||||
sample_count INTEGER,
|
||||
reviewed_count INTEGER DEFAULT 0,
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
remark TEXT,
|
||||
del_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- 处方点评记录表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.review_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
plan_id BIGINT,
|
||||
prescription_id BIGINT,
|
||||
prescription_no VARCHAR(50),
|
||||
encounter_id BIGINT,
|
||||
patient_id BIGINT,
|
||||
patient_name VARCHAR(100),
|
||||
doctor_id BIGINT,
|
||||
doctor_name VARCHAR(100),
|
||||
department_name VARCHAR(100),
|
||||
review_result VARCHAR(20),
|
||||
problem_type VARCHAR(50),
|
||||
problem_detail TEXT,
|
||||
unreasonable_type VARCHAR(200),
|
||||
comment TEXT,
|
||||
reviewer_id BIGINT,
|
||||
reviewer_name VARCHAR(100),
|
||||
review_time TIMESTAMP,
|
||||
del_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_review_plan_status ON healthlink_his.review_plan(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_review_record_plan ON healthlink_his.review_record(plan_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_review_record_result ON healthlink_his.review_record(review_result);
|
||||
@@ -0,0 +1,41 @@
|
||||
-- 医嘱执行记录表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.order_execute_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
order_no VARCHAR(50) NOT NULL,
|
||||
encounter_id BIGINT,
|
||||
patient_id BIGINT,
|
||||
patient_name VARCHAR(100),
|
||||
order_type VARCHAR(20) NOT NULL,
|
||||
order_content TEXT,
|
||||
current_step VARCHAR(50),
|
||||
execute_status VARCHAR(20) DEFAULT 'pending',
|
||||
executor_id BIGINT,
|
||||
executor_name VARCHAR(100),
|
||||
execute_time TIMESTAMP,
|
||||
cancel_reason TEXT,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- 医嘱执行步骤表(闭环流程)
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.order_execute_step (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
order_no VARCHAR(50) NOT NULL,
|
||||
step_name VARCHAR(100) NOT NULL,
|
||||
step_order INTEGER NOT NULL,
|
||||
completed BOOLEAN DEFAULT FALSE,
|
||||
executor_id BIGINT,
|
||||
executor_name VARCHAR(100),
|
||||
execute_time TIMESTAMP,
|
||||
remark TEXT,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_order_exec_order_no ON healthlink_his.order_execute_record(order_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_order_exec_status ON healthlink_his.order_execute_record(execute_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_order_step_order_no ON healthlink_his.order_execute_step(order_no);
|
||||
@@ -0,0 +1,166 @@
|
||||
-- 中医方剂表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.tcm_prescription (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
prescription_name VARCHAR(200) NOT NULL,
|
||||
prescription_type VARCHAR(50),
|
||||
herbs TEXT,
|
||||
dosage VARCHAR(500),
|
||||
usage VARCHAR(500),
|
||||
indication TEXT,
|
||||
source VARCHAR(200),
|
||||
enabled CHAR(1) DEFAULT '1',
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- 中医体质辨识表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.tcm_constitution_assessment (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
encounter_id BIGINT,
|
||||
patient_id BIGINT,
|
||||
constitution_type VARCHAR(50),
|
||||
score INTEGER,
|
||||
recommendation TEXT,
|
||||
assessor_id BIGINT,
|
||||
assessment_time TIMESTAMP,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- ESB消息表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.sys_esb_message (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
message_id VARCHAR(100) NOT NULL,
|
||||
message_type VARCHAR(50),
|
||||
source_system VARCHAR(100),
|
||||
target_system VARCHAR(100),
|
||||
message_content TEXT,
|
||||
message_format VARCHAR(50),
|
||||
status VARCHAR(20) DEFAULT 'PENDING',
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
error_message TEXT,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- ESB服务注册表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.sys_esb_service_registry (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
service_name VARCHAR(200) NOT NULL,
|
||||
service_version VARCHAR(50),
|
||||
service_endpoint VARCHAR(500),
|
||||
service_description TEXT,
|
||||
service_status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
protocol VARCHAR(50),
|
||||
timeout_ms INTEGER DEFAULT 5000,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- ESB监控统计表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.esb_monitor_stats (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
stat_hour TIMESTAMP,
|
||||
source_system VARCHAR(100),
|
||||
target_system VARCHAR(100),
|
||||
total_count INTEGER DEFAULT 0,
|
||||
success_count INTEGER DEFAULT 0,
|
||||
fail_count INTEGER DEFAULT 0,
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
avg_duration_ms INTEGER DEFAULT 0,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ESB死信队列表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.esb_dead_letter (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
message_id VARCHAR(100) NOT NULL,
|
||||
source_system VARCHAR(100),
|
||||
target_system VARCHAR(100),
|
||||
message_type VARCHAR(50),
|
||||
message_content TEXT,
|
||||
error_message TEXT,
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
max_retry INTEGER DEFAULT 3,
|
||||
first_fail_time TIMESTAMP,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- ESB CDA文档表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.esb_cda_document (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
document_type VARCHAR(50),
|
||||
document_title VARCHAR(200),
|
||||
encounter_id BIGINT,
|
||||
patient_id BIGINT,
|
||||
cda_xml TEXT,
|
||||
status VARCHAR(20) DEFAULT 'DRAFT',
|
||||
version_id INTEGER DEFAULT 1,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- ESB编码映射表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.esb_code_mapping (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
source_system VARCHAR(100),
|
||||
source_code VARCHAR(100),
|
||||
target_system VARCHAR(100),
|
||||
target_code VARCHAR(100),
|
||||
mapping_type VARCHAR(50),
|
||||
description TEXT,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ESB FHIR资源表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.esb_fhir_resource (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
resource_type VARCHAR(50),
|
||||
resource_id VARCHAR(100),
|
||||
encounter_id BIGINT,
|
||||
patient_id BIGINT,
|
||||
resource_json TEXT,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
version_id INTEGER DEFAULT 1,
|
||||
tenant_id INTEGER DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX IF NOT EXISTS idx_tcm_prescription_type ON healthlink_his.tcm_prescription(prescription_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_tcm_constitution_encounter ON healthlink_his.tcm_constitution_assessment(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_esb_message_status ON healthlink_his.sys_esb_message(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_esb_message_source ON healthlink_his.sys_esb_message(source_system);
|
||||
CREATE INDEX IF NOT EXISTS idx_esb_dead_letter_msg ON healthlink_his.esb_dead_letter(message_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_esb_monitor_hour ON healthlink_his.esb_monitor_stats(stat_hour);
|
||||
@@ -3,15 +3,28 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import java.util.Date;
|
||||
@Data @TableName("nursing_assessment") @Accessors(chain = true) @EqualsAndHashCode(callSuper = false)
|
||||
|
||||
@Data
|
||||
@TableName("nursing_assessment")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class NursingAssessment extends HisBaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private Long encounterId; private Long patientId; private String patientName;
|
||||
private Long assessorId; private String assessorName;
|
||||
private String assessmentType; private String assessmentTool;
|
||||
private Integer totalScore; private String riskLevel;
|
||||
private String itemScores; private String detail;
|
||||
private Date assessmentTime; private String delFlag;
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long encounterId;
|
||||
private Long patientId;
|
||||
private String patientName;
|
||||
private Long assessorId;
|
||||
private String assessorName;
|
||||
private String assessmentType;
|
||||
private String assessmentTool;
|
||||
private Integer totalScore;
|
||||
private String riskLevel;
|
||||
private String itemScores;
|
||||
private String detail;
|
||||
private Date assessmentTime;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.healthlink.his.orderclosedloop.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@TableName("order_execute_record")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OrderExecuteRecord extends HisBaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
private String orderNo;
|
||||
private Long encounterId;
|
||||
private Long patientId;
|
||||
private String patientName;
|
||||
private String orderType;
|
||||
private String orderContent;
|
||||
private String currentStep;
|
||||
private String executeStatus;
|
||||
private Long executorId;
|
||||
private String executorName;
|
||||
private Date executeTime;
|
||||
private String cancelReason;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.healthlink.his.orderclosedloop.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import lombok.Data;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@TableName("order_execute_step")
|
||||
public class OrderExecuteStep {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
private String orderNo;
|
||||
private String stepName;
|
||||
private Integer stepOrder;
|
||||
private Boolean completed;
|
||||
private Long executorId;
|
||||
private String executorName;
|
||||
private Date executeTime;
|
||||
private String remark;
|
||||
private Integer tenantId;
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.healthlink.his.orderclosedloop.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface OrderExecuteRecordMapper extends BaseMapper<OrderExecuteRecord> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.healthlink.his.orderclosedloop.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.orderclosedloop.domain.OrderExecuteStep;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface OrderExecuteStepMapper extends BaseMapper<OrderExecuteStep> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.orderclosedloop.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord;
|
||||
public interface IOrderExecuteRecordService extends IService<OrderExecuteRecord> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.orderclosedloop.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.orderclosedloop.domain.OrderExecuteStep;
|
||||
public interface IOrderExecuteStepService extends IService<OrderExecuteStep> {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.orderclosedloop.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord;
|
||||
import com.healthlink.his.orderclosedloop.mapper.OrderExecuteRecordMapper;
|
||||
import com.healthlink.his.orderclosedloop.service.IOrderExecuteRecordService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class OrderExecuteRecordServiceImpl extends ServiceImpl<OrderExecuteRecordMapper, OrderExecuteRecord> implements IOrderExecuteRecordService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.orderclosedloop.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.orderclosedloop.domain.OrderExecuteStep;
|
||||
import com.healthlink.his.orderclosedloop.mapper.OrderExecuteStepMapper;
|
||||
import com.healthlink.his.orderclosedloop.service.IOrderExecuteStepService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class OrderExecuteStepServiceImpl extends ServiceImpl<OrderExecuteStepMapper, OrderExecuteStep> implements IOrderExecuteStepService {}
|
||||
@@ -12,5 +12,5 @@ public class ReviewPlan extends HisBaseEntity {
|
||||
private String departmentIds; private String doctorIds;
|
||||
private Date startDate; private Date endDate;
|
||||
private Integer sampleCount; private Integer reviewedCount;
|
||||
private String status; private String delFlag;
|
||||
private String deptName; private Integer targetCount; private String remark; private String status;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class ReviewRecord extends HisBaseEntity {
|
||||
private Long planId; private Long prescriptionId; private Long encounterId;
|
||||
private Long patientId; private String patientName;
|
||||
private Long doctorId; private String doctorName; private String departmentName;
|
||||
private String reviewResult; private String problemType; private String problemDetail;
|
||||
private String reviewResult; private String problemType; private String problemDetail; private String unreasonableType; private String comment; private String prescriptionNo;
|
||||
private Long reviewerId; private String reviewerName; private Date reviewTime;
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,23 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import java.util.Date;
|
||||
@Data @TableName("tcm_constitution_assessment") @Accessors(chain = true) @EqualsAndHashCode(callSuper = false)
|
||||
|
||||
@Data
|
||||
@TableName("tcm_constitution_assessment")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TcmConstitutionAssessment extends HisBaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private Long encounterId; private Long patientId;
|
||||
private String constitutionType; private Integer score;
|
||||
private String recommendation; private Long assessorId;
|
||||
private Date assessmentTime; private String delFlag;
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long encounterId;
|
||||
private Long patientId;
|
||||
private String constitutionType;
|
||||
private Integer score;
|
||||
private String recommendation;
|
||||
private Long assessorId;
|
||||
private Date assessmentTime;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
package com.healthlink.his.tcm.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors;
|
||||
@Data @TableName("tcm_prescription") @Accessors(chain = true) @EqualsAndHashCode(callSuper = false)
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
@TableName("tcm_prescription")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TcmPrescription extends HisBaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private String prescriptionName; private String prescriptionType;
|
||||
private String herbs; private String dosage; private String usage;
|
||||
private String indication; private String source; private String enabled; private String delFlag;
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private String prescriptionName;
|
||||
private String prescriptionType;
|
||||
private String herbs;
|
||||
private String dosage;
|
||||
@TableField("\"usage\"")
|
||||
private String usage;
|
||||
private String indication;
|
||||
private String source;
|
||||
private String enabled;
|
||||
}
|
||||
|
||||
22
healthlink-his-ui/src/api/antibiotic.js
Normal file
22
healthlink-his-ui/src/api/antibiotic.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// ==================== 抗菌药物管控 ====================
|
||||
export function getRules(drugCode) {
|
||||
return request({ url: `/healthlink-his/api/v1/antibiotic/rules/${drugCode}`, method: 'get' })
|
||||
}
|
||||
|
||||
export function checkRestriction(drugCode, doctorLevel) {
|
||||
return request({ url: '/healthlink-his/api/v1/antibiotic/check-restriction', method: 'get', params: { drugCode, doctorLevel } })
|
||||
}
|
||||
|
||||
export function requestApproval(data) {
|
||||
return request({ url: '/healthlink-his/api/v1/antibiotic/approval', method: 'post', data })
|
||||
}
|
||||
|
||||
export function approve(id, approverId, approverName, result) {
|
||||
return request({ url: `/healthlink-his/api/v1/antibiotic/approval/${id}`, method: 'put', params: { approverId, approverName, result } })
|
||||
}
|
||||
|
||||
export function getStatistics(startDate, endDate) {
|
||||
return request({ url: '/healthlink-his/api/v1/antibiotic/statistics', method: 'get', params: { startDate, endDate } })
|
||||
}
|
||||
30
healthlink-his-ui/src/api/orderclosedloop.js
Normal file
30
healthlink-his-ui/src/api/orderclosedloop.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// ==================== 医嘱闭环 ====================
|
||||
export function listOrderExecuteRecord(params) {
|
||||
return request({ url: '/api/v1/order-closed-loop/list', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getOrderClosedLoopStatus(orderId) {
|
||||
return request({ url: `/api/v1/order-closed-loop/status/${orderId}`, method: 'get' })
|
||||
}
|
||||
|
||||
export function getOrderStatistics() {
|
||||
return request({ url: '/api/v1/order-closed-loop/statistics', method: 'get' })
|
||||
}
|
||||
|
||||
export function executeOrder(data) {
|
||||
return request({ url: '/api/v1/order-closed-loop/execute', method: 'post', data })
|
||||
}
|
||||
|
||||
export function completeOrder(data) {
|
||||
return request({ url: '/api/v1/order-closed-loop/complete', method: 'post', data })
|
||||
}
|
||||
|
||||
export function cancelOrder(data) {
|
||||
return request({ url: '/api/v1/order-closed-loop/cancel', method: 'post', data })
|
||||
}
|
||||
|
||||
export function getClosedLoopStatistics(params) {
|
||||
return request({ url: '/api/v1/order-closed-loop/statistics', method: 'get', params })
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
export function runtimeCheck(encounterId) { return request({ url: '/healthlink-his/api/v1/emr-quality/runtime-check/' + encounterId, method: 'post' }) }
|
||||
export function terminalCheck(encounterId) { return request({ url: '/healthlink-his/api/v1/emr-quality/terminal-check/' + encounterId, method: 'post' }) }
|
||||
export function getScores(encounterId) { return request({ url: '/healthlink-his/api/v1/emr-quality/score/' + encounterId, method: 'get' }) }
|
||||
export function getDefects(encounterId) { return request({ url: '/healthlink-his/api/v1/emr-quality/defect/' + encounterId, method: 'get' }) }
|
||||
export function getDefectStatistics() { return request({ url: '/healthlink-his/api/v1/emr-quality/defect-statistics', method: 'get' }) }
|
||||
export function getCompletionRate() { return request({ url: '/healthlink-his/api/v1/emr-quality/completion-rate', method: 'get' }) }
|
||||
export function runtimeCheck(encounterId) { return request({ url: '/api/v1/emr-quality/runtime-check/' + encounterId, method: 'post' }) }
|
||||
export function terminalCheck(encounterId) { return request({ url: '/api/v1/emr-quality/terminal-check/' + encounterId, method: 'post' }) }
|
||||
export function getScores(encounterId) { return request({ url: '/api/v1/emr-quality/score/' + encounterId, method: 'get' }) }
|
||||
export function getDefects(encounterId) { return request({ url: '/api/v1/emr-quality/defect/' + encounterId, method: 'get' }) }
|
||||
export function getDefectStatistics() { return request({ url: '/api/v1/emr-quality/defect-statistics', method: 'get' }) }
|
||||
export function getCompletionRate() { return request({ url: '/api/v1/emr-quality/completion-rate', method: 'get' }) }
|
||||
export function getQualityStatistics(params) { return request({ url: '/api/v1/emr-quality/defect-statistics', method: 'get', params }) }
|
||||
|
||||
52
healthlink-his-ui/src/api/rationaldrug.js
Normal file
52
healthlink-his-ui/src/api/rationaldrug.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// ==================== 处方审核 ====================
|
||||
export function auditPrescription(data) {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/audit', method: 'post', data })
|
||||
}
|
||||
|
||||
export function batchAudit(prescriptionIds) {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/batch-audit', method: 'post', data: prescriptionIds })
|
||||
}
|
||||
|
||||
export function getAuditStatistics() {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/statistics', method: 'get' })
|
||||
}
|
||||
|
||||
export function getAuditTrend(startDate) {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/trend', method: 'get', params: { startDate } })
|
||||
}
|
||||
|
||||
export function getAuditLog(encounterId) {
|
||||
return request({ url: `/healthlink-his/api/v1/rational-drug/audit-log/${encounterId}`, method: 'get' })
|
||||
}
|
||||
|
||||
// ==================== 配伍禁忌 ====================
|
||||
export function checkInteraction(drugCodes) {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/check-interaction', method: 'post', data: drugCodes })
|
||||
}
|
||||
|
||||
export function listInteractionRules(params) {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/interaction-rules', method: 'get', params })
|
||||
}
|
||||
|
||||
export function addInteractionRule(data) {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/interaction-rules', method: 'post', data })
|
||||
}
|
||||
|
||||
export function updateInteractionRule(data) {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/interaction-rules', method: 'put', data })
|
||||
}
|
||||
|
||||
export function delInteractionRule(id) {
|
||||
return request({ url: `/healthlink-his/api/v1/rational-drug/interaction-rules/${id}`, method: 'delete' })
|
||||
}
|
||||
|
||||
// ==================== 剂量规则 ====================
|
||||
export function listDosageRules(params) {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/dosage-rules', method: 'get', params })
|
||||
}
|
||||
|
||||
export function checkDosage(drugCode, dosage, population) {
|
||||
return request({ url: '/healthlink-his/api/v1/rational-drug/check-dosage', method: 'get', params: { drugCode, dosage, population } })
|
||||
}
|
||||
46
healthlink-his-ui/src/api/review.js
Normal file
46
healthlink-his-ui/src/api/review.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// ==================== 处方点评 ====================
|
||||
export function createPlan(data) {
|
||||
return request({ url: '/api/v1/review/plan', method: 'post', data })
|
||||
}
|
||||
|
||||
export function listPlans(params) {
|
||||
return request({ url: '/api/v1/review/plans', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getPlanDetail(id) {
|
||||
return request({ url: `/api/v1/review/plan/${id}`, method: 'get' })
|
||||
}
|
||||
|
||||
export function updatePlan(data) {
|
||||
return request({ url: '/api/v1/review/plan', method: 'put', data })
|
||||
}
|
||||
|
||||
export function deletePlan(id) {
|
||||
return request({ url: `/api/v1/review/plan/${id}`, method: 'delete' })
|
||||
}
|
||||
|
||||
export function submitReview(data) {
|
||||
return request({ url: '/api/v1/review/record', method: 'post', data })
|
||||
}
|
||||
|
||||
export function getRecordsByPlan(planId) {
|
||||
return request({ url: `/api/v1/review/records/${planId}`, method: 'get' })
|
||||
}
|
||||
|
||||
export function listRecords(params) {
|
||||
return request({ url: '/api/v1/review/records', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getStatistics(params) {
|
||||
return request({ url: '/api/v1/review/statistics', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getDoctorRanking(params) {
|
||||
return request({ url: '/api/v1/review/ranking', method: 'get', params })
|
||||
}
|
||||
|
||||
export function autoScreen(data) {
|
||||
return request({ url: '/api/v1/review/auto-screen', method: 'post', data })
|
||||
}
|
||||
@@ -147,7 +147,7 @@ export const constantRoutes = [
|
||||
|
||||
// 动态路由,基于用户权限动态去加载
|
||||
export const dynamicRoutes = [
|
||||
|
||||
// ===== 系统管理子路由(含动态参数,无法由后端菜单生成)=====
|
||||
{
|
||||
path: '/system/tenant-user',
|
||||
component: Layout,
|
||||
@@ -204,48 +204,7 @@ export const dynamicRoutes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/monitor',
|
||||
component: Layout,
|
||||
redirect: '/monitor/operlog',
|
||||
name: 'Monitor',
|
||||
meta: {title: '系统监控', icon: 'monitor'},
|
||||
children: [
|
||||
{
|
||||
path: 'operlog',
|
||||
component: () => import('@/views/monitor/operlog/index.vue'),
|
||||
name: 'Operlog',
|
||||
meta: {title: '操作日志', icon: 'operlog', permissions: ['monitor:operlog:list']}
|
||||
},
|
||||
{
|
||||
path: 'logininfor',
|
||||
component: () => import('@/views/monitor/logininfor/index.vue'),
|
||||
name: 'Logininfor',
|
||||
meta: {title: '登录日志', icon: 'logininfor', permissions: ['monitor:logininfor:list']}
|
||||
},
|
||||
{
|
||||
path: 'job',
|
||||
component: () => import('@/views/monitor/job/index.vue'),
|
||||
name: 'Job',
|
||||
meta: {title: '定时任务', icon: 'job', permissions: ['monitor:job:list']}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/tool',
|
||||
component: Layout,
|
||||
redirect: '/tool/gen',
|
||||
name: 'Tool',
|
||||
meta: {title: '系统工具', icon: 'tool'},
|
||||
children: [
|
||||
{
|
||||
path: 'gen',
|
||||
component: () => import('@/views/tool/gen/index.vue'),
|
||||
name: 'Gen',
|
||||
meta: {title: '代码生成', icon: 'gen', permissions: ['tool:gen:list']}
|
||||
}
|
||||
]
|
||||
},
|
||||
// ===== 监控/工具子路由(含动态参数)=====
|
||||
{
|
||||
path: '/monitor/job-log',
|
||||
component: Layout,
|
||||
@@ -274,6 +233,7 @@ export const dynamicRoutes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
// ===== 帮助中心 =====
|
||||
{
|
||||
path: '/help-center',
|
||||
component: Layout,
|
||||
@@ -290,41 +250,7 @@ export const dynamicRoutes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/doctorstation',
|
||||
component: Layout,
|
||||
redirect: '/doctorstation/index',
|
||||
name: 'DoctorStation',
|
||||
meta: {title: '医生工作站', icon: 'operation'},
|
||||
children: [
|
||||
{
|
||||
path: 'pending-emr',
|
||||
component: () => import('@/views/doctorstation/pendingEmr.vue'),
|
||||
name: 'PendingEmr',
|
||||
meta: {title: '待写病历', icon: 'document', permissions: ['doctorstation:pending-emr:view']}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/features',
|
||||
component: Layout,
|
||||
name: 'Features',
|
||||
meta: {title: '全部功能', icon: 'menu'},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/features/index.vue'),
|
||||
name: 'FeaturesIndex',
|
||||
meta: {title: '功能列表', icon: 'menu'}
|
||||
},
|
||||
{
|
||||
path: 'config',
|
||||
component: () => import('@/views/features/config.vue'),
|
||||
name: 'FeaturesConfig',
|
||||
meta: {title: '功能配置', icon: 'setting'}
|
||||
}
|
||||
]
|
||||
},
|
||||
// ===== 待办事项 =====
|
||||
{
|
||||
path: '/todo',
|
||||
component: Layout,
|
||||
@@ -339,121 +265,30 @@ export const dynamicRoutes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
// ===== 功能特性 =====
|
||||
{
|
||||
path: '/appoinmentmanage',
|
||||
path: '/features',
|
||||
component: Layout,
|
||||
name: 'AppoinmentManage',
|
||||
meta: {title: '预约管理', icon: 'appointment'},
|
||||
name: 'Features',
|
||||
meta: {title: '功能特性', icon: 'feature'},
|
||||
children: [
|
||||
{
|
||||
path: 'deptManage',
|
||||
component: () => import('@/views/appoinmentmanage/deptManage/index.vue'),
|
||||
name: 'DeptManage',
|
||||
meta: {title: '科室排班管理', icon: 'calendar'}
|
||||
path: '',
|
||||
component: () => import('@/views/features/index.vue'),
|
||||
name: 'FeaturesIndex',
|
||||
meta: {title: '功能列表', icon: 'feature'}
|
||||
},
|
||||
{
|
||||
path: 'config',
|
||||
component: () => import('@/views/features/config.vue'),
|
||||
name: 'FeaturesConfig',
|
||||
meta: {title: '功能配置', icon: 'setting'}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/clinicmanagement',
|
||||
component: Layout,
|
||||
name: 'ClinicManagement',
|
||||
meta: {title: '门诊管理', icon: 'operation'},
|
||||
children: [
|
||||
{
|
||||
path: 'dayEnd',
|
||||
component: () => import('@/views/clinicmanagement/dayEnd/index.vue'),
|
||||
name: 'DayEnd',
|
||||
meta: {title: '门诊日结', icon: 'document'}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/consultationmanagement',
|
||||
component: Layout,
|
||||
name: 'ConsultationManagement',
|
||||
meta: {title: '会诊管理', icon: 'operation'},
|
||||
children: [
|
||||
{
|
||||
path: 'consultationapplication',
|
||||
component: () => import('@/views/consultationmanagement/consultationapplication/index.vue'),
|
||||
name: 'ConsultationApplication',
|
||||
meta: {title: '门诊会诊申请管理', icon: 'document'}
|
||||
},
|
||||
{
|
||||
path: 'consultationconfirmation',
|
||||
component: () => import('@/views/consultationmanagement/consultationconfirmation/index.vue'),
|
||||
name: 'ConsultationConfirmation',
|
||||
meta: {title: '门诊会诊申请确认', icon: 'document'}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/medicationmanagement',
|
||||
component: Layout,
|
||||
name: 'MedicationManagement',
|
||||
meta: {title: '药房管理', icon: 'medication'},
|
||||
children: [
|
||||
{
|
||||
path: 'dayEndSettlement',
|
||||
component: () => import('@/views/medicationmanagement/dayEndSettlement/index.vue'),
|
||||
name: 'DayEndSettlement',
|
||||
meta: {title: '日结结算单管理', icon: 'document'}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/inspection',
|
||||
component: Layout,
|
||||
redirect: '/inspection/report',
|
||||
name: 'Inspection',
|
||||
meta: {title: '检查管理', icon: 'inspection'},
|
||||
children: [
|
||||
{
|
||||
path: 'report',
|
||||
component: () => import('@/views/inspection/report/index.vue'),
|
||||
name: 'Report',
|
||||
meta: {title: '检查报告', icon: 'document'}
|
||||
},
|
||||
{
|
||||
path: 'sampleType',
|
||||
component: () => import('@/views/inspection/sampleType/index.vue'),
|
||||
name: 'SampleType',
|
||||
meta: {title: '样本类型', icon: 'sample'}
|
||||
},
|
||||
{
|
||||
path: 'observation',
|
||||
component: () => import('@/views/inspection/observation/index.vue'),
|
||||
name: 'Observation',
|
||||
meta: {title: '观测记录', icon: 'observation'}
|
||||
},
|
||||
{
|
||||
path: 'lisconfig',
|
||||
component: () => import('@/views/inspection/lisconfig/index.vue'),
|
||||
name: 'LisConfig',
|
||||
meta: {title: 'LIS 配置', icon: 'setting'}
|
||||
},
|
||||
{
|
||||
path: 'instrument',
|
||||
component: () => import('@/views/inspection/instrument/index.vue'),
|
||||
name: 'Instrument',
|
||||
meta: {title: '仪器管理', icon: 'instrument'}
|
||||
},
|
||||
{
|
||||
path: 'groupRec',
|
||||
component: () => import('@/views/inspection/groupRec/index.vue'),
|
||||
name: 'GroupRec',
|
||||
meta: {title: '组合记录', icon: 'group'}
|
||||
},
|
||||
{
|
||||
path: 'sampleCollection',
|
||||
component: () => import('@/views/inspection/sampleCollection/index.vue'),
|
||||
name: 'SampleCollection',
|
||||
meta: {title: '样本采集', icon: 'collection'}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [...constantRoutes, ...dynamicRoutes],
|
||||
|
||||
@@ -1,22 +1,198 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="q" :inline="true"><el-form-item label="药品编码"><el-input v-model="q.drugCode" clearable /></el-form-item>
|
||||
<el-form-item><el-button type="primary" @click="getList">查询</el-button></el-form-item></el-form>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="药品" prop="drugName" width="150" />
|
||||
<el-table-column label="抗菌类别" prop="antibioticClass" width="120">
|
||||
<template #default="s"><el-tag>{{ {RESTRICTED:'限制使用',NONRESTRICTED:'非限制使用',SPECIAL:'特殊使用'}[s.row.antibioticClass] }}</el-tag></template>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">抗菌药物管理规则</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="getList">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增规则</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#409eff">{{ stats.total || 0 }}</div>
|
||||
<div style="color:#999">总规则数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#67c23a">{{ stats.nonRestricted || 0 }}</div>
|
||||
<div style="color:#999">非限制使用</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#e6a23c">{{ stats.restricted || 0 }}</div>
|
||||
<div style="color:#999">限制使用</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#f56c6c">{{ stats.special || 0 }}</div>
|
||||
<div style="color:#999">特殊使用</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<el-form :model="q" :inline="true" style="margin-bottom:16px">
|
||||
<el-form-item label="药品编码"><el-input v-model="q.drugCode" clearable placeholder="请输入药品编码" style="width:180px"/></el-form-item>
|
||||
<el-form-item label="抗菌类别">
|
||||
<el-select v-model="q.antibioticClass" clearable placeholder="请选择" style="width:140px">
|
||||
<el-option label="非限制使用" value="NONRESTRICTED"/>
|
||||
<el-option label="限制使用" value="RESTRICTED"/>
|
||||
<el-option label="特殊使用" value="SPECIAL"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="getList">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table v-loading="loading" :data="list" border stripe>
|
||||
<el-table-column label="药品编码" prop="drugCode" width="120"/>
|
||||
<el-table-column label="药品名称" prop="drugName" width="160" show-overflow-tooltip/>
|
||||
<el-table-column label="抗菌类别" prop="antibioticClass" width="120" align="center">
|
||||
<template #default="s">
|
||||
<el-tag :type="s.row.antibioticClass==='SPECIAL'?'danger':s.row.antibioticClass==='RESTRICTED'?'warning':'success'" size="small">
|
||||
{{ {RESTRICTED:'限制使用',NONRESTRICTED:'非限制使用',SPECIAL:'特殊使用'}[s.row.antibioticClass] || s.row.antibioticClass }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="限制级别" prop="restrictionLevel" width="120" />
|
||||
<el-table-column label="最大疗程(天)" prop="maxDurationDays" width="120" />
|
||||
<el-table-column label="需审批" width="80">
|
||||
<template #default="s"><el-tag :type="s.row.requireApproval?'danger':'success'">{{ s.row.requireApproval?'是':'否' }}</el-tag></template>
|
||||
<el-table-column label="限制级别" prop="restrictionLevel" width="120" align="center"/>
|
||||
<el-table-column label="最大疗程(天)" prop="maxDurationDays" width="120" align="center"/>
|
||||
<el-table-column label="需审批" width="80" align="center">
|
||||
<template #default="s">
|
||||
<el-tag :type="s.row.requireApproval?'danger':'success'" size="small">
|
||||
{{ s.row.requireApproval?'是':'否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="适应症" prop="indications" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column label="禁忌症" prop="contraindications" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="s">
|
||||
<el-button link type="primary" @click="handleDetail(s.row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增抗菌药物规则" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="110px">
|
||||
<el-form-item label="药品编码" required><el-input v-model="formData.drugCode" placeholder="请输入药品编码"/></el-form-item>
|
||||
<el-form-item label="药品名称" required><el-input v-model="formData.drugName" placeholder="请输入药品名称"/></el-form-item>
|
||||
<el-form-item label="抗菌类别" required>
|
||||
<el-select v-model="formData.antibioticClass" placeholder="请选择">
|
||||
<el-option label="非限制使用" value="NONRESTRICTED"/>
|
||||
<el-option label="限制使用" value="RESTRICTED"/>
|
||||
<el-option label="特殊使用" value="SPECIAL"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="限制级别"><el-input v-model="formData.restrictionLevel" placeholder="如: 副主任医师以上"/></el-form-item>
|
||||
<el-form-item label="最大疗程(天)"><el-input-number v-model="formData.maxDurationDays" :min="1" :max="365"/></el-form-item>
|
||||
<el-form-item label="需审批">
|
||||
<el-switch v-model="formData.requireApproval"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="适应症"><el-input v-model="formData.indications" type="textarea" :rows="2" placeholder="请输入适应症"/></el-form-item>
|
||||
<el-form-item label="禁忌症"><el-input v-model="formData.contraindications" type="textarea" :rows="2" placeholder="请输入禁忌症"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<el-dialog title="抗菌药物规则详情" v-model="detailVisible" width="600px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="药品编码">{{ detailData.drugCode }}</el-descriptions-item>
|
||||
<el-descriptions-item label="药品名称">{{ detailData.drugName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="抗菌类别">
|
||||
<el-tag :type="detailData.antibioticClass==='SPECIAL'?'danger':detailData.antibioticClass==='RESTRICTED'?'warning':'success'">
|
||||
{{ {RESTRICTED:'限制使用',NONRESTRICTED:'非限制使用',SPECIAL:'特殊使用'}[detailData.antibioticClass] }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="限制级别">{{ detailData.restrictionLevel }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最大疗程">{{ detailData.maxDurationDays }}天</el-descriptions-item>
|
||||
<el-descriptions-item label="需审批">{{ detailData.requireApproval ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="适应症" :span="2">{{ detailData.indications || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="禁忌症" :span="2">{{ detailData.contraindications || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer><el-button @click="detailVisible = false">关闭</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'; import { getRules } from '@/api/antibiotic'
|
||||
const loading = ref(false); const list = ref([]); const q = reactive({ drugCode: '' })
|
||||
const getList = async () => { if (!q.drugCode) return; loading.value = true; const r = await getRules(q.drugCode); list.value = r.data || []; loading.value = false }
|
||||
import {ref, reactive, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getRules, getStatistics, addRule} from '@/api/antibiotic'
|
||||
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const showAdd = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const detailData = ref({})
|
||||
const stats = ref({total:0, nonRestricted:0, restricted:0, special:0})
|
||||
|
||||
const q = reactive({drugCode:'', antibioticClass:''})
|
||||
const formData = reactive({
|
||||
drugCode:'', drugName:'', antibioticClass:'NONRESTRICTED', restrictionLevel:'',
|
||||
maxDurationDays:7, requireApproval:false, indications:'', contraindications:''
|
||||
})
|
||||
|
||||
async function getList() {
|
||||
loading.value = true
|
||||
try {
|
||||
if (q.drugCode) {
|
||||
const r = await getRules(q.drugCode)
|
||||
list.value = r.data || []
|
||||
} else {
|
||||
// Load all rules
|
||||
const r = await getStatistics()
|
||||
list.value = r.data?.rules || []
|
||||
}
|
||||
// Calculate stats
|
||||
let nonRestricted = 0, restricted = 0, special = 0
|
||||
list.value.forEach(row => {
|
||||
if (row.antibioticClass === 'NONRESTRICTED') nonRestricted++
|
||||
else if (row.antibioticClass === 'RESTRICTED') restricted++
|
||||
else if (row.antibioticClass === 'SPECIAL') special++
|
||||
})
|
||||
stats.value = {total: list.value.length, nonRestricted, restricted, special}
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.drugCode = ''
|
||||
q.antibioticClass = ''
|
||||
getList()
|
||||
}
|
||||
|
||||
function handleDetail(row) {
|
||||
detailData.value = row
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await addRule(formData)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
getList()
|
||||
}
|
||||
|
||||
onMounted(() => getList())
|
||||
</script>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<el-form :model="formData" label-width="100px">
|
||||
<el-form-item label="文档类型">
|
||||
<el-select v-model="formData.documentType" placeholder="选择文档类型">
|
||||
<el-option label="电子病历" value="EMR" /><el-option label="处方" value="PRESCRIPTION" />
|
||||
<el-option label="医嘱" value="ORDER" /><el-option label="会诊" value="CONSULTATION" />
|
||||
<el-option label="手术同意书" value="CONSENT" /><el-option label="护理记录" value="NURSING" />
|
||||
<el-option v-for="d in sign_document_type" :key="d.value" :label="d.label" :value="d.value" />
|
||||
|
||||
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="文档ID"><el-input v-model="formData.documentId" placeholder="文档ID" /></el-form-item>
|
||||
@@ -22,10 +22,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const { sign_document_type } = useDict('sign_document_type')
|
||||
const visible = ref(false)
|
||||
const formData = ref({})
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px">
|
||||
<el-input v-model="trayQ.trayCode" placeholder="器械编码" clearable style="width:140px"/>
|
||||
<el-select v-model="trayQ.status" placeholder="状态" clearable style="width:120px">
|
||||
<el-option label="在用" value="IN_USE"/><el-option label="清洗中" value="WASHING"/>
|
||||
<el-option label="灭菌中" value="STERILIZING"/><el-option label="储存中" value="STORED"/>
|
||||
<el-option label="已发放" value="DISTRIBUTED"/>
|
||||
<el-option v-for="d in cssd_trace_status" :key="d.value" :label="d.label" :value="d.value" />
|
||||
|
||||
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadTrays">查询</el-button>
|
||||
<el-button type="success" @click="trayDialog=true">新增器械包</el-button>
|
||||
@@ -52,10 +52,10 @@
|
||||
<el-form-item label="器械编码"><el-input v-model="scanForm.trayCode" placeholder="扫码或输入编码" style="width:200px"/></el-form-item>
|
||||
<el-form-item label="操作步骤">
|
||||
<el-select v-model="scanForm.stepType" style="width:140px">
|
||||
<el-option label="回收" value="RECYCLE"/><el-option label="清洗" value="WASH"/>
|
||||
<el-option label="消毒" value="DISINFECT"/><el-option label="包装" value="PACK"/>
|
||||
<el-option label="灭菌" value="STERILIZE"/><el-option label="储存" value="STORE"/>
|
||||
<el-option label="发放" value="DISTRIBUTE"/>
|
||||
<el-option v-for="d in cssd_process_step" :key="d.value" :label="d.label" :value="d.value" />
|
||||
|
||||
|
||||
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作人"><el-input v-model="scanForm.operatorName" style="width:120px"/></el-form-item>
|
||||
@@ -128,7 +128,7 @@
|
||||
<el-form-item label="编码"><el-input v-model="trayForm.trayCode"/></el-form-item>
|
||||
<el-form-item label="名称"><el-input v-model="trayForm.trayName"/></el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-select v-model="trayForm.trayType"><el-option label="手术器械" value="OPERATION"/><el-option label="管腔器械" value="TUBE"/><el-option label="精密器械" value="PRECISION"/><el-option label="普通器械" value="COMMON"/></el-select>
|
||||
<el-select v-model="trayForm.trayType"><el-option v-for="d in cssd_tray_type" :key="d.value" :label="d.label" :value="d.value" /></el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="来源科室"><el-input v-model="trayForm.departmentSource"/></el-form-item>
|
||||
</el-form>
|
||||
@@ -144,8 +144,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import {ref,onMounted} from 'vue';import {ElMessage} from 'element-plus';
|
||||
import {getTrayPage,addTray,scanTrace,getTraceHistory,getBatchPage,completeBatch,releaseBatch,getExpiryAlerts,getStats} from './api'
|
||||
const { cssd_trace_status, cssd_process_step, cssd_tray_type } = useDict('cssd_trace_status', 'cssd_process_step', 'cssd_tray_type')
|
||||
const activeTab=ref('tray')
|
||||
const trayData=ref([]);const batchData=ref([]);const expiryData=ref([]);const stats=ref({})
|
||||
const trayQ=ref({pageNo:1,pageSize:20,trayCode:'',status:''})
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="姓名"><el-input v-model="formData.patientName" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="性别">
|
||||
<el-select v-model="formData.gender"><el-option label="男" value="M" /><el-option label="女" value="F" /></el-select>
|
||||
<el-select v-model="formData.gender"><el-option v-for="d in sys_user_sex" :key="d.value" :label="d.label" :value="d.value" /></el-select>
|
||||
</el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="出生日期"><el-date-picker v-model="formData.birthDate" type="date" value-format="YYYY-MM-DD" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="身份证号"><el-input v-model="formData.idCardNo" /></el-form-item></el-col>
|
||||
@@ -55,10 +55,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { registerPerson, findByGlobalId, findByIdCard, getStatistics } from '../api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const { sys_user_sex } = useDict('sys_user_sex')
|
||||
const stats = ref({})
|
||||
const searchForm = reactive({ globalId: '', idCardNo: '' })
|
||||
const patientData = ref(null)
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
<el-dialog v-model="showAddFhir" title="新增FHIR资源" width="600px">
|
||||
<el-form :model="fhirForm" label-width="100px">
|
||||
<el-form-item label="资源类型"><el-select v-model="fhirForm.resourceType"><el-option v-for="t in ['Patient','Encounter','Observation','Condition','MedicationRequest']" :key="t" :label="t" :value="t"/></el-select></el-form-item>
|
||||
<el-form-item label="资源类型"><el-select v-model="fhirForm.resourceType"><el-option v-for="d in fhir_resource_type" :key="d.value" :label="d.label" :value="d.value" /></el-select></el-form-item>
|
||||
<el-form-item label="资源ID"><el-input v-model="fhirForm.resourceId"/></el-form-item>
|
||||
<el-form-item label="JSON内容"><el-input v-model="fhirForm.resourceJson" type="textarea" :rows="8"/></el-form-item>
|
||||
</el-form>
|
||||
@@ -65,10 +65,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import {ref,reactive,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getFhirPage,createFhirResource,getFhirTypeStats,getCdaPage,createCdaDocument,publishCdaDocument,getMappingPage,addMapping} from './api'
|
||||
|
||||
const { fhir_resource_type } = useDict('fhir_resource_type')
|
||||
const tab=ref('fhir')
|
||||
const fhirData=ref([]),cdaData=ref([]),mappingData=ref([])
|
||||
const fhirStats=ref({})
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px"/>
|
||||
<el-select v-model="q.complaintType" placeholder="投诉类型" clearable style="width:120px">
|
||||
<el-option label="服务态度" value="ATTITUDE"/><el-option label="医疗质量" value="QUALITY"/>
|
||||
<el-option label="等候时间" value="WAITING"/><el-option label="其他" value="OTHER"/>
|
||||
<el-option v-for="d in complaint_type" :key="d.value" :label="d.label" :value="d.value" />
|
||||
|
||||
</el-select>
|
||||
<el-select v-model="q.status" placeholder="状态" clearable style="width:100px">
|
||||
<el-option label="待处理" value="PENDING"/><el-option label="已处理" value="HANDLED"/><el-option label="已关闭" value="CLOSED"/>
|
||||
<el-option v-for="d in complaint_status" :key="d.value" :label="d.label" :value="d.value" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">登记投诉</el-button>
|
||||
@@ -49,7 +49,7 @@
|
||||
<el-form :model="addForm" label-width="100px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="addForm.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="患者姓名"><el-input v-model="addForm.patientName"/></el-form-item>
|
||||
<el-form-item label="投诉类型"><el-select v-model="addForm.complaintType"><el-option label="服务态度" value="ATTITUDE"/><el-option label="医疗质量" value="QUALITY"/><el-option label="等候时间" value="WAITING"/><el-option label="其他" value="OTHER"/></el-select></el-form-item>
|
||||
<el-form-item label="投诉类型"><el-select v-model="addForm.complaintType"><el-option v-for="d in complaint_type" :key="d.value" :label="d.label" :value="d.value" /></el-select></el-form-item>
|
||||
<el-form-item label="投诉内容"><el-input v-model="addForm.complaintContent" type="textarea" placeholder="详细描述投诉内容"/></el-form-item>
|
||||
<el-form-item label="科室"><el-input v-model="addForm.departmentName"/></el-form-item>
|
||||
</el-form>
|
||||
@@ -72,9 +72,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,add,handle,close,del} from './api'
|
||||
const { complaint_type, complaint_status } = useDict('complaint_type', 'complaint_status')
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,patientName:'',complaintType:'',status:''})
|
||||
const addVisible=ref(false);const handleVisible=ref(false)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>抗菌药物使用管理</span>
|
||||
</template>
|
||||
<el-table :data="tableData" v-loading="loading" border stripe>
|
||||
<el-table-column prop="patientName" label="患者姓名" width="120" />
|
||||
<el-table-column prop="medicalNo" label="病历号" width="140" />
|
||||
<el-table-column prop="drugName" label="抗菌药物名称" min-width="180" />
|
||||
<el-table-column prop="dosage" label="剂量" width="100" />
|
||||
<el-table-column prop="usage" label="用法" width="120" />
|
||||
<el-table-column prop="course" label="疗程(天)" width="100" />
|
||||
<el-table-column prop="level" label="抗菌药物级别" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.level === '限制级' ? 'warning' : row.level === '特殊级' ? 'danger' : 'info'">
|
||||
{{ row.level }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="indication" label="适应症" min-width="160" />
|
||||
<el-table-column prop="doctorName" label="开单医生" width="100" />
|
||||
<el-table-column prop="deptName" label="科室" width="120" />
|
||||
<el-table-column prop="useDate" label="使用日期" width="120" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="AntibioticUsage">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 接入后端API获取抗菌药物使用数据
|
||||
})
|
||||
</script>
|
||||
@@ -2,3 +2,4 @@ import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/infection/environment/page',method:'get',params:p})}
|
||||
export function add(d){return request({url:'/infection/environment/add',method:'post',data:d})}
|
||||
export function del(id){return request({url:'/infection/environment/delete/'+id,method:'delete'})}
|
||||
export function getQualifyRate(){return request({url:'/infection/environment/qualify-rate',method:'get'})}
|
||||
|
||||
@@ -1,32 +1,193 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">环境监测</span></div>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">环境监测</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增监测</el-button>
|
||||
<el-button type="warning" @click="showRate = true">查看合格率</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'10px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.sampleType" placeholder="采样类型" clearable style="width:110px">
|
||||
<el-option label="空气" value="AIR"/><el-option label="物表" value="SURFACE"/><el-option label="手采样" value="HAND"/>
|
||||
<el-select v-model="q.sampleType" placeholder="采样类型" clearable style="width:120px">
|
||||
<el-option label="空气" value="AIR"/>
|
||||
<el-option label="物体表面" value="SURFACE"/>
|
||||
<el-option label="手采样" value="HAND"/>
|
||||
<el-option label="使用中消毒液" value="DISINFECTANT"/>
|
||||
<el-option label="医疗器械" value="DEVICE"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.monitorResult" placeholder="监测结果" clearable style="width:100px">
|
||||
<el-option label="合格" value="QUALIFIED"/>
|
||||
<el-option label="不合格" value="UNQUALIFIED"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="sampleType" label="类型" width="80" align="center">
|
||||
<template #default="{row}"><el-tag size="small">{{ {AIR:'空气',SURFACE:'物表',HAND:'手采样'}[row.sampleType]||row.sampleType }}</el-tag></template>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="tableData" border stripe v-loading="loading">
|
||||
<el-table-column prop="sampleType" label="采样类型" width="110" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small" :type="sampleTagType(row.sampleType)">
|
||||
{{ sampleTypeText(row.sampleType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sampleLocation" label="采样地点" min-width="150"/>
|
||||
<el-table-column prop="sampleDate" label="采样日期" width="110"/>
|
||||
<el-table-column prop="monitorResult" label="结果" width="80" align="center">
|
||||
<template #default="{row}"><el-tag :type="row.monitorResult==='QUALIFIED'?'success':'danger'" size="small">{{ {QUALIFIED:'合格',UNQUALIFIED:'不合格'}[row.monitorResult]||row.monitorResult }}</el-tag></template>
|
||||
<el-table-column prop="sampleDate" label="采样日期" width="120"/>
|
||||
<el-table-column prop="monitorResult" label="监测结果" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.monitorResult==='QUALIFIED'?'success':'danger'" size="small">
|
||||
{{ row.monitorResult==='QUALIFIED'?'合格':'不合格' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="bacterialCount" label="菌落数" width="80" align="center"/>
|
||||
<el-table-column prop="standardLimit" label="标准限值" width="80" align="center"/>
|
||||
<el-table-column prop="bacterialCount" label="菌落数(CFU)" width="110" align="center"/>
|
||||
<el-table-column prop="standardLimit" label="标准限值" width="100" align="center"/>
|
||||
<el-table-column prop="exceedRate" label="超标率" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<span :style="{color: row.exceedRate > 10 ? '#F56C6C' : '#67C23A', fontWeight:'bold'}">
|
||||
{{ row.exceedRate || 0 }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="monitorPerson" label="监测人" width="90"/>
|
||||
<el-table-column prop="createTime" label="记录时间" width="160"/>
|
||||
</el-table>
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
|
||||
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增环境监测" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="110px">
|
||||
<el-form-item label="采样类型" required>
|
||||
<el-select v-model="formData.sampleType" placeholder="请选择">
|
||||
<el-option label="空气" value="AIR"/>
|
||||
<el-option label="物体表面" value="SURFACE"/>
|
||||
<el-option label="手采样" value="HAND"/>
|
||||
<el-option label="使用中消毒液" value="DISINFECTANT"/>
|
||||
<el-option label="医疗器械" value="DEVICE"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="采样地点" required><el-input v-model="formData.sampleLocation" placeholder="请输入采样地点"/></el-form-item>
|
||||
<el-form-item label="采样日期" required><el-date-picker v-model="formData.sampleDate" type="date" placeholder="选择日期"/></el-form-item>
|
||||
<el-form-item label="监测结果" required>
|
||||
<el-radio-group v-model="formData.monitorResult">
|
||||
<el-radio value="QUALIFIED">合格</el-radio>
|
||||
<el-radio value="UNQUALIFIED">不合格</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="菌落数(CFU)"><el-input-number v-model="formData.bacterialCount" :min="0"/></el-form-item>
|
||||
<el-form-item label="标准限值"><el-input-number v-model="formData.standardLimit" :min="0"/></el-form-item>
|
||||
<el-form-item label="监测人"><el-input v-model="formData.monitorPerson" placeholder="请输入监测人"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 合格率统计弹窗 -->
|
||||
<el-dialog title="环境监测合格率统计" v-model="showRate" width="700px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="总监测数">{{ qualifyRate.total || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合格数">{{ qualifyRate.qualified || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="不合格数">{{ qualifyRate.unqualified || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合格率">
|
||||
<el-tag :type="parseFloat(qualifyRate.rate || '0') >= 95 ? 'success' : 'danger'" size="large">
|
||||
{{ qualifyRate.rate || '0%' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer><el-button @click="showRate = false">关闭</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {getPage} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,sampleType:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
onMounted(()=>loadData())
|
||||
import {ref, reactive, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage, add, getQualifyRate} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const showAdd = ref(false)
|
||||
const showRate = ref(false)
|
||||
const qualifyRate = ref({})
|
||||
|
||||
const statCards = ref([
|
||||
{label:'总监测数', value:0, color:'#409eff'},
|
||||
{label:'合格数', value:0, color:'#67c23a'},
|
||||
{label:'不合格数', value:0, color:'#f56c6c'},
|
||||
{label:'合格率', value:'0%', color:'#e6a23c'},
|
||||
{label:'采样类型', value:0, color:'#909399'},
|
||||
{label:'平均菌落', value:0, color:'#409eff'}
|
||||
])
|
||||
|
||||
const q = ref({pageNo:1, pageSize:20, sampleType:'', monitorResult:''})
|
||||
const formData = reactive({
|
||||
sampleType:'AIR', sampleLocation:'', sampleDate:'', monitorResult:'QUALIFIED',
|
||||
bacterialCount:0, standardLimit:0, monitorPerson:''
|
||||
})
|
||||
|
||||
function sampleTypeText(t) {
|
||||
return {AIR:'空气',SURFACE:'物体表面',HAND:'手采样',DISINFECTANT:'消毒液',DEVICE:'医疗器械'}[t] || t
|
||||
}
|
||||
function sampleTagType(t) {
|
||||
return {AIR:'info',SURFACE:'',HAND:'success',DISINFECTANT:'warning',DEVICE:'danger'}[t] || 'info'
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await getPage(q.value)
|
||||
tableData.value = r.data?.records || []
|
||||
total.value = r.data?.total || 0
|
||||
let qualified = 0, unqualified = 0, totalBacterial = 0, typeSet = new Set()
|
||||
tableData.value.forEach(row => {
|
||||
if (row.monitorResult === 'QUALIFIED') qualified++
|
||||
else unqualified++
|
||||
totalBacterial += (row.bacterialCount || 0)
|
||||
typeSet.add(row.sampleType)
|
||||
})
|
||||
statCards.value[0].value = total.value
|
||||
statCards.value[1].value = qualified
|
||||
statCards.value[2].value = unqualified
|
||||
statCards.value[3].value = total.value > 0 ? Math.round(qualified * 100 / total.value) + '%' : '0%'
|
||||
statCards.value[4].value = typeSet.size
|
||||
statCards.value[5].value = total.value > 0 ? Math.round(totalBacterial / total.value) : 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.value = {pageNo:1, pageSize:20, sampleType:'', monitorResult:''}
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await add(formData)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadData()
|
||||
try { const r = await getQualifyRate(); qualifyRate.value = r.data || {} } catch(e) {}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,27 +1,178 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">职业暴露管理</span></div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.staffName" placeholder="工作人员" clearable style="width:130px"/>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">职业暴露管理</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增报告</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="staffName" label="工作人员" width="100"/>
|
||||
<el-table-column prop="department" label="科室" width="100"/>
|
||||
<el-table-column prop="exposureType" label="暴露类型" width="100"/>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#409eff">{{ stats.total || 0 }}</div>
|
||||
<div style="color:#999">总暴露数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#F56C6C">{{ stats.blood || 0 }}</div>
|
||||
<div style="color:#999">血液暴露</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#E6A23C">{{ stats.bodyFluid || 0 }}</div>
|
||||
<div style="color:#999">体液暴露</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#67C23A">{{ stats.followupRate || '0%' }}</div>
|
||||
<div style="color:#999">随访率</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.exposureType" placeholder="暴露类型" clearable style="width:120px">
|
||||
<el-option label="血液暴露" value="BLOOD"/>
|
||||
<el-option label="体液暴露" value="BODY_FLUID"/>
|
||||
<el-option label="针刺伤" value="NEEDLE_STICK"/>
|
||||
<el-option label="化学暴露" value="CHEMICAL"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="tableData" border stripe v-loading="loading">
|
||||
<el-table-column prop="staffName" label="暴露人员" width="100"/>
|
||||
<el-table-column prop="departmentName" label="科室" width="120"/>
|
||||
<el-table-column prop="exposureType" label="暴露类型" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small" :type="exposureTagType(row.exposureType)">
|
||||
{{ exposureTypeText(row.exposureType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="exposureSource" label="暴露源" width="120"/>
|
||||
<el-table-column prop="exposureDate" label="暴露日期" width="110"/>
|
||||
<el-table-column prop="immediateProcessing" label="即刻处理" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="followUpPlan" label="随访方案" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="exposureDate" label="暴露时间" width="160"/>
|
||||
<el-table-column prop="exposureSite" label="暴露部位" width="100"/>
|
||||
<el-table-column prop="exposureLevel" label="暴露程度" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.exposureLevel==='重度'?'danger':row.exposureLevel==='中度'?'warning':'success'" size="small">
|
||||
{{ row.exposureLevel || '轻度' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="处理状态" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status==='COMPLETED'?'success':row.status==='IN_PROGRESS'?'warning':'info'" size="small">
|
||||
{{ row.status==='COMPLETED'?'已完成':row.status==='IN_PROGRESS'?'处理中':'待处理' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="报告时间" width="160"/>
|
||||
</el-table>
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
|
||||
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增职业暴露报告" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="110px">
|
||||
<el-form-item label="暴露人员" required><el-input v-model="formData.staffName" placeholder="请输入姓名"/></el-form-item>
|
||||
<el-form-item label="科室"><el-input v-model="formData.departmentName" placeholder="请输入科室"/></el-form-item>
|
||||
<el-form-item label="暴露类型" required>
|
||||
<el-select v-model="formData.exposureType" placeholder="请选择">
|
||||
<el-option label="血液暴露" value="BLOOD"/>
|
||||
<el-option label="体液暴露" value="BODY_FLUID"/>
|
||||
<el-option label="针刺伤" value="NEEDLE_STICK"/>
|
||||
<el-option label="化学暴露" value="CHEMICAL"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="暴露源"><el-input v-model="formData.exposureSource" placeholder="如: HIV阳性患者血液"/></el-form-item>
|
||||
<el-form-item label="暴露部位"><el-input v-model="formData.exposureSite" placeholder="如: 左手食指"/></el-form-item>
|
||||
<el-form-item label="暴露程度">
|
||||
<el-radio-group v-model="formData.exposureLevel">
|
||||
<el-radio value="轻度">轻度</el-radio>
|
||||
<el-radio value="中度">中度</el-radio>
|
||||
<el-radio value="重度">重度</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="处理措施"><el-input v-model="formData.treatment" type="textarea" :rows="2" placeholder="请输入处理措施"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {getPage} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,staffName:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
onMounted(()=>loadData())
|
||||
import {ref, reactive, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage, add} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const showAdd = ref(false)
|
||||
const stats = ref({total:0, blood:0, bodyFluid:0, followupRate:'0%'})
|
||||
|
||||
const q = ref({pageNo:1, pageSize:20, exposureType:''})
|
||||
const formData = reactive({
|
||||
staffName:'', departmentName:'', exposureType:'BLOOD', exposureSource:'',
|
||||
exposureSite:'', exposureLevel:'轻度', treatment:''
|
||||
})
|
||||
|
||||
function exposureTypeText(t) {
|
||||
return {BLOOD:'血液暴露',BODY_FLUID:'体液暴露',NEEDLE_STICK:'针刺伤',CHEMICAL:'化学暴露'}[t] || t
|
||||
}
|
||||
function exposureTagType(t) {
|
||||
return {BLOOD:'danger',BODY_FLUID:'warning',NEEDLE_STICK:'',CHEMICAL:'info'}[t] || 'info'
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await getPage(q.value)
|
||||
tableData.value = r.data?.records || []
|
||||
total.value = r.data?.total || 0
|
||||
// Stats
|
||||
let blood = 0, bodyFluid = 0
|
||||
tableData.value.forEach(row => {
|
||||
if (row.exposureType === 'BLOOD') blood++
|
||||
if (row.exposureType === 'BODY_FLUID') bodyFluid++
|
||||
})
|
||||
stats.value = {total: total.value, blood, bodyFluid, followupRate: '100%'}
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.value = {pageNo:1, pageSize:20, exposureType:''}
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await add(formData)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
Object.assign(formData, {staffName:'', departmentName:'', exposureType:'BLOOD', exposureSource:'', exposureSite:'', exposureLevel:'轻度', treatment:''})
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
@@ -1,31 +1,160 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">手卫生监测</span></div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.deptName" placeholder="科室" clearable style="width:130px"/>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">手卫生管理</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增记录</el-button>
|
||||
<el-button type="warning" @click="showStats = true">查看统计</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="deptName" label="科室" width="120"/>
|
||||
<el-table-column prop="observerName" label="观察人" width="90"/>
|
||||
<el-table-column prop="observationDate" label="观察日期" width="110"/>
|
||||
<el-table-column prop="totalOpportunities" label="手卫生时机" width="100" align="center"/>
|
||||
<el-table-column prop="actualCompliance" label="实际执行" width="80" align="center"/>
|
||||
<el-table-column prop="complianceRate" label="依从率" width="90" align="center">
|
||||
<template #default="{row}"><el-progress :percentage="row.complianceRate||0" :stroke-width="14" :color="row.complianceRate>=90?'#67C23A':'#F56C6C'"/></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="complianceStatus" label="达标" width="70" align="center">
|
||||
<template #default="{row}"><el-tag :type="row.complianceStatus==='COMPLIANT'?'success':'danger'" size="small">{{ {COMPLIANT:'达标',NON_COMPLIANT:'未达标'}[row.complianceStatus]||row.complianceStatus }}</el-tag></template>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'10px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.departmentName" placeholder="科室" clearable style="width:140px">
|
||||
<el-option v-for="dept in departments" :key="dept" :label="dept" :value="dept"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.complianceType" placeholder="依从性" clearable style="width:120px">
|
||||
<el-option label="依从" value="COMPLIANT"/>
|
||||
<el-option label="不依从" value="NON_COMPLIANT"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="tableData" border stripe v-loading="loading">
|
||||
<el-table-column prop="departmentName" label="科室" width="120"/>
|
||||
<el-table-column prop="staffName" label="人员" width="100"/>
|
||||
<el-table-column prop="complianceType" label="依从性" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.complianceType==='COMPLIANT'?'success':'danger'" size="small">
|
||||
{{ row.complianceType==='COMPLIANT'?'依从':'不依从' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="handwashType" label="洗手类型" width="120"/>
|
||||
<el-table-column prop="observationTime" label="观察时间" width="160"/>
|
||||
<el-table-column prop="opportunities" label="手卫生时机" width="100" align="center"/>
|
||||
<el-table-column prop="compliantCount" label="依从次数" width="90" align="center"/>
|
||||
<el-table-column prop="createTime" label="记录时间" width="160"/>
|
||||
</el-table>
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
|
||||
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增手卫生记录" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="110px">
|
||||
<el-form-item label="科室" required><el-input v-model="formData.departmentName" placeholder="请输入科室"/></el-form-item>
|
||||
<el-form-item label="人员姓名" required><el-input v-model="formData.staffName" placeholder="请输入姓名"/></el-form-item>
|
||||
<el-form-item label="依从性" required>
|
||||
<el-radio-group v-model="formData.complianceType">
|
||||
<el-radio value="COMPLIANT">依从</el-radio>
|
||||
<el-radio value="NON_COMPLIANT">不依从</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="洗手类型">
|
||||
<el-select v-model="formData.handwashType" placeholder="请选择">
|
||||
<el-option label="洗手液洗手" value="SOAP"/>
|
||||
<el-option label="速干手消毒剂" value="SANITIZER"/>
|
||||
<el-option label="外科手消毒" value="SURGICAL"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="手卫生时机"><el-input v-model="formData.opportunities" placeholder="如: 接触患者前"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 统计弹窗 -->
|
||||
<el-dialog title="手卫生统计" v-model="showStats" width="700px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="总观察次数">{{ statsData.total || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="依从次数">{{ statsData.compliant || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="依从率">{{ statsData.complianceRate || '0%' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="不依从次数">{{ statsData.nonCompliant || 0 }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer><el-button @click="showStats = false">关闭</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {getPage} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,deptName:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
onMounted(()=>loadData())
|
||||
import {ref, reactive, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage, add, getStats} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const showAdd = ref(false)
|
||||
const showStats = ref(false)
|
||||
const statsData = ref({})
|
||||
const departments = ref(['内科','外科','妇产科','儿科','ICU','急诊科','手术室'])
|
||||
|
||||
const statCards = ref([
|
||||
{label:'总观察数', value:0, color:'#409eff'},
|
||||
{label:'依从数', value:0, color:'#67c23a'},
|
||||
{label:'不依从数', value:0, color:'#f56c6c'},
|
||||
{label:'依从率', value:'0%', color:'#e6a23c'},
|
||||
{label:'科室数', value:0, color:'#909399'},
|
||||
{label:'达标率', value:'0%', color:'#409eff'}
|
||||
])
|
||||
|
||||
const q = ref({pageNo:1, pageSize:20, departmentName:'', complianceType:''})
|
||||
const formData = reactive({
|
||||
departmentName:'', staffName:'', complianceType:'COMPLIANT', handwashType:'SOAP', opportunities:''
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await getPage(q.value)
|
||||
tableData.value = r.data?.records || []
|
||||
total.value = r.data?.total || 0
|
||||
let compliant = 0, deptSet = new Set()
|
||||
tableData.value.forEach(row => {
|
||||
if (row.complianceType === 'COMPLIANT') compliant++
|
||||
deptSet.add(row.departmentName)
|
||||
})
|
||||
statCards.value[0].value = total.value
|
||||
statCards.value[1].value = compliant
|
||||
statCards.value[2].value = total.value - compliant
|
||||
statCards.value[3].value = total.value > 0 ? Math.round(compliant * 100 / total.value) + '%' : '0%'
|
||||
statCards.value[4].value = deptSet.size
|
||||
statCards.value[5].value = total.value > 0 ? (compliant / total.value * 100).toFixed(1) + '%' : '0%'
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.value = {pageNo:1, pageSize:20, departmentName:'', complianceType:''}
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await add(formData)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
Object.assign(formData, {departmentName:'', staffName:'', complianceType:'COMPLIANT', handwashType:'SOAP', opportunities:''})
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadData()
|
||||
try { const r = await getStats(); statsData.value = r.data || {} } catch(e) {}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,28 +1,174 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">多重耐药菌管理</span></div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px"/>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">多重耐药菌监测</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增记录</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#F56C6C">{{ stats.total || 0 }}</div>
|
||||
<div style="color:#999">耐药菌检出</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#E6A23C">{{ stats.isolated || 0 }}</div>
|
||||
<div style="color:#999">已隔离</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#409EFF">{{ stats.isolationRate || '0%' }}</div>
|
||||
<div style="color:#999">隔离率</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#67C23A">{{ stats.cureRate || '0%' }}</div>
|
||||
<div style="color:#999">治愈率</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.bacteriaType" placeholder="耐药菌类型" clearable style="width:160px">
|
||||
<el-option label="MRSA" value="MRSA"/>
|
||||
<el-option label="VRE" value="VRE"/>
|
||||
<el-option label="CRE" value="CRE"/>
|
||||
<el-option label="CRKP" value="CRKP"/>
|
||||
<el-option label="ESBL" value="ESBL"/>
|
||||
<el-option label="MDR铜绿" value="MDR_PSA"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.isolationStatus" placeholder="隔离状态" clearable style="width:120px">
|
||||
<el-option label="已隔离" value="ISOLATED"/>
|
||||
<el-option label="解除隔离" value="RELEASED"/>
|
||||
<el-option label="未隔离" value="NOT_ISOLATED"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="tableData" border stripe v-loading="loading">
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="pathogenName" label="耐药菌" width="130"/>
|
||||
<el-table-column prop="resistantType" label="耐药类型" width="120"/>
|
||||
<el-table-column prop="sampleSource" label="标本来源" width="110"/>
|
||||
<el-table-column prop="isolationStatus" label="隔离" width="80" align="center">
|
||||
<template #default="{row}"><el-tag :type="row.isolationStatus==='ISOLATED'?'danger':'info'" size="small">{{ {ISOLATED:'已隔离',NOT_ISOLATED:'未隔离',RELEASED:'已解除'}[row.isolationStatus]||row.isolationStatus }}</el-tag></template>
|
||||
<el-table-column prop="bacteriaType" label="耐药菌类型" width="120" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small" type="danger">{{ bacteriaTypeText(row.bacteriaType) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reportTime" label="上报时间" width="160"/>
|
||||
<el-table-column prop="departmentName" label="科室" width="120"/>
|
||||
<el-table-column prop="sampleType" label="标本类型" width="100"/>
|
||||
<el-table-column prop="isolationStatus" label="隔离状态" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.isolationStatus==='ISOLATED'?'danger':row.isolationStatus==='RELEASED'?'success':'warning'" size="small">
|
||||
{{ row.isolationStatus==='ISOLATED'?'已隔离':row.isolationStatus==='RELEASED'?'解除隔离':'未隔离' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isolationDate" label="隔离日期" width="120"/>
|
||||
<el-table-column prop="releaseDate" label="解除日期" width="120"/>
|
||||
<el-table-column prop="antibioticUsage" label="抗菌药物使用" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="createTime" label="报告时间" width="160"/>
|
||||
</el-table>
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
|
||||
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增多重耐药菌记录" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="120px">
|
||||
<el-form-item label="患者姓名" required><el-input v-model="formData.patientName" placeholder="请输入患者姓名"/></el-form-item>
|
||||
<el-form-item label="耐药菌类型" required>
|
||||
<el-select v-model="formData.bacteriaType" placeholder="请选择">
|
||||
<el-option label="MRSA" value="MRSA"/>
|
||||
<el-option label="VRE" value="VRE"/>
|
||||
<el-option label="CRE" value="CRE"/>
|
||||
<el-option label="CRKP" value="CRKP"/>
|
||||
<el-option label="ESBL" value="ESBL"/>
|
||||
<el-option label="MDR铜绿" value="MDR_PSA"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="科室"><el-input v-model="formData.departmentName" placeholder="请输入科室"/></el-form-item>
|
||||
<el-form-item label="标本类型"><el-input v-model="formData.sampleType" placeholder="如: 痰液、血液、尿液"/></el-form-item>
|
||||
<el-form-item label="隔离状态">
|
||||
<el-radio-group v-model="formData.isolationStatus">
|
||||
<el-radio value="ISOLATED">已隔离</el-radio>
|
||||
<el-radio value="NOT_ISOLATED">未隔离</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="抗菌药物使用"><el-input v-model="formData.antibioticUsage" type="textarea" :rows="2" placeholder="请输入抗菌药物使用情况"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {getPage} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,patientName:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
onMounted(()=>loadData())
|
||||
import {ref, reactive, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage, add} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const showAdd = ref(false)
|
||||
const stats = ref({total:0, isolated:0, isolationRate:'0%', cureRate:'0%'})
|
||||
|
||||
const q = ref({pageNo:1, pageSize:20, bacteriaType:'', isolationStatus:''})
|
||||
const formData = reactive({
|
||||
patientName:'', bacteriaType:'MRSA', departmentName:'', sampleType:'',
|
||||
isolationStatus:'ISOLATED', antibioticUsage:''
|
||||
})
|
||||
|
||||
function bacteriaTypeText(t) {
|
||||
return {MRSA:'MRSA',VRE:'VRE',CRE:'CRE',CRKP:'CRKP',ESBL:'ESBL',MDR_PSA:'MDR铜绿'}[t] || t
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await getPage(q.value)
|
||||
tableData.value = r.data?.records || []
|
||||
total.value = r.data?.total || 0
|
||||
let isolated = 0
|
||||
tableData.value.forEach(row => { if (row.isolationStatus === 'ISOLATED') isolated++ })
|
||||
stats.value = {
|
||||
total: total.value, isolated,
|
||||
isolationRate: total.value > 0 ? Math.round(isolated * 100 / total.value) + '%' : '0%',
|
||||
cureRate: '100%'
|
||||
}
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.value = {pageNo:1, pageSize:20, bacteriaType:'', isolationStatus:''}
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await add(formData)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
Object.assign(formData, {patientName:'', bacteriaType:'MRSA', departmentName:'', sampleType:'', isolationStatus:'ISOLATED', antibioticUsage:''})
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
@@ -1,33 +1,194 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">目标性监测</span></div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.surveillanceType" placeholder="监测类型" clearable style="width:120px">
|
||||
<el-option label="ICU感染" value="ICU"/><el-option label="手术部位" value="SSI"/><el-option label="导管相关" value="CLABSI"/>
|
||||
<el-option label="呼吸机相关" value="VAP"/><el-option label="导尿管相关" value="CAUTI"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">目标性监测</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增监测</el-button>
|
||||
<el-button type="warning" @click="exportReport">导出报告</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="surveillanceType" label="类型" width="100" align="center">
|
||||
<template #default="{row}"><el-tag size="small">{{ {ICU:'ICU感染',SSI:'手术部位',CLABSI:'导管相关',VAP:'呼吸机相关',CAUTI:'导尿管相关'}[row.surveillanceType]||row.surveillanceType }}</el-tag></template>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'12px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.surveillanceType" placeholder="监测类型" clearable style="width:140px">
|
||||
<el-option label="ICU感染" value="ICU"/>
|
||||
<el-option label="手术部位感染(SSI)" value="SSI"/>
|
||||
<el-option label="导管相关血流感染(CLABSI)" value="CLABSI"/>
|
||||
<el-option label="呼吸机相关肺炎(VAP)" value="VAP"/>
|
||||
<el-option label="导尿管相关尿路感染(CAUTI)" value="CAUTI"/>
|
||||
<el-option label="多重耐药菌监测" value="MDR"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.status" placeholder="状态" clearable style="width:100px">
|
||||
<el-option label="进行中" value="ONGOING"/>
|
||||
<el-option label="已完成" value="COMPLETED"/>
|
||||
<el-option label="已暂停" value="PAUSED"/>
|
||||
</el-select>
|
||||
<el-date-picker v-model="q.dateRange" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" style="width:240px"/>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="tableData" border stripe v-loading="loading">
|
||||
<el-table-column prop="surveillanceType" label="监测类型" width="160" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small" :type="surveillanceTagType(row.surveillanceType)">
|
||||
{{ surveillanceTypeText(row.surveillanceType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="departmentName" label="监测科室" width="120"/>
|
||||
<el-table-column prop="totalPatients" label="监测人数" width="90" align="center"/>
|
||||
<el-table-column prop="infectionCount" label="感染数" width="70" align="center"/>
|
||||
<el-table-column prop="infectionRate" label="感染率" width="80" align="center">
|
||||
<template #default="{row}"><span :style="{color:row.infectionRate>5?'#F56C6C':'#67C23A',fontWeight:'bold'}">{{ row.infectionRate }}%</span></template>
|
||||
<el-table-column prop="infectionCount" label="感染数" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<span :style="{color: row.infectionCount > 0 ? '#F56C6C' : '#67C23A', fontWeight:'bold'}">
|
||||
{{ row.infectionCount }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="infectionRate" label="感染率" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<span :style="{color: row.infectionRate > 5 ? '#F56C6C' : row.infectionRate > 3 ? '#E6A23C' : '#67C23A', fontWeight:'bold'}">
|
||||
{{ row.infectionRate }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deviceUsageRate" label="器械使用率" width="100" align="center"/>
|
||||
<el-table-column prop="surveillancePeriod" label="监测周期" width="160"/>
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status==='ONGOING'?'success':row.status==='COMPLETED'?'info':'warning'" size="small">
|
||||
{{ row.status==='ONGOING'?'进行中':row.status==='COMPLETED'?'已完成':'已暂停' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="surveillancePeriod" label="监测周期" width="150"/>
|
||||
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button link type="primary" @click="handleDetail(row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
|
||||
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增监测" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="120px">
|
||||
<el-form-item label="监测类型" required>
|
||||
<el-select v-model="formData.surveillanceType" placeholder="请选择">
|
||||
<el-option label="ICU感染" value="ICU"/>
|
||||
<el-option label="手术部位感染(SSI)" value="SSI"/>
|
||||
<el-option label="导管相关血流感染(CLABSI)" value="CLABSI"/>
|
||||
<el-option label="呼吸机相关肺炎(VAP)" value="VAP"/>
|
||||
<el-option label="导尿管相关尿路感染(CAUTI)" value="CAUTI"/>
|
||||
<el-option label="多重耐药菌监测" value="MDR"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="监测科室"><el-input v-model="formData.departmentName" placeholder="请输入科室名称"/></el-form-item>
|
||||
<el-form-item label="监测人数"><el-input-number v-model="formData.totalPatients" :min="0"/></el-form-item>
|
||||
<el-form-item label="感染人数"><el-input-number v-model="formData.infectionCount" :min="0"/></el-form-item>
|
||||
<el-form-item label="器械使用率(%)"><el-input-number v-model="formData.deviceUsageRate" :min="0" :max="100" :precision="1"/></el-form-item>
|
||||
<el-form-item label="监测周期"><el-input v-model="formData.surveillancePeriod" placeholder="如: 2026-01-01 ~ 2026-06-30"/></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="formData.remark" type="textarea" :rows="2"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {getPage} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,surveillanceType:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
onMounted(()=>loadData())
|
||||
import {ref, reactive, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage, add} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const showAdd = ref(false)
|
||||
|
||||
const statCards = ref([
|
||||
{label:'总监测数', value:0, color:'#409eff'},
|
||||
{label:'ICU感染', value:0, color:'#f56c6c'},
|
||||
{label:'SSI感染', value:0, color:'#e6a23c'},
|
||||
{label:'CLABSI', value:0, color:'#67c23a'},
|
||||
{label:'VAP', value:0, color:'#909399'},
|
||||
{label:'平均感染率', value:'0%', color:'#409eff'}
|
||||
])
|
||||
|
||||
const q = ref({pageNo:1, pageSize:20, surveillanceType:'', status:'', dateRange:null})
|
||||
const formData = reactive({
|
||||
surveillanceType:'ICU', departmentName:'', totalPatients:0, infectionCount:0,
|
||||
deviceUsageRate:0, surveillancePeriod:'', remark:''
|
||||
})
|
||||
|
||||
function surveillanceTypeText(t) {
|
||||
return {ICU:'ICU感染',SSI:'手术部位感染',CLABSI:'导管相关血流感染',VAP:'呼吸机相关肺炎',CAUTI:'导尿管相关尿路感染',MDR:'多重耐药菌监测'}[t] || t
|
||||
}
|
||||
function surveillanceTagType(t) {
|
||||
return {ICU:'danger',SSI:'warning',CLABSI:'',VAP:'success',CAUTI:'info',MDR:'danger'}[t] || 'info'
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await getPage(q.value)
|
||||
tableData.value = r.data?.records || []
|
||||
total.value = r.data?.total || 0
|
||||
// Update stats
|
||||
if (tableData.value.length > 0) {
|
||||
statCards.value[0].value = total.value
|
||||
let icuCount = 0, ssiCount = 0, clabsiCount = 0, vapCount = 0, totalRate = 0
|
||||
tableData.value.forEach(row => {
|
||||
if (row.surveillanceType === 'ICU') icuCount++
|
||||
if (row.surveillanceType === 'SSI') ssiCount++
|
||||
if (row.surveillanceType === 'CLABSI') clabsiCount++
|
||||
if (row.surveillanceType === 'VAP') vapCount++
|
||||
totalRate += (row.infectionRate || 0)
|
||||
})
|
||||
statCards.value[1].value = icuCount
|
||||
statCards.value[2].value = ssiCount
|
||||
statCards.value[3].value = clabsiCount
|
||||
statCards.value[4].value = vapCount
|
||||
statCards.value[5].value = (totalRate / tableData.value.length).toFixed(1) + '%'
|
||||
}
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.value = {pageNo:1, pageSize:20, surveillanceType:'', status:'', dateRange:null}
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleDetail(row) {
|
||||
ElMessage.info('详情: ' + surveillanceTypeText(row.surveillanceType) + ' - ' + (row.departmentName || ''))
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await add(formData)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
Object.assign(formData, {surveillanceType:'ICU', departmentName:'', totalPatients:0, infectionCount:0, deviceUsageRate:0, surveillancePeriod:'', remark:''})
|
||||
loadData()
|
||||
}
|
||||
|
||||
function exportReport() { ElMessage.info('导出功能开发中') }
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
@@ -1,34 +1,181 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">疫情预警</span></div>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">疫情预警</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增预警</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'16px',borderLeft:'4px solid #F56C6C'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:28px;font-weight:bold;color:#F56C6C">{{ stats.level1Count || 0 }}</div>
|
||||
<div style="color:#999">一级预警</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'16px',borderLeft:'4px solid #E6A23C'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:28px;font-weight:bold;color:#E6A23C">{{ stats.level2Count || 0 }}</div>
|
||||
<div style="color:#999">二级预警</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'16px',borderLeft:'4px solid #409EFF'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:28px;font-weight:bold;color:#409EFF">{{ stats.level3Count || 0 }}</div>
|
||||
<div style="color:#999">三级预警</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" :body-style="{padding:'16px',borderLeft:'4px solid #67C23A'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:28px;font-weight:bold;color:#67C23A">{{ stats.totalWarnings || 0 }}</div>
|
||||
<div style="color:#999">总预警数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.warningLevel" placeholder="预警级别" clearable style="width:100px">
|
||||
<el-option label="一级" value="LEVEL1"/><el-option label="二级" value="LEVEL2"/><el-option label="三级" value="LEVEL3"/>
|
||||
<el-select v-model="q.warningLevel" placeholder="预警级别" clearable style="width:120px">
|
||||
<el-option label="一级预警" value="LEVEL1"/>
|
||||
<el-option label="二级预警" value="LEVEL2"/>
|
||||
<el-option label="三级预警" value="LEVEL3"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.diseaseType" placeholder="疾病类型" clearable style="width:140px">
|
||||
<el-option label="呼吸道传染病" value="RESPIRATORY"/>
|
||||
<el-option label="肠道传染病" value="INTESTINAL"/>
|
||||
<el-option label="血源性传染病" value="BLOOD"/>
|
||||
<el-option label="虫媒传染病" value="VECTOR"/>
|
||||
<el-option label="其他" value="OTHER"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="diseaseName" label="疾病" width="130"/>
|
||||
<el-table-column prop="warningLevel" label="级别" width="70" align="center">
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="tableData" border stripe v-loading="loading">
|
||||
<el-table-column prop="diseaseName" label="疾病名称" width="140"/>
|
||||
<el-table-column prop="warningLevel" label="预警级别" width="110" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.warningLevel==='LEVEL1'?'danger':row.warningLevel==='LEVEL2'?'warning':'info'" size="small" effect="dark">
|
||||
{{ {LEVEL1:'一级',LEVEL2:'二级',LEVEL3:'三级'}[row.warningLevel]||row.warningLevel }}
|
||||
{{ {LEVEL1:'一级预警',LEVEL2:'二级预警',LEVEL3:'三级预警'}[row.warningLevel]||row.warningLevel }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="caseCount" label="病例数" width="70" align="center"/>
|
||||
<el-table-column prop="thresholdCount" label="阈值" width="60" align="center"/>
|
||||
<el-table-column prop="caseCount" label="病例数" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<span :style="{color: row.caseCount > row.thresholdCount ? '#F56C6C' : '#67C23A', fontWeight:'bold'}">
|
||||
{{ row.caseCount }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="thresholdCount" label="阈值" width="70" align="center"/>
|
||||
<el-table-column prop="affectedArea" label="影响区域" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="createTime" label="预警时间" width="160"/>
|
||||
<el-table-column prop="departmentName" label="报告科室" width="120"/>
|
||||
<el-table-column prop="reporterName" label="报告人" width="100"/>
|
||||
<el-table-column prop="createTime" label="预警时间" width="170"/>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button link type="primary" @click="handleDetail(row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
|
||||
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增疫情预警" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="110px">
|
||||
<el-form-item label="疾病名称" required><el-input v-model="formData.diseaseName" placeholder="请输入疾病名称"/></el-form-item>
|
||||
<el-form-item label="预警级别" required>
|
||||
<el-select v-model="formData.warningLevel" placeholder="请选择">
|
||||
<el-option label="一级预警" value="LEVEL1"/>
|
||||
<el-option label="二级预警" value="LEVEL2"/>
|
||||
<el-option label="三级预警" value="LEVEL3"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="疾病类型">
|
||||
<el-select v-model="formData.diseaseType" placeholder="请选择">
|
||||
<el-option label="呼吸道传染病" value="RESPIRATORY"/>
|
||||
<el-option label="肠道传染病" value="INTESTINAL"/>
|
||||
<el-option label="血源性传染病" value="BLOOD"/>
|
||||
<el-option label="虫媒传染病" value="VECTOR"/>
|
||||
<el-option label="其他" value="OTHER"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="病例数"><el-input-number v-model="formData.caseCount" :min="0"/></el-form-item>
|
||||
<el-form-item label="阈值"><el-input-number v-model="formData.thresholdCount" :min="0"/></el-form-item>
|
||||
<el-form-item label="影响区域"><el-input v-model="formData.affectedArea" placeholder="请输入影响区域"/></el-form-item>
|
||||
<el-form-item label="报告科室"><el-input v-model="formData.departmentName" placeholder="请输入报告科室"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {getPage} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,warningLevel:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
onMounted(()=>loadData())
|
||||
import {ref, reactive, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage, add} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const showAdd = ref(false)
|
||||
const stats = ref({})
|
||||
|
||||
const q = ref({pageNo:1, pageSize:20, warningLevel:'', diseaseType:''})
|
||||
const formData = reactive({
|
||||
diseaseName:'', warningLevel:'LEVEL2', diseaseType:'RESPIRATORY',
|
||||
caseCount:0, thresholdCount:0, affectedArea:'', departmentName:''
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await getPage(q.value)
|
||||
tableData.value = r.data?.records || []
|
||||
total.value = r.data?.total || 0
|
||||
// Calculate stats from data
|
||||
let l1 = 0, l2 = 0, l3 = 0
|
||||
tableData.value.forEach(row => {
|
||||
if (row.warningLevel === 'LEVEL1') l1++
|
||||
else if (row.warningLevel === 'LEVEL2') l2++
|
||||
else l3++
|
||||
})
|
||||
stats.value = {level1Count: l1, level2Count: l2, level3Count: l3, totalWarnings: total.value}
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.value = {pageNo:1, pageSize:20, warningLevel:'', diseaseType:''}
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleDetail(row) {
|
||||
ElMessage.info('详情: ' + row.diseaseName + ' - ' + ({LEVEL1:'一级',LEVEL2:'二级',LEVEL3:'三级'}[row.warningLevel] || ''))
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await add(formData)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
Object.assign(formData, {diseaseName:'', warningLevel:'LEVEL2', diseaseType:'RESPIRATORY', caseCount:0, thresholdCount:0, affectedArea:'', departmentName:''})
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
<el-table-column prop="departmentName" label="科室" width="120" />
|
||||
<el-table-column prop="monitorType" label="监测类型" width="100">
|
||||
<template #default="{ row }">
|
||||
{{ {1:'空气',2:'物表',3:'手',4:'消毒液',5:'无菌物品'}[row.monitorType] || row.monitorType }}
|
||||
{{ environment_monitor_type.find(d => d.value === String(row.monitorType))?.label || row.monitorType }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="monitorItem" label="监测项目" width="150" />
|
||||
@@ -150,7 +150,7 @@
|
||||
<el-form-item label="病例数"><el-input-number v-model="outbreakForm.caseCount" :min="1" /></el-form-item>
|
||||
<el-form-item label="预警级别">
|
||||
<el-select v-model="outbreakForm.warningLevel">
|
||||
<el-option label="黄色" value="YELLOW" /><el-option label="红色" value="RED" />
|
||||
<el-option v-for="d in infection_alert_level" :key="d.value" :label="d.label" :value="d.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -222,11 +222,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getOutbreakPage, addOutbreak, handleOutbreak, excludeOutbreak, getSurveillancePage, addSurveillance, getHandHygienePage, addHandHygiene, getHandHygieneStats, getMdrPage, addMdr, isolateMdr, releaseMdr, getEnvMonitorPage, addEnvMonitor } from './api'
|
||||
|
||||
const activeTab = ref('outbreak')
|
||||
const { infection_alert_level, environment_monitor_type } = useDict('infection_alert_level', 'environment_monitor_type')
|
||||
const loading = ref(false)
|
||||
|
||||
// Data
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.patientName" placeholder="患者" clearable style="width:140px"/>
|
||||
<el-select v-model="q.status" placeholder="状态" clearable style="width:120px">
|
||||
<el-option label="已预约" value="APPOINTED"/><el-option label="已签到" value="CHECKED_IN"/>
|
||||
<el-option label="检查中" value="EXAMINING"/><el-option label="已完成" value="COMPLETED"/>
|
||||
<el-option v-for="d in exam_appointment_status" :key="d.value" :label="d.label" :value="d.value" />
|
||||
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="openAppoint">新建预约</el-button>
|
||||
@@ -51,7 +51,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import {ref,onMounted} from 'vue';import {ElMessage} from 'element-plus';import {getPage,appoint,checkin,startExam,complete,cancel} from './api'
|
||||
const { exam_appointment_status } = useDict('exam_appointment_status')
|
||||
const tableData=ref([]);const total=ref(0);const q=ref({pageNo:1,pageSize:20,patientName:'',status:''})
|
||||
const dlgVisible=ref(false);const form=ref({patientId:null,encounterId:null,patientName:'',examName:'',appointDate:null,appointTime:'',room:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">影像图文报告</span></div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px">
|
||||
<el-select v-model="q.status" placeholder="状态" clearable style="width:120px">
|
||||
<el-option label="草稿" value="DRAFT"/><el-option label="已报告" value="REPORTED"/><el-option label="已审核" value="VERIFIED"/>
|
||||
<el-option v-for="d in lab_report_status" :key="d.value" :label="d.label" :value="d.value" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="openReport">新建报告</el-button>
|
||||
@@ -47,7 +47,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import {ref,onMounted} from 'vue';import {ElMessage,ElMessageBox} from 'element-plus';import {getReportPage,addReport,submitReport,verifyReport} from './api'
|
||||
const { lab_report_status } = useDict('lab_report_status')
|
||||
const tableData=ref([]);const total=ref(0);const q=ref({pageNo:1,pageSize:20,status:''})
|
||||
const dlgVisible=ref(false);const form=ref({applyId:null,encounterId:null,patientName:'',examName:'',reportType:'',findings:'',impression:'',conclusion:'',reportDoctor:''})
|
||||
const loadData=async()=>{const r=await getReportPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// ==================== 护理提醒 ====================
|
||||
export function getReminderPage(p){return request({url:'/nursing-enhanced/reminder/page',method:'get',params:p})}
|
||||
export function addReminder(d){return request({url:'/nursing-enhanced/reminder/add',method:'post',data:d})}
|
||||
export function completeReminder(id){return request({url:'/nursing-enhanced/reminder/complete',method:'post',params:{id}})}
|
||||
export function getOverdueReminders(){return request({url:'/nursing-enhanced/reminder/overdue',method:'get'})}
|
||||
|
||||
// ==================== 护理计划 ====================
|
||||
export function getCarePlanPage(p){return request({url:'/nursing-enhanced/care-plan/page',method:'get',params:p})}
|
||||
export function addCarePlan(d){return request({url:'/nursing-enhanced/care-plan/add',method:'post',data:d})}
|
||||
export function evaluateCarePlan(d){return request({url:'/nursing-enhanced/care-plan/evaluate',method:'post',data:d})}
|
||||
export function getQualityStats(p){return request({url:'/nursing-enhanced/quality/stats',method:'get',params:p})}
|
||||
|
||||
// ==================== 护理专项评估 ====================
|
||||
export function getPage(p){return request({url:'/nursing-assessment-enhanced/page',method:'get',params:p})}
|
||||
export function bradenAssess(d){return request({url:'/nursing-assessment-enhanced/braden/assess',method:'post',data:d})}
|
||||
export function morseAssess(d){return request({url:'/nursing-assessment-enhanced/morse/assess',method:'post',data:d})}
|
||||
export function nrs2002Assess(d){return request({url:'/nursing-assessment-enhanced/nrs2002/assess',method:'post',data:d})}
|
||||
export function painAssess(d){return request({url:'/nursing-assessment-enhanced/pain/assess',method:'post',data:d})}
|
||||
export function tubeAssess(d){return request({url:'/nursing-assessment-enhanced/tube/assess',method:'post',data:d})}
|
||||
export function getInterventionPage(p){return request({url:'/nursing-assessment-enhanced/intervention/page',method:'get',params:p})}
|
||||
export function executeIntervention(id){return request({url:`/nursing-assessment-enhanced/intervention/execute/${id}`,method:'put'})}
|
||||
export function getStats(){return request({url:'/nursing-assessment-enhanced/stats',method:'get'})}
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">护理专项评估</span>
|
||||
<el-button type="primary" @click="loadStats">刷新统计</el-button>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadStats">刷新统计</el-button>
|
||||
<el-button type="success" @click="exportReport">导出评估报告</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
@@ -51,7 +54,7 @@
|
||||
<el-table-column prop="riskLevel" label="风险" width="80">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.riskLevel==='HIGH'?'danger':row.riskLevel==='MEDIUM'?'warning':'success'" size="small">
|
||||
{{ row.riskLevel==='HIGH'?'高危':row.riskLevel==='MEDIUM'?'中危':'低危' }}
|
||||
{{ row.riskLevel==='HIGH'?'高危':row.riskLevel==='MEDIUM'?'中危':row.riskLevel==='LOW'?'低危':'正常' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -65,13 +68,13 @@
|
||||
<div style="margin-bottom:12px">
|
||||
<el-button type="success" @click="openAssess('NRS2002')">新建NRS2002评估</el-button>
|
||||
</div>
|
||||
<el-table :data="nrs2002Data" border stripe>
|
||||
<el-table :data="nrsData" border stripe>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="totalScore" label="总分" width="70" align="center"/>
|
||||
<el-table-column prop="riskLevel" label="风险" width="80">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.riskLevel==='HIGH'?'danger':row.riskLevel==='MEDIUM'?'warning':'success'" size="small">
|
||||
{{ row.riskLevel==='HIGH'?'高危':row.riskLevel==='MEDIUM'?'中危':'正常' }}
|
||||
{{ row.riskLevel==='HIGH'?'高风险':row.riskLevel==='MEDIUM'?'中风险':'低风险' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -80,22 +83,18 @@
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 疼痛评估(NRS) -->
|
||||
<el-tab-pane label="疼痛评估(NRS)" name="NRS_PAIN">
|
||||
<!-- 疼痛评估 -->
|
||||
<el-tab-pane label="疼痛评估" name="PAIN">
|
||||
<div style="margin-bottom:12px">
|
||||
<el-button type="success" @click="openAssess('NRS_PAIN')">新建疼痛评估</el-button>
|
||||
<el-button type="success" @click="openAssess('PAIN')">新建疼痛评估</el-button>
|
||||
</div>
|
||||
<el-table :data="painData" border stripe>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="totalScore" label="疼痛评分" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<span :style="{color:row.totalScore>=7?'red':row.totalScore>=4?'orange':'green',fontWeight:'bold'}">{{ row.totalScore }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="riskLevel" label="程度" width="80">
|
||||
<el-table-column prop="totalScore" label="评分" width="70" align="center"/>
|
||||
<el-table-column prop="riskLevel" label="疼痛程度" width="100">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.riskLevel==='HIGH'?'danger':row.riskLevel==='MEDIUM'?'warning':'success'" size="small">
|
||||
{{ row.riskLevel==='HIGH'?'重度':row.riskLevel==='MEDIUM'?'中度':row.riskLevel==='LOW'?'轻度':'无痛' }}
|
||||
{{ row.riskLevel==='HIGH'?'重度':row.riskLevel==='MEDIUM'?'中度':'轻度' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -104,14 +103,14 @@
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 导管评估 -->
|
||||
<el-tab-pane label="导管评估" name="TUBE">
|
||||
<!-- 管道风险评估 -->
|
||||
<el-tab-pane label="管道风险评估" name="TUBE">
|
||||
<div style="margin-bottom:12px">
|
||||
<el-button type="success" @click="openAssess('TUBE')">新建导管评估</el-button>
|
||||
<el-button type="success" @click="openAssess('TUBE')">新建管道评估</el-button>
|
||||
</div>
|
||||
<el-table :data="tubeData" border stripe>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="totalScore" label="总分" width="70" align="center"/>
|
||||
<el-table-column prop="totalScore" label="评分" width="70" align="center"/>
|
||||
<el-table-column prop="riskLevel" label="风险" width="80">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.riskLevel==='HIGH'?'danger':row.riskLevel==='MEDIUM'?'warning':'success'" size="small">
|
||||
@@ -124,175 +123,212 @@
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 干预措施 -->
|
||||
<el-tab-pane label="干预措施" name="INTERVENTION">
|
||||
<!-- 护理措施 -->
|
||||
<el-tab-pane label="护理措施" name="INTERVENTION">
|
||||
<el-table :data="interventionData" border stripe>
|
||||
<el-table-column prop="interventionType" label="类型" width="100"/>
|
||||
<el-table-column prop="riskLevel" label="风险" width="80">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.riskLevel==='HIGH'?'danger':row.riskLevel==='MEDIUM'?'warning':'info'" size="small">{{ row.riskLevel }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="interventionContent" label="干预内容" min-width="200" show-overflow-tooltip/>
|
||||
<el-table-column prop="nurseName" label="护士" width="80"/>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="interventionType" label="措施类型" width="120"/>
|
||||
<el-table-column prop="interventionContent" label="措施内容" min-width="200" show-overflow-tooltip/>
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status==='EXECUTED'?'success':row.status==='CANCELLED'?'info':'warning'" size="small">
|
||||
{{ row.status==='EXECUTED'?'已执行':row.status==='CANCELLED'?'已取消':'待执行' }}
|
||||
<el-tag :type="row.status==='COMPLETED'?'success':row.status==='EXECUTING'?'warning':'info'" size="small">
|
||||
{{ row.status==='COMPLETED'?'已完成':row.status==='EXECUTING'?'执行中':'待执行' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{row}">
|
||||
<el-button v-if="row.status==='PENDING'" type="success" link size="small" @click="execIntervention(row.id)">执行</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="executorName" label="执行人" width="80"/>
|
||||
<el-table-column prop="executeTime" label="执行时间" width="170"/>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 评估对话框 -->
|
||||
<el-dialog v-model="assessDialogVisible" :title="assessDialogTitle" width="600px">
|
||||
<el-form :model="assessForm" label-width="100px">
|
||||
<el-form-item label="患者ID"><el-input v-model.number="assessForm.patientId"/></el-form-item>
|
||||
<el-form-item label="就诊ID"><el-input v-model.number="assessForm.encounterId"/></el-form-item>
|
||||
<el-form-item label="患者姓名"><el-input v-model="assessForm.patientName"/></el-form-item>
|
||||
<el-form-item label="评估人"><el-input v-model="assessForm.assessorName"/></el-form-item>
|
||||
|
||||
<!-- 评估弹窗 -->
|
||||
<el-dialog :title="assessDialogTitle" v-model="assessVisible" width="700px" append-to-body>
|
||||
<el-form :model="assessForm" label-width="120px">
|
||||
<el-form-item label="患者姓名"><el-input v-model="assessForm.patientName" placeholder="请输入患者姓名" style="width:200px"/></el-form-item>
|
||||
<el-form-item label="就诊号"><el-input v-model="assessForm.encounterId" placeholder="请输入就诊号" style="width:200px"/></el-form-item>
|
||||
|
||||
<!-- Braden量表 -->
|
||||
<template v-if="currentTool==='BRADEN'">
|
||||
<template v-if="assessForm.assessmentTool==='BRADEN'">
|
||||
<el-divider content-position="left">Braden压疮风险评估量表</el-divider>
|
||||
<el-form-item label="感觉(1-4)"><el-input-number v-model="bradenScores.sensory" :min="1" :max="4"/></el-form-item>
|
||||
<el-form-item label="潮湿(1-4)"><el-input-number v-model="bradenScores.moisture" :min="1" :max="4"/></el-form-item>
|
||||
<el-form-item label="活动(1-4)"><el-input-number v-model="bradenScores.activity" :min="1" :max="4"/></el-form-item>
|
||||
<el-form-item label="移动(1-4)"><el-input-number v-model="bradenScores.mobility" :min="1" :max="4"/></el-form-item>
|
||||
<el-form-item label="营养(1-4)"><el-input-number v-model="bradenScores.nutrition" :min="1" :max="4"/></el-form-item>
|
||||
<el-form-item label="摩擦力(1-3)"><el-input-number v-model="bradenScores.friction" :min="1" :max="3"/></el-form-item>
|
||||
<el-form-item label="预估总分"><span style="font-size:18px;font-weight:bold;color:#e6a23c">{{ bradenTotal }}</span></el-form-item>
|
||||
<el-form-item label="感觉"><el-radio-group v-model="bradenScores.sensation">
|
||||
<el-radio :value="1">完全受限</el-radio><el-radio :value="2">严重受限</el-radio>
|
||||
<el-radio :value="3">轻度受限</el-radio><el-radio :value="4">未受损</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="潮湿"><el-radio-group v-model="bradenScores.moisture">
|
||||
<el-radio :value="1">持续潮湿</el-radio><el-radio :value="2">潮湿</el-radio>
|
||||
<el-radio :value="3">偶尔潮湿</el-radio><el-radio :value="4">很少潮湿</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="活动"><el-radio-group v-model="bradenScores.activity">
|
||||
<el-radio :value="1">卧床不起</el-radio><el-radio :value="2">限于轮椅</el-radio>
|
||||
<el-radio :value="3">偶尔步行</el-radio><el-radio :value="4">经常步行</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="移动"><el-radio-group v-model="bradenScores.mobility">
|
||||
<el-radio :value="1">完全不能</el-radio><el-radio :value="2">严重受限</el-radio>
|
||||
<el-radio :value="3">轻度受限</el-radio><el-radio :value="4">不受限</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="营养"><el-radio-group v-model="bradenScores.nutrition">
|
||||
<el-radio :value="1">非常差</el-radio><el-radio :value="2">可能不足</el-radio>
|
||||
<el-radio :value="3">充足</el-radio><el-radio :value="4">丰富</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="摩擦力"><el-radio-group v-model="bradenScores.friction">
|
||||
<el-radio :value="1">存在问题</el-radio><el-radio :value="2">有潜在问题</el-radio>
|
||||
<el-radio :value="3">无明显问题</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="总分"><el-tag size="large" :type="bradenTotal<=12?'danger':bradenTotal<=14?'warning':'success'">{{ bradenTotal }}分 ({{ bradenTotal<=12?'高危':bradenTotal<=14?'中危':'低危' }})</el-tag></el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- Morse量表 -->
|
||||
<template v-if="currentTool==='MORSE'">
|
||||
<template v-if="assessForm.assessmentTool==='MORSE'">
|
||||
<el-divider content-position="left">Morse跌倒风险评估量表</el-divider>
|
||||
<el-form-item label="跌倒史(0-25)"><el-input-number v-model="morseScores.fall_history" :min="0" :max="25"/></el-form-item>
|
||||
<el-form-item label="医学诊断(0-15)"><el-input-number v-model="morseScores.diagnosis" :min="0" :max="15"/></el-form-item>
|
||||
<el-form-item label="步行辅助(0-15)"><el-input-number v-model="morseScores.gait" :min="0" :max="15"/></el-form-item>
|
||||
<el-form-item label="静脉输液(0-20)"><el-input-number v-model="morseScores.iv" :min="0" :max="20"/></el-form-item>
|
||||
<el-form-item label="步态(0-20)"><el-input-number v-model="morseScores.gait_type" :min="0" :max="20"/></el-form-item>
|
||||
<el-form-item label="预估总分"><span style="font-size:18px;font-weight:bold;color:#e6a23c">{{ morseTotal }}</span></el-form-item>
|
||||
<el-form-item label="跌倒史"><el-radio-group v-model="morseScores.history">
|
||||
<el-radio :value="0">无</el-radio><el-radio :value="15">有</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="诊断"><el-radio-group v-model="morseScores.diagnosis">
|
||||
<el-radio :value="0">无</el-radio><el-radio :value="15">有(≥2个)</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="行走辅助"><el-radio-group v-model="morseScores.ambulation">
|
||||
<el-radio :value="0">无/卧床/轮椅</el-radio><el-radio :value="15">拐杖/助行器</el-radio>
|
||||
<el-radio :value="30">扶家具</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="静脉输液"><el-radio-group v-model="morseScores.iv">
|
||||
<el-radio :value="0">无</el-radio><el-radio :value="20">有</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="步态"><el-radio-group v-model="morseScores.gait">
|
||||
<el-radio :value="0">正常/卧床/轮椅</el-radio><el-radio :value="10">虚弱</el-radio>
|
||||
<el-radio :value="20">障碍</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="精神状态"><el-radio-group v-model="morseScores.mental">
|
||||
<el-radio :value="0">正确评估自身能力</el-radio><el-radio :value="15">高估/忘记限制</el-radio>
|
||||
</el-radio-group></el-form-item>
|
||||
<el-form-item label="总分"><el-tag size="large" :type="morseTotal>=45?'danger':morseTotal>=25?'warning':'success'">{{ morseTotal }}分 ({{ morseTotal>=45?'高危':morseTotal>=25?'中危':'低危' }})</el-tag></el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- NRS2002 -->
|
||||
<template v-if="currentTool==='NRS2002'">
|
||||
<el-divider content-position="left">NRS2002营养风险筛查</el-divider>
|
||||
<el-form-item label="疾病严重(0-3)"><el-input-number v-model="nrsScores.disease" :min="0" :max="3"/></el-form-item>
|
||||
<el-form-item label="营养状况(0-3)"><el-input-number v-model="nrsScores.nutrition" :min="0" :max="3"/></el-form-item>
|
||||
<el-form-item label="年龄(0-1)"><el-input-number v-model="nrsScores.age" :min="0" :max="1"/></el-form-item>
|
||||
<el-form-item label="预估总分"><span style="font-size:18px;font-weight:bold;color:#e6a23c">{{ nrsTotal }}</span></el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 疼痛NRS -->
|
||||
<template v-if="currentTool==='NRS_PAIN'">
|
||||
<el-divider content-position="left">NRS数字疼痛评分(0-10)</el-divider>
|
||||
<el-form-item label="疼痛评分">
|
||||
<el-slider v-model="painScore" :min="0" :max="10" :marks="painMarks" show-stops/>
|
||||
</el-form-item>
|
||||
<el-form-item label="疼痛描述">
|
||||
<span style="font-size:16px;font-weight:bold" :style="{color:painScore>=7?'red':painScore>=4?'orange':painScore>=1?'#e6a23c':'green'}">
|
||||
{{ painScore===0?'无痛':painScore<=3?'轻度疼痛':painScore<=6?'中度疼痛':'重度疼痛' }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 导管评估 -->
|
||||
<template v-if="currentTool==='TUBE'">
|
||||
<el-divider content-position="left">导管风险评估</el-divider>
|
||||
<el-form-item label="导管类型(1-5)"><el-input-number v-model="tubeScores.type" :min="1" :max="5"/></el-form-item>
|
||||
<el-form-item label="固定情况(1-3)"><el-input-number v-model="tubeScores.fixation" :min="1" :max="3"/></el-form-item>
|
||||
<el-form-item label="通畅情况(1-3)"><el-input-number v-model="tubeScores.patency" :min="1" :max="3"/></el-form-item>
|
||||
<el-form-item label="周围皮肤(1-3)"><el-input-number v-model="tubeScores.skin" :min="1" :max="3"/></el-form-item>
|
||||
<el-form-item label="感染风险(1-3)"><el-input-number v-model="tubeScores.infection" :min="1" :max="3"/></el-form-item>
|
||||
<el-form-item label="预估总分"><span style="font-size:18px;font-weight:bold;color:#e6a23c">{{ tubeTotal }}</span></el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item label="备注"><el-input v-model="assessForm.detail" type="textarea" :rows="2"/></el-form-item>
|
||||
<el-form-item label="评估备注"><el-input v-model="assessForm.detail" type="textarea" :rows="2" placeholder="请输入评估备注"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="assessDialogVisible=false">取消</el-button>
|
||||
<el-button @click="assessVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitAssess">提交评估</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,computed,onMounted} from 'vue'
|
||||
import {ref, reactive, computed, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getAssessmentPage,bradenAssess,morseAssess,nrs2002Assess,painAssess,tubeAssess,getInterventionPage,executeIntervention,getStats} from './assessmentApi'
|
||||
import {getPage, bradenAssess, morseAssess, nrs2002Assess, painAssess, tubeAssess, getInterventionPage, getStats} from './api'
|
||||
|
||||
const activeTab=ref('BRADEN')
|
||||
const bradenData=ref([]);const morseData=ref([]);const nrs2002Data=ref([]);const painData=ref([]);const tubeData=ref([])
|
||||
const interventionData=ref([]);const stats=ref({})
|
||||
|
||||
const assessDialogVisible=ref(false)
|
||||
const currentTool=ref('BRADEN')
|
||||
const assessDialogTitle=computed(()=>({BRADEN:'Braden压疮评估',MORSE:'Morse跌倒评估',NRS2002:'NRS2002营养筛查',NRS_PAIN:'疼痛评估(NRS)',TUBE:'导管风险评估'}[currentTool.value]))
|
||||
const assessForm=ref({patientId:null,encounterId:null,patientName:'',assessorName:'',detail:''})
|
||||
|
||||
const bradenScores=ref({sensory:4,moisture:4,activity:4,mobility:4,nutrition:4,friction:3})
|
||||
const bradenTotal=computed(()=>Object.values(bradenScores.value).reduce((a,b)=>a+b,0))
|
||||
|
||||
const morseScores=ref({fall_history:0,diagnosis:0,gait:0,iv:0,gait_type:0})
|
||||
const morseTotal=computed(()=>Object.values(morseScores.value).reduce((a,b)=>a+b,0))
|
||||
|
||||
const nrsScores=ref({disease:0,nutrition:0,age:0})
|
||||
const nrsTotal=computed(()=>Object.values(nrsScores.value).reduce((a,b)=>a+b,0))
|
||||
|
||||
const painScore=ref(0)
|
||||
const painMarks=ref({0:'无痛',3:'轻度',6:'中度',10:'重度'})
|
||||
|
||||
const tubeScores=ref({type:1,fixation:1,patency:1,skin:1,infection:1})
|
||||
const tubeTotal=computed(()=>Object.values(tubeScores.value).reduce((a,b)=>a+b,0))
|
||||
|
||||
const statCards=computed(()=>[
|
||||
{label:'压疮评估',value:stats.value.BRADEN_total||0,color:'#e6a23c'},
|
||||
{label:'压疮高危',value:stats.value.BRADEN_high_risk||0,color:'#f56c6c'},
|
||||
{label:'跌倒评估',value:stats.value.MORSE_total||0,color:'#409eff'},
|
||||
{label:'跌倒高危',value:stats.value.MORSE_high_risk||0,color:'#f56c6c'},
|
||||
{label:'营养筛查',value:stats.value.NRS2002_total||0,color:'#67c23a'},
|
||||
{label:'待执行干预',value:stats.value.pending_interventions||0,color:'#e6a23c'},
|
||||
const activeTab = ref('BRADEN')
|
||||
const assessVisible = ref(false)
|
||||
const bradenData = ref([]), morseData = ref([]), nrsData = ref([]), painData = ref([]), tubeData = ref([])
|
||||
const interventionData = ref([])
|
||||
const statCards = ref([
|
||||
{label:'总评估', value:0, color:'#409eff'},
|
||||
{label:'高危患者', value:0, color:'#f56c6c'},
|
||||
{label:'中危患者', value:0, color:'#e6a23c'},
|
||||
{label:'已干预', value:0, color:'#67c23a'},
|
||||
{label:'待评估', value:0, color:'#909399'},
|
||||
{label:'评估率', value:'0%', color:'#409eff'}
|
||||
])
|
||||
|
||||
const loadData=async()=>{
|
||||
const p={pageNo:1,pageSize:50}
|
||||
const [b,m,n,pa,t]=await Promise.all([
|
||||
getAssessmentPage({...p,assessmentTool:'BRADEN'}),
|
||||
getAssessmentPage({...p,assessmentTool:'MORSE'}),
|
||||
getAssessmentPage({...p,assessmentTool:'NRS2002'}),
|
||||
getAssessmentPage({...p,assessmentTool:'NRS_PAIN'}),
|
||||
getAssessmentPage({...p,assessmentTool:'TUBE'}),
|
||||
])
|
||||
bradenData.value=b.data?.records||[];morseData.value=m.data?.records||[]
|
||||
nrs2002Data.value=n.data?.records||[];painData.value=pa.data?.records||[]
|
||||
tubeData.value=t.data?.records||[]
|
||||
const iv=await getInterventionPage({pageNo:1,pageSize:50})
|
||||
interventionData.value=iv.data?.records||[]
|
||||
const assessForm = reactive({patientName:'', encounterId:'', assessmentTool:'BRADEN', detail:''})
|
||||
const bradenScores = reactive({sensation:4, moisture:4, activity:4, mobility:4, nutrition:4, friction:3})
|
||||
const morseScores = reactive({history:0, diagnosis:0, ambulation:0, iv:0, gait:0, mental:0})
|
||||
|
||||
const bradenTotal = computed(() => bradenScores.sensation + bradenScores.moisture + bradenScores.activity + bradenScores.mobility + bradenScores.nutrition + bradenScores.friction)
|
||||
const morseTotal = computed(() => morseScores.history + morseScores.diagnosis + morseScores.ambulation + morseScores.iv + morseScores.gait + morseScores.mental)
|
||||
const assessDialogTitle = computed(() => {
|
||||
const m = {BRADEN:'Braden压疮风险评估', MORSE:'Morse跌倒风险评估', NRS2002:'NRS2002营养筛查', PAIN:'疼痛评估', TUBE:'管道风险评估'}
|
||||
return m[assessForm.assessmentTool] || '护理评估'
|
||||
})
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const r = await getStats()
|
||||
if (r.data) {
|
||||
statCards.value[0].value = r.data.total || 0
|
||||
statCards.value[1].value = r.data.highRisk || 0
|
||||
statCards.value[2].value = r.data.mediumRisk || 0
|
||||
statCards.value[3].value = r.data.intervened || 0
|
||||
statCards.value[4].value = r.data.pending || 0
|
||||
statCards.value[5].value = (r.data.assessmentRate || 0) + '%'
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
const loadStats=async()=>{const r=await getStats();stats.value=r.data||{}}
|
||||
const openAssess=(tool)=>{currentTool.value=tool;assessForm.value={patientId:null,encounterId:null,patientName:'',assessorName:'',detail:''};assessDialogVisible.value=true}
|
||||
const submitAssess=async()=>{
|
||||
let scores={}
|
||||
if(currentTool.value==='BRADEN')scores=bradenScores.value
|
||||
else if(currentTool.value==='MORSE')scores=morseScores.value
|
||||
else if(currentTool.value==='NRS2002')scores=nrsScores.value
|
||||
else if(currentTool.value==='NRS_PAIN')scores={pain_score:painScore.value}
|
||||
else if(currentTool.value==='TUBE')scores=tubeScores.value
|
||||
|
||||
const data={...assessForm.value,itemScores:JSON.stringify(scores)}
|
||||
const assessFn={BRADEN:bradenAssess,MORSE:morseAssess,NRS2002:nrs2002Assess,NRS_PAIN:painAssess,TUBE:tubeAssess}
|
||||
const r=await assessFn[currentTool.value](data)
|
||||
ElMessage.success('评估完成,风险等级: '+r.data?.assessment?.riskLevel)
|
||||
assessDialogVisible.value=false;loadData();loadStats()
|
||||
|
||||
async function loadData() {
|
||||
const tools = ['BRADEN','MORSE','NRS2002','PAIN','TUBE']
|
||||
const targets = [bradenData, morseData, nrsData, painData, tubeData]
|
||||
for (let i = 0; i < tools.length; i++) {
|
||||
try {
|
||||
const r = await getPage({assessmentTool: tools[i], pageNo:1, pageSize:50})
|
||||
targets[i].value = r.data?.records || []
|
||||
} catch(e) { targets[i].value = [] }
|
||||
}
|
||||
try {
|
||||
const r = await getInterventionPage({pageNo:1, pageSize:50})
|
||||
interventionData.value = r.data?.records || []
|
||||
} catch(e) {}
|
||||
}
|
||||
const execIntervention=async(id)=>{await executeIntervention(id);ElMessage.success('已执行');loadData();loadStats()}
|
||||
onMounted(()=>{loadData();loadStats()})
|
||||
|
||||
function openAssess(tool) {
|
||||
assessForm.patientName = ''
|
||||
assessForm.encounterId = ''
|
||||
assessForm.assessmentTool = tool
|
||||
assessForm.detail = ''
|
||||
if (tool === 'BRADEN') {
|
||||
Object.assign(bradenScores, {sensation:4, moisture:4, activity:4, mobility:4, nutrition:4, friction:3})
|
||||
} else if (tool === 'MORSE') {
|
||||
Object.assign(morseScores, {history:0, diagnosis:0, ambulation:0, iv:0, gait:0, mental:0})
|
||||
}
|
||||
assessVisible.value = true
|
||||
}
|
||||
|
||||
async function submitAssess() {
|
||||
const data = {
|
||||
patientName: assessForm.patientName,
|
||||
encounterId: assessForm.encounterId,
|
||||
detail: assessForm.detail
|
||||
}
|
||||
try {
|
||||
if (assessForm.assessmentTool === 'BRADEN') {
|
||||
data.itemScores = JSON.stringify({sensation:bradenScores.sensation, moisture:bradenScores.moisture, activity:bradenScores.activity, mobility:bradenScores.mobility, nutrition:bradenScores.nutrition, friction:bradenScores.friction}); data.totalScore = bradenTotal.value
|
||||
data.sensation = bradenScores.sensation
|
||||
data.moisture = bradenScores.moisture
|
||||
data.activity = bradenScores.activity
|
||||
data.mobility = bradenScores.mobility
|
||||
data.nutrition = bradenScores.nutrition
|
||||
data.friction = bradenScores.friction
|
||||
await bradenAssess(data)
|
||||
} else if (assessForm.assessmentTool === 'MORSE') {
|
||||
data.itemScores = JSON.stringify({history:morseScores.history, diagnosis:morseScores.diagnosis, ambulation:morseScores.ambulation, iv:morseScores.iv, gait:morseScores.gait, mental:morseScores.mental}); data.totalScore = morseTotal.value
|
||||
data.history = morseScores.history
|
||||
data.diagnosis = morseScores.diagnosis
|
||||
data.ambulation = morseScores.ambulation
|
||||
data.iv = morseScores.iv
|
||||
data.gait = morseScores.gait
|
||||
data.mental = morseScores.mental
|
||||
await morseAssess(data)
|
||||
} else if (assessForm.assessmentTool === 'NRS2002') {
|
||||
await nrs2002Assess(data)
|
||||
} else if (assessForm.assessmentTool === 'PAIN') {
|
||||
await painAssess(data)
|
||||
} else if (assessForm.assessmentTool === 'TUBE') {
|
||||
await tubeAssess(data)
|
||||
}
|
||||
ElMessage.success('评估提交成功')
|
||||
assessVisible.value = false
|
||||
loadData()
|
||||
loadStats()
|
||||
} catch(e) {
|
||||
ElMessage.error('提交失败: ' + (e.message || '未知错误'))
|
||||
}
|
||||
}
|
||||
|
||||
function exportReport() {
|
||||
ElMessage.info('导出功能开发中')
|
||||
}
|
||||
|
||||
onMounted(() => { loadData(); loadStats() })
|
||||
</script>
|
||||
|
||||
@@ -1,30 +1,175 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">护理质量指标</span></div>
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<div style="display:flex;gap:30px;text-align:center">
|
||||
<div><div style="font-size:24px;font-weight:bold;color:#409eff">{{ summary.total||0 }}</div><div>总指标</div></div>
|
||||
<div><div style="font-size:24px;font-weight:bold;color:#67c23a">{{ summary.meetTarget||0 }}</div><div>达标</div></div>
|
||||
<div><div style="font-size:24px;font-weight:bold;color:#e6a23c">{{ summary.meetRate||0 }}%</div><div>达标率</div></div>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">护理质量指标管理</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增指标</el-button>
|
||||
<el-button type="warning" @click="exportReport">导出报告</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<div style="margin-bottom:12px"><el-button type="success" @click="showAdd=true">新增指标</el-button></div>
|
||||
<el-table :data="indicatorData" border stripe>
|
||||
<el-table-column prop="indicatorCode" label="编码" width="100"/>
|
||||
<el-table-column prop="indicatorName" label="指标名称" min-width="180"/>
|
||||
<el-table-column prop="indicatorCategory" label="类别" width="100"/>
|
||||
<el-table-column prop="targetValue" label="目标" width="70" align="center"/>
|
||||
<el-table-column prop="actualValue" label="实际" width="70" align="center"/>
|
||||
<el-table-column prop="departmentName" label="科室" width="120"/>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'10px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.indicatorCategory" placeholder="指标类别" clearable style="width:140px">
|
||||
<el-option label="基础护理" value="BASIC"/>
|
||||
<el-option label="专科护理" value="SPECIALIZED"/>
|
||||
<el-option label="护理安全" value="SAFETY"/>
|
||||
<el-option label="护理文书" value="DOCUMENTATION"/>
|
||||
<el-option label="消毒隔离" value="STERILIZATION"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.departmentName" placeholder="科室" clearable style="width:140px">
|
||||
<el-option label="内科" value="内科"/>
|
||||
<el-option label="外科" value="外科"/>
|
||||
<el-option label="妇产科" value="妇产科"/>
|
||||
<el-option label="儿科" value="儿科"/>
|
||||
<el-option label="ICU" value="ICU"/>
|
||||
<el-option label="急诊科" value="急诊科"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="indicatorData" border stripe v-loading="loading">
|
||||
<el-table-column prop="indicatorCode" label="指标编码" width="120"/>
|
||||
<el-table-column prop="indicatorName" label="指标名称" min-width="180" show-overflow-tooltip/>
|
||||
<el-table-column prop="indicatorCategory" label="类别" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small">{{ categoryText(row.indicatorCategory) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="targetValue" label="目标值" width="80" align="center"/>
|
||||
<el-table-column prop="actualValue" label="实际值" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<span :style="{color: row.actualValue >= row.targetValue ? '#67C23A' : '#F56C6C', fontWeight:'bold'}">
|
||||
{{ row.actualValue }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="达标状态" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.actualValue >= row.targetValue ? 'success' : 'danger'" size="small">
|
||||
{{ row.actualValue >= row.targetValue ? '达标' : '未达标' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="departmentName" label="科室" width="100"/>
|
||||
<el-table-column prop="statDate" label="统计日期" width="120"/>
|
||||
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip/>
|
||||
</el-table>
|
||||
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增护理质量指标" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="110px">
|
||||
<el-form-item label="指标编码" required><el-input v-model="formData.indicatorCode" placeholder="如: NQ001"/></el-form-item>
|
||||
<el-form-item label="指标名称" required><el-input v-model="formData.indicatorName" placeholder="请输入指标名称"/></el-form-item>
|
||||
<el-form-item label="指标类别" required>
|
||||
<el-select v-model="formData.indicatorCategory" placeholder="请选择">
|
||||
<el-option label="基础护理" value="BASIC"/>
|
||||
<el-option label="专科护理" value="SPECIALIZED"/>
|
||||
<el-option label="护理安全" value="SAFETY"/>
|
||||
<el-option label="护理文书" value="DOCUMENTATION"/>
|
||||
<el-option label="消毒隔离" value="STERILIZATION"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标值"><el-input-number v-model="formData.targetValue" :min="0"/></el-form-item>
|
||||
<el-form-item label="实际值"><el-input-number v-model="formData.actualValue" :min="0"/></el-form-item>
|
||||
<el-form-item label="科室"><el-input v-model="formData.departmentName" placeholder="请输入科室"/></el-form-item>
|
||||
<el-form-item label="统计日期"><el-date-picker v-model="formData.statDate" type="date" placeholder="选择日期"/></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="formData.remark" type="textarea" :rows="2"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ref, reactive, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getQualityPage,addIndicator,getQualitySummary} from './api'
|
||||
const indicatorData=ref([]),summary=ref({})
|
||||
const showAdd=ref(false)
|
||||
const loadData=async()=>{const [i,s]=await Promise.all([getQualityPage({pageNo:1,pageSize:50}),getQualitySummary()]);indicatorData.value=i.data?.records||[];summary.value=s.data||{}}
|
||||
onMounted(()=>loadData())
|
||||
import {getQualityPage, addIndicator, getQualitySummary} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const indicatorData = ref([])
|
||||
const total = ref(0)
|
||||
const showAdd = ref(false)
|
||||
const summary = ref({})
|
||||
|
||||
const statCards = ref([
|
||||
{label:'总指标', value:0, color:'#409eff'},
|
||||
{label:'达标数', value:0, color:'#67c23a'},
|
||||
{label:'未达标数', value:0, color:'#f56c6c'},
|
||||
{label:'达标率', value:'0%', color:'#e6a23c'},
|
||||
{label:'科室数', value:0, color:'#909399'},
|
||||
{label:'平均达标率', value:'0%', color:'#409eff'}
|
||||
])
|
||||
|
||||
const q = ref({pageNo:1, pageSize:20, indicatorCategory:'', departmentName:''})
|
||||
const formData = reactive({
|
||||
indicatorCode:'', indicatorName:'', indicatorCategory:'BASIC',
|
||||
targetValue:0, actualValue:0, departmentName:'', statDate:'', remark:''
|
||||
})
|
||||
|
||||
function categoryText(c) {
|
||||
return {BASIC:'基础护理',SPECIALIZED:'专科护理',SAFETY:'护理安全',DOCUMENTATION:'护理文书',STERILIZATION:'消毒隔离'}[c] || c
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [i, s] = await Promise.all([
|
||||
getQualityPage(q.value),
|
||||
getQualitySummary()
|
||||
])
|
||||
indicatorData.value = i.data?.records || []
|
||||
total.value = i.data?.total || 0
|
||||
summary.value = s.data || {}
|
||||
// Calculate stats
|
||||
let met = 0, unmet = 0, deptSet = new Set()
|
||||
indicatorData.value.forEach(row => {
|
||||
if (row.actualValue >= row.targetValue) met++
|
||||
else unmet++
|
||||
deptSet.add(row.departmentName)
|
||||
})
|
||||
statCards.value[0].value = total.value
|
||||
statCards.value[1].value = met
|
||||
statCards.value[2].value = unmet
|
||||
statCards.value[3].value = total.value > 0 ? Math.round(met * 100 / total.value) + '%' : '0%'
|
||||
statCards.value[4].value = deptSet.size
|
||||
statCards.value[5].value = summary.value.meetRate ? summary.value.meetRate + '%' : '0%'
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.value = {pageNo:1, pageSize:20, indicatorCategory:'', departmentName:''}
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await addIndicator(formData)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
loadData()
|
||||
}
|
||||
|
||||
function exportReport() { ElMessage.info('导出功能开发中') }
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
@@ -1,31 +1,167 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">药品库存预警</span></div>
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<div style="display:flex;gap:40px;text-align:center">
|
||||
<div><div style="font-size:28px;font-weight:bold;color:#e6a23c">{{ stats.low||0 }}</div><div>低库存</div></div>
|
||||
<div><div style="font-size:28px;font-weight:bold;color:#f56c6c">{{ stats.out||0 }}</div><div>缺货</div></div>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">药品库存预警管理</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增预警</el-button>
|
||||
<el-button type="warning" @click="exportReport">导出报告</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<div style="margin-bottom:12px"><el-button type="success" @click="showAdd=true">新增预警</el-button></div>
|
||||
<el-table :data="alertData" border stripe>
|
||||
<el-table-column prop="drugName" label="药品" min-width="150"/>
|
||||
<el-table-column prop="drugSpec" label="规格" width="100"/>
|
||||
<el-table-column prop="currentStock" label="库存" width="80" align="center"/>
|
||||
<el-table-column prop="minStock" label="最低" width="80" align="center"/>
|
||||
<el-table-column prop="alertLevel" label="级别" width="100">
|
||||
<template #default="{row}"><el-tag :type="row.alertLevel==='OUT'?'danger':'warning'" size="small">{{ row.alertLevel==='OUT'?'缺货':'低库存' }}</el-tag></template>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'10px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-select v-model="q.alertLevel" placeholder="预警级别" clearable style="width:120px">
|
||||
<el-option label="缺货" value="OUT"/>
|
||||
<el-option label="低库存" value="LOW"/>
|
||||
<el-option label="近效期" value="EXPIRING"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.drugCategory" placeholder="药品类别" clearable style="width:140px">
|
||||
<el-option label="西药" value="WESTERN"/>
|
||||
<el-option label="中成药" value="CHINESE"/>
|
||||
<el-option label="中草药" value="HERBAL"/>
|
||||
<el-option label="生物制品" value="BIOLOGICAL"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="alertData" border stripe v-loading="loading">
|
||||
<el-table-column prop="drugCode" label="药品编码" width="120"/>
|
||||
<el-table-column prop="drugName" label="药品名称" min-width="160" show-overflow-tooltip/>
|
||||
<el-table-column prop="drugSpec" label="规格" width="120"/>
|
||||
<el-table-column prop="currentStock" label="当前库存" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<span :style="{color: row.currentStock <= 0 ? '#F56C6C' : row.currentStock < row.minStock ? '#E6A23C' : '#67C23A', fontWeight:'bold'}">
|
||||
{{ row.currentStock }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="minStock" label="最低库存" width="100" align="center"/>
|
||||
<el-table-column prop="maxStock" label="最高库存" width="100" align="center"/>
|
||||
<el-table-column prop="alertLevel" label="预警级别" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.alertLevel==='OUT'?'danger':row.alertLevel==='LOW'?'warning':'info'" size="small">
|
||||
{{ row.alertLevel==='OUT'?'缺货':row.alertLevel==='LOW'?'低库存':row.alertLevel==='EXPIRING'?'近效期':'正常' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="supplierName" label="供应商" width="140"/>
|
||||
<el-table-column prop="lastRestockDate" label="上次进货" width="120"/>
|
||||
<el-table-column prop="expiryDate" label="有效期至" width="120"/>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button link type="primary" @click="handleOrder(row)">补货申请</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="supplierName" label="供应商" width="120"/>
|
||||
</el-table>
|
||||
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增药品库存预警" v-model="showAdd" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="110px">
|
||||
<el-form-item label="药品编码" required><el-input v-model="formData.drugCode" placeholder="请输入药品编码"/></el-form-item>
|
||||
<el-form-item label="药品名称" required><el-input v-model="formData.drugName" placeholder="请输入药品名称"/></el-form-item>
|
||||
<el-form-item label="规格"><el-input v-model="formData.drugSpec" placeholder="如: 0.5g*24片"/></el-form-item>
|
||||
<el-form-item label="当前库存"><el-input-number v-model="formData.currentStock" :min="0"/></el-form-item>
|
||||
<el-form-item label="最低库存"><el-input-number v-model="formData.minStock" :min="0"/></el-form-item>
|
||||
<el-form-item label="最高库存"><el-input-number v-model="formData.maxStock" :min="0"/></el-form-item>
|
||||
<el-form-item label="预警级别">
|
||||
<el-select v-model="formData.alertLevel" placeholder="请选择">
|
||||
<el-option label="缺货" value="OUT"/>
|
||||
<el-option label="低库存" value="LOW"/>
|
||||
<el-option label="近效期" value="EXPIRING"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="供应商"><el-input v-model="formData.supplierName" placeholder="请输入供应商"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ref, reactive, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getAlertPage,addAlert,getAlertStats} from './api'
|
||||
const alertData=ref([]),stats=ref({})
|
||||
const showAdd=ref(false)
|
||||
const loadData=async()=>{const [a,s]=await Promise.all([getAlertPage({pageNo:1,pageSize:50}),getAlertStats()]);alertData.value=a.data?.records||[];stats.value=s.data||{}}
|
||||
onMounted(()=>loadData())
|
||||
import {getAlertPage, addAlert, getAlertStats} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const alertData = ref([])
|
||||
const total = ref(0)
|
||||
const showAdd = ref(false)
|
||||
|
||||
const statCards = ref([
|
||||
{label:'总预警数', value:0, color:'#409eff'},
|
||||
{label:'缺货', value:0, color:'#f56c6c'},
|
||||
{label:'低库存', value:0, color:'#e6a23c'},
|
||||
{label:'近效期', value:0, color:'#909399'},
|
||||
{label:'供应商数', value:0, color:'#67c23a'},
|
||||
{label:'待补货', value:0, color:'#409eff'}
|
||||
])
|
||||
|
||||
const q = ref({pageNo:1, pageSize:20, alertLevel:'', drugCategory:''})
|
||||
const formData = reactive({
|
||||
drugCode:'', drugName:'', drugSpec:'', currentStock:0, minStock:0,
|
||||
maxStock:100, alertLevel:'LOW', supplierName:''
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [a, s] = await Promise.all([getAlertPage(q.value), getAlertStats()])
|
||||
alertData.value = a.data?.records || []
|
||||
total.value = a.data?.total || 0
|
||||
// Stats
|
||||
let out = 0, low = 0, expiring = 0, supplierSet = new Set()
|
||||
alertData.value.forEach(row => {
|
||||
if (row.alertLevel === 'OUT') out++
|
||||
else if (row.alertLevel === 'LOW') low++
|
||||
else if (row.alertLevel === 'EXPIRING') expiring++
|
||||
supplierSet.add(row.supplierName)
|
||||
})
|
||||
statCards.value[0].value = total.value
|
||||
statCards.value[1].value = out
|
||||
statCards.value[2].value = low
|
||||
statCards.value[3].value = expiring
|
||||
statCards.value[4].value = supplierSet.size
|
||||
statCards.value[5].value = s.data?.pendingOrders || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.value = {pageNo:1, pageSize:20, alertLevel:'', drugCategory:''}
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await addAlert(formData)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleOrder(row) {
|
||||
ElMessage.info('补货申请: ' + row.drugName)
|
||||
}
|
||||
|
||||
function exportReport() { ElMessage.info('导出功能开发中') }
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
@@ -1,28 +1,181 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="q" :inline="true"><el-form-item label="就诊号"><el-input v-model="q.encounterId" clearable /></el-form-item>
|
||||
<el-form-item><el-button type="primary" @click="loadData">查询</el-button></el-form-item></el-form>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="12"><el-card shadow="hover"><el-statistic title="运行质控结果" :value="runtimeResult.status || '-'" /></el-card></el-col>
|
||||
<el-col :span="12"><el-card shadow="hover"><el-statistic title="终末质控评分" :value="terminalResult.score || 0" suffix="分" /></el-card></el-col>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">病历质量统计</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
<el-button type="warning" @click="exportReport">导出报告</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'10px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-card class="mt8"><template #header>缺陷记录</template>
|
||||
<el-table :data="defects" size="small">
|
||||
<el-table-column label="缺陷类型" prop="defectType" width="120" />
|
||||
<el-table-column label="缺陷项" prop="defectItem" width="150" />
|
||||
<el-table-column label="严重程度" prop="severity" width="100">
|
||||
<template #default="s"><el-tag :type="s.row.severity==='CRITICAL'?'danger':s.row.severity==='MAJOR'?'warning':'info'">{{ s.row.severity }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="整改状态" prop="rectifyStatus" width="100" />
|
||||
</el-table></el-card>
|
||||
|
||||
<!-- 查询表单 -->
|
||||
<el-form :model="q" :inline="true" style="margin-bottom:16px">
|
||||
<el-form-item label="就诊号"><el-input v-model="q.encounterId" clearable placeholder="请输入就诊号" style="width:180px"/></el-form-item>
|
||||
<el-form-item label="科室">
|
||||
<el-select v-model="q.departmentName" clearable placeholder="请选择科室" style="width:140px">
|
||||
<el-option label="内科" value="内科"/>
|
||||
<el-option label="外科" value="外科"/>
|
||||
<el-option label="妇产科" value="妇产科"/>
|
||||
<el-option label="儿科" value="儿科"/>
|
||||
<el-option label="ICU" value="ICU"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期范围">
|
||||
<el-date-picker v-model="q.dateRange" type="daterange" start-placeholder="开始" end-placeholder="结束" style="width:240px"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 质控结果 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header><div style="display:flex;justify-content:space-between;align-items:center"><span>运行质控结果</span><el-tag :type="runtimeResult.status==='PASS'?'success':'danger'" size="small">{{ runtimeResult.status || '待检测' }}</el-tag></div></template>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="质控时间">{{ runtimeResult.checkTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="缺陷数">{{ runtimeResult.defectCount || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="严重缺陷">{{ runtimeResult.criticalDefects || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="一般缺陷">{{ runtimeResult.normalDefects || 0 }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header><div style="display:flex;justify-content:space-between;align-items:center"><span>终末质控评分</span><el-tag :type="terminalResult.score>=80?'success':terminalResult.score>=60?'warning':'danger'" size="large">{{ terminalResult.score || 0 }}分</el-tag></div></template>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="评分时间">{{ terminalResult.checkTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="评分等级">{{ scoreLevel(terminalResult.score) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="甲级病历">{{ terminalResult.gradeA || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="乙级病历">{{ terminalResult.gradeB || 0 }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 缺陷记录 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<span>缺陷记录 ({{ defects.length }})</span>
|
||||
<el-select v-model="defectFilter" clearable placeholder="筛选缺陷类型" style="width:140px" @change="filterDefects">
|
||||
<el-option label="全部" value=""/>
|
||||
<el-option label="严重缺陷" value="CRITICAL"/>
|
||||
<el-option label="主要缺陷" value="MAJOR"/>
|
||||
<el-option label="一般缺陷" value="MINOR"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="filteredDefects" border stripe>
|
||||
<el-table-column label="缺陷类型" prop="defectType" width="140"/>
|
||||
<el-table-column label="缺陷项" prop="defectItem" min-width="180" show-overflow-tooltip/>
|
||||
<el-table-column label="严重程度" prop="severity" width="110" align="center">
|
||||
<template #default="s">
|
||||
<el-tag :type="s.row.severity==='CRITICAL'?'danger':s.row.severity==='MAJOR'?'warning':'info'" size="small">
|
||||
{{ s.row.severity==='CRITICAL'?'严重缺陷':s.row.severity==='MAJOR'?'主要缺陷':'一般缺陷' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="整改状态" prop="rectifyStatus" width="100" align="center">
|
||||
<template #default="s">
|
||||
<el-tag :type="s.row.rectifyStatus==='RECTIFIED'?'success':s.row.rectifyStatus==='RECTIFYING'?'warning':'info'" size="small">
|
||||
{{ s.row.rectifyStatus==='RECTIFIED'?'已整改':s.row.rectifyStatus==='RECTIFYING'?'整改中':'待整改' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="发现时间" prop="discoverTime" width="160"/>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'; import { runtimeCheck, terminalCheck, getDefects } from '@/api/quality'
|
||||
const q = reactive({ encounterId: '' }); const runtimeResult = ref({}); const terminalResult = ref({}); const defects = ref([])
|
||||
const loadData = async () => {
|
||||
if (!q.encounterId) return
|
||||
const [r1, r2, r3] = await Promise.all([runtimeCheck(q.encounterId), terminalCheck(q.encounterId), getDefects(q.encounterId)])
|
||||
runtimeResult.value = r1.data || {}; terminalResult.value = r2.data || {}; defects.value = r3.data || []
|
||||
import {ref, reactive, computed, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {runtimeCheck, terminalCheck, getDefects, getQualityStatistics} from '@/api/quality'
|
||||
|
||||
const q = reactive({encounterId:'', departmentName:'', dateRange:null})
|
||||
const runtimeResult = ref({})
|
||||
const terminalResult = ref({})
|
||||
const defects = ref([])
|
||||
const defectFilter = ref('')
|
||||
const qualityStats = ref({})
|
||||
|
||||
const statCards = ref([
|
||||
{label:'总病历数', value:0, color:'#409eff'},
|
||||
{label:'甲级病历', value:0, color:'#67c23a'},
|
||||
{label:'乙级病历', value:0, color:'#e6a23c'},
|
||||
{label:'丙级病历', value:0, color:'#f56c6c'},
|
||||
{label:'缺陷总数', value:0, color:'#909399'},
|
||||
{label:'甲级率', value:'0%', color:'#409eff'}
|
||||
])
|
||||
|
||||
const filteredDefects = computed(() => {
|
||||
if (!defectFilter.value) return defects.value
|
||||
return defects.value.filter(d => d.severity === defectFilter.value)
|
||||
})
|
||||
|
||||
function scoreLevel(score) {
|
||||
if (!score) return '-'
|
||||
if (score >= 90) return '甲级'
|
||||
if (score >= 75) return '乙级'
|
||||
if (score >= 60) return '丙级'
|
||||
return '不合格'
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
if (q.encounterId) {
|
||||
const [r1, r2, r3] = await Promise.all([
|
||||
runtimeCheck(q.encounterId),
|
||||
terminalCheck(q.encounterId),
|
||||
getDefects(q.encounterId)
|
||||
])
|
||||
runtimeResult.value = r1.data || {}
|
||||
terminalResult.value = r2.data || {}
|
||||
defects.value = r3.data || []
|
||||
}
|
||||
// Load overall statistics
|
||||
try {
|
||||
const r4 = await getQualityStatistics(q)
|
||||
qualityStats.value = r4.data || {}
|
||||
statCards.value[0].value = qualityStats.value.totalRecords || 0
|
||||
statCards.value[1].value = qualityStats.value.gradeA || 0
|
||||
statCards.value[2].value = qualityStats.value.gradeB || 0
|
||||
statCards.value[3].value = qualityStats.value.gradeC || 0
|
||||
statCards.value[4].value = qualityStats.value.totalDefects || 0
|
||||
statCards.value[5].value = (qualityStats.value.gradeARate || 0) + '%'
|
||||
} catch(e) {}
|
||||
} catch(e) { ElMessage.error('查询失败') }
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
q.encounterId = ''
|
||||
q.departmentName = ''
|
||||
q.dateRange = null
|
||||
runtimeResult.value = {}
|
||||
terminalResult.value = {}
|
||||
defects.value = []
|
||||
loadData()
|
||||
}
|
||||
|
||||
function filterDefects() {}
|
||||
|
||||
function exportReport() { ElMessage.info('导出功能开发中') }
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
@@ -131,10 +131,12 @@
|
||||
</template>
|
||||
|
||||
<script setup name="InteractionRule" lang="ts">
|
||||
import { useDict } from '@/utils/dict'
|
||||
import { ref, reactive, toRefs, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { listInteractionRules, addInteractionRule, updateInteractionRule, delInteractionRule } from '@/api/rationaldrug'
|
||||
|
||||
const { interaction_severity } = useDict('interaction_severity')
|
||||
const ruleList = ref([])
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
@@ -146,11 +148,7 @@ const dialogTitle = ref('')
|
||||
const queryForm = ref(null)
|
||||
const ruleForm = ref(null)
|
||||
|
||||
const severityOptions = [
|
||||
{ label: '严重', value: '严重' },
|
||||
{ label: '中度', value: '中度' },
|
||||
{ label: '轻度', value: '轻度' }
|
||||
]
|
||||
const severityOptions = computed(() => interaction_severity.value || [])
|
||||
|
||||
const data = reactive({
|
||||
queryParams: {
|
||||
|
||||
86
healthlink-his-ui/src/views/review/plan/index.vue
Normal file
86
healthlink-his-ui/src/views/review/plan/index.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="100px">
|
||||
<el-form-item label="计划名称" prop="planName">
|
||||
<el-input v-model="queryParams.planName" placeholder="请输入计划名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择" clearable>
|
||||
<el-option label="进行中" value="ACTIVE" /><el-option label="已完成" value="COMPLETED" /><el-option label="已终止" value="TERMINATED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5"><el-button type="primary" plain icon="Plus" @click="handleAdd">新增计划</el-button></el-col>
|
||||
</el-row>
|
||||
<el-table v-loading="loading" :data="planList" border>
|
||||
<el-table-column label="计划名称" prop="planName" min-width="180" />
|
||||
<el-table-column label="点评类型" prop="reviewType" width="120" />
|
||||
<el-table-column label="科室" prop="deptName" width="120" />
|
||||
<el-table-column label="点评处方数" prop="targetCount" width="110" />
|
||||
<el-table-column label="已点评数" prop="reviewedCount" width="100" />
|
||||
<el-table-column label="开始日期" prop="startDate" width="120" />
|
||||
<el-table-column label="结束日期" prop="endDate" width="120" />
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="s">
|
||||
<el-tag :type="s.row.status==='ACTIVE'?'success':s.row.status==='COMPLETED'?'info':'danger'">
|
||||
{{ {ACTIVE:'进行中',COMPLETED:'已完成',TERMINATED:'已终止'}[s.row.status] || s.row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="s">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(s.row)">修改</el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-show="total>0" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10,20,50]" :total="total" layout="total, sizes, prev, pager, next" @size-change="handleQuery" @current-change="handleQuery" />
|
||||
|
||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" append-to-body>
|
||||
<el-form ref="planForm" :model="form" :rules="rules" label-width="110px">
|
||||
<el-form-item label="计划名称" prop="planName"><el-input v-model="form.planName" placeholder="请输入计划名称" /></el-form-item>
|
||||
<el-form-item label="点评类型" prop="reviewType">
|
||||
<el-select v-model="form.reviewType" placeholder="请选择" style="width:100%">
|
||||
<el-option label="处方点评" value="PRESCRIPTION" /><el-option label="抗菌药物专项" value="ANTIBIOTIC" /><el-option label="重点监控药品" value="MONITORED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="科室" prop="deptName"><el-input v-model="form.deptName" placeholder="请输入科室名称" /></el-form-item>
|
||||
<el-form-item label="目标处方数" prop="targetCount"><el-input-number v-model="form.targetCount" :min="1" :max="10000" /></el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="开始日期" prop="startDate"><el-date-picker v-model="form.startDate" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="结束日期" prop="endDate"><el-date-picker v-model="form.endDate" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { createPlan, listPlans, updatePlan, deletePlan } from '@/api/review'
|
||||
|
||||
const loading = ref(false); const total = ref(0); const planList = ref([])
|
||||
const dialogVisible = ref(false); const dialogTitle = ref(''); const queryForm = ref(null); const planForm = ref(null)
|
||||
const queryParams = reactive({ pageNum: 1, pageSize: 10, planName: '', status: '' })
|
||||
const form = reactive({ id: null, planName: '', reviewType: 'PRESCRIPTION', deptName: '', targetCount: 50, startDate: '', endDate: '', remark: '' })
|
||||
const rules = { planName: [{ required: true, message: '请输入计划名称', trigger: 'blur' }], reviewType: [{ required: true, message: '请选择点评类型', trigger: 'change' }] }
|
||||
|
||||
function handleQuery() { loading.value = true; listPlans(queryParams).then(res => { planList.value = res.data?.records || res.data || []; total.value = res.data?.total || 0 }).finally(() => loading.value = false) }
|
||||
function handleReset() { queryForm.value?.resetFields(); queryParams.pageNum = 1; handleQuery() }
|
||||
function handleAdd() { Object.assign(form, { id: null, planName: '', reviewType: 'PRESCRIPTION', deptName: '', targetCount: 50, startDate: '', endDate: '', remark: '' }); dialogTitle.value = '新增点评计划'; dialogVisible.value = true }
|
||||
function handleUpdate(row) { Object.assign(form, row); dialogTitle.value = '修改点评计划'; dialogVisible.value = true }
|
||||
function handleSubmit() { planForm.value?.validate(valid => { if (valid) { const action = form.id ? updatePlan(form) : createPlan(form); action.then(() => { ElMessage.success(form.id ? '修改成功' : '新增成功'); dialogVisible.value = false; handleQuery() }) } }) }
|
||||
function handleDelete(row) { ElMessageBox.confirm('确认删除该计划?', '提示', { type: 'warning' }).then(() => deletePlan(row.id)).then(() => { ElMessage.success('删除成功'); handleQuery() }) }
|
||||
onMounted(() => handleQuery())
|
||||
</script>
|
||||
39
healthlink-his-ui/src/views/review/ranking/index.vue
Normal file
39
healthlink-his-ui/src/views/review/ranking/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="科室合理率TOP5" :value="topDeptRate" suffix="%" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="不合理处方总数" :value="stats.unreasonableCount || 0" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="总点评数" :value="stats.totalRecords || 0" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="整体合理率" :value="stats.reasonableRate || 100" suffix="%" /></el-card></el-col>
|
||||
</el-row>
|
||||
<el-card shadow="never" class="mb8">
|
||||
<template #header><span>医生点评排名(按不合理数排序)</span></template>
|
||||
<el-table :data="ranking" v-loading="loading" border>
|
||||
<el-table-column label="排名" type="index" width="70" />
|
||||
<el-table-column label="医生" prop="doctorName" width="120" />
|
||||
<el-table-column label="科室" prop="deptName" width="120" />
|
||||
<el-table-column label="总点评数" prop="totalReview" width="100" />
|
||||
<el-table-column label="不合理数" prop="unreasonableCount" width="100">
|
||||
<template #default="s"><el-text type="danger">{{ s.row.unreasonableCount }}</el-text></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合理率" width="100">
|
||||
<template #default="s">
|
||||
<el-progress :percentage="s.row.reasonableRate || 0" :color="s.row.reasonableRate>=90?'#67C23A':s.row.reasonableRate>=70?'#E6A23C':'#F56C6C'" :stroke-width="16" :text-inside="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getStatistics, getDoctorRanking } from '@/api/review'
|
||||
const loading = ref(false); const stats = ref({}); const ranking = ref([])
|
||||
const topDeptRate = computed(() => stats.value.reasonableRate || 100)
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
const [s, r] = await Promise.all([getStatistics(), getDoctorRanking()])
|
||||
stats.value = s.data || {}; ranking.value = r.data || []
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
38
healthlink-his-ui/src/views/review/records/index.vue
Normal file
38
healthlink-his-ui/src/views/review/records/index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" :inline="true" label-width="80px">
|
||||
<el-form-item label="处方号"><el-input v-model="queryParams.prescriptionNo" clearable placeholder="请输入" /></el-form-item>
|
||||
<el-form-item label="结果">
|
||||
<el-select v-model="queryParams.reviewResult" clearable placeholder="全部">
|
||||
<el-option label="合理" value="REASONABLE" /><el-option label="不合理" value="UNREASONABLE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="queryParams.pageNum=1;handleQuery()">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="loading" :data="records" border>
|
||||
<el-table-column label="处方号" prop="prescriptionNo" width="140" />
|
||||
<el-table-column label="患者" prop="patientName" width="100" />
|
||||
<el-table-column label="医生" prop="doctorName" width="100" />
|
||||
<el-table-column label="点评结果" width="100">
|
||||
<template #default="s"><el-tag :type="s.row.reviewResult==='REASONABLE'?'success':'danger'">{{ s.row.reviewResult==='REASONABLE'?'合理':'不合理' }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="不合理类型" prop="unreasonableType" width="140" />
|
||||
<el-table-column label="点评意见" prop="comment" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="点评人" prop="reviewerName" width="100" />
|
||||
<el-table-column label="点评时间" prop="reviewTime" width="170" />
|
||||
</el-table>
|
||||
<el-pagination v-show="total>0" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10,20,50]" :total="total" layout="total, sizes, prev, pager, next" @size-change="handleQuery" @current-change="handleQuery" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { listRecords } from '@/api/review'
|
||||
const loading = ref(false); const total = ref(0); const records = ref([])
|
||||
const queryParams = reactive({ pageNum: 1, pageSize: 10, prescriptionNo: '', reviewResult: '' })
|
||||
function handleQuery() { loading.value = true; listRecords(queryParams).then(res => { records.value = res.data?.records || res.data || []; total.value = res.data?.total || 0 }).finally(() => loading.value = false) }
|
||||
onMounted(() => handleQuery())
|
||||
</script>
|
||||
73
healthlink-his-ui/src/views/review/workbench/index.vue
Normal file
73
healthlink-his-ui/src/views/review/workbench/index.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>待点评处方</span></template>
|
||||
<el-table :data="pendingList" v-loading="loading" highlight-current-row @current-change="handleSelect" size="small">
|
||||
<el-table-column label="患者" prop="patientName" width="80" />
|
||||
<el-table-column label="处方号" prop="prescriptionNo" width="120" />
|
||||
<el-table-column label="药品" prop="drugNames" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="医生" prop="doctorName" width="80" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>处方详情与点评</span></template>
|
||||
<div v-if="!selectedPrescription" style="text-align:center;color:#999;padding:60px 0;">← 请从左侧选择一条处方</div>
|
||||
<div v-else>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="患者">{{ selectedPrescription.patientName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="处方号">{{ selectedPrescription.prescriptionNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="诊断">{{ selectedPrescription.diagnosis }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开方医生">{{ selectedPrescription.doctorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="药品明细" :span="2">{{ selectedPrescription.drugNames }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用法用量" :span="2">{{ selectedPrescription.dosageInfo }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<el-form :model="reviewForm" label-width="100px">
|
||||
<el-form-item label="点评结果" required>
|
||||
<el-radio-group v-model="reviewForm.reviewResult">
|
||||
<el-radio value="REASONABLE">合理</el-radio>
|
||||
<el-radio value="UNREASONABLE">不合理</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="不合理类型" v-if="reviewForm.reviewResult==='UNREASONABLE'">
|
||||
<el-select v-model="reviewForm.unreasonableType" placeholder="请选择" multiple style="width:100%">
|
||||
<el-option label="适应症不适宜" value="INDICATION" /><el-option label="用法用量不适宜" value="DOSAGE" />
|
||||
<el-option label="重复用药" value="DUPLICATE" /><el-option label="配伍禁忌" value="INTERACTION" />
|
||||
<el-option label="超说明书用药" value="OFF_LABEL" /><el-option label="其他" value="OTHER" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="点评意见"><el-input v-model="reviewForm.comment" type="textarea" :rows="3" placeholder="请输入点评意见" /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSubmitReview" :disabled="!reviewForm.reviewResult">提交点评</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { listRecords, submitReview } from '@/api/review'
|
||||
|
||||
const loading = ref(false); const pendingList = ref([]); const selectedPrescription = ref(null)
|
||||
const reviewForm = reactive({ reviewResult: '', unreasonableType: [], comment: '' })
|
||||
|
||||
function handleSelect(row) { selectedPrescription.value = row; reviewForm.reviewResult = ''; reviewForm.unreasonableType = []; reviewForm.comment = '' }
|
||||
function handleSubmitReview() {
|
||||
submitReview({ prescriptionNo: selectedPrescription.value.prescriptionNo, encounterId: selectedPrescription.value.encounterId,
|
||||
reviewResult: reviewForm.reviewResult, unreasonableType: reviewForm.unreasonableType.join(','), comment: reviewForm.comment,
|
||||
reviewerName: '当前用户' }).then(() => {
|
||||
ElMessage.success('点评提交成功')
|
||||
pendingList.value = pendingList.value.filter(p => p.prescriptionNo !== selectedPrescription.value.prescriptionNo)
|
||||
selectedPrescription.value = null
|
||||
})
|
||||
}
|
||||
onMounted(() => { loading.value = true; listRecords({ status: 'PENDING', pageSize: 50 }).then(res => { pendingList.value = res.data?.records || res.data || [] }).finally(() => loading.value = false) })
|
||||
</script>
|
||||
@@ -5,8 +5,8 @@
|
||||
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:140px"/>
|
||||
<el-input v-model="q.barcode" placeholder="条码号" clearable style="width:140px"/>
|
||||
<el-select v-model="q.status" placeholder="状态" clearable style="width:120px">
|
||||
<el-option label="已采集" value="COLLECTED"/>
|
||||
<el-option label="已扫码" value="SCANNED"/>
|
||||
<el-option v-for="d in specimen_status" :key="d.value" :label="d.label" :value="d.value" />
|
||||
|
||||
<el-option label="已拒收" value="REJECTED"/>
|
||||
</el-select>
|
||||
<el-input v-model="scanInput" placeholder="扫码(回车确认)" style="width:180px" @keyup.enter="doScan"/>
|
||||
@@ -58,9 +58,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage,ElMessageBox} from 'element-plus'
|
||||
import {getPage,add,update,scanConfirm,reject,del} from './api'
|
||||
const { specimen_status } = useDict('specimen_status')
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,patientName:'',barcode:'',status:''})
|
||||
const scanInput=ref('')
|
||||
|
||||
88
healthlink-his-ui/src/views/tcm/constitution/index.vue
Normal file
88
healthlink-his-ui/src/views/tcm/constitution/index.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="100px">
|
||||
<el-form-item label="就诊号" prop="encounterId">
|
||||
<el-input v-model="queryParams.encounterId" placeholder="请输入就诊号" clearable style="width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
|
||||
<el-button type="success" icon="Plus" @click="handleAdd">新增评估</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="loading" :data="assessmentList" border>
|
||||
<el-table-column label="患者" align="center" prop="patientName" width="120" />
|
||||
<el-table-column label="就诊号" align="center" prop="encounterId" width="140" />
|
||||
<el-table-column label="体质类型" align="center" prop="constitutionType" width="140">
|
||||
<template #default="scope"><el-tag>{{ constitutionText(scope.row.constitutionType) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="评估得分" align="center" prop="score" width="100" />
|
||||
<el-table-column label="评估医生" align="center" prop="assessorName" width="120" />
|
||||
<el-table-column label="评估时间" align="center" prop="assessTime" width="180" />
|
||||
<el-table-column label="调理建议" align="center" prop="suggestion" min-width="200" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-dialog title="中医体质辨识" v-model="addVisible" width="650px" append-to-body>
|
||||
<el-form :model="formData" label-width="110px">
|
||||
<el-form-item label="患者姓名"><el-input v-model="formData.patientName" placeholder="请输入患者姓名" /></el-form-item>
|
||||
<el-form-item label="就诊号"><el-input v-model="formData.encounterId" placeholder="请输入就诊号" /></el-form-item>
|
||||
<el-form-item label="体质类型">
|
||||
<el-select v-model="formData.constitutionType" placeholder="请选择">
|
||||
<el-option v-for="item in constitutionTypes" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评估得分"><el-input-number v-model="formData.score" :min="0" :max="100" /></el-form-item>
|
||||
<el-form-item label="调理建议"><el-input v-model="formData.suggestion" type="textarea" :rows="3" placeholder="请输入调理建议" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="addVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="TcmConstitution" lang="js">
|
||||
import { ref, reactive, toRefs, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
const assessmentList = ref([])
|
||||
const loading = ref(false)
|
||||
const addVisible = ref(false)
|
||||
const constitutionTypes = [
|
||||
{ value: 'PINGHE', label: '平和质' },
|
||||
{ value: 'QIXU', label: '气虚质' },
|
||||
{ value: 'YANGXU', label: '阳虚质' },
|
||||
{ value: 'YINXU', label: '阴虚质' },
|
||||
{ value: 'TANSHI', label: '痰湿质' },
|
||||
{ value: 'SHIRE', label: '湿热质' },
|
||||
{ value: 'XUEYU', label: '血瘀质' },
|
||||
{ value: 'QIYU', label: '气郁质' },
|
||||
{ value: 'TEBING', label: '特禀质' }
|
||||
]
|
||||
const data = reactive({
|
||||
queryParams: { encounterId: undefined },
|
||||
formData: { patientName: '', encounterId: '', constitutionType: 'PINGHE', score: 0, suggestion: '' }
|
||||
})
|
||||
const { queryParams, formData } = toRefs(data)
|
||||
function constitutionText(t) {
|
||||
const m = { PINGHE: '平和质', QIXU: '气虚质', YANGXU: '阳虚质', YINXU: '阴虚质', TANSHI: '痰湿质', SHIRE: '湿热质', XUEYU: '血瘀质', QIYU: '气郁质', TEBING: '特禀质' }
|
||||
return m[t] || t
|
||||
}
|
||||
function handleQuery() {
|
||||
loading.value = true
|
||||
const url = queryParams.value.encounterId
|
||||
? `/api/v1/tcm/constitution/encounter/${queryParams.value.encounterId}`
|
||||
: '/api/v1/tcm/statistics'
|
||||
request({ url, method: 'get' }).then(res => {
|
||||
assessmentList.value = Array.isArray(res.data) ? res.data : (res.data?.records || [])
|
||||
}).catch(() => { assessmentList.value = [] }).finally(() => { loading.value = false })
|
||||
}
|
||||
function handleAdd() {
|
||||
formData.value = { patientName: '', encounterId: '', constitutionType: 'PINGHE', score: 0, suggestion: '' }
|
||||
addVisible.value = true
|
||||
}
|
||||
function submitForm() {
|
||||
request({ url: '/api/v1/tcm/constitution', method: 'post', data: formData.value }).then(() => {
|
||||
ElMessage.success('评估完成'); addVisible.value = false; handleQuery()
|
||||
})
|
||||
}
|
||||
onMounted(() => { handleQuery() })
|
||||
</script>
|
||||
104
healthlink-his-ui/src/views/tcm/traditional/index.vue
Normal file
104
healthlink-his-ui/src/views/tcm/traditional/index.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="100px">
|
||||
<el-form-item label="方剂类型" prop="type">
|
||||
<el-select v-model="queryParams.type" placeholder="请选择" clearable style="width: 160px">
|
||||
<el-option label="经典方剂" value="CLASSIC" />
|
||||
<el-option label="经验方" value="EXPERIENCE" />
|
||||
<el-option label="协定方" value="AGREEMENT" />
|
||||
<el-option label="壮医方" value="ZHUANG" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
<el-button type="success" icon="Plus" @click="handleAdd">新增方剂</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="loading" :data="prescriptionList" border>
|
||||
<el-table-column label="方剂名称" align="center" prop="name" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="方剂类型" align="center" prop="type" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag>{{ typeText(scope.row.type) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="组成" align="center" prop="composition" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="功效" align="center" prop="effect" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="主治" align="center" prop="indication" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="用法用量" align="center" prop="dosage" width="120" />
|
||||
<el-table-column label="操作" align="center" width="80" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-dialog title="方剂详情" v-model="detailVisible" width="650px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="方剂名称">{{ detailData.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="方剂类型">{{ typeText(detailData.type) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="组成" :span="2">{{ detailData.composition }}</el-descriptions-item>
|
||||
<el-descriptions-item label="功效" :span="2">{{ detailData.effect }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主治" :span="2">{{ detailData.indication }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用法用量">{{ detailData.dosage }}</el-descriptions-item>
|
||||
<el-descriptions-item label="方解">{{ detailData.analysis }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer><el-button @click="detailVisible = false">关闭</el-button></template>
|
||||
</el-dialog>
|
||||
<el-dialog title="新增方剂" v-model="addVisible" width="600px" append-to-body>
|
||||
<el-form :model="formData" label-width="100px">
|
||||
<el-form-item label="方剂名称"><el-input v-model="formData.name" placeholder="请输入方剂名称" /></el-form-item>
|
||||
<el-form-item label="方剂类型">
|
||||
<el-select v-model="formData.type" placeholder="请选择">
|
||||
<el-option label="经典方剂" value="CLASSIC" />
|
||||
<el-option label="经验方" value="EXPERIENCE" />
|
||||
<el-option label="协定方" value="AGREEMENT" />
|
||||
<el-option label="壮医方" value="ZHUANG" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="组成"><el-input v-model="formData.composition" type="textarea" :rows="3" placeholder="请输入药物组成" /></el-form-item>
|
||||
<el-form-item label="功效"><el-input v-model="formData.effect" placeholder="请输入功效" /></el-form-item>
|
||||
<el-form-item label="主治"><el-input v-model="formData.indication" placeholder="请输入主治病症" /></el-form-item>
|
||||
<el-form-item label="用法用量"><el-input v-model="formData.dosage" placeholder="请输入用法用量" /></el-form-item>
|
||||
<el-form-item label="方解"><el-input v-model="formData.analysis" type="textarea" :rows="2" placeholder="请输入方解" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="addVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="TcmPrescription" lang="js">
|
||||
import { ref, reactive, toRefs, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
const prescriptionList = ref([])
|
||||
const loading = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const addVisible = ref(false)
|
||||
const detailData = ref({})
|
||||
const data = reactive({
|
||||
queryParams: { type: undefined },
|
||||
formData: { name: '', type: 'CLASSIC', composition: '', effect: '', indication: '', dosage: '', analysis: '' }
|
||||
})
|
||||
const { queryParams, formData } = toRefs(data)
|
||||
function typeText(t) {
|
||||
const m = { CLASSIC: '经典方剂', EXPERIENCE: '经验方', AGREEMENT: '协定方', ZHUANG: '壮医方' }
|
||||
return m[t] || t
|
||||
}
|
||||
function handleQuery() {
|
||||
loading.value = true
|
||||
request({ url: '/api/v1/tcm/prescriptions', method: 'get', params: queryParams.value }).then(res => {
|
||||
prescriptionList.value = res.data || []
|
||||
}).finally(() => { loading.value = false })
|
||||
}
|
||||
function handleReset() { queryParams.value.type = undefined; handleQuery() }
|
||||
function handleDetail(row) { detailData.value = row; detailVisible.value = true }
|
||||
function handleAdd() { formData.value = { name: '', type: 'CLASSIC', composition: '', effect: '', indication: '', dosage: '', analysis: '' }; addVisible.value = true }
|
||||
function submitForm() {
|
||||
request({ url: '/api/v1/tcm/prescription', method: 'post', data: formData.value }).then(() => {
|
||||
ElMessage.success('新增成功'); addVisible.value = false; handleQuery()
|
||||
})
|
||||
}
|
||||
onMounted(() => { handleQuery() })
|
||||
</script>
|
||||
Reference in New Issue
Block a user