feat(V30): 剩余能力模块 — 参考范围/预约排队/图像报告/DICOM打印/临床路径/ICD-10/DRG分析

- V30 Flyway: 8张新表(参考范围/预约排队/影像图像/图文报告/DICOM打印/临床路径/路径执行/ICD-10)
- 8个实体+8个Service+8个Mapper
- 6个Controller: LabReferenceRange/ExamAppointment/RadiologyImage/ClinicalPathway/Icd10/DrgAnalysis
- 5个前端页面: refrange/appointment/radiologyreport/pathway/icd10
- 后端编译通过,前端构建通过
This commit is contained in:
2026-06-07 00:17:59 +08:00
parent 8c52442ed5
commit 2f04f518f9
49 changed files with 993 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
package com.healthlink.his.web.Inspection.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.lab.domain.Icd10Code;
import com.healthlink.his.lab.service.IIcd10CodeService;
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.util.*;
@RestController @RequestMapping("/icd10") @Slf4j @AllArgsConstructor
public class Icd10Controller {
private final IIcd10CodeService icd10Service;
@GetMapping("/page")
public R<?> getPage(@RequestParam(value="keyword",required=false) String keyword,
@RequestParam(value="category",required=false) String category,
@RequestParam(value="pageNo",defaultValue="1") Integer pageNo,
@RequestParam(value="pageSize",defaultValue="20") Integer pageSize) {
LambdaQueryWrapper<Icd10Code> w = new LambdaQueryWrapper<>();
w.and(StringUtils.hasText(keyword), q -> q.like(Icd10Code::getCode, keyword).or().like(Icd10Code::getName, keyword).or().like(Icd10Code::getPinyin, keyword))
.eq(StringUtils.hasText(category), Icd10Code::getCategory, category)
.eq(Icd10Code::getIsActive, true);
return R.ok(icd10Service.page(new Page<>(pageNo, pageSize), w));
}
@GetMapping("/suggest")
public R<?> suggest(@RequestParam("keyword") String keyword) {
LambdaQueryWrapper<Icd10Code> w = new LambdaQueryWrapper<>();
w.and(q -> q.like(Icd10Code::getCode, keyword).or().like(Icd10Code::getName, keyword).or().like(Icd10Code::getPinyin, keyword))
.eq(Icd10Code::getIsActive, true).last("LIMIT 10");
return R.ok(icd10Service.list(w));
}
@PostMapping("/add") @Transactional(rollbackFor=Exception.class)
public R<?> add(@RequestBody Icd10Code c) { c.setIsActive(true); c.setCreateTime(new Date()); icd10Service.save(c); return R.ok(c); }
@PutMapping("/update") @Transactional(rollbackFor=Exception.class)
public R<?> update(@RequestBody Icd10Code c) { icd10Service.updateById(c); return R.ok(c); }
@DeleteMapping("/delete/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> delete(@PathVariable Long id) { icd10Service.removeById(id); return R.ok(); }
}

View File

@@ -0,0 +1,44 @@
package com.healthlink.his.web.Inspection.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.lab.domain.LabReferenceRange;
import com.healthlink.his.lab.service.ILabReferenceRangeService;
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.util.*;
@RestController @RequestMapping("/lab-ref-range") @Slf4j @AllArgsConstructor
public class LabReferenceRangeController {
private final ILabReferenceRangeService refRangeService;
@GetMapping("/page")
public R<?> getPage(@RequestParam(value="itemCode",required=false) String itemCode,
@RequestParam(value="gender",required=false) String gender,
@RequestParam(value="pageNo",defaultValue="1") Integer pageNo,
@RequestParam(value="pageSize",defaultValue="20") Integer pageSize) {
LambdaQueryWrapper<LabReferenceRange> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(itemCode), LabReferenceRange::getItemCode, itemCode)
.eq(StringUtils.hasText(gender), LabReferenceRange::getGender, gender)
.eq(LabReferenceRange::getIsActive, true);
return R.ok(refRangeService.page(new Page<>(pageNo, pageSize), w));
}
@GetMapping("/by-item")
public R<?> getByItem(@RequestParam("itemCode") String itemCode,
@RequestParam(value="gender",required=false) String gender,
@RequestParam(value="age",required=false) Integer age) {
LambdaQueryWrapper<LabReferenceRange> w = new LambdaQueryWrapper<>();
w.eq(LabReferenceRange::getItemCode, itemCode).eq(LabReferenceRange::getIsActive, true);
if (StringUtils.hasText(gender)) w.eq(LabReferenceRange::getGender, gender);
List<LabReferenceRange> list = refRangeService.list(w);
if (age != null) {
list.removeIf(r -> (r.getAgeMin() != null && age < r.getAgeMin()) || (r.getAgeMax() != null && age > r.getAgeMax()));
}
return R.ok(list);
}
@PostMapping("/add") @Transactional(rollbackFor=Exception.class)
public R<?> add(@RequestBody LabReferenceRange r) { r.setIsActive(true); r.setCreateTime(new Date()); refRangeService.save(r); return R.ok(r); }
@PutMapping("/update") @Transactional(rollbackFor=Exception.class)
public R<?> update(@RequestBody LabReferenceRange r) { refRangeService.updateById(r); return R.ok(r); }
@DeleteMapping("/delete/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> delete(@PathVariable Long id) { refRangeService.removeById(id); return R.ok(); }
}

View File

@@ -0,0 +1,61 @@
package com.healthlink.his.web.check.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.check.domain.ExamAppointment;
import com.healthlink.his.check.service.IExamAppointmentService;
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.util.*;
@RestController @RequestMapping("/exam-appointment") @Slf4j @AllArgsConstructor
public class ExamAppointmentController {
private final IExamAppointmentService appointmentService;
@GetMapping("/page")
public R<?> getPage(@RequestParam(value="status",required=false) String status,
@RequestParam(value="patientName",required=false) String patientName,
@RequestParam(value="appointDate",required=false) String appointDate,
@RequestParam(value="pageNo",defaultValue="1") Integer pageNo,
@RequestParam(value="pageSize",defaultValue="20") Integer pageSize) {
LambdaQueryWrapper<ExamAppointment> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(status), ExamAppointment::getStatus, status)
.like(StringUtils.hasText(patientName), ExamAppointment::getPatientName, patientName)
.orderByAsc(ExamAppointment::getQueueNumber);
return R.ok(appointmentService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/appoint") @Transactional(rollbackFor=Exception.class)
public R<?> appoint(@RequestBody ExamAppointment a) {
a.setStatus("APPOINTED"); a.setCreateTime(new Date());
LambdaQueryWrapper<ExamAppointment> w = new LambdaQueryWrapper<>();
w.eq(ExamAppointment::getAppointDate, a.getAppointDate()).orderByDesc(ExamAppointment::getQueueNumber).last("LIMIT 1");
ExamAppointment last = appointmentService.getOne(w);
a.setQueueNumber(last == null ? 1 : last.getQueueNumber() + 1);
appointmentService.save(a); return R.ok(a);
}
@PutMapping("/checkin/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> checkin(@PathVariable Long id) {
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在");
a.setStatus("CHECKED_IN"); appointmentService.updateById(a); return R.ok();
}
@PutMapping("/start/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> startExam(@PathVariable Long id) {
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在");
a.setStatus("EXAMINING"); appointmentService.updateById(a); return R.ok();
}
@PutMapping("/complete/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> complete(@PathVariable Long id) {
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在");
a.setStatus("COMPLETED"); appointmentService.updateById(a); return R.ok();
}
@PutMapping("/cancel/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> cancel(@PathVariable Long id) {
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在");
a.setStatus("CANCELLED"); appointmentService.updateById(a); return R.ok();
}
@GetMapping("/queue")
public R<?> getQueue(@RequestParam("appointDate") String date) {
LambdaQueryWrapper<ExamAppointment> w = new LambdaQueryWrapper<>();
w.eq(ExamAppointment::getAppointDate, date).orderByAsc(ExamAppointment::getQueueNumber);
return R.ok(appointmentService.list(w));
}
}

View File

@@ -0,0 +1,57 @@
package com.healthlink.his.web.check.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.check.domain.*;
import com.healthlink.his.check.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.util.*;
@RestController @RequestMapping("/radiology-image") @Slf4j @AllArgsConstructor
public class RadiologyImageController {
private final IRadiologyImageService imageService;
private final IRadiologyImageReportService reportService;
private final IDicomPrintRecordService printService;
// 图像列表
@GetMapping("/list")
public R<?> getImageList(@RequestParam("applyId") Long applyId) {
LambdaQueryWrapper<RadiologyImage> w = new LambdaQueryWrapper<>();
w.eq(RadiologyImage::getApplyId, applyId).orderByAsc(RadiologyImage::getInstanceNumber);
return R.ok(imageService.list(w));
}
@PostMapping("/upload") @Transactional(rollbackFor=Exception.class)
public R<?> uploadImage(@RequestBody RadiologyImage img) { img.setCreateTime(new Date()); imageService.save(img); return R.ok(img); }
// 图文报告
@GetMapping("/report/page")
public R<?> getReportPage(@RequestParam(value="status",required=false) String status,
@RequestParam(value="patientName",required=false) String patientName,
@RequestParam(value="pageNo",defaultValue="1") Integer pageNo,
@RequestParam(value="pageSize",defaultValue="20") Integer pageSize) {
LambdaQueryWrapper<RadiologyImageReport> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(status), RadiologyImageReport::getStatus, status)
.like(StringUtils.hasText(patientName), RadiologyImageReport::getPatientName, patientName)
.orderByDesc(RadiologyImageReport::getCreateTime);
return R.ok(reportService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/report/add") @Transactional(rollbackFor=Exception.class)
public R<?> addReport(@RequestBody RadiologyImageReport r) { r.setStatus("DRAFT"); r.setCreateTime(new Date()); reportService.save(r); return R.ok(r); }
@PutMapping("/report/submit/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> submitReport(@PathVariable Long id) {
RadiologyImageReport r = reportService.getById(id); if (r == null) return R.fail("报告不存在");
r.setStatus("REPORTED"); r.setReportTime(new Date()); reportService.updateById(r); return R.ok();
}
@PutMapping("/report/verify/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> verifyReport(@PathVariable Long id, @RequestParam("doctor") String doctor) {
RadiologyImageReport r = reportService.getById(id); if (r == null) return R.fail("报告不存在");
r.setStatus("VERIFIED"); r.setVerifyDoctor(doctor); r.setVerifyTime(new Date()); reportService.updateById(r); return R.ok();
}
// DICOM打印
@PostMapping("/print") @Transactional(rollbackFor=Exception.class)
public R<?> printDicom(@RequestBody DicomPrintRecord p) { p.setPrintTime(new Date()); p.setCreateTime(new Date()); printService.save(p); return R.ok(p); }
@GetMapping("/print/page")
public R<?> getPrintPage(@RequestParam(value="pageNo",defaultValue="1") Integer pageNo,
@RequestParam(value="pageSize",defaultValue="20") Integer pageSize) {
return R.ok(printService.page(new Page<>(pageNo, pageSize), new LambdaQueryWrapper<DicomPrintRecord>().orderByDesc(DicomPrintRecord::getPrintTime)));
}
}

View File

@@ -0,0 +1,57 @@
package com.healthlink.his.web.clinical.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.clinical.domain.*;
import com.healthlink.his.clinical.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.util.*;
@RestController @RequestMapping("/clinical-pathway") @Slf4j @AllArgsConstructor
public class ClinicalPathwayController {
private final IClinicalPathwayService pathwayService;
private final IClinicalPathwayExecutionService executionService;
@GetMapping("/page")
public R<?> getPage(@RequestParam(value="diseaseCode",required=false) String diseaseCode,
@RequestParam(value="pageNo",defaultValue="1") Integer pageNo,
@RequestParam(value="pageSize",defaultValue="20") Integer pageSize) {
LambdaQueryWrapper<ClinicalPathway> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(diseaseCode), ClinicalPathway::getDiseaseCode, diseaseCode)
.eq(ClinicalPathway::getStatus, "ACTIVE");
return R.ok(pathwayService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/add") @Transactional(rollbackFor=Exception.class)
public R<?> add(@RequestBody ClinicalPathway p) { p.setVersion(1); p.setStatus("ACTIVE"); p.setCreateTime(new Date()); pathwayService.save(p); return R.ok(p); }
@PostMapping("/enter") @Transactional(rollbackFor=Exception.class)
public R<?> enterPathway(@RequestBody ClinicalPathwayExecution e) {
e.setStatus("IN_PATH"); e.setEnterDate(java.time.LocalDate.now()); e.setCreateTime(new Date());
executionService.save(e); return R.ok(e);
}
@PutMapping("/complete/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> completePathway(@PathVariable Long id) {
ClinicalPathwayExecution e = executionService.getById(id); if (e == null) return R.fail("执行记录不存在");
e.setStatus("COMPLETED"); e.setCompleteDate(java.time.LocalDate.now());
executionService.updateById(e); return R.ok();
}
@PutMapping("/vary/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> varyPathway(@PathVariable Long id, @RequestParam("reason") String reason) {
ClinicalPathwayExecution e = executionService.getById(id); if (e == null) return R.fail("执行记录不存在");
e.setStatus("VARIATION"); e.setVariationReason(reason); executionService.updateById(e); return R.ok();
}
@GetMapping("/stats")
public R<?> getStats() {
Map<String, Object> stats = new HashMap<>();
stats.put("totalPathways", pathwayService.count());
stats.put("totalExecutions", executionService.count());
LambdaQueryWrapper<ClinicalPathwayExecution> cw = new LambdaQueryWrapper<>();
cw.eq(ClinicalPathwayExecution::getStatus, "COMPLETED");
stats.put("completedExecutions", executionService.count(cw));
cw.eq(ClinicalPathwayExecution::getStatus, "VARIATION");
stats.put("variedExecutions", executionService.count(cw));
long total = executionService.count();
long completed = stats.containsKey("completedExecutions") ? (long) stats.get("completedExecutions") : 0;
stats.put("completionRate", total > 0 ? Math.round(completed * 100.0 / total) : 0);
return R.ok(stats);
}
}

View File

@@ -0,0 +1,24 @@
package com.healthlink.his.web.mrhomepage.controller;
import com.core.common.core.domain.R;
import com.healthlink.his.mrhomepage.domain.MrDrgGrouping;
import com.healthlink.his.mrhomepage.service.IMrDrgGroupingService;
import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;import java.util.*;
@RestController @RequestMapping("/drg-analysis") @Slf4j @AllArgsConstructor
public class DrgAnalysisController {
private final IMrDrgGroupingService drgService;
@GetMapping("/cost-efficiency")
public R<?> getCostEfficiency() {
List<MrDrgGrouping> all = drgService.list();
BigDecimal totalCost = BigDecimal.ZERO; int count = 0;
for (MrDrgGrouping g : all) {
if (g.getTotalCost() != null) { totalCost = totalCost.add(g.getTotalCost()); count++; }
}
Map<String, Object> result = new HashMap<>();
result.put("avgCost", count > 0 ? totalCost.divide(BigDecimal.valueOf(count), 2, BigDecimal.ROUND_HALF_UP) : BigDecimal.ZERO);
result.put("totalCases", count);
result.put("totalCost", totalCost);
return R.ok(result);
}
}

View File

@@ -0,0 +1,186 @@
-- V30: 剩余能力模块 — 参考范围/预约排队/图像/图文/DICOM/临床路径/EMPI/DRG/ICD10
-- 1. 检验参考范围
CREATE TABLE IF NOT EXISTS lab_reference_range (
id BIGSERIAL PRIMARY KEY,
item_code VARCHAR(50) NOT NULL,
item_name VARCHAR(100),
gender VARCHAR(10),
age_min INT,
age_max INT,
low_value DECIMAL(10,4),
high_value DECIMAL(10,4),
unit VARCHAR(20),
critical_low DECIMAL(10,4),
critical_high DECIMAL(10,4),
is_active BOOLEAN DEFAULT TRUE,
tenant_id BIGINT DEFAULT 0,
is_deleted INT NOT NULL DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE lab_reference_range IS '检验参考范围(按年龄/性别)';
CREATE INDEX idx_lrr_item ON lab_reference_range(item_code);
-- 2. 检查预约排队
CREATE TABLE IF NOT EXISTS exam_appointment (
id BIGSERIAL PRIMARY KEY,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(50),
exam_type VARCHAR(50),
exam_name VARCHAR(200),
apply_id BIGINT,
appoint_date DATE NOT NULL,
appoint_time VARCHAR(20),
queue_number INT,
status VARCHAR(20) DEFAULT 'APPOINTED',
room VARCHAR(50),
device VARCHAR(100),
tenant_id BIGINT DEFAULT 0,
is_deleted INT NOT NULL DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE exam_appointment IS '检查预约排队';
COMMENT ON COLUMN exam_appointment.status IS '状态(APPOINTED已预约/CHECKED_IN已签到/EXAMINING检查中/COMPLETED已完成/CANCELLED已取消)';
CREATE INDEX idx_ea_date ON exam_appointment(appoint_date);
CREATE INDEX idx_ea_status ON exam_appointment(status);
-- 3. 影像图像记录
CREATE TABLE IF NOT EXISTS radiology_image (
id BIGSERIAL PRIMARY KEY,
apply_id BIGINT,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(50),
exam_name VARCHAR(200),
image_type VARCHAR(20),
image_path VARCHAR(500),
image_size BIGINT,
dicom_uid VARCHAR(100),
series_desc VARCHAR(200),
instance_number INT,
tenant_id BIGINT DEFAULT 0,
is_deleted INT NOT NULL DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE radiology_image IS '影像图像记录(DICOM)';
CREATE INDEX idx_ri_apply ON radiology_image(apply_id);
-- 4. 图文报告
CREATE TABLE IF NOT EXISTS radiology_image_report (
id BIGSERIAL PRIMARY KEY,
apply_id BIGINT NOT NULL,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(50),
exam_name VARCHAR(200),
report_type VARCHAR(20),
findings TEXT,
impression TEXT,
conclusion TEXT,
report_doctor VARCHAR(50),
report_time TIMESTAMP,
verify_doctor VARCHAR(50),
verify_time TIMESTAMP,
status VARCHAR(20) DEFAULT 'DRAFT',
tenant_id BIGINT DEFAULT 0,
is_deleted INT NOT NULL DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE radiology_image_report IS '影像图文报告';
COMMENT ON COLUMN radiology_image_report.status IS '状态(DRAFT草稿/REPORTED已报告/VERIFIED已审核)';
CREATE INDEX idx_rir_apply ON radiology_image_report(apply_id);
-- 5. DICOM打印记录
CREATE TABLE IF NOT EXISTS dicom_print_record (
id BIGSERIAL PRIMARY KEY,
apply_id BIGINT,
patient_name VARCHAR(50),
exam_name VARCHAR(200),
image_count INT,
print_count INT DEFAULT 1,
printer_name VARCHAR(100),
print_by VARCHAR(64),
print_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
tenant_id BIGINT DEFAULT 0,
is_deleted INT NOT NULL DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE dicom_print_record IS 'DICOM胶片打印记录';
-- 6. 临床路径
CREATE TABLE IF NOT EXISTS clinical_pathway (
id BIGSERIAL PRIMARY KEY,
pathway_name VARCHAR(200) NOT NULL,
disease_code VARCHAR(20),
disease_name VARCHAR(100),
department_name VARCHAR(100),
avg_days INT,
avg_cost DECIMAL(12,2),
pathway_json TEXT,
version INT DEFAULT 1,
status VARCHAR(20) DEFAULT 'ACTIVE',
tenant_id BIGINT DEFAULT 0,
is_deleted INT NOT NULL DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE clinical_pathway IS '临床路径定义';
CREATE INDEX idx_cp_disease ON clinical_pathway(disease_code);
-- 临床路径执行记录
CREATE TABLE IF NOT EXISTS clinical_pathway_execution (
id BIGSERIAL PRIMARY KEY,
pathway_id BIGINT NOT NULL,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(50),
enter_date DATE,
expected_days INT,
actual_days INT,
expected_cost DECIMAL(12,2),
actual_cost DECIMAL(12,2),
variation_reason TEXT,
status VARCHAR(20) DEFAULT 'IN_PATH',
complete_date DATE,
tenant_id BIGINT DEFAULT 0,
is_deleted INT NOT NULL DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE clinical_pathway_execution IS '临床路径执行记录';
COMMENT ON COLUMN clinical_pathway_execution.status IS '状态(IN_PATH入径/COMPLETED完成/VARIATION变异/EXIT退出)';
CREATE INDEX idx_cpe_pathway ON clinical_pathway_execution(pathway_id);
-- 7. DRG/DIP分析统计
CREATE TABLE IF NOT EXISTS drg_analysis_stats (
id BIGSERIAL PRIMARY KEY,
stat_month VARCHAR(7) NOT NULL,
department_name VARCHAR(100),
drg_code VARCHAR(20),
case_count INT DEFAULT 0,
avg_cost DECIMAL(12,2),
avg_los DECIMAL(6,1),
avg_weight DECIMAL(8,4),
cost_efficiency DECIMAL(6,2),
time_efficiency DECIMAL(6,2),
tenant_id BIGINT DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE drg_analysis_stats IS 'DRG/DIP月度分析统计';
CREATE UNIQUE INDEX idx_das_month ON drg_analysis_stats(stat_month, department_name, drg_code);
-- 8. ICD-10编码库
CREATE TABLE IF NOT EXISTS icd10_code (
id BIGSERIAL PRIMARY KEY,
code VARCHAR(20) NOT NULL,
name VARCHAR(200) NOT NULL,
category VARCHAR(10),
parent_code VARCHAR(20),
is_chapter BOOLEAN DEFAULT FALSE,
pinyin VARCHAR(200),
is_active BOOLEAN DEFAULT TRUE,
tenant_id BIGINT DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE icd10_code IS 'ICD-10诊断编码库';
CREATE UNIQUE INDEX idx_icd10_code ON icd10_code(code);
CREATE INDEX idx_icd10_pinyin ON icd10_code(pinyin);

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.check.domain;
import com.baomidou.mybatisplus.annotation.*;import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;import lombok.EqualsAndHashCode;
import java.util.Date;
@Data @EqualsAndHashCode(callSuper=true) @TableName("dicom_print_record")
public class DicomPrintRecord extends HisBaseEntity {
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
private Long applyId; private String patientName; private String examName;
private Integer imageCount; private Integer printCount; private String printerName; private String printBy;
private Date printTime;
}

View File

@@ -0,0 +1,12 @@
package com.healthlink.his.check.domain;
import com.baomidou.mybatisplus.annotation.*;import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;import lombok.EqualsAndHashCode;
import java.time.LocalDate;
@Data @EqualsAndHashCode(callSuper=true) @TableName("exam_appointment")
public class ExamAppointment extends HisBaseEntity {
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
private Long encounterId; private Long patientId; private String patientName;
private String examType; private String examName; private Long applyId;
private LocalDate appointDate; private String appointTime; private Integer queueNumber;
private String status; private String room; private String device;
}

View File

@@ -0,0 +1,10 @@
package com.healthlink.his.check.domain;
import com.baomidou.mybatisplus.annotation.*;import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;import lombok.EqualsAndHashCode;
@Data @EqualsAndHashCode(callSuper=true) @TableName("radiology_image")
public class RadiologyImage extends HisBaseEntity {
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
private Long applyId; private Long encounterId; private Long patientId; private String patientName;
private String examName; private String imageType; private String imagePath;
private Long imageSize; private String dicomUid; private String seriesDesc; private Integer instanceNumber;
}

View File

@@ -0,0 +1,12 @@
package com.healthlink.his.check.domain;
import com.baomidou.mybatisplus.annotation.*;import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;import lombok.EqualsAndHashCode;
import java.util.Date;
@Data @EqualsAndHashCode(callSuper=true) @TableName("radiology_image_report")
public class RadiologyImageReport extends HisBaseEntity {
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
private Long applyId; private Long encounterId; private Long patientId; private String patientName;
private String examName; private String reportType; private String findings;
private String impression; private String conclusion; private String reportDoctor;
private Date reportTime; private String verifyDoctor; private Date verifyTime; private String status;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.check.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.check.domain.DicomPrintRecord;
import com.healthlink.his.check.mapper.DicomPrintRecordMapper;
import com.healthlink.his.check.service.IDicomPrintRecordService;
import org.springframework.stereotype.Service;
@Service
public class DicomPrintRecordServiceImpl extends ServiceImpl<DicomPrintRecordMapper, DicomPrintRecord> implements IDicomPrintRecordService {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.check.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.check.domain.ExamAppointment;
import com.healthlink.his.check.mapper.ExamAppointmentMapper;
import com.healthlink.his.check.service.IExamAppointmentService;
import org.springframework.stereotype.Service;
@Service
public class ExamAppointmentServiceImpl extends ServiceImpl<ExamAppointmentMapper, ExamAppointment> implements IExamAppointmentService {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.check.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.check.domain.RadiologyImageReport;
import com.healthlink.his.check.mapper.RadiologyImageReportMapper;
import com.healthlink.his.check.service.IRadiologyImageReportService;
import org.springframework.stereotype.Service;
@Service
public class RadiologyImageReportServiceImpl extends ServiceImpl<RadiologyImageReportMapper, RadiologyImageReport> implements IRadiologyImageReportService {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.check.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.check.domain.RadiologyImage;
import com.healthlink.his.check.mapper.RadiologyImageMapper;
import com.healthlink.his.check.service.IRadiologyImageService;
import org.springframework.stereotype.Service;
@Service
public class RadiologyImageServiceImpl extends ServiceImpl<RadiologyImageMapper, RadiologyImage> implements IRadiologyImageService {}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.clinical.domain;
import com.baomidou.mybatisplus.annotation.*;import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data @EqualsAndHashCode(callSuper=true) @TableName("clinical_pathway")
public class ClinicalPathway extends HisBaseEntity {
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
private String pathwayName; private String diseaseCode; private String diseaseName;
private String departmentName; private Integer avgDays; private BigDecimal avgCost;
private String pathwayJson; private Integer version; private String status;
}

View File

@@ -0,0 +1,12 @@
package com.healthlink.his.clinical.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;
@Data @EqualsAndHashCode(callSuper=true) @TableName("clinical_pathway_execution")
public class ClinicalPathwayExecution extends HisBaseEntity {
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
private Long pathwayId; private Long encounterId; private Long patientId; private String patientName;
private LocalDate enterDate; private Integer expectedDays; private Integer actualDays;
private BigDecimal expectedCost; private BigDecimal actualCost; private String variationReason;
private String status; private LocalDate completeDate;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.clinical.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.clinical.domain.ClinicalPathwayExecution;
import com.healthlink.his.clinical.mapper.ClinicalPathwayExecutionMapper;
import com.healthlink.his.clinical.service.IClinicalPathwayExecutionService;
import org.springframework.stereotype.Service;
@Service
public class ClinicalPathwayExecutionServiceImpl extends ServiceImpl<ClinicalPathwayExecutionMapper, ClinicalPathwayExecution> implements IClinicalPathwayExecutionService {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.clinical.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.clinical.domain.ClinicalPathway;
import com.healthlink.his.clinical.mapper.ClinicalPathwayMapper;
import com.healthlink.his.clinical.service.IClinicalPathwayService;
import org.springframework.stereotype.Service;
@Service
public class ClinicalPathwayServiceImpl extends ServiceImpl<ClinicalPathwayMapper, ClinicalPathway> implements IClinicalPathwayService {}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.lab.domain;
import com.baomidou.mybatisplus.annotation.*;import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;import lombok.EqualsAndHashCode;
@Data @EqualsAndHashCode(callSuper=true) @TableName("icd10_code")
public class Icd10Code extends HisBaseEntity {
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
private String code; private String name; private String category;
private String parentCode; private Boolean isChapter; private String pinyin; private Boolean isActive;
}

View File

@@ -0,0 +1,12 @@
package com.healthlink.his.lab.domain;
import com.baomidou.mybatisplus.annotation.*;import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data @EqualsAndHashCode(callSuper=true) @TableName("lab_reference_range")
public class LabReferenceRange extends HisBaseEntity {
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
private String itemCode; private String itemName; private String gender;
private Integer ageMin; private Integer ageMax;
private BigDecimal lowValue; private BigDecimal highValue; private String unit;
private BigDecimal criticalLow; private BigDecimal criticalHigh; private Boolean isActive;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.lab.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.lab.domain.Icd10Code;
import com.healthlink.his.lab.mapper.Icd10CodeMapper;
import com.healthlink.his.lab.service.IIcd10CodeService;
import org.springframework.stereotype.Service;
@Service
public class Icd10CodeServiceImpl extends ServiceImpl<Icd10CodeMapper, Icd10Code> implements IIcd10CodeService {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.lab.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.lab.domain.LabReferenceRange;
import com.healthlink.his.lab.mapper.LabReferenceRangeMapper;
import com.healthlink.his.lab.service.ILabReferenceRangeService;
import org.springframework.stereotype.Service;
@Service
public class LabReferenceRangeServiceImpl extends ServiceImpl<LabReferenceRangeMapper, LabReferenceRange> implements ILabReferenceRangeService {}

View File

@@ -0,0 +1,8 @@
import request from '@/utils/request'
export function getPage(p){return request({url:'/exam-appointment/page',method:'get',params:p})}
export function appoint(d){return request({url:'/exam-appointment/appoint',method:'post',data:d})}
export function checkin(id){return request({url:'/exam-appointment/checkin/'+id,method:'put'})}
export function startExam(id){return request({url:'/exam-appointment/start/'+id,method:'put'})}
export function complete(id){return request({url:'/exam-appointment/complete/'+id,method:'put'})}
export function cancel(id){return request({url:'/exam-appointment/cancel/'+id,method:'put'})}
export function getQueue(p){return request({url:'/exam-appointment/queue',method:'get',params:p})}

View File

@@ -0,0 +1,65 @@
<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: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-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button type="success" @click="openAppoint">新建预约</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="queueNumber" label="队号" width="60" align="center"/>
<el-table-column prop="patientName" label="患者" width="100"/>
<el-table-column prop="examName" label="检查项目" width="150"/>
<el-table-column prop="appointDate" label="预约日期" width="110"/>
<el-table-column prop="appointTime" label="时段" width="80"/>
<el-table-column prop="room" label="诊室" width="80"/>
<el-table-column prop="status" label="状态" width="90">
<template #default="{row}">
<el-tag v-if="row.status==='APPOINTED'" type="info" size="small">已预约</el-tag>
<el-tag v-else-if="row.status==='CHECKED_IN'" type="warning" size="small">已签到</el-tag>
<el-tag v-else-if="row.status==='EXAMINING'" type="primary" size="small">检查中</el-tag>
<el-tag v-else-if="row.status==='COMPLETED'" type="success" size="small">已完成</el-tag>
<el-tag v-else type="info" size="small">{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="220">
<template #default="{row}">
<el-button v-if="row.status==='APPOINTED'" type="warning" link size="small" @click="doCheckin(row.id)">签到</el-button>
<el-button v-if="row.status==='CHECKED_IN'" type="primary" link size="small" @click="doStart(row.id)">开始</el-button>
<el-button v-if="row.status==='EXAMINING'" type="success" link size="small" @click="doComplete(row.id)">完成</el-button>
<el-button v-if="row.status!=='COMPLETED'&&row.status!=='CANCELLED'" type="danger" link size="small" @click="doCancel(row.id)">取消</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-dialog v-model="dlgVisible" title="新建预约" width="500px">
<el-form :model="form" label-width="80px">
<el-form-item label="患者ID"><el-input v-model.number="form.patientId"/></el-form-item>
<el-form-item label="就诊ID"><el-input v-model.number="form.encounterId"/></el-form-item>
<el-form-item label="患者姓名"><el-input v-model="form.patientName"/></el-form-item>
<el-form-item label="检查项目"><el-input v-model="form.examName"/></el-form-item>
<el-form-item label="预约日期"><el-date-picker v-model="form.appointDate" type="date"/></el-form-item>
<el-form-item label="时段"><el-input v-model="form.appointTime" placeholder="如: 上午/下午"/></el-form-item>
<el-form-item label="诊室"><el-input v-model="form.room"/></el-form-item>
</el-form>
<template #footer><el-button @click="dlgVisible=false">取消</el-button><el-button type="primary" @click="doAppoint">预约</el-button></template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue';import {ElMessage} from 'element-plus';import {getPage,appoint,checkin,startExam,complete,cancel} from './api'
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}
const openAppoint=()=>{form.value={patientId:null,encounterId:null,patientName:'',examName:'',appointDate:null,appointTime:'',room:''};dlgVisible.value=true}
const doAppoint=async()=>{await appoint(form.value);ElMessage.success('预约成功');dlgVisible.value=false;loadData()}
const doCheckin=async(id)=>{await checkin(id);ElMessage.success('签到完成');loadData()}
const doStart=async(id)=>{await startExam(id);ElMessage.success('开始检查');loadData()}
const doComplete=async(id)=>{await complete(id);ElMessage.success('检查完成');loadData()}
const doCancel=async(id)=>{await cancel(id);ElMessage.success('已取消');loadData()}
onMounted(()=>loadData())
</script>

View File

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

View File

@@ -0,0 +1,39 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">ICD-10诊断编码库</span></div>
<div style="margin-bottom:12px;display:flex;gap:8px">
<el-input v-model="q.keyword" placeholder="编码/名称/拼音" clearable style="width:200px" @keyup.enter="loadData"/>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button type="success" @click="dlgVisible=true;isEdit=false;form={code:'',name:'',category:'',pinyin:''}">新增</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="code" label="编码" width="120"/><el-table-column prop="name" label="诊断名称" min-width="200"/>
<el-table-column prop="category" label="分类" width="80"/><el-table-column prop="pinyin" label="拼音" width="120"/>
<el-table-column label="操作" width="140">
<template #default="{row}">
<el-button type="primary" link size="small" @click="isEdit=true;form={...row};dlgVisible=true">编辑</el-button>
<el-popconfirm title="确定删除?" @confirm="delItem(row.id)"><template #reference><el-button type="danger" link size="small">删除</el-button></template></el-popconfirm>
</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-dialog v-model="dlgVisible" :title="isEdit?'编辑':'新增'" width="500px">
<el-form :model="form" label-width="80px">
<el-form-item label="编码"><el-input v-model="form.code"/></el-form-item>
<el-form-item label="名称"><el-input v-model="form.name"/></el-form-item>
<el-form-item label="分类"><el-input v-model="form.category"/></el-form-item>
<el-form-item label="拼音"><el-input v-model="form.pinyin"/></el-form-item>
</el-form>
<template #footer><el-button @click="dlgVisible=false">取消</el-button><el-button type="primary" @click="saveData">保存</el-button></template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue';import {ElMessage} from 'element-plus';import {getPage,add,update,del} from './api'
const tableData=ref([]);const total=ref(0);const q=ref({pageNo:1,pageSize:20,keyword:''})
const dlgVisible=ref(false);const isEdit=ref(false);const form=ref({})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
const saveData=async()=>{if(isEdit.value){await update(form.value)}else{await add(form.value)}ElMessage.success('保存成功');dlgVisible.value=false;loadData()}
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
onMounted(()=>loadData())
</script>

View File

@@ -0,0 +1,7 @@
import request from '@/utils/request'
export function getPage(p){return request({url:'/clinical-pathway/page',method:'get',params:p})}
export function add(d){return request({url:'/clinical-pathway/add',method:'post',data:d})}
export function enterPathway(d){return request({url:'/clinical-pathway/enter',method:'post',data:d})}
export function completePathway(id){return request({url:'/clinical-pathway/complete/'+id,method:'put'})}
export function varyPathway(id,reason){return request({url:'/clinical-pathway/vary/'+id,method:'put',params:{reason}})}
export function getStats(){return request({url:'/clinical-pathway/stats',method:'get'})}

View File

@@ -0,0 +1,51 @@
<template>
<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-row :gutter="12" style="margin-bottom:16px">
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center"><div style="font-size:20px;font-weight:bold;color:#409eff">{{ stats.totalPathways||0 }}</div><div style="font-size:12px;color:#999">路径数</div></div></el-card></el-col>
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center"><div style="font-size:20px;font-weight:bold;color:#67c23a">{{ stats.totalExecutions||0 }}</div><div style="font-size:12px;color:#999">入径数</div></div></el-card></el-col>
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center"><div style="font-size:20px;font-weight:bold;color:#e6a23c">{{ stats.completedExecutions||0 }}</div><div style="font-size:12px;color:#999">完成数</div></div></el-card></el-col>
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center"><div style="font-size:20px;font-weight:bold;color:#f56c6c">{{ stats.variedExecutions||0 }}</div><div style="font-size:12px;color:#999">变异数</div></div></el-card></el-col>
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center"><div style="font-size:20px;font-weight:bold;color:#909399">{{ stats.completionRate||0 }}%</div><div style="font-size:12px;color:#999">完成率</div></div></el-card></el-col>
</el-row>
<el-table :data="tableData" border stripe>
<el-table-column prop="pathwayName" label="路径名称" min-width="150"/>
<el-table-column prop="diseaseCode" label="疾病编码" width="100"/>
<el-table-column prop="diseaseName" label="疾病名称" width="120"/>
<el-table-column prop="departmentName" label="科室" width="100"/>
<el-table-column prop="avgDays" label="平均天数" width="80" align="center"/>
<el-table-column prop="avgCost" label="平均费用" width="100" align="right"/>
<el-table-column prop="version" label="版本" width="60" align="center"/>
<el-table-column label="操作" width="100">
<template #default="{row}">
<el-button type="primary" link size="small" @click="openEnter(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-dialog v-model="enterDialog" title="患者入径" width="500px">
<el-form :model="enterForm" label-width="80px">
<el-form-item label="路径"><el-input :model-value="enterForm.pathwayName" disabled/></el-form-item>
<el-form-item label="患者ID"><el-input v-model.number="enterForm.patientId"/></el-form-item>
<el-form-item label="就诊ID"><el-input v-model.number="enterForm.encounterId"/></el-form-item>
<el-form-item label="患者姓名"><el-input v-model="enterForm.patientName"/></el-form-item>
<el-form-item label="预期天数"><el-input-number v-model="enterForm.expectedDays" :min="1"/></el-form-item>
<el-form-item label="预期费用"><el-input-number v-model="enterForm.expectedCost" :min="0" :precision="2"/></el-form-item>
</el-form>
<template #footer><el-button @click="enterDialog=false">取消</el-button><el-button type="primary" @click="doEnter">确认入径</el-button></template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue';import {ElMessage} from 'element-plus';import {getPage,enterPathway,getStats} from './api'
const tableData=ref([]);const total=ref(0);const stats=ref({});const q=ref({pageNo:1,pageSize:20})
const enterDialog=ref(false);const enterForm=ref({pathwayId:null,pathwayName:'',patientId:null,encounterId:null,patientName:'',expectedDays:7,expectedCost:0})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
const loadStats=async()=>{const r=await getStats();stats.value=r.data||{}}
const openEnter=(row)=>{enterForm.value.pathwayId=row.id;enterForm.value.pathwayName=row.pathwayName;enterForm.value.expectedDays=row.avgDays;enterForm.value.expectedCost=row.avgCost;enterDialog.value=true}
const doEnter=async()=>{await enterPathway(enterForm.value);ElMessage.success('入径成功');enterDialog.value=false;loadStats()}
onMounted(()=>{loadData();loadStats()})
</script>

View File

@@ -0,0 +1,7 @@
import request from '@/utils/request'
export function getReportPage(p){return request({url:'/radiology-image/report/page',method:'get',params:p})}
export function addReport(d){return request({url:'/radiology-image/report/add',method:'post',data:d})}
export function submitReport(id){return request({url:'/radiology-image/report/submit/'+id,method:'put'})}
export function verifyReport(id,doctor){return request({url:'/radiology-image/report/verify/'+id,method:'put',params:{doctor}})}
export function getImageList(p){return request({url:'/radiology-image/list',method:'get',params:p})}
export function printDicom(d){return request({url:'/radiology-image/print',method:'post',data:d})}

View File

@@ -0,0 +1,59 @@
<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">
<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-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button type="success" @click="openReport">新建报告</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="patientName" label="患者" width="100"/>
<el-table-column prop="examName" label="检查" width="150" show-overflow-tooltip/>
<el-table-column prop="reportType" label="类型" width="80"/>
<el-table-column prop="findings" label="所见" min-width="150" show-overflow-tooltip/>
<el-table-column prop="impression" label="印象" min-width="150" show-overflow-tooltip/>
<el-table-column prop="reportDoctor" label="报告人" width="80"/>
<el-table-column prop="status" label="状态" width="80">
<template #default="{row}">
<el-tag v-if="row.status==='DRAFT'" type="info" size="small">草稿</el-tag>
<el-tag v-else-if="row.status==='REPORTED'" type="warning" size="small">已报告</el-tag>
<el-tag v-else type="success" size="small">已审核</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template #default="{row}">
<el-button v-if="row.status==='DRAFT'" type="primary" link size="small" @click="doSubmit(row.id)">提交</el-button>
<el-button v-if="row.status==='REPORTED'" type="success" link size="small" @click="doVerify(row.id)">审核</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-dialog v-model="dlgVisible" title="新建影像报告" width="600px">
<el-form :model="form" label-width="80px">
<el-form-item label="申请ID"><el-input v-model.number="form.applyId"/></el-form-item>
<el-form-item label="就诊ID"><el-input v-model.number="form.encounterId"/></el-form-item>
<el-form-item label="患者姓名"><el-input v-model="form.patientName"/></el-form-item>
<el-form-item label="检查名称"><el-input v-model="form.examName"/></el-form-item>
<el-form-item label="报告类型"><el-input v-model="form.reportType" placeholder="如: X光/CT/MRI"/></el-form-item>
<el-form-item label="所见"><el-input v-model="form.findings" type="textarea" :rows="3"/></el-form-item>
<el-form-item label="印象"><el-input v-model="form.impression" type="textarea" :rows="3"/></el-form-item>
<el-form-item label="结论"><el-input v-model="form.conclusion" type="textarea" :rows="2"/></el-form-item>
<el-form-item label="报告医生"><el-input v-model="form.reportDoctor"/></el-form-item>
</el-form>
<template #footer><el-button @click="dlgVisible=false">取消</el-button><el-button type="primary" @click="doAddReport">保存</el-button></template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue';import {ElMessage,ElMessageBox} from 'element-plus';import {getReportPage,addReport,submitReport,verifyReport} from './api'
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}
const openReport=()=>{form.value={applyId:null,encounterId:null,patientName:'',examName:'',reportType:'',findings:'',impression:'',conclusion:'',reportDoctor:''};dlgVisible.value=true}
const doAddReport=async()=>{await addReport(form.value);ElMessage.success('保存成功');dlgVisible.value=false;loadData()}
const doSubmit=async(id)=>{await submitReport(id);ElMessage.success('已提交');loadData()}
const doVerify=async(id)=>{const {value}=await ElMessageBox.prompt('审核医生','审核报告');if(value){await verifyReport(id,value);ElMessage.success('已审核');loadData()}}
onMounted(()=>loadData())
</script>

View File

@@ -0,0 +1,6 @@
import request from '@/utils/request'
export function getPage(p){return request({url:'/lab-ref-range/page',method:'get',params:p})}
export function getByItem(p){return request({url:'/lab-ref-range/by-item',method:'get',params:p})}
export function add(d){return request({url:'/lab-ref-range/add',method:'post',data:d})}
export function update(d){return request({url:'/lab-ref-range/update',method:'put',data:d})}
export function del(id){return request({url:'/lab-ref-range/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-input v-model="q.itemCode" placeholder="项目编码" clearable style="width:140px"/>
<el-select v-model="q.gender" placeholder="性别" clearable style="width:100px"><el-option label="男" value="男"/><el-option label="女" value="女"/></el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button type="success" @click="dlgVisible=true;isEdit=false;form={itemCode:'',itemName:'',gender:'',ageMin:null,ageMax:null,lowValue:null,highValue:null,unit:'',criticalLow:null,criticalHigh:null}">新增</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="itemCode" label="编码" width="120"/><el-table-column prop="itemName" label="名称" width="150"/>
<el-table-column prop="gender" label="性别" width="70"/><el-table-column prop="ageMin" label="最小年龄" width="80" align="center"/>
<el-table-column prop="ageMax" label="最大年龄" width="80" align="center"/><el-table-column prop="lowValue" label="下限" width="80" align="center"/>
<el-table-column prop="highValue" label="上限" width="80" align="center"/><el-table-column prop="unit" label="单位" width="70"/>
<el-table-column prop="criticalLow" label="危急低" width="80" align="center"/><el-table-column prop="criticalHigh" label="危急高" width="80" align="center"/>
<el-table-column label="操作" width="140">
<template #default="{row}"><el-button type="primary" link size="small" @click="isEdit=true;form={...row};dlgVisible=true">编辑</el-button>
<el-popconfirm title="确定删除?" @confirm="delItem(row.id)"><template #reference><el-button type="danger" link size="small">删除</el-button></template></el-popconfirm></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-dialog v-model="dlgVisible" :title="isEdit?'编辑':'新增'" width="500px">
<el-form :model="form" label-width="80px">
<el-form-item label="编码"><el-input v-model="form.itemCode"/></el-form-item><el-form-item label="名称"><el-input v-model="form.itemName"/></el-form-item>
<el-form-item label="性别"><el-select v-model="form.gender"><el-option label="通用" value=""/><el-option label="男" value="男"/><el-option label="女" value="女"/></el-select></el-form-item>
<el-form-item label="年龄范围"><el-input-number v-model="form.ageMin" :min="0"/> ~ <el-input-number v-model="form.ageMax" :min="0"/></el-form-item>
<el-form-item label="参考下限"><el-input-number v-model="form.lowValue" :precision="4"/></el-form-item>
<el-form-item label="参考上限"><el-input-number v-model="form.highValue" :precision="4"/></el-form-item>
<el-form-item label="单位"><el-input v-model="form.unit" style="width:120px"/></el-form-item>
<el-form-item label="危急低"><el-input-number v-model="form.criticalLow" :precision="4"/></el-form-item>
<el-form-item label="危急高"><el-input-number v-model="form.criticalHigh" :precision="4"/></el-form-item>
</el-form>
<template #footer><el-button @click="dlgVisible=false">取消</el-button><el-button type="primary" @click="saveData">保存</el-button></template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue';import {ElMessage} from 'element-plus';import {getPage,add,update,del} from './api'
const tableData=ref([]);const total=ref(0);const q=ref({pageNo:1,pageSize:20,itemCode:'',gender:''})
const dlgVisible=ref(false);const isEdit=ref(false);const form=ref({})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
const saveData=async()=>{if(isEdit.value){await update(form.value)}else{await add(form.value)}ElMessage.success('保存成功');dlgVisible.value=false;loadData()}
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
onMounted(()=>loadData())
</script>