feat(V35): P2深度优化 — 护理评估/知情同意/DRG预警/抗菌管理/120联动

V35 Flyway迁移:
- 护理评估动态评分+干预效果追踪(nursing_assessment_dynamic)
- 知情同意电子签名+版本管理(informed_consent_enhanced)
- DRG费用异常预警(drg_cost_alert)
- 抗菌药物分级管理增强(antibiotic_management_enhanced)
- 急诊120院前联动(emergency_ambulance_link)

后端 EnhancementController:
1. 护理评估: 自动风险等级计算(FALL/PRESSURE/NUTRITION/PAIN/THROMBOSIS)
   干预→再评估→效果自动判断(IMPROVED/STABLE/WORSENED)+趋势分析
2. 知情同意: 草稿→待签→已签,电子签名+版本管理+撤销+过期管理
3. DRG预警: 费用偏差自动计算+级别判定(CRITICAL/WARNING/INFO)
4. 抗菌药物: 限制/特殊/非限制三级分级,DDD值追踪,联合用药审核
5. 120联动: 派车→到达→转运→到达→交接全流程追踪

前端 5个页面:
- enhanced-nursing: 评估动态评分+趋势箭头(↑↓→)
- enhanced-consent: 电子签名状态+版本管理
- enhanced-drg-alert: 费用偏差百分比可视化
- enhanced-antibiotic: 分级标签+审核状态
- enhanced-ambulance: 120全流程状态追踪
This commit is contained in:
2026-06-07 11:22:46 +08:00
parent 11803ae9a4
commit 21dd790dd9
32 changed files with 998 additions and 0 deletions

View File

@@ -0,0 +1,369 @@
package com.healthlink.his.web.crossmodule.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.healthlink.his.crossmodule.domain.*;
import com.healthlink.his.crossmodule.service.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.*;
/**
* P2深度优化 Controller — V35
*
* 1. 护理评估动态评分+干预效果追踪: 评估→干预→再评估→效果判断,动态趋势分析
* 2. 知情同意电子签名+版本管理: 草稿→待签→已签,版本追溯+撤销+过期管理
* 3. DRG费用异常预警: 费用偏差/住院日偏差/非计划再入院自动检测
* 4. 抗菌药物分级管理增强: 限制/特殊/非限制分级,DDD值追踪,联合用药审核
* 5. 急诊120院前联动: 派车→到达→转运→到达→交接全流程追踪+院前预警
*/
@RestController
@RequestMapping("/enhancement")
@Slf4j
@AllArgsConstructor
public class EnhancementController {
private final INursingAssessmentDynamicService assessService;
private final IInformedConsentEnhancedService consentService;
private final IDrgCostAlertService drgAlertService;
private final IAntibioticManagementEnhancedService antibioticService;
private final IEmergencyAmbulanceLinkService ambulanceService;
// ==================== 1. 护理评估动态评分 ====================
@GetMapping("/nursing-assessment/page")
public R<?> getAssessPage(
@RequestParam(value = "patientName", required = false) String patientName,
@RequestParam(value = "assessmentType", required = false) String type,
@RequestParam(value = "riskLevel", required = false) String risk,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<NursingAssessmentDynamic> w = new LambdaQueryWrapper<>();
w.like(StringUtils.hasText(patientName), NursingAssessmentDynamic::getPatientName, patientName)
.eq(StringUtils.hasText(type), NursingAssessmentDynamic::getAssessmentType, type)
.eq(StringUtils.hasText(risk), NursingAssessmentDynamic::getRiskLevel, risk)
.orderByDesc(NursingAssessmentDynamic::getAssessmentDate);
return R.ok(assessService.page(new Page<>(pageNo, pageSize), w));
}
@GetMapping("/nursing-assessment/trend")
public R<?> getAssessmentTrend(
@RequestParam("encounterId") Long encounterId,
@RequestParam("assessmentType") String type) {
LambdaQueryWrapper<NursingAssessmentDynamic> w = new LambdaQueryWrapper<>();
w.eq(NursingAssessmentDynamic::getEncounterId, encounterId)
.eq(NursingAssessmentDynamic::getAssessmentType, type)
.orderByAsc(NursingAssessmentDynamic::getAssessmentDate);
List<NursingAssessmentDynamic> list = assessService.list(w);
Map<String, Object> trend = new HashMap<>();
trend.put("assessments", list);
trend.put("count", list.size());
if (list.size() >= 2) {
int first = list.get(0).getScore();
int last = list.get(list.size() - 1).getScore();
trend.put("trend", last < first ? "IMPROVED" : last > first ? "WORSENED" : "STABLE");
trend.put("scoreChange", last - first);
}
return R.ok(trend);
}
@PostMapping("/nursing-assessment/add")
@Transactional(rollbackFor = Exception.class)
public R<?> addAssessment(@RequestBody NursingAssessmentDynamic assess) {
// 自动计算风险等级
assess.setRiskLevel(calculateRiskLevel(assess.getAssessmentType(), assess.getScore()));
assess.setAssessmentDate(assess.getAssessmentDate() != null ? assess.getAssessmentDate() : java.time.LocalDate.now());
assess.setCreateTime(new Date());
assessService.save(assess);
return R.ok(assess);
}
@PutMapping("/nursing-assessment/{id}/reassess")
@Transactional(rollbackFor = Exception.class)
public R<?> reassess(@PathVariable Long id, @RequestBody NursingAssessmentDynamic data) {
NursingAssessmentDynamic assess = assessService.getById(id);
if (assess != null) {
assess.setReAssessmentScore(data.getReAssessmentScore());
assess.setReAssessmentDate(java.time.LocalDate.now());
// 自动判断效果
if (assess.getReAssessmentScore() < assess.getScore()) {
assess.setEffectResult("IMPROVED");
} else if (assess.getReAssessmentScore() > assess.getScore()) {
assess.setEffectResult("WORSENED");
} else {
assess.setEffectResult("STABLE");
}
assess.setEffectDescription(data.getEffectDescription());
// 自动判断趋势
assess.setTrend(assess.getReAssessmentScore() < assess.getScore() ? "DOWN" :
assess.getReAssessmentScore() > assess.getScore() ? "UP" : "FLAT");
assessService.updateById(assess);
}
return R.ok(assess);
}
private String calculateRiskLevel(String type, int score) {
return switch (type) {
case "FALL" -> score >= 45 ? "HIGH" : score >= 25 ? "MEDIUM" : "LOW";
case "PRESSURE" -> score >= 14 ? "HIGH" : score >= 10 ? "MEDIUM" : "LOW";
case "NUTRITION" -> score <= 10 ? "CRITICAL" : score <= 14 ? "HIGH" : score <= 17 ? "MEDIUM" : "LOW";
case "PAIN" -> score >= 7 ? "HIGH" : score >= 4 ? "MEDIUM" : "LOW";
case "THROMBOSIS" -> score >= 5 ? "HIGH" : score >= 3 ? "MEDIUM" : "LOW";
default -> "MEDIUM";
};
}
@DeleteMapping("/nursing-assessment/delete/{id}")
@Transactional(rollbackFor = Exception.class)
public R<?> deleteAssessment(@PathVariable Long id) { assessService.removeById(id); return R.ok(); }
// ==================== 2. 知情同意电子签名 ====================
@GetMapping("/consent/page")
public R<?> getConsentPage(
@RequestParam(value = "patientName", required = false) String patientName,
@RequestParam(value = "consentType", required = false) String type,
@RequestParam(value = "consentStatus", required = false) String status,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<InformedConsentEnhanced> w = new LambdaQueryWrapper<>();
w.like(StringUtils.hasText(patientName), InformedConsentEnhanced::getPatientName, patientName)
.eq(StringUtils.hasText(type), InformedConsentEnhanced::getConsentType, type)
.eq(StringUtils.hasText(status), InformedConsentEnhanced::getConsentStatus, status)
.orderByDesc(InformedConsentEnhanced::getCreateTime);
return R.ok(consentService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/consent/add")
@Transactional(rollbackFor = Exception.class)
public R<?> addConsent(@RequestBody InformedConsentEnhanced consent) {
consent.setVersion(1);
consent.setConsentStatus("DRAFT");
consent.setCreateTime(new Date());
consentService.save(consent);
return R.ok(consent);
}
@PutMapping("/consent/{id}/sign")
@Transactional(rollbackFor = Exception.class)
public R<?> signConsent(@PathVariable Long id, @RequestBody InformedConsentEnhanced data) {
InformedConsentEnhanced consent = consentService.getById(id);
if (consent != null) {
if (StringUtils.hasText(data.getPatientSignature())) {
consent.setPatientSignature(data.getPatientSignature());
consent.setPatientSignTime(new Date());
}
if (StringUtils.hasText(data.getDoctorSignature())) {
consent.setDoctorSignature(data.getDoctorSignature());
consent.setDoctorSignTime(new Date());
}
if (StringUtils.hasText(data.getWitnessSignature())) {
consent.setWitnessSignature(data.getWitnessSignature());
consent.setWitnessName(data.getWitnessName());
}
// 检查是否全部签完
if (StringUtils.hasText(consent.getPatientSignature()) && StringUtils.hasText(consent.getDoctorSignature())) {
consent.setConsentStatus("SIGNED");
consent.setEffectiveTime(new Date());
} else {
consent.setConsentStatus("PENDING");
}
consentService.updateById(consent);
}
return R.ok(consent);
}
@PutMapping("/consent/{id}/revoke")
@Transactional(rollbackFor = Exception.class)
public R<?> revokeConsent(@PathVariable Long id, @RequestBody InformedConsentEnhanced data) {
InformedConsentEnhanced consent = consentService.getById(id);
if (consent != null) {
consent.setRevokeFlag(true);
consent.setRevokeTime(new Date());
consent.setRevokeReason(data.getRevokeReason());
consent.setConsentStatus("REVOKED");
consentService.updateById(consent);
}
return R.ok();
}
@DeleteMapping("/consent/delete/{id}")
@Transactional(rollbackFor = Exception.class)
public R<?> deleteConsent(@PathVariable Long id) { consentService.removeById(id); return R.ok(); }
// ==================== 3. DRG费用异常预警 ====================
@GetMapping("/drg-alert/page")
public R<?> getDrgAlertPage(
@RequestParam(value = "alertType", required = false) String type,
@RequestParam(value = "alertLevel", required = false) String level,
@RequestParam(value = "handleStatus", required = false) String status,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<DrgCostAlert> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(type), DrgCostAlert::getAlertType, type)
.eq(StringUtils.hasText(level), DrgCostAlert::getAlertLevel, level)
.eq(StringUtils.hasText(status), DrgCostAlert::getHandleStatus, status)
.orderByDesc(DrgCostAlert::getCreateTime);
return R.ok(drgAlertService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/drg-alert/check")
@Transactional(rollbackFor = Exception.class)
public R<?> checkDrgAlert(@RequestBody DrgCostAlert alert) {
// 自动计算偏差
if (alert.getExpectedCost() != null && alert.getActualCost() != null && alert.getExpectedCost().compareTo(BigDecimal.ZERO) > 0) {
alert.setCostDeviation(alert.getActualCost().subtract(alert.getExpectedCost())
.multiply(BigDecimal.valueOf(100)).divide(alert.getExpectedCost(), 2, BigDecimal.ROUND_HALF_UP));
}
// 自动判断预警级别
if (alert.getCostDeviation() != null) {
double dev = alert.getCostDeviation().doubleValue();
alert.setAlertLevel(dev > 50 ? "CRITICAL" : dev > 20 ? "WARNING" : "INFO");
alert.setAlertContent(String.format("费用偏差 %.1f%%, 实际费用 %.2f, 预期费用 %.2f",
dev, alert.getActualCost(), alert.getExpectedCost()));
}
// Auto-detect alert type
if (!StringUtils.hasText(alert.getAlertType())) {
if (alert.getCostDeviation() != null && alert.getCostDeviation().doubleValue() > 0) alert.setAlertType("COST_OVER");
else if (alert.getLosActual() != null && alert.getLosExpected() != null && alert.getLosActual() > alert.getLosExpected()) alert.setAlertType("LOS_OVER");
else if (Boolean.TRUE.equals(alert.getReadmitFlag())) alert.setAlertType("READMIT");
else alert.setAlertType("COST_OVER");
}
alert.setHandleStatus("PENDING");
alert.setCreateTime(new Date());
drgAlertService.save(alert);
return R.ok(alert);
}
@PutMapping("/drg-alert/{id}/handle")
@Transactional(rollbackFor = Exception.class)
public R<?> handleDrgAlert(@PathVariable Long id, @RequestBody DrgCostAlert data) {
DrgCostAlert alert = drgAlertService.getById(id);
if (alert != null) {
alert.setHandleStatus("HANDLED");
alert.setHandler(data.getHandler());
alert.setHandleTime(new Date());
alert.setHandleResult(data.getHandleResult());
drgAlertService.updateById(alert);
}
return R.ok();
}
@DeleteMapping("/drg-alert/delete/{id}")
@Transactional(rollbackFor = Exception.class)
public R<?> deleteDrgAlert(@PathVariable Long id) { drgAlertService.removeById(id); return R.ok(); }
// ==================== 4. 抗菌药物分级管理 ====================
@GetMapping("/antibiotic/page")
public R<?> getAntibioticPage(
@RequestParam(value = "patientName", required = false) String patientName,
@RequestParam(value = "antibioticLevel", required = false) String level,
@RequestParam(value = "auditStatus", required = false) String status,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<AntibioticManagementEnhanced> w = new LambdaQueryWrapper<>();
w.like(StringUtils.hasText(patientName), AntibioticManagementEnhanced::getPatientName, patientName)
.eq(StringUtils.hasText(level), AntibioticManagementEnhanced::getAntibioticLevel, level)
.eq(StringUtils.hasText(status), AntibioticManagementEnhanced::getAuditStatus, status)
.orderByDesc(AntibioticManagementEnhanced::getCreateTime);
return R.ok(antibioticService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/antibiotic/add")
@Transactional(rollbackFor = Exception.class)
public R<?> addAntibiotic(@RequestBody AntibioticManagementEnhanced ab) {
ab.setAuditStatus("PENDING");
ab.setCreateTime(new Date());
antibioticService.save(ab);
return R.ok(ab);
}
@PutMapping("/antibiotic/{id}/audit")
@Transactional(rollbackFor = Exception.class)
public R<?> auditAntibiotic(@PathVariable Long id, @RequestBody AntibioticManagementEnhanced data) {
AntibioticManagementEnhanced ab = antibioticService.getById(id);
if (ab != null) {
ab.setAuditStatus("AUDITED");
ab.setAuditor(data.getAuditor());
ab.setAuditTime(new Date());
ab.setAuditResult(data.getAuditResult());
antibioticService.updateById(ab);
}
return R.ok();
}
@DeleteMapping("/antibiotic/delete/{id}")
@Transactional(rollbackFor = Exception.class)
public R<?> deleteAntibiotic(@PathVariable Long id) { antibioticService.removeById(id); return R.ok(); }
// ==================== 5. 急诊120院前联动 ====================
@GetMapping("/ambulance/page")
public R<?> getAmbulancePage(
@RequestParam(value = "ambulanceId", required = false) String ambId,
@RequestParam(value = "linkStatus", required = false) String status,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<EmergencyAmbulanceLink> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(ambId), EmergencyAmbulanceLink::getAmbulanceId, ambId)
.eq(StringUtils.hasText(status), EmergencyAmbulanceLink::getLinkStatus, status)
.orderByDesc(EmergencyAmbulanceLink::getDispatchTime);
return R.ok(ambulanceService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/ambulance/dispatch")
@Transactional(rollbackFor = Exception.class)
public R<?> dispatch(@RequestBody EmergencyAmbulanceLink link) {
link.setDispatchTime(new Date());
link.setLinkStatus("DISPATCHED");
link.setNotificationSent(true);
link.setNotificationTime(new Date());
link.setCreateTime(new Date());
ambulanceService.save(link);
return R.ok(link);
}
@PutMapping("/ambulance/{id}/arrive-scene")
@Transactional(rollbackFor = Exception.class)
public R<?> arriveAtScene(@PathVariable Long id) {
EmergencyAmbulanceLink link = ambulanceService.getById(id);
if (link != null) { link.setArrivalSceneTime(new Date()); link.setLinkStatus("ON_SCENE"); ambulanceService.updateById(link); }
return R.ok();
}
@PutMapping("/ambulance/{id}/transport")
@Transactional(rollbackFor = Exception.class)
public R<?> startTransport(@PathVariable Long id) {
EmergencyAmbulanceLink link = ambulanceService.getById(id);
if (link != null) { link.setLeaveSceneTime(new Date()); link.setLinkStatus("TRANSPORTING"); ambulanceService.updateById(link); }
return R.ok();
}
@PutMapping("/ambulance/{id}/arrive-hospital")
@Transactional(rollbackFor = Exception.class)
public R<?> arriveHospital(@PathVariable Long id) {
EmergencyAmbulanceLink link = ambulanceService.getById(id);
if (link != null) { link.setArriveHospitalTime(new Date()); link.setLinkStatus("ARRIVED"); ambulanceService.updateById(link); }
return R.ok();
}
@PutMapping("/ambulance/{id}/handoff")
@Transactional(rollbackFor = Exception.class)
public R<?> completeHandoff(@PathVariable Long id) {
EmergencyAmbulanceLink link = ambulanceService.getById(id);
if (link != null) { link.setHandoffTime(new Date()); link.setLinkStatus("HANDOFF"); ambulanceService.updateById(link); }
return R.ok();
}
@DeleteMapping("/ambulance/delete/{id}")
@Transactional(rollbackFor = Exception.class)
public R<?> deleteAmbulance(@PathVariable Long id) { ambulanceService.removeById(id); return R.ok(); }
}

View File

@@ -0,0 +1,185 @@
-- V35: P2深度优化 — 护理评估/知情同意/DRG/抗菌/急诊120
-- 1. 护理评估动态评分+干预效果追踪
CREATE TABLE IF NOT EXISTS nursing_assessment_dynamic (
id BIGSERIAL PRIMARY KEY,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(50),
assessment_type VARCHAR(30) NOT NULL,
assessment_tool VARCHAR(50),
score INT NOT NULL,
risk_level VARCHAR(20),
assessment_date DATE NOT NULL,
assessor VARCHAR(64),
intervention_plan TEXT,
intervention_executor VARCHAR(64),
intervention_time TIMESTAMP,
re_assessment_date DATE,
re_assessment_score INT,
effect_result VARCHAR(20),
effect_description TEXT,
trend VARCHAR(20),
delete_flag VARCHAR(1) DEFAULT '0',
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
tenant_id BIGINT DEFAULT 0
);
COMMENT ON TABLE nursing_assessment_dynamic IS '护理评估动态评分+干预效果追踪';
COMMENT ON COLUMN nursing_assessment_dynamic.assessment_type IS '类型(FALL跌倒/PRESSURE压疮/NUTRITION营养/PAIN疼痛/THROMBOSIS血栓)';
COMMENT ON COLUMN nursing_assessment_dynamic.risk_level IS '风险(LOW低/MEDIUM中/HIGH高/CRITICAL极高)';
COMMENT ON COLUMN nursing_assessment_dynamic.effect_result IS '效果(IMPROVED改善/STABLE稳定/WORSENED恶化)';
COMMENT ON COLUMN nursing_assessment_dynamic.trend IS '趋势(UP上升/DOWN下降/FLAT持平)';
CREATE INDEX IF NOT EXISTS nad_encounter ON nursing_assessment_dynamic(encounter_id);
CREATE INDEX IF NOT EXISTS nad_type ON nursing_assessment_dynamic(assessment_type);
-- 2. 知情同意电子签名+版本管理
CREATE TABLE IF NOT EXISTS informed_consent_enhanced (
id BIGSERIAL PRIMARY KEY,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(50),
consent_type VARCHAR(30) NOT NULL,
consent_title VARCHAR(200) NOT NULL,
consent_content TEXT NOT NULL,
version INT NOT NULL DEFAULT 1,
version_reason TEXT,
previous_version_id BIGINT,
patient_signature VARCHAR(500),
patient_sign_time TIMESTAMP,
doctor_signature VARCHAR(500),
doctor_sign_time TIMESTAMP,
witness_signature VARCHAR(500),
witness_name VARCHAR(64),
consent_status VARCHAR(20) DEFAULT 'DRAFT',
effective_time TIMESTAMP,
expire_time TIMESTAMP,
revoke_flag BOOLEAN DEFAULT FALSE,
revoke_time TIMESTAMP,
revoke_reason TEXT,
delete_flag VARCHAR(1) DEFAULT '0',
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
tenant_id BIGINT DEFAULT 0
);
COMMENT ON TABLE informed_consent_enhanced IS '知情同意电子签名+版本管理';
COMMENT ON COLUMN informed_consent_enhanced.consent_type IS '类型(SURGERY手术/ANESTHESIA麻醉/BLOOD输血/EXAM检查/REFUSE拒绝治疗)';
COMMENT ON COLUMN informed_consent_enhanced.consent_status IS '状态(DRAFT草稿/PENDING待签/SIGNED已签/REVOKED已撤销/EXPIRED已过期)';
CREATE INDEX IF NOT EXISTS ice_encounter ON informed_consent_enhanced(encounter_id);
CREATE INDEX IF NOT EXISTS ice_status ON informed_consent_enhanced(consent_status);
-- 3. DRG费用异常预警
CREATE TABLE IF NOT EXISTS drg_cost_alert (
id BIGSERIAL PRIMARY KEY,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(50),
drg_code VARCHAR(20),
drg_name VARCHAR(200),
expected_cost DECIMAL(12,2),
actual_cost DECIMAL(12,2),
cost_deviation DECIMAL(5,2),
alert_type VARCHAR(30) NOT NULL,
alert_level VARCHAR(10) DEFAULT 'WARNING',
alert_content TEXT,
suggested_action TEXT,
los_expected INT,
los_actual INT,
los_deviation INT,
readmit_flag BOOLEAN DEFAULT FALSE,
readmit_days INT,
handle_status VARCHAR(20) DEFAULT 'PENDING',
handler VARCHAR(64),
handle_time TIMESTAMP,
handle_result TEXT,
delete_flag VARCHAR(1) DEFAULT '0',
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
tenant_id BIGINT DEFAULT 0
);
COMMENT ON TABLE drg_cost_alert IS 'DRG费用异常预警';
COMMENT ON COLUMN drg_cost_alert.alert_type IS '类型(COST_OVER费用超标/LOS_OVER住院日超标/READMIT非计划再入院/WEIGHT_LOW权重偏低)';
COMMENT ON COLUMN drg_cost_alert.alert_level IS '级别(INFO提示/WARNING警告/CRITICAL严重)';
CREATE INDEX IF NOT EXISTS dca_encounter ON drg_cost_alert(encounter_id);
CREATE INDEX IF NOT EXISTS dca_alert ON drg_cost_alert(alert_type, alert_level);
-- 4. 抗菌药物分级管理增强
CREATE TABLE IF NOT EXISTS antibiotic_management_enhanced (
id BIGSERIAL PRIMARY KEY,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(50),
drug_code VARCHAR(50) NOT NULL,
drug_name VARCHAR(200) NOT NULL,
antibiotic_level VARCHAR(20) NOT NULL,
usage_indication TEXT,
dosage VARCHAR(100),
frequency VARCHAR(20),
duration_days INT,
use_method VARCHAR(20),
pathogen_result VARCHAR(200),
culture_result VARCHAR(200),
dose_adjusted BOOLEAN DEFAULT FALSE,
ddd_value DECIMAL(8,2),
ddd_usage_days INT,
_flag BOOLEAN DEFAULT FALSE,
_names TEXT,
doctor_id BIGINT,
doctor_name VARCHAR(64),
audit_status VARCHAR(20) DEFAULT 'PENDING',
auditor VARCHAR(64),
audit_time TIMESTAMP,
audit_result VARCHAR(50),
delete_flag VARCHAR(1) DEFAULT '0',
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
tenant_id BIGINT DEFAULT 0
);
COMMENT ON TABLE antibiotic_management_enhanced IS '抗菌药物分级管理增强';
COMMENT ON COLUMN antibiotic_management_enhanced.antibiotic_level IS '分级(RESTRICTED限制使用/SPECIAL特殊使用/NON_RESTRICTED非限制)';
COMMENT ON COLUMN antibiotic_management_enhanced.use_method IS '给药途径(IV静脉/PO口服/IM肌注)';
CREATE INDEX IF NOT EXISTS ame_encounter ON antibiotic_management_enhanced(encounter_id);
CREATE INDEX IF NOT EXISTS ame_level ON antibiotic_management_enhanced(antibiotic_level);
-- 5. 急诊120院前联动
CREATE TABLE IF NOT EXISTS emergency_ambulance_link (
id BIGSERIAL PRIMARY KEY,
encounter_id BIGINT,
patient_id BIGINT,
patient_name VARCHAR(50),
ambulance_id VARCHAR(50),
dispatch_time TIMESTAMP,
arrival_scene_time TIMESTAMP,
leave_scene_time TIMESTAMP,
arrive_hospital_time TIMESTAMP,
handoff_time TIMESTAMP,
distance_km DECIMAL(6,2),
route_description TEXT,
pre_diagnosis TEXT,
pre_vitals TEXT,
pre_medications TEXT,
ekg_sent BOOLEAN DEFAULT FALSE,
ekg_data TEXT,
notification_sent BOOLEAN DEFAULT FALSE,
notification_time TIMESTAMP,
bed_ready BOOLEAN DEFAULT FALSE,
team_ready BOOLEAN DEFAULT FALSE,
link_status VARCHAR(20) DEFAULT 'DISPATCHED',
delete_flag VARCHAR(1) DEFAULT '0',
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
tenant_id BIGINT DEFAULT 0
);
COMMENT ON TABLE emergency_ambulance_link IS '急诊120院前联动';
COMMENT ON COLUMN emergency_ambulance_link.link_status IS '状态(DISPATCHED已派车/ON_SCENE到达现场/TRANSPORTING转运中/ARRIVED已到达/HANDOFF已完成交接)';
CREATE INDEX IF NOT EXISTS eal_status ON emergency_ambulance_link(link_status);

View File

@@ -0,0 +1,21 @@
package com.healthlink.his.crossmodule.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
@Data @EqualsAndHashCode(callSuper = true) @TableName("antibiotic_management_enhanced")
public class AntibioticManagementEnhanced extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID) private Long id;
private Long encounterId; private Long patientId; private String patientName;
private String drugCode; private String drugName; private String antibioticLevel;
private String usageIndication; private String dosage; private String frequency;
private Integer durationDays; private String useMethod;
private String pathogenResult; private String cultureResult;
private Boolean doseAdjusted; private BigDecimal dddValue; private Integer dddUsageDays;
private Boolean combinationFlag; private String combinationNames;
private Long doctorId; private String doctorName;
private String auditStatus; private String auditor; private Date auditTime; private String auditResult;
}

View File

@@ -0,0 +1,19 @@
package com.healthlink.his.crossmodule.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
@Data @EqualsAndHashCode(callSuper = true) @TableName("drg_cost_alert")
public class DrgCostAlert extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID) private Long id;
private Long encounterId; private Long patientId; private String patientName;
private String drgCode; private String drgName;
private BigDecimal expectedCost; private BigDecimal actualCost; private BigDecimal costDeviation;
private String alertType; private String alertLevel; private String alertContent; private String suggestedAction;
private Integer losExpected; private Integer losActual; private Integer losDeviation;
private Boolean readmitFlag; private Integer readmitDays;
private String handleStatus; private String handler; private Date handleTime; private String handleResult;
}

View File

@@ -0,0 +1,20 @@
package com.healthlink.his.crossmodule.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
@Data @EqualsAndHashCode(callSuper = true) @TableName("emergency_ambulance_link")
public class EmergencyAmbulanceLink extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID) private Long id;
private Long encounterId; private Long patientId; private String patientName;
private String ambulanceId; private Date dispatchTime; private Date arrivalSceneTime;
private Date leaveSceneTime; private Date arriveHospitalTime; private Date handoffTime;
private BigDecimal distanceKm; private String routeDescription;
private String preDiagnosis; private String preVitals; private String preMedications;
private Boolean ekgSent; private String ekgData;
private Boolean notificationSent; private Date notificationTime;
private Boolean bedReady; private Boolean teamReady; private String linkStatus;
}

View File

@@ -0,0 +1,20 @@
package com.healthlink.his.crossmodule.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
@Data @EqualsAndHashCode(callSuper = true) @TableName("informed_consent_enhanced")
public class InformedConsentEnhanced extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID) private Long id;
private Long encounterId; private Long patientId; private String patientName;
private String consentType; private String consentTitle; private String consentContent;
private Integer version; private String versionReason; private Long previousVersionId;
private String patientSignature; private Date patientSignTime;
private String doctorSignature; private Date doctorSignTime;
private String witnessSignature; private String witnessName;
private String consentStatus; private Date effectiveTime; private Date expireTime;
private Boolean revokeFlag; private Date revokeTime; private String revokeReason;
}

View File

@@ -0,0 +1,19 @@
package com.healthlink.his.crossmodule.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
@Data @EqualsAndHashCode(callSuper = true) @TableName("nursing_assessment_dynamic")
public class NursingAssessmentDynamic extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID) private Long id;
private Long encounterId; private Long patientId; private String patientName;
private String assessmentType; private String assessmentTool;
private Integer score; private String riskLevel; private LocalDate assessmentDate;
private String assessor; private String interventionPlan;
private String interventionExecutor; private Date interventionTime;
private LocalDate reAssessmentDate; private Integer reAssessmentScore;
private String effectResult; private String effectDescription; private String trend;
}

View File

@@ -0,0 +1,6 @@
package com.healthlink.his.crossmodule.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.crossmodule.domain.AntibioticManagementEnhanced;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AntibioticManagementEnhancedMapper extends BaseMapper<AntibioticManagementEnhanced> {}

View File

@@ -0,0 +1,6 @@
package com.healthlink.his.crossmodule.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.crossmodule.domain.DrgCostAlert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DrgCostAlertMapper extends BaseMapper<DrgCostAlert> {}

View File

@@ -0,0 +1,6 @@
package com.healthlink.his.crossmodule.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.crossmodule.domain.EmergencyAmbulanceLink;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EmergencyAmbulanceLinkMapper extends BaseMapper<EmergencyAmbulanceLink> {}

View File

@@ -0,0 +1,6 @@
package com.healthlink.his.crossmodule.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.crossmodule.domain.InformedConsentEnhanced;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface InformedConsentEnhancedMapper extends BaseMapper<InformedConsentEnhanced> {}

View File

@@ -0,0 +1,6 @@
package com.healthlink.his.crossmodule.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.crossmodule.domain.NursingAssessmentDynamic;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface NursingAssessmentDynamicMapper extends BaseMapper<NursingAssessmentDynamic> {}

View File

@@ -0,0 +1,4 @@
package com.healthlink.his.crossmodule.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.crossmodule.domain.AntibioticManagementEnhanced;
public interface IAntibioticManagementEnhancedService extends IService<AntibioticManagementEnhanced> {}

View File

@@ -0,0 +1,4 @@
package com.healthlink.his.crossmodule.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.crossmodule.domain.DrgCostAlert;
public interface IDrgCostAlertService extends IService<DrgCostAlert> {}

View File

@@ -0,0 +1,4 @@
package com.healthlink.his.crossmodule.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.crossmodule.domain.EmergencyAmbulanceLink;
public interface IEmergencyAmbulanceLinkService extends IService<EmergencyAmbulanceLink> {}

View File

@@ -0,0 +1,4 @@
package com.healthlink.his.crossmodule.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.crossmodule.domain.InformedConsentEnhanced;
public interface IInformedConsentEnhancedService extends IService<InformedConsentEnhanced> {}

View File

@@ -0,0 +1,4 @@
package com.healthlink.his.crossmodule.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.crossmodule.domain.NursingAssessmentDynamic;
public interface INursingAssessmentDynamicService extends IService<NursingAssessmentDynamic> {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.crossmodule.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.crossmodule.domain.AntibioticManagementEnhanced;
import com.healthlink.his.crossmodule.mapper.AntibioticManagementEnhancedMapper;
import com.healthlink.his.crossmodule.service.IAntibioticManagementEnhancedService;
import org.springframework.stereotype.Service;
@Service
public class AntibioticManagementEnhancedServiceImpl extends ServiceImpl<AntibioticManagementEnhancedMapper, AntibioticManagementEnhanced> implements IAntibioticManagementEnhancedService {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.crossmodule.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.crossmodule.domain.DrgCostAlert;
import com.healthlink.his.crossmodule.mapper.DrgCostAlertMapper;
import com.healthlink.his.crossmodule.service.IDrgCostAlertService;
import org.springframework.stereotype.Service;
@Service
public class DrgCostAlertServiceImpl extends ServiceImpl<DrgCostAlertMapper, DrgCostAlert> implements IDrgCostAlertService {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.crossmodule.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.crossmodule.domain.EmergencyAmbulanceLink;
import com.healthlink.his.crossmodule.mapper.EmergencyAmbulanceLinkMapper;
import com.healthlink.his.crossmodule.service.IEmergencyAmbulanceLinkService;
import org.springframework.stereotype.Service;
@Service
public class EmergencyAmbulanceLinkServiceImpl extends ServiceImpl<EmergencyAmbulanceLinkMapper, EmergencyAmbulanceLink> implements IEmergencyAmbulanceLinkService {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.crossmodule.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.crossmodule.domain.InformedConsentEnhanced;
import com.healthlink.his.crossmodule.mapper.InformedConsentEnhancedMapper;
import com.healthlink.his.crossmodule.service.IInformedConsentEnhancedService;
import org.springframework.stereotype.Service;
@Service
public class InformedConsentEnhancedServiceImpl extends ServiceImpl<InformedConsentEnhancedMapper, InformedConsentEnhanced> implements IInformedConsentEnhancedService {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.crossmodule.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.crossmodule.domain.NursingAssessmentDynamic;
import com.healthlink.his.crossmodule.mapper.NursingAssessmentDynamicMapper;
import com.healthlink.his.crossmodule.service.INursingAssessmentDynamicService;
import org.springframework.stereotype.Service;
@Service
public class NursingAssessmentDynamicServiceImpl extends ServiceImpl<NursingAssessmentDynamicMapper, NursingAssessmentDynamic> implements INursingAssessmentDynamicService {}

View File

@@ -0,0 +1,4 @@
import request from '@/utils/request'
export function getPage(p){return request({url:'/enhancement/ambulance/page',method:'get',params:p})}
export function add(d){return request({url:'/enhancement/ambulance/add',method:'post',data:d})}
export function del(id){return request({url:'/enhancement/ambulance/delete/'+id,method:'delete'})}

View File

@@ -0,0 +1,43 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">急诊120院前联动</span></div>
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-input v-model="q.ambulanceId" placeholder="急救车编号" clearable style="width:130px"/>
<el-select v-model="q.linkStatus" placeholder="状态" clearable style="width:110px">
<el-option label="已派车" value="DISPATCHED"/><el-option label="到达现场" value="ON_SCENE"/>
<el-option label="转运中" value="TRANSPORTING"/><el-option label="已到达" value="ARRIVED"/><el-option label="已交接" value="HANDOFF"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="ambulanceId" label="急救车" width="100"/>
<el-table-column prop="patientName" label="患者" width="100"/>
<el-table-column prop="linkStatus" label="状态" width="90" align="center">
<template #default="{row}">
<el-tag :type="row.linkStatus==='HANDOFF'?'success':row.linkStatus==='TRANSPORTING'?'warning':row.linkStatus==='ARRIVED'?'':''" size="small">
{{ {DISPATCHED:'已派车',ON_SCENE:'到达现场',TRANSPORTING:'转运中',ARRIVED:'已到达',HANDOFF:'已交接'}[row.linkStatus]||row.linkStatus }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="preDiagnosis" label="院前诊断" min-width="150" show-overflow-tooltip/>
<el-table-column prop="dispatchTime" label="派车时间" width="160"/>
<el-table-column prop="arriveHospitalTime" label="到达时间" width="160"/>
<el-table-column prop="handoffTime" label="交接时间" width="160"/>
<el-table-column prop="bedReady" label="床位" width="60" align="center">
<template #default="{row}"><el-tag v-if="row.bedReady" type="success" size="small">就绪</el-tag></template>
</el-table-column>
<el-table-column prop="teamReady" label="团队" width="60" align="center">
<template #default="{row}"><el-tag v-if="row.teamReady" type="success" size="small">就绪</el-tag></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"/>
</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,ambulanceId:'',linkStatus:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
</script>

View File

@@ -0,0 +1,4 @@
import request from '@/utils/request'
export function getPage(p){return request({url:'/enhancement/antibiotic/page',method:'get',params:p})}
export function add(d){return request({url:'/enhancement/antibiotic/add',method:'post',data:d})}
export function del(id){return request({url:'/enhancement/antibiotic/delete/'+id,method:'delete'})}

View File

@@ -0,0 +1,41 @@
<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.antibioticLevel" placeholder="分级" clearable style="width:110px">
<el-option label="非限制" value="NON_RESTRICTED"/><el-option label="限制使用" value="RESTRICTED"/><el-option label="特殊使用" value="SPECIAL"/>
</el-select>
<el-select v-model="q.auditStatus" placeholder="审核" clearable style="width:100px">
<el-option label="待审核" value="PENDING"/><el-option label="已审核" value="AUDITED"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="patientName" label="患者" width="100"/>
<el-table-column prop="drugName" label="药品" min-width="150"/>
<el-table-column prop="antibioticLevel" label="分级" width="100" align="center">
<template #default="{row}">
<el-tag :type="row.antibioticLevel==='SPECIAL'?'danger':row.antibioticLevel==='RESTRICTED'?'warning':'success'" size="small">
{{ {NON_RESTRICTED:'非限制',RESTRICTED:'限制使用',SPECIAL:'特殊使用'}[row.antibioticLevel]||row.antibioticLevel }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="dosage" label="剂量" width="100"/>
<el-table-column prop="durationDays" label="疗程(天)" width="80" align="center"/>
<el-table-column prop="pathogenResult" label="病原学" width="120" show-overflow-tooltip/>
<el-table-column prop="auditStatus" label="审核" width="80" align="center">
<template #default="{row}"><el-tag :type="row.auditStatus==='AUDITED'?'success':'info'" size="small">{{ {PENDING:'待审核',AUDITED:'已审核'}[row.auditStatus]||row.auditStatus }}</el-tag></template>
</el-table-column>
<el-table-column prop="auditResult" label="审核意见" width="120" 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,prev,pager,next" @current-change="loadData"/>
</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,antibioticLevel:'',auditStatus:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
</script>

View File

@@ -0,0 +1,4 @@
import request from '@/utils/request'
export function getPage(p){return request({url:'/enhancement/consent/page',method:'get',params:p})}
export function add(d){return request({url:'/enhancement/consent/add',method:'post',data:d})}
export function del(id){return request({url:'/enhancement/consent/delete/'+id,method:'delete'})}

View File

@@ -0,0 +1,45 @@
<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.consentType" placeholder="类型" clearable style="width:110px">
<el-option label="手术" value="SURGERY"/><el-option label="麻醉" value="ANESTHESIA"/>
<el-option label="输血" value="BLOOD"/><el-option label="检查" value="EXAM"/><el-option label="拒绝治疗" value="REFUSE"/>
</el-select>
<el-select v-model="q.consentStatus" placeholder="状态" clearable style="width:100px">
<el-option label="草稿" value="DRAFT"/><el-option label="待签" value="PENDING"/><el-option label="已签" value="SIGNED"/><el-option label="已撤销" value="REVOKED"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="patientName" label="患者" width="100"/>
<el-table-column prop="consentType" label="类型" width="80" align="center">
<template #default="{row}"><el-tag size="small">{{ {SURGERY:'手术',ANESTHESIA:'麻醉',BLOOD:'输血',EXAM:'检查',REFUSE:'拒绝'}[row.consentType]||row.consentType }}</el-tag></template>
</el-table-column>
<el-table-column prop="consentTitle" label="标题" min-width="180" show-overflow-tooltip/>
<el-table-column prop="version" label="版本" width="60" align="center"><template #default="{row}"><el-tag type="info" size="small">v{{ row.version }}</el-tag></template></el-table-column>
<el-table-column prop="consentStatus" label="状态" width="80" align="center">
<template #default="{row}">
<el-tag :type="row.consentStatus==='SIGNED'?'success':row.consentStatus==='REVOKED'?'danger':row.consentStatus==='PENDING'?'warning':'info'" size="small">
{{ {DRAFT:'草稿',PENDING:'待签',SIGNED:'已签',REVOKED:'已撤销',EXPIRED:'过期'}[row.consentStatus]||row.consentStatus }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="patientSignTime" label="患者签名" width="160">
<template #default="{row}"><span v-if="row.patientSignature" style="color:#67C23A"> {{ row.patientSignTime }}</span></template>
</el-table-column>
<el-table-column prop="doctorSignTime" label="医生签名" width="160">
<template #default="{row}"><span v-if="row.doctorSignature" style="color:#409EFF"> {{ row.doctorSignTime }}</span></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"/>
</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,consentType:'',consentStatus:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
</script>

View File

@@ -0,0 +1,4 @@
import request from '@/utils/request'
export function getPage(p){return request({url:'/enhancement/drg-alert/page',method:'get',params:p})}
export function add(d){return request({url:'/enhancement/drg-alert/add',method:'post',data:d})}
export function del(id){return request({url:'/enhancement/drg-alert/delete/'+id,method:'delete'})}

View File

@@ -0,0 +1,48 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">DRG费用异常预警</span></div>
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.alertType" placeholder="预警类型" clearable style="width:120px">
<el-option label="费用超标" value="COST_OVER"/><el-option label="住院日超标" value="LOS_OVER"/>
<el-option label="非计划再入院" value="READMIT"/><el-option label="权重偏低" value="WEIGHT_LOW"/>
</el-select>
<el-select v-model="q.alertLevel" placeholder="级别" clearable style="width:100px">
<el-option label="严重" value="CRITICAL"/><el-option label="警告" value="WARNING"/><el-option label="提示" value="INFO"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="patientName" label="患者" width="100"/>
<el-table-column prop="drgCode" label="DRG编码" width="90"/>
<el-table-column prop="alertType" label="类型" width="100" align="center">
<template #default="{row}">
<el-tag size="small">{{ {COST_OVER:'费用超标',LOS_OVER:'住院日超标',READMIT:'非计划再入院',WEIGHT_LOW:'权重偏低'}[row.alertType]||row.alertType }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="expectedCost" label="预期费用" width="100" align="center"/>
<el-table-column prop="actualCost" label="实际费用" width="100" align="center"/>
<el-table-column prop="costDeviation" label="偏差%" width="80" align="center">
<template #default="{row}"><span :style="{color:row.costDeviation>20?'#F56C6C':row.costDeviation>10?'#E6A23C':'#67C23A',fontWeight:'bold'}">{{ row.costDeviation }}%</span></template>
</el-table-column>
<el-table-column prop="alertLevel" label="级别" width="70" align="center">
<template #default="{row}">
<el-tag :type="row.alertLevel==='CRITICAL'?'danger':row.alertLevel==='WARNING'?'warning':'info'" size="small" effect="dark">
{{ {CRITICAL:'严重',WARNING:'警告',INFO:'提示'}[row.alertLevel]||row.alertLevel }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="handleStatus" label="处理" width="80" align="center">
<template #default="{row}"><el-tag :type="row.handleStatus==='HANDLED'?'success':'info'" size="small">{{ {PENDING:'待处理',HANDLED:'已处理'}[row.handleStatus]||row.handleStatus }}</el-tag></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"/>
</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,alertType:'',alertLevel:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
</script>

View File

@@ -0,0 +1,4 @@
import request from '@/utils/request'
export function getPage(p){return request({url:'/enhancement/nursing-assessment/page',method:'get',params:p})}
export function add(d){return request({url:'/enhancement/nursing-assessment/add',method:'post',data:d})}
export function del(id){return request({url:'/enhancement/nursing-assessment/delete/'+id,method:'delete'})}

View File

@@ -0,0 +1,58 @@
<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.assessmentType" placeholder="评估类型" clearable style="width:110px">
<el-option label="跌倒" value="FALL"/><el-option label="压疮" value="PRESSURE"/><el-option label="营养" value="NUTRITION"/>
<el-option label="疼痛" value="PAIN"/><el-option label="血栓" value="THROMBOSIS"/>
</el-select>
<el-select v-model="q.riskLevel" placeholder="风险等级" clearable style="width:100px">
<el-option label="低" value="LOW"/><el-option label="中" value="MEDIUM"/><el-option label="高" value="HIGH"/><el-option label="极高" value="CRITICAL"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="patientName" label="患者" width="100"/>
<el-table-column prop="assessmentType" label="评估" width="70" align="center">
<template #default="{row}"><el-tag size="small">{{ {FALL:'跌倒',PRESSURE:'压疮',NUTRITION:'营养',PAIN:'疼痛',THROMBOSIS:'血栓'}[row.assessmentType]||row.assessmentType }}</el-tag></template>
</el-table-column>
<el-table-column prop="score" label="初评分" width="70" align="center">
<template #default="{row}"><span style="font-weight:bold;font-size:16px">{{ row.score }}</span></template>
</el-table-column>
<el-table-column prop="riskLevel" label="风险" width="70" align="center">
<template #default="{row}">
<el-tag :type="row.riskLevel==='CRITICAL'?'danger':row.riskLevel==='HIGH'?'warning':row.riskLevel==='MEDIUM'?'':'success'" size="small" effect="dark">
{{ {LOW:'低',MEDIUM:'中',HIGH:'高',CRITICAL:'极高'}[row.riskLevel]||row.riskLevel }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="interventionPlan" label="干预措施" min-width="150" show-overflow-tooltip/>
<el-table-column prop="reAssessmentScore" label="复评分" width="70" align="center">
<template #default="{row}"><span v-if="row.reAssessmentScore" :style="{color:row.reAssessmentScore<row.score?'#67C23A':row.reAssessmentScore>row.score?'#F56C6C':'#909399',fontWeight:'bold',fontSize:'16px'}">{{ row.reAssessmentScore }}</span></template>
</el-table-column>
<el-table-column prop="effectResult" label="效果" width="80" align="center">
<template #default="{row}">
<el-tag v-if="row.effectResult" :type="row.effectResult==='IMPROVED'?'success':row.effectResult==='WORSENED'?'danger':'info'" size="small">
{{ {IMPROVED:'改善',STABLE:'稳定',WORSENED:'恶化'}[row.effectResult]||row.effectResult }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="trend" label="趋势" width="60" align="center">
<template #default="{row}">
<span v-if="row.trend==='DOWN'" style="color:#67C23A;font-size:18px"></span>
<span v-else-if="row.trend==='UP'" style="color:#F56C6C;font-size:18px"></span>
<span v-else-if="row.trend==='FLAT'" style="color:#909399;font-size:18px"></span>
</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"/>
</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,assessmentType:'',riskLevel:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
</script>