feat(V32): Phase A — 随访/病理/急诊 完整实现
后端: - FollowupController: 随访计划/任务/记录/满意度调查/投诉记录(完整CRUD+业务联动) - PathologyController: 病理医嘱/标本管理/三级审核报告(完整CRUD+业务联动) - EmergencyController: 急诊分诊(五级)/抢救/留观/绿色通道(完整CRUD+统计) 前端: - followup/plan: 随访计划管理(进度条+筛选+联动) - followup/task: 随访任务(今日任务+执行+异常标记) - followup/record: 随访记录(用药依从+复查+转诊) - followup/survey: 满意度调查(星级评分+统计面板) - followup/complaint: 投诉管理(登记→处理→关闭闭环) - pathology/order: 病理医嘱(加急标记+状态流转) - pathology/specimen: 标本管理(扫码接收+质检) - pathology/report: 病理报告(初诊→提交→审核→终审签发) - emergency/triage: 急诊分诊(五级分诊+实时看板+行列高亮) - emergency/rescue: 抢救管理(开始→过程记录→结果) - emergency/observation: 留观管理(时长计算+转归处置) - emergency/greentrack: 绿色通道(Door-to-Tx追踪+达标统计) 修复: - V24-V32 Flyway迁移: is_deleted→delete_flag对齐HisBaseEntity - V24: drug_interaction_rule表兼容V2已有结构(ADD COLUMN IF NOT EXISTS) - V28: CURRENT CURRENT_TIMESTAMP→CURRENT_TIMESTAMP修复 - 所有INDEX添加IF NOT EXISTS防止重复创建
This commit is contained in:
383
MD/architecture/CROSS_MODULE_BUSINESS_ANALYSIS.md
Normal file
383
MD/architecture/CROSS_MODULE_BUSINESS_ANALYSIS.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# HealthLink-HIS 三甲医院交叉业务流程分析与系统不足诊断
|
||||
|
||||
> **文档类型**: 业务分析+系统诊断
|
||||
> **版本**: v1.0
|
||||
> **编制日期**: 2026-06-07
|
||||
> **依据**: 《三级医院评审标准(2022版)》+ 广西实施细则 + 电子病历4级 + 互联互通四级甲等
|
||||
|
||||
---
|
||||
|
||||
## 一、三甲医院核心业务流程全景
|
||||
|
||||
### 1.1 十大核心流程
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 三甲医院业务全景 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 门诊流程: 挂号→候诊→就诊→检查检验→处方→收费→取药→随访 │
|
||||
│ 住院流程: 入院→医嘱→护理→检查检验→手术→用药→出院→结算→病案 │
|
||||
│ 急诊流程: 急诊挂号→分诊→抢救→留观→会诊→住院/出院 │
|
||||
│ 手术流程: 术前讨论→手术申请→麻醉评估→手术→术后恢复→病理 │
|
||||
│ 护理流程: 入院评估→护理计划→医嘱执行→体征→护理记录→交接班 │
|
||||
│ 药品流程: 采购→验收→入库→处方→调配→发药→退药→库存→盘点 │
|
||||
│ 检验流程: 申请→采集→送检→检验→审核→报告→危急值→随访 │
|
||||
│ 检查流程: 申请→预约→排队→检查→报告→审核→3D重建→图文报告 │
|
||||
│ 病案流程: 归档→质控→借阅→封存→统计→DRG→上报 │
|
||||
│ 院感流程: 监测→预警→上报→抗菌药物→消毒供应→统计 │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、交叉业务流程深度分析
|
||||
|
||||
### 2.1 门诊全流程交叉分析
|
||||
|
||||
```
|
||||
患者到达
|
||||
↓
|
||||
[挂号模块] ←→ [排班模块] ←→ [预约模块]
|
||||
↓ (分配诊室+队列)
|
||||
[候诊叫号模块] ←→ [分诊模块]
|
||||
↓ (叫号)
|
||||
[医生工作站] ←→ [电子病历] ←→ [处方模块]
|
||||
↓ (开检查/检验/处方)
|
||||
[检查模块] ←→ [检验模块] ←→ [药房模块]
|
||||
↓ (检查/检验完成)
|
||||
[报告模块] ←→ [医生工作站] (查看结果)
|
||||
↓ (开处方)
|
||||
[合理用药模块] ←→ [处方审核]
|
||||
↓ (处方通过)
|
||||
[收费模块] ←→ [医保模块] ←→ [发票模块]
|
||||
↓ (缴费完成)
|
||||
[药房发药模块] ←→ [药品库存模块]
|
||||
↓ (取药完成)
|
||||
[随访模块] ←→ [患者管理模块]
|
||||
```
|
||||
|
||||
**🔍 已有模块**: 挂号✅ 候诊✅ 医生站✅ 处方✅ 收费✅ 药房✅ 检查✅ 检验✅ 报告✅
|
||||
**🔍 随访模块**: ❌ 缺失 — 门诊患者随访是三甲评审必查项
|
||||
|
||||
### 2.2 住院全流程交叉分析
|
||||
|
||||
```
|
||||
门诊/急诊 → 入院
|
||||
↓
|
||||
[入院登记模块] ←→ [床位管理模块] ←→ [护士站]
|
||||
↓
|
||||
[住院医嘱模块] ←→ [护士执行模块] ←→ [药房模块]
|
||||
↓ (长期/临时医嘱)
|
||||
[检查申请] ←→ [检验申请] ←→ [手术申请]
|
||||
↓
|
||||
[检查报告] ←→ [检验报告] ←→ [手术记录]
|
||||
↓
|
||||
[护理评估] ←→ [护理计划] ←→ [护理记录]
|
||||
↓
|
||||
[病程记录模块] ←→ [知情同意模块]
|
||||
↓
|
||||
[出院医嘱] ←→ [出院结算] ←→ [出院小结]
|
||||
↓
|
||||
[病案归档] ←→ [DRG分组] ←→ [统计上报]
|
||||
```
|
||||
|
||||
**🔍 已有模块**: 入院✅ 床位✅ 医嘱✅ 护理✅ 检查✅ 检验✅ 手术✅ 病程✅ 知情同意✅ 出院✅ 结算✅ 病案✅ DRG✅
|
||||
**🔍 交叉验证**: 各模块间数据流转基本完整
|
||||
|
||||
### 2.3 手术全流程交叉分析
|
||||
|
||||
```
|
||||
[住院医嘱] → 手术申请
|
||||
↓
|
||||
[术前讨论模块] ←→ [手术分级管理]
|
||||
↓
|
||||
[麻醉评估模块] ←→ [麻醉前核查]
|
||||
↓
|
||||
[手术安全核查(WS/T 313)] ←→ [器械追溯(CSSD)]
|
||||
↓
|
||||
[手术执行模块] ←→ [麻醉记录模块]
|
||||
↓
|
||||
[术后恢复模块] ←→ [术后访视]
|
||||
↓
|
||||
[病理送检模块] ←→ [病理报告模块]
|
||||
↓
|
||||
[护理记录] ←→ [病程记录]
|
||||
```
|
||||
|
||||
**🔍 已有模块**: 术前讨论✅ 手术申请✅ 麻醉✅ 安核查✅ CSSD✅ 手术记录✅
|
||||
**🔍 病理模块**: ❌ 缺失 — 病理送检+病理报告是手术闭环的关键环节
|
||||
|
||||
### 2.4 药品全流程交叉分析
|
||||
|
||||
```
|
||||
[采购申请] ←→ [采购订单]
|
||||
↓
|
||||
[验收入库] ←→ [库存管理]
|
||||
↓
|
||||
[处方开具] ←→ [处方审核(合理用药)]
|
||||
↓
|
||||
[药房调配] ←→ [发药/退药]
|
||||
↓
|
||||
[库存预警] ←→ [效期管理]
|
||||
↓
|
||||
[药品追溯(毒麻)] ←→ [抗菌药物管理]
|
||||
↓
|
||||
[处方点评] ←→ [合理用药统计]
|
||||
```
|
||||
|
||||
**🔍 已有模块**: 采购✅ 库存✅ 处方✅ 审核✅ 发药✅ 抗菌✅ 点评✅
|
||||
**🔍 效期管理**: ⚠️ 基础 — 药品效期预警+近效期自动提醒功能待完善
|
||||
|
||||
### 2.5 检验全流程交叉分析
|
||||
|
||||
```
|
||||
[检验申请] ←→ [医嘱模块]
|
||||
↓
|
||||
[条码打印] ←→ [标本采集] ←→ [扫码确认]
|
||||
↓
|
||||
[标本接收] ←→ [标本拒收]
|
||||
↓
|
||||
[LIS检验] ←→ [仪器对接]
|
||||
↓
|
||||
[危急值判定] ←→ [危急值报告] ←→ [危急值处理]
|
||||
↓
|
||||
[审核发布] ←→ [报告查询]
|
||||
↓
|
||||
[参考范围] ←→ [结果解读]
|
||||
```
|
||||
|
||||
**🔍 已有模块**: 申请✅ 条码✅ 采集✅ 危急值✅ 审核✅ 报告✅ 参考范围✅
|
||||
**🔍 闭环完整**: 检验全流程已基本完整
|
||||
|
||||
---
|
||||
|
||||
## 三、系统不足诊断
|
||||
|
||||
### 3.1 缺失模块 (❌ 从未实现)
|
||||
|
||||
| # | 模块名称 | 业务价值 | 三甲依据 | 优先级 |
|
||||
|---|---------|---------|---------|--------|
|
||||
| 1 | **门诊随访管理** | 慢病管理/出院随访/满意度调查 | 评审标准: 患者服务 | 🔴 P0 |
|
||||
| 2 | **病理管理** | 病理送检→取材→制片→诊断→报告 | 手术闭环/肿瘤诊疗 | 🔴 P0 |
|
||||
| 3 | **急诊分诊+抢救** | 急诊分级(1-4级)/抢救记录/绿色通道 | 急诊医学科评审 | 🔴 P0 |
|
||||
| 4 | **患者满意度调查** | 门诊/住院满意度/投诉管理 | 评审标准: 患者服务 | 🟡 P1 |
|
||||
| 5 | **处方点评统计** | 科室排名/医生排名/合理率趋势 | 合理用药评审 | 🟡 P1 |
|
||||
| 6 | **药品效期管理** | 近效期预警/自动停售/效期报表 | 药品管理规范 | 🟡 P1 |
|
||||
| 7 | **护理交接班统计** | 交接班完成率/重点患者统计 | 护理质量指标 | 🟡 P1 |
|
||||
| 8 | **DRG绩效考核** | 科室DRG绩效/费用控制/时间效率 | 医保支付改革 | 🟡 P1 |
|
||||
| 9 | **会诊时限监控** | 会诊超时预警/完成率统计 | 会诊制度 | 🟡 P1 |
|
||||
| 10 | **病案首页质量** | 首页数据校验/编码正确率 | 病案管理规范 | 🟡 P1 |
|
||||
|
||||
### 3.2 待完善模块 (⚠️ 功能不足)
|
||||
|
||||
| # | 模块名称 | 当前状态 | 缺失功能 | 优先级 |
|
||||
|---|---------|---------|---------|--------|
|
||||
| 1 | **预约管理** | 基础预约 | 诊间预约/复诊预约/预约规则配置 | 🟡 P1 |
|
||||
| 2 | **排班管理** | 基础排班 | 弹性排班/节假日排班/停诊管理 | 🟡 P1 |
|
||||
| 3 | **住院押金** | 基础功能 | 押金不足预警/催缴通知/医保预结算 | 🟡 P1 |
|
||||
| 4 | **护理评估** | 已实现5种量表 | 跌倒/压疮动态评估+干预效果追踪 | ⚠️ 可优化 |
|
||||
| 5 | **知情同意** | 已实现 | 电子签名+版本管理+患者确认流程 | ⚠️ 可优化 |
|
||||
| 6 | **DRG分组** | 已实现基础 | 分组结果校验+费用异常预警+绩效分析 | ⚠️ 可优化 |
|
||||
|
||||
### 3.3 交叉业务断裂点
|
||||
|
||||
| # | 断裂点 | 涉及模块 | 影响 | 优先级 |
|
||||
|---|--------|---------|------|--------|
|
||||
| 1 | **门诊→住院转科** | 门诊/住院/床位 | 转科时患者信息丢失 | 🔴 P0 |
|
||||
| 2 | **手术→病理送检** | 手术/病理/检验 | 手术后标本无法自动送检 | 🔴 P0 |
|
||||
| 3 | **检验→临床决策** | 检验/合理用药 | 检验结果未联动用药调整 | 🟡 P1 |
|
||||
| 4 | **检查→报告→医嘱** | 检查/报告/医嘱 | 报告完成后未自动回写医嘱状态 | 🟡 P1 |
|
||||
| 5 | **护理→医嘱→执行** | 护理/医嘱/执行 | 护士执行后未自动更新医嘱完成状态 | ⚠️ 可优化 |
|
||||
| 6 | **药品→库存→预警** | 药品/库存/效期 | 库存不足时未联动处方拦截 | ⚠️ 可优化 |
|
||||
|
||||
---
|
||||
|
||||
## 四、深度详细设计 — 缺失模块
|
||||
|
||||
### 4.1 门诊随访管理模块
|
||||
|
||||
#### 4.1.1 业务流程
|
||||
```
|
||||
出院/门诊结束
|
||||
↓
|
||||
[随访计划生成] ←→ [患者分类(慢病/手术/肿瘤/普通)]
|
||||
↓
|
||||
[随访任务分配] ←→ [责任医生/护士]
|
||||
↓
|
||||
[电话/短信/微信随访] ←→ [随访记录]
|
||||
↓
|
||||
[随访结果录入] ←→ [异常处理(再入院/转诊)]
|
||||
↓
|
||||
[满意度调查] ←→ [投诉管理]
|
||||
↓
|
||||
[随访统计] ←→ [质控指标]
|
||||
```
|
||||
|
||||
#### 4.1.2 数据模型
|
||||
- **FollowupPlan** (随访计划): plan_id, patient_id, disease_type, followup_type, frequency, responsible_doctor
|
||||
- **FollowupTask** (随访任务): task_id, plan_id, scheduled_date, actual_date, contact_method, result
|
||||
- **FollowupRecord** (随访记录): record_id, task_id, contact_content, patient_condition, abnormal_flag
|
||||
- **SatisfactionSurvey** (满意度): survey_id, patient_id, survey_type, score, suggestions
|
||||
- **ComplaintRecord** (投诉): complaint_id, patient_id, complaint_type, content,处理状态
|
||||
|
||||
#### 4.1.3 接口设计
|
||||
| API | 方法 | 说明 |
|
||||
|-----|------|------|
|
||||
| /followup/plan/page | GET | 随访计划列表 |
|
||||
| /followup/plan/add | POST | 新建随访计划 |
|
||||
| /followup/task/page | GET | 随访任务列表(按责任人) |
|
||||
| /followup/task/complete/{id} | PUT | 完成随访任务 |
|
||||
| /followup/record/add | POST | 录入随访记录 |
|
||||
| /followup/survey/add | POST | 提交满意度 |
|
||||
| /followup/complaint/page | GET | 投诉列表 |
|
||||
| /followup/stats | GET | 随访统计(完成率/满意度) |
|
||||
|
||||
### 4.2 病理管理模块
|
||||
|
||||
#### 4.2.1 业务流程
|
||||
```
|
||||
[手术/活检] → 病理申请
|
||||
↓
|
||||
[标本接收] ←→ [标本核对(条码)]
|
||||
↓
|
||||
[取材] ←→ [组织处理(固定/脱水/包埋)]
|
||||
↓
|
||||
[切片] ←→ [染色(HE/免疫组化)]
|
||||
↓
|
||||
[阅片] ←→ [病理诊断]
|
||||
↓
|
||||
[报告编写] ←→ [报告审核(三级审核)]
|
||||
↓
|
||||
[报告发布] ←→ [临床科室]
|
||||
↓
|
||||
[病理随访] ←→ [肿瘤登记]
|
||||
```
|
||||
|
||||
#### 4.2.2 数据模型
|
||||
- **PathologyOrder** (病理申请): order_id, patient_id, specimen_type, clinical_diagnosis
|
||||
- **PathologySpecimen** (病理标本): specimen_id, order_id, barcode, collection_site, fixative
|
||||
- **PathologyProcess** (病理处理): process_id, specimen_id, process_type, operator, time
|
||||
- **PathologyDiagnosis** (病理诊断): diagnosis_id, specimen_id, diagnosis_type, result
|
||||
- **PathologyReport** (病理报告): report_id, order_id, findings, diagnosis, report_doctor, verify_doctor
|
||||
|
||||
#### 4.2.3 接口设计
|
||||
| API | 方法 | 说明 |
|
||||
|-----|------|------|
|
||||
| /pathology/order/page | GET | 病理申请列表 |
|
||||
| /pathology/order/add | POST | 新建病理申请 |
|
||||
| /pathology/specimen/scan | POST | 标本扫码接收 |
|
||||
| /pathology/process/record | POST | 记录处理过程 |
|
||||
| /pathology/diagnosis/add | POST | 录入诊断 |
|
||||
| /pathology/report/page | GET | 病理报告列表 |
|
||||
| /pathology/report/verify/{id} | PUT | 审核报告(三级) |
|
||||
|
||||
### 4.3 急诊分诊+抢救模块
|
||||
|
||||
#### 4.3.1 业务流程
|
||||
```
|
||||
患者到达急诊
|
||||
↓
|
||||
[预检分诊] ←→ [生命体征采集]
|
||||
↓ (按病情分级)
|
||||
┌─Ⅰ级(濒死)→ 抢救室 → 绿色通道
|
||||
├─Ⅱ级(危重)→ 抢救室 → 优先处理
|
||||
├─Ⅲ级(急症)→ 急诊诊室 → 按序就诊
|
||||
└─Ⅳ级(非急)→ 普通门诊 → 引导转诊
|
||||
↓
|
||||
[抢救记录] ←→ [抢救医嘱]
|
||||
↓
|
||||
[会诊申请] ←→ [住院转科/留观/出院]
|
||||
↓
|
||||
[急诊病历] ←→ [急诊统计]
|
||||
```
|
||||
|
||||
#### 4.3.2 数据模型
|
||||
- **EmergencyTriage** (急诊分诊): triage_id, patient_id, triage_level(1-4), vital_signs, triage_nurse
|
||||
- **EmergencyRescue** (抢救记录): rescue_id, patient_id, rescue_start, rescue_end, result
|
||||
- **EmergencyObservation** (留观记录): observation_id, patient_id, observation_start, bed_no
|
||||
- **Emergency绿色通道**: green_channel_id, patient_id, disease_type, door_to_treatment_time
|
||||
|
||||
#### 4.3.3 接口设计
|
||||
| API | 方法 | 说明 |
|
||||
|-----|------|------|
|
||||
| /emergency/triage/add | POST | 急诊分诊 |
|
||||
| /emergency/triage/queue | GET | 分诊队列(按级别) |
|
||||
| /emergency/rescue/add | POST | 开始抢救 |
|
||||
| /emergency/rescue/complete/{id} | PUT | 抢救完成 |
|
||||
| /emergency/observation/add | POST | 留观登记 |
|
||||
| /emergency/green-channel | POST | 绿色通道启动 |
|
||||
| /emergency/stats | GET | 急诊统计(分级/抢救率/等候时间) |
|
||||
|
||||
### 4.4 药品效期管理模块
|
||||
|
||||
#### 4.4.1 业务流程
|
||||
```
|
||||
[入库验收] → 记录效期
|
||||
↓
|
||||
[效期监控] ←→ [每日扫描]
|
||||
↓ (近效期预警)
|
||||
┌─ 6个月内 → 近效期提醒 → 优先使用
|
||||
├─ 3个月内 → 紧急预警 → 限制开方
|
||||
└─ 过期 → 自动停售 → 退回供应商
|
||||
↓
|
||||
[效期报表] ←→ [过期药品销毁]
|
||||
```
|
||||
|
||||
#### 4.4.2 数据模型
|
||||
- **DrugExpiryAlert** (效期预警): alert_id, drug_code, drug_name, batch_no, expiry_date, alert_level
|
||||
- **DrugExpiryStats** (效期统计): 按月统计近效期/过期/销毁金额
|
||||
|
||||
### 4.5 处方点评统计模块
|
||||
|
||||
#### 4.5.1 业务流程
|
||||
```
|
||||
[处方数据] → 自动筛选
|
||||
↓ (不合理处方)
|
||||
[系统点评] ←→ [人工点评]
|
||||
↓
|
||||
[点评结果] ←→ [医生反馈]
|
||||
↓
|
||||
[科室排名] ←→ [医生排名]
|
||||
↓
|
||||
[合理率趋势] ←→ [改进措施]
|
||||
```
|
||||
|
||||
#### 4.5.2 数据模型
|
||||
- **PrescriptionReviewStats** (点评统计): 按科室/医生/月份统计合理率
|
||||
- **PrescriptionReviewRanking** (排名): 科室排名/医生排名
|
||||
|
||||
---
|
||||
|
||||
## 五、实施优先级排序
|
||||
|
||||
### Phase A: 缺失核心模块 (P0 — 立即开发)
|
||||
1. **门诊随访管理** — 三甲评审必查
|
||||
2. **病理管理** — 手术闭环关键
|
||||
3. **急诊分诊+抢救** — 急诊评审必查
|
||||
|
||||
### Phase B: 待完善功能 (P1 — 尽快开发)
|
||||
4. **药品效期管理** — 药品安全
|
||||
5. **处方点评统计** — 合理用药
|
||||
6. **患者满意度** — 评审指标
|
||||
7. **DRG绩效考核** — 医保改革
|
||||
8. **护理交接班统计** — 护理质量
|
||||
9. **会诊时限监控** — 会诊制度
|
||||
10. **病案首页质量** — 数据质量
|
||||
|
||||
### Phase C: 交叉业务修复 (P1 — 尽快修复)
|
||||
11. **门诊→住院转科** — 信息连续性
|
||||
12. **手术→病理送检** — 标本追溯
|
||||
13. **检验→临床决策** — 检验联动
|
||||
14. **检查→报告→医嘱** — 状态联动
|
||||
|
||||
---
|
||||
|
||||
## 六、文档产出清单
|
||||
|
||||
| 文档 | 内容 | 用途 |
|
||||
|------|------|------|
|
||||
| CROSS_MODULE_BUSINESS_ANALYSIS.md | 本文档 | 业务分析+系统诊断 |
|
||||
| PHASE_A_FOLLOWUP_DESIGN.md | 门诊随访深度设计 | 开发依据 |
|
||||
| PHASE_A_PATHOLOGY_DESIGN.md | 病理管理深度设计 | 开发依据 |
|
||||
| PHASE_A_EMERGENCY_DESIGN.md | 急诊分诊抢救深度设计 | 开发依据 |
|
||||
| PHASE_B_*.md | 各P1模块深度设计 | 开发依据 |
|
||||
128
MD/architecture/PHASE_A_EMERGENCY_DESIGN.md
Normal file
128
MD/architecture/PHASE_A_EMERGENCY_DESIGN.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# 急诊分诊+抢救模块 — 深度设计文档
|
||||
|
||||
> **版本**: v1.0 | **编制日期**: 2026-06-07
|
||||
> **依据**: 《急诊科建设与管理指南》+ 《急诊预检分诊专家共识》
|
||||
|
||||
---
|
||||
|
||||
## 一、业务背景
|
||||
|
||||
急诊是三甲医院的关键科室,必须实现:
|
||||
- 四级预检分诊(Ⅰ级濒死/Ⅱ级危重/Ⅲ级急症/Ⅳ级非急)
|
||||
- 绿色通道(胸痛/卒中/创伤)
|
||||
- 抢救记录电子化
|
||||
- 急诊绿色通道时间监控(门-药/门-球囊/门-手术)
|
||||
|
||||
## 二、业务流程
|
||||
|
||||
```
|
||||
患者到达急诊
|
||||
↓
|
||||
[预检分诊台]
|
||||
├─ 采集生命体征(体温/脉搏/呼吸/血压/血氧/意识)
|
||||
├─ 评估病情分级
|
||||
└─ 分配就诊区域
|
||||
↓
|
||||
┌─ Ⅰ级(濒死) → 立即抢救 → 绿色通道
|
||||
├─ Ⅱ级(危重) → 10分钟内就诊
|
||||
├─ Ⅲ级(急症) → 30分钟内就诊
|
||||
└─ Ⅳ级(非急) → 引导至门诊
|
||||
↓
|
||||
[急诊医生接诊] ←→ [急诊医嘱]
|
||||
↓
|
||||
[检查/检验] ←→ [抢救/留观/住院/出院]
|
||||
↓
|
||||
[急诊病历] ←→ [急诊统计]
|
||||
```
|
||||
|
||||
## 三、数据模型
|
||||
|
||||
```sql
|
||||
-- 急诊分诊
|
||||
CREATE TABLE emergency_triage (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id BIGINT,
|
||||
patient_name VARCHAR(50),
|
||||
triage_level INT NOT NULL, -- 1-4级
|
||||
chief_complaint TEXT,
|
||||
temperature DECIMAL(4,1),
|
||||
pulse INT,
|
||||
respiration INT,
|
||||
systolic_bp INT,
|
||||
diastolic_bp INT,
|
||||
spo2 INT,
|
||||
consciousness VARCHAR(20),
|
||||
triage_nurse VARCHAR(64),
|
||||
triage_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
area VARCHAR(20), -- RESUS/EMERGENCY/OBSERVATION/GREEN
|
||||
status VARCHAR(20) DEFAULT 'WAITING',
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 抢救记录
|
||||
CREATE TABLE emergency_rescue (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id BIGINT NOT NULL,
|
||||
triage_id BIGINT,
|
||||
rescue_start TIMESTAMP,
|
||||
rescue_end TIMESTAMP,
|
||||
rescue_result VARCHAR(20),
|
||||
chief_doctor VARCHAR(64),
|
||||
rescue_team TEXT,
|
||||
procedures TEXT,
|
||||
medications TEXT,
|
||||
outcome VARCHAR(20), -- ADMITTED/DISCHARGED/TRANSFERRED/DECEASED
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 留观记录
|
||||
CREATE TABLE emergency_observation (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id BIGINT NOT NULL,
|
||||
triage_id BIGINT,
|
||||
observation_start TIMESTAMP,
|
||||
observation_end TIMESTAMP,
|
||||
bed_no VARCHAR(20),
|
||||
doctor VARCHAR(64),
|
||||
diagnosis TEXT,
|
||||
disposition VARCHAR(20), -- ADMITTED/DISCHARGED/TRANSFERRED
|
||||
observation_hours INT,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 绿色通道
|
||||
CREATE TABLE emergency_green_channel (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id BIGINT NOT NULL,
|
||||
disease_type VARCHAR(30), -- CHEST_PAIN/STROKE/TRAUMA/POISONING
|
||||
door_to_treatment_time INT, -- 门到治疗时间(分钟)
|
||||
target_time INT, -- 目标时间
|
||||
is_achieved BOOLEAN,
|
||||
doctor VARCHAR(64),
|
||||
activate_time TIMESTAMP,
|
||||
complete_time TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
## 四、接口设计
|
||||
|
||||
| API | 方法 | 说明 |
|
||||
|-----|------|------|
|
||||
| /emergency/triage/add | POST | 预检分诊 |
|
||||
| /emergency/triage/queue | GET | 分诊队列(按级别排序) |
|
||||
| /emergency/rescue/add | POST | 开始抢救 |
|
||||
| /emergency/rescue/complete/{id} | PUT | 抢救完成 |
|
||||
| /emergency/observation/add | POST | 留观登记 |
|
||||
| /emergency/observation/discharge/{id} | PUT | 留观出院 |
|
||||
| /emergency/green-channel/activate | POST | 启动绿色通道 |
|
||||
| /emergency/green-channel/stats | GET | 绿色通道统计(达标率) |
|
||||
| /emergency/stats | GET | 急诊统计(分级分布/抢救率/等候时间) |
|
||||
195
MD/architecture/PHASE_A_FOLLOWUP_DESIGN.md
Normal file
195
MD/architecture/PHASE_A_FOLLOWUP_DESIGN.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# 门诊随访管理模块 — 深度设计文档
|
||||
|
||||
> **版本**: v1.0 | **编制日期**: 2026-06-07
|
||||
> **依据**: 《三级医院评审标准》患者服务条款 + 慢病管理规范
|
||||
|
||||
---
|
||||
|
||||
## 一、业务背景
|
||||
|
||||
门诊随访是三甲医院患者服务的核心环节:
|
||||
- 慢病患者(高血压/糖尿病/冠心病等)出院后需定期随访
|
||||
- 手术患者术后需随访恢复情况
|
||||
- 肿瘤患者需长期随访复发/转移
|
||||
- 满意度调查是医院服务质量的核心指标
|
||||
|
||||
## 二、业务流程
|
||||
|
||||
### 2.1 随访计划生成
|
||||
```
|
||||
出院/门诊结束
|
||||
↓
|
||||
[自动触发] ← 根据病种+诊断自动生成随访计划
|
||||
↓ (规则引擎)
|
||||
┌─ 高血压: 每月1次电话随访, 持续1年
|
||||
├─ 糖尿病: 每2周1次, 持续6个月
|
||||
├─ 手术后: 术后1周/1月/3月/6月/1年
|
||||
├─ 肿瘤: 每3个月复查, 持续5年
|
||||
└─ 普通: 出院后1周电话随访1次
|
||||
↓
|
||||
[分配责任人] ← 根据科室+医生自动分配
|
||||
```
|
||||
|
||||
### 2.2 随访执行
|
||||
```
|
||||
[随访任务列表] ← 责任医生/护士查看今日任务
|
||||
↓
|
||||
[选择联系方式] ← 电话/短信/微信/门诊
|
||||
↓
|
||||
[拨打电话/发送短信]
|
||||
↓
|
||||
[录入随访结果]
|
||||
├─ 患者情况良好 → 标记完成
|
||||
├─ 有异常症状 → 创建复查预约
|
||||
├─ 需要调药 → 转诊门诊
|
||||
└─ 失访 → 标记失访原因
|
||||
↓
|
||||
[更新随访记录]
|
||||
```
|
||||
|
||||
### 2.3 满意度调查
|
||||
```
|
||||
[出院时] → 发放满意度问卷(纸质/电子)
|
||||
↓
|
||||
[患者填写] → 评分+建议
|
||||
↓
|
||||
[数据汇总] → 按科室/医生统计
|
||||
↓
|
||||
[问题整改] → 质量改进措施
|
||||
```
|
||||
|
||||
## 三、数据模型
|
||||
|
||||
### 3.1 核心表
|
||||
|
||||
```sql
|
||||
-- 随访计划
|
||||
CREATE TABLE followup_plan (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id BIGINT NOT NULL,
|
||||
patient_name VARCHAR(50),
|
||||
encounter_id BIGINT,
|
||||
disease_code VARCHAR(20),
|
||||
disease_name VARCHAR(100),
|
||||
followup_type VARCHAR(20) NOT NULL, -- PHONE/SMS/WECHAT/OUTPATIENT
|
||||
frequency VARCHAR(20), -- DAILY/WEEKLY/MONTHLY/QUARTERLY
|
||||
total_times INT DEFAULT 1,
|
||||
completed_times INT DEFAULT 0,
|
||||
responsible_doctor VARCHAR(64),
|
||||
responsible_nurse VARCHAR(64),
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE', -- ACTIVE/COMPLETED/CANCELLED
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 随访任务
|
||||
CREATE TABLE followup_task (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
plan_id BIGINT NOT NULL,
|
||||
patient_id BIGINT NOT NULL,
|
||||
patient_name VARCHAR(50),
|
||||
phone VARCHAR(20),
|
||||
scheduled_date DATE NOT NULL,
|
||||
actual_date DATE,
|
||||
contact_method VARCHAR(20),
|
||||
operator_name VARCHAR(64),
|
||||
result VARCHAR(20), -- SUCCESS/FAILED/NO_ANSWER/WRONG_NUMBER/LOST
|
||||
abnormal_flag BOOLEAN DEFAULT FALSE,
|
||||
next_action VARCHAR(200),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 随访记录
|
||||
CREATE TABLE followup_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
task_id BIGINT NOT NULL,
|
||||
patient_id BIGINT NOT NULL,
|
||||
contact_content TEXT,
|
||||
patient_condition TEXT,
|
||||
medication_compliance VARCHAR(20),
|
||||
symptoms TEXT,
|
||||
vital_signs JSONB,
|
||||
reappointment_flag BOOLEAN DEFAULT FALSE,
|
||||
reappointment_date DATE,
|
||||
transfer_flag BOOLEAN DEFAULT FALSE,
|
||||
transfer_reason TEXT,
|
||||
operator_name VARCHAR(64),
|
||||
operate_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 满意度调查
|
||||
CREATE TABLE satisfaction_survey (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id BIGINT,
|
||||
patient_name VARCHAR(50),
|
||||
survey_type VARCHAR(20) NOT NULL, -- INPATIENT/OUTPATIENT/EMERGENCY
|
||||
department_name VARCHAR(100),
|
||||
doctor_name VARCHAR(64),
|
||||
overall_score INT,
|
||||
service_score INT,
|
||||
environment_score INT,
|
||||
suggestions TEXT,
|
||||
survey_date DATE DEFAULT CURRENT_DATE,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 投诉记录
|
||||
CREATE TABLE complaint_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id BIGINT,
|
||||
patient_name VARCHAR(50),
|
||||
complaint_type VARCHAR(30) NOT NULL, -- SERVICE/TECHNIQUE/ENVIRONMENT/BILLING/OTHER
|
||||
complaint_content TEXT NOT NULL,
|
||||
department_name VARCHAR(100),
|
||||
handler VARCHAR(64),
|
||||
handle_result TEXT,
|
||||
handle_time TIMESTAMP,
|
||||
status VARCHAR(20) DEFAULT 'PENDING', -- PENDING/PROCESSING/RESOLVED/CLOSED
|
||||
satisfaction_after INT,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### 3.2 接口设计
|
||||
|
||||
| API | 方法 | 说明 | 参数 |
|
||||
|-----|------|------|------|
|
||||
| /followup/plan/page | GET | 随访计划列表 | patientName,status,pageNo,pageSize |
|
||||
| /followup/plan/add | POST | 新建随访计划 | patientId,diseaseCode,frequency,totalTimes |
|
||||
| /followup/task/my | GET | 我的今日任务 | operatorName |
|
||||
| /followup/task/page | GET | 任务列表(分页) | status,scheduledDate,pageNo,pageSize |
|
||||
| /followup/task/complete/{id} | PUT | 完成任务 | result,abnormalFlag,nextAction |
|
||||
| /followup/record/add | POST | 录入随访记录 | taskId,contactContent,patientCondition |
|
||||
| /followup/survey/add | POST | 提交满意度 | surveyType,overallScore,suggestions |
|
||||
| /followup/survey/stats | GET | 满意度统计 | departmentName,startDate,endDate |
|
||||
| /followup/complaint/page | GET | 投诉列表 | status,complaintType,pageNo,pageSize |
|
||||
| /followup/complaint/handle/{id} | PUT | 处理投诉 | handler,handleResult |
|
||||
| /followup/stats/overview | GET | 随访概览 | startDate,endDate |
|
||||
|
||||
## 四、前端页面设计
|
||||
|
||||
### 4.1 页面结构
|
||||
```
|
||||
followup/
|
||||
├── plan/ # 随访计划管理
|
||||
├── task/ # 随访任务(今日任务/我的任务)
|
||||
├── record/ # 随访记录查询
|
||||
├── survey/ # 满意度调查
|
||||
├── complaint/ # 投诉管理
|
||||
└── stats/ # 统计分析
|
||||
```
|
||||
|
||||
### 4.2 核心交互
|
||||
- **今日任务看板**: 按优先级排序(异常>待随访>已完成)
|
||||
- **一键拨号**: 点击患者电话直接拨打(集成HIS电话模块)
|
||||
- **随访结果快速录入**: 预设选项+自由文本
|
||||
- **满意度雷达图**: 多维度评分可视化
|
||||
142
MD/architecture/PHASE_A_PATHOLOGY_DESIGN.md
Normal file
142
MD/architecture/PHASE_A_PATHOLOGY_DESIGN.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# 病理管理模块 — 深度设计文档
|
||||
|
||||
> **版本**: v1.0 | **编制日期**: 2026-06-07
|
||||
> **依据**: 《病理科建设与管理指南》+ 《临床病理质量控制标准》
|
||||
|
||||
---
|
||||
|
||||
## 一、业务背景
|
||||
|
||||
病理诊断是手术后诊断的"金标准",三甲医院必须具备:
|
||||
- 病理标本全流程追溯(从手术室到病理科)
|
||||
- 三级审核制度(住院医师→主治→副主任)
|
||||
- 病理报告质量控制
|
||||
- 肿瘤登记上报
|
||||
|
||||
## 二、业务流程
|
||||
|
||||
```
|
||||
[手术/活检/穿刺] → 病理申请单
|
||||
↓
|
||||
[标本采集] ←→ [条码打印] ←→ [标本固定(10%福尔马林)]
|
||||
↓ (运送至病理科)
|
||||
[标本接收] ←→ [标本核对(条码扫码)]
|
||||
↓ (不合格退回)
|
||||
[取材描述] ←→ [组织处理(脱水/包埋)]
|
||||
↓
|
||||
[切片制作] ←→ [染色(HE/免疫组化/特殊染色)]
|
||||
↓
|
||||
[初诊阅片] ←→ [上级医师审核]
|
||||
↓ (疑难病例会诊)
|
||||
[病理会诊] ←→ [最终诊断]
|
||||
↓
|
||||
[报告编写] ←→ [三级审核]
|
||||
↓
|
||||
[报告签发] ←→ [临床科室领取]
|
||||
↓
|
||||
[肿瘤登记] ←→ [随访]
|
||||
```
|
||||
|
||||
## 三、数据模型
|
||||
|
||||
```sql
|
||||
-- 病理申请
|
||||
CREATE TABLE pathology_order (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id BIGINT NOT NULL,
|
||||
patient_name VARCHAR(50),
|
||||
encounter_id BIGINT,
|
||||
specimen_type VARCHAR(50), -- 手术标本/活检/穿刺/细胞学
|
||||
clinical_diagnosis TEXT,
|
||||
sample_site VARCHAR(100),
|
||||
urgency VARCHAR(20) DEFAULT 'NORMAL', -- URGENT/NORMAL
|
||||
order_status VARCHAR(20) DEFAULT 'PENDING',
|
||||
apply_doctor VARCHAR(64),
|
||||
apply_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 病理标本
|
||||
CREATE TABLE pathology_specimen (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
order_id BIGINT NOT NULL,
|
||||
barcode VARCHAR(100) NOT NULL,
|
||||
specimen_desc TEXT,
|
||||
collection_site VARCHAR(100),
|
||||
fixative VARCHAR(50),
|
||||
fixative_time TIMESTAMP,
|
||||
receive_time TIMESTAMP,
|
||||
receiver VARCHAR(64),
|
||||
is_qualified BOOLEAN DEFAULT TRUE,
|
||||
reject_reason TEXT,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 病理处理
|
||||
CREATE TABLE pathology_process (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
specimen_id BIGINT NOT NULL,
|
||||
process_type VARCHAR(30), -- 取材/脱水/包埋/切片/染色/免疫组化
|
||||
process_desc TEXT,
|
||||
operator VARCHAR(64),
|
||||
operate_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 病理诊断
|
||||
CREATE TABLE pathology_diagnosis (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
order_id BIGINT NOT NULL,
|
||||
specimen_id BIGINT,
|
||||
diagnosis_type VARCHAR(30), -- 初诊/复诊/会诊/最终
|
||||
diagnosis_result TEXT,
|
||||
immunostain_result TEXT,
|
||||
doctor_name VARCHAR(64),
|
||||
doctor_title VARCHAR(20),
|
||||
diagnose_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 病理报告
|
||||
CREATE TABLE pathology_report (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
order_id BIGINT NOT NULL,
|
||||
patient_id BIGINT,
|
||||
specimen_desc TEXT,
|
||||
macroscopic_desc TEXT, -- 肉眼所见
|
||||
microscopic_desc TEXT, -- 镜下所见
|
||||
diagnosis_result TEXT, -- 病理诊断
|
||||
suggestion TEXT,
|
||||
report_doctor VARCHAR(64),
|
||||
report_time TIMESTAMP,
|
||||
audit_doctor VARCHAR(64),
|
||||
audit_time TIMESTAMP,
|
||||
final_doctor VARCHAR(64),
|
||||
final_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
|
||||
);
|
||||
```
|
||||
|
||||
## 四、接口设计
|
||||
|
||||
| API | 方法 | 说明 |
|
||||
|-----|------|------|
|
||||
| /pathology/order/page | GET | 病理申请列表 |
|
||||
| /pathology/order/add | POST | 新建病理申请 |
|
||||
| /pathology/specimen/scan | POST | 标本扫码接收 |
|
||||
| /pathology/specimen/reject/{id} | PUT | 标本拒收 |
|
||||
| /pathology/process/record | POST | 记录处理过程 |
|
||||
| /pathology/diagnosis/add | POST | 录入诊断 |
|
||||
| /pathology/report/page | GET | 病理报告列表 |
|
||||
| /pathology/report/add | POST | 新建报告 |
|
||||
| /pathology/report/audit/{id} | PUT | 审核报告 |
|
||||
| /pathology/stats | GET | 病理统计(标本数/诊断分布/周转时间) |
|
||||
@@ -0,0 +1,357 @@
|
||||
package com.healthlink.his.web.emergency.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.emergency.domain.*;
|
||||
import com.healthlink.his.emergency.service.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 急诊管理 Controller — 深度业务逻辑
|
||||
*
|
||||
* 业务说明:
|
||||
* 1. 急诊分诊: 按五级分诊标准(Ⅰ级濒死→Ⅴ级非紧急)进行快速评估,决定就诊区域和优先级
|
||||
* 2. 抢救管理: 抢救过程记录(开始时间、团队、操作、用药、结果),抢救成功/失败闭环
|
||||
* 3. 留观管理: 急诊留观患者观察记录,观察时长追踪,转归处置(住院/出院/转科)
|
||||
* 4. 绿色通道: 急危重症患者快速通道,追踪Door-to-Treatment时间,评估是否达标
|
||||
* 5. 与住院联动: 急诊→住院转科信息传递,保证诊疗连续性
|
||||
* 6. 与120联动: 院前急救信息接收和跟踪
|
||||
*
|
||||
* 调用关系:
|
||||
* EmergencyController → IEmergencyTriageService → 分诊评估→分级→分配区域
|
||||
* → IEmergencyRescueService → 抢救记录→用药→结果
|
||||
* → IEmergencyObservationService → 留观→转归
|
||||
* → IEmergencyGreenChannelService → 时间追踪→达标评估
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/emergency")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class EmergencyController {
|
||||
|
||||
private final IEmergencyTriageService triageService;
|
||||
private final IEmergencyRescueService rescueService;
|
||||
private final IEmergencyObservationService observationService;
|
||||
private final IEmergencyGreenChannelService greenChannelService;
|
||||
|
||||
// ==================== 急诊分诊 ====================
|
||||
|
||||
@GetMapping("/triage/page")
|
||||
public R<?> getTriagePage(
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "triageLevel", required = false) Integer triageLevel,
|
||||
@RequestParam(value = "area", required = false) String area,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "triageNurse", required = false) String triageNurse,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<EmergencyTriage> w = new LambdaQueryWrapper<>();
|
||||
w.like(StringUtils.hasText(patientName), EmergencyTriage::getPatientName, patientName)
|
||||
.eq(triageLevel != null, EmergencyTriage::getTriageLevel, triageLevel)
|
||||
.eq(StringUtils.hasText(area), EmergencyTriage::getArea, area)
|
||||
.eq(StringUtils.hasText(status), EmergencyTriage::getStatus, status)
|
||||
.eq(StringUtils.hasText(triageNurse), EmergencyTriage::getTriageNurse, triageNurse)
|
||||
.orderByAsc(EmergencyTriage::getTriageLevel)
|
||||
.orderByDesc(EmergencyTriage::getTriageTime);
|
||||
return R.ok(triageService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/triage/realtime")
|
||||
public R<?> getRealtimeStatus() {
|
||||
Map<String, Object> realtime = new HashMap<>();
|
||||
// 各级别待诊人数
|
||||
for (int level = 1; level <= 5; level++) {
|
||||
LambdaQueryWrapper<EmergencyTriage> w = new LambdaQueryWrapper<>();
|
||||
w.eq(EmergencyTriage::getTriageLevel, level)
|
||||
.eq(EmergencyTriage::getStatus, "WAITING");
|
||||
realtime.put("level" + level + "Waiting", triageService.count(w));
|
||||
}
|
||||
// 当前就诊中人数
|
||||
LambdaQueryWrapper<EmergencyTriage> w2 = new LambdaQueryWrapper<>();
|
||||
w2.eq(EmergencyTriage::getStatus, "IN_TREATMENT");
|
||||
realtime.put("inTreatment", triageService.count(w2));
|
||||
// 今日总分诊数
|
||||
LambdaQueryWrapper<EmergencyTriage> w3 = new LambdaQueryWrapper<>();
|
||||
realtime.put("todayTotal", triageService.count(w3));
|
||||
return R.ok(realtime);
|
||||
}
|
||||
|
||||
@GetMapping("/triage/{id}")
|
||||
public R<?> getTriageById(@PathVariable Long id) {
|
||||
return R.ok(triageService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/triage/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addTriage(@RequestBody EmergencyTriage triage) {
|
||||
if (!StringUtils.hasText(triage.getStatus())) {
|
||||
triage.setStatus("WAITING");
|
||||
}
|
||||
triage.setTriageTime(new Date());
|
||||
triage.setCreateTime(new Date());
|
||||
triageService.save(triage);
|
||||
return R.ok(triage);
|
||||
}
|
||||
|
||||
@PutMapping("/triage/update")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> updateTriage(@RequestBody EmergencyTriage triage) {
|
||||
triageService.updateById(triage);
|
||||
return R.ok(triage);
|
||||
}
|
||||
|
||||
@PutMapping("/triage/{id}/start-treatment")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> startTreatment(@PathVariable Long id) {
|
||||
EmergencyTriage triage = triageService.getById(id);
|
||||
if (triage != null) {
|
||||
triage.setStatus("IN_TREATMENT");
|
||||
triageService.updateById(triage);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PutMapping("/triage/{id}/complete")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> completeTriage(@PathVariable Long id) {
|
||||
EmergencyTriage triage = triageService.getById(id);
|
||||
if (triage != null) {
|
||||
triage.setStatus("COMPLETED");
|
||||
triageService.updateById(triage);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/triage/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteTriage(@PathVariable Long id) {
|
||||
triageService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 抢救管理 ====================
|
||||
|
||||
@GetMapping("/rescue/page")
|
||||
public R<?> getRescuePage(
|
||||
@RequestParam(value = "patientId", required = false) Long patientId,
|
||||
@RequestParam(value = "chiefDoctor", required = false) String chiefDoctor,
|
||||
@RequestParam(value = "rescueResult", required = false) String rescueResult,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<EmergencyRescue> w = new LambdaQueryWrapper<>();
|
||||
w.eq(patientId != null, EmergencyRescue::getPatientId, patientId)
|
||||
.eq(StringUtils.hasText(chiefDoctor), EmergencyRescue::getChiefDoctor, chiefDoctor)
|
||||
.eq(StringUtils.hasText(rescueResult), EmergencyRescue::getRescueResult, rescueResult)
|
||||
.orderByDesc(EmergencyRescue::getRescueStart);
|
||||
return R.ok(rescueService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/rescue/{id}")
|
||||
public R<?> getRescueById(@PathVariable Long id) {
|
||||
return R.ok(rescueService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/rescue/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addRescue(@RequestBody EmergencyRescue rescue) {
|
||||
rescue.setRescueStart(new Date());
|
||||
rescue.setCreateTime(new Date());
|
||||
rescueService.save(rescue);
|
||||
// 联动更新分诊状态
|
||||
if (rescue.getTriageId() != null) {
|
||||
EmergencyTriage triage = triageService.getById(rescue.getTriageId());
|
||||
if (triage != null) {
|
||||
triage.setStatus("IN_TREATMENT");
|
||||
triageService.updateById(triage);
|
||||
}
|
||||
}
|
||||
return R.ok(rescue);
|
||||
}
|
||||
|
||||
@PutMapping("/rescue/end")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> endRescue(@RequestBody EmergencyRescue rescue) {
|
||||
rescue.setRescueEnd(new Date());
|
||||
rescueService.updateById(rescue);
|
||||
return R.ok(rescue);
|
||||
}
|
||||
|
||||
@PutMapping("/rescue/{id}/result")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> updateRescueResult(@PathVariable Long id, @RequestBody EmergencyRescue data) {
|
||||
EmergencyRescue rescue = rescueService.getById(id);
|
||||
if (rescue != null) {
|
||||
rescue.setRescueResult(data.getRescueResult());
|
||||
rescue.setOutcome(data.getOutcome());
|
||||
rescue.setRescueEnd(new Date());
|
||||
rescueService.updateById(rescue);
|
||||
// 联动更新分诊状态为完成
|
||||
if (rescue.getTriageId() != null) {
|
||||
EmergencyTriage triage = triageService.getById(rescue.getTriageId());
|
||||
if (triage != null) {
|
||||
triage.setStatus("COMPLETED");
|
||||
triageService.updateById(triage);
|
||||
}
|
||||
}
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/rescue/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteRescue(@PathVariable Long id) {
|
||||
rescueService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 留观管理 ====================
|
||||
|
||||
@GetMapping("/observation/page")
|
||||
public R<?> getObservationPage(
|
||||
@RequestParam(value = "patientId", required = false) Long patientId,
|
||||
@RequestParam(value = "doctor", required = false) String doctor,
|
||||
@RequestParam(value = "disposition", required = false) String disposition,
|
||||
@RequestParam(value = "bedNo", required = false) String bedNo,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<EmergencyObservation> w = new LambdaQueryWrapper<>();
|
||||
w.eq(patientId != null, EmergencyObservation::getPatientId, patientId)
|
||||
.eq(StringUtils.hasText(doctor), EmergencyObservation::getDoctor, doctor)
|
||||
.eq(StringUtils.hasText(disposition), EmergencyObservation::getDisposition, disposition)
|
||||
.eq(StringUtils.hasText(bedNo), EmergencyObservation::getBedNo, bedNo)
|
||||
.orderByDesc(EmergencyObservation::getObservationStart);
|
||||
return R.ok(observationService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/observation/{id}")
|
||||
public R<?> getObservationById(@PathVariable Long id) {
|
||||
return R.ok(observationService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/observation/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addObservation(@RequestBody EmergencyObservation obs) {
|
||||
obs.setObservationStart(new Date());
|
||||
obs.setCreateTime(new Date());
|
||||
observationService.save(obs);
|
||||
return R.ok(obs);
|
||||
}
|
||||
|
||||
@PutMapping("/observation/discharge")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> dischargeObservation(@RequestBody EmergencyObservation obs) {
|
||||
obs.setObservationEnd(new Date());
|
||||
if (obs.getObservationStart() != null) {
|
||||
long hours = (obs.getObservationEnd().getTime() - obs.getObservationStart().getTime()) / (1000 * 60 * 60);
|
||||
obs.setObservationHours((int) hours);
|
||||
}
|
||||
observationService.updateById(obs);
|
||||
return R.ok(obs);
|
||||
}
|
||||
|
||||
@PutMapping("/observation/{id}/disposition")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> updateDisposition(@PathVariable Long id, @RequestParam("disposition") String disposition) {
|
||||
EmergencyObservation obs = observationService.getById(id);
|
||||
if (obs != null) {
|
||||
obs.setDisposition(disposition);
|
||||
obs.setObservationEnd(new Date());
|
||||
observationService.updateById(obs);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/observation/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteObservation(@PathVariable Long id) {
|
||||
observationService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 绿色通道 ====================
|
||||
|
||||
@GetMapping("/green-channel/page")
|
||||
public R<?> getGreenChannelPage(
|
||||
@RequestParam(value = "patientId", required = false) Long patientId,
|
||||
@RequestParam(value = "diseaseType", required = false) String diseaseType,
|
||||
@RequestParam(value = "isAchieved", required = false) Boolean isAchieved,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<EmergencyGreenChannel> w = new LambdaQueryWrapper<>();
|
||||
w.eq(patientId != null, EmergencyGreenChannel::getPatientId, patientId)
|
||||
.eq(StringUtils.hasText(diseaseType), EmergencyGreenChannel::getDiseaseType, diseaseType)
|
||||
.eq(isAchieved != null, EmergencyGreenChannel::getIsAchieved, isAchieved)
|
||||
.orderByDesc(EmergencyGreenChannel::getActivateTime);
|
||||
return R.ok(greenChannelService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/green-channel/{id}")
|
||||
public R<?> getGreenChannelById(@PathVariable Long id) {
|
||||
return R.ok(greenChannelService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/green-channel/activate")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> activateGreenChannel(@RequestBody EmergencyGreenChannel gc) {
|
||||
gc.setActivateTime(new Date());
|
||||
gc.setCreateTime(new Date());
|
||||
greenChannelService.save(gc);
|
||||
return R.ok(gc);
|
||||
}
|
||||
|
||||
@PutMapping("/green-channel/{id}/complete")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> completeGreenChannel(@PathVariable Long id, @RequestBody EmergencyGreenChannel data) {
|
||||
EmergencyGreenChannel gc = greenChannelService.getById(id);
|
||||
if (gc != null) {
|
||||
gc.setCompleteTime(new Date());
|
||||
gc.setDoorToTreatmentTime(data.getDoorToTreatmentTime());
|
||||
// 判断是否达标
|
||||
if (gc.getTargetTime() != null && gc.getDoorToTreatmentTime() != null) {
|
||||
gc.setIsAchieved(gc.getDoorToTreatmentTime() <= gc.getTargetTime());
|
||||
}
|
||||
greenChannelService.updateById(gc);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/green-channel/stats")
|
||||
public R<?> getGreenChannelStats(
|
||||
@RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") java.time.LocalDate startDate,
|
||||
@RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") java.time.LocalDate endDate) {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
LambdaQueryWrapper<EmergencyGreenChannel> w = new LambdaQueryWrapper<>();
|
||||
w.ge(startDate != null, EmergencyGreenChannel::getActivateTime,
|
||||
startDate != null ? java.sql.Date.valueOf(startDate) : null)
|
||||
.le(endDate != null, EmergencyGreenChannel::getActivateTime,
|
||||
endDate != null ? java.sql.Date.valueOf(endDate) : null);
|
||||
List<EmergencyGreenChannel> list = greenChannelService.list(w);
|
||||
stats.put("total", list.size());
|
||||
long achieved = list.stream().filter(g -> Boolean.TRUE.equals(g.getIsAchieved())).count();
|
||||
stats.put("achieved", achieved);
|
||||
stats.put("achievementRate", list.isEmpty() ? 0 : Math.round(achieved * 100.0 / list.size()));
|
||||
stats.put("avgTime", list.stream()
|
||||
.filter(g -> g.getDoorToTreatmentTime() != null)
|
||||
.mapToInt(EmergencyGreenChannel::getDoorToTreatmentTime)
|
||||
.average().orElse(0));
|
||||
return R.ok(stats);
|
||||
}
|
||||
|
||||
@DeleteMapping("/green-channel/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteGreenChannel(@PathVariable Long id) {
|
||||
greenChannelService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
package com.healthlink.his.web.followup.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.followup.domain.*;
|
||||
import com.healthlink.his.followup.service.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 门诊随访管理 Controller — 深度业务逻辑
|
||||
*
|
||||
* 业务说明:
|
||||
* 1. 随访计划: 出院/门诊结束后按病种规则自动生成随访计划,分配责任人
|
||||
* 2. 随访任务: 按计划周期拆解成独立随访任务,支持电话/短信/微信/门诊随访
|
||||
* 3. 随访记录: 每次随访的详细结果,包含用药依从性、症状、是否需复查/转诊
|
||||
* 4. 满意度调查: 按科室/医生维度汇总评分,驱动质量改进
|
||||
* 5. 投诉记录: 投诉受理→处理→回访→关闭闭环管理
|
||||
*
|
||||
* 调用关系:
|
||||
* FollowupController → IFollowupPlanService → FollowupPlanMapper → DB
|
||||
* → IFollowupTaskService → 自动拆解任务
|
||||
* → IFollowupRecordService → 记录随访结果,更新任务状态
|
||||
* → ISatisfactionSurveyService → 满意度统计
|
||||
* → IComplaintRecordService → 投诉闭环
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/followup")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class FollowupController {
|
||||
|
||||
private final IFollowupPlanService planService;
|
||||
private final IFollowupTaskService taskService;
|
||||
private final IFollowupRecordService recordService;
|
||||
private final ISatisfactionSurveyService surveyService;
|
||||
private final IComplaintRecordService complaintService;
|
||||
|
||||
// ==================== 随访计划 ====================
|
||||
|
||||
@GetMapping("/plan/page")
|
||||
public R<?> getPlanPage(
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "diseaseCode", required = false) String diseaseCode,
|
||||
@RequestParam(value = "followupType", required = false) String followupType,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "responsibleDoctor", required = false) String responsibleDoctor,
|
||||
@RequestParam(value = "startDateFrom", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDateFrom,
|
||||
@RequestParam(value = "startDateTo", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDateTo,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<FollowupPlan> w = new LambdaQueryWrapper<>();
|
||||
w.like(StringUtils.hasText(patientName), FollowupPlan::getPatientName, patientName)
|
||||
.eq(StringUtils.hasText(diseaseCode), FollowupPlan::getDiseaseCode, diseaseCode)
|
||||
.eq(StringUtils.hasText(followupType), FollowupPlan::getFollowupType, followupType)
|
||||
.eq(StringUtils.hasText(status), FollowupPlan::getStatus, status)
|
||||
.eq(StringUtils.hasText(responsibleDoctor), FollowupPlan::getResponsibleDoctor, responsibleDoctor)
|
||||
.ge(startDateFrom != null, FollowupPlan::getStartDate, startDateFrom)
|
||||
.le(startDateTo != null, FollowupPlan::getStartDate, startDateTo)
|
||||
.orderByDesc(FollowupPlan::getCreateTime);
|
||||
return R.ok(planService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/plan/list")
|
||||
public R<?> getPlanList(
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "responsibleDoctor", required = false) String responsibleDoctor) {
|
||||
LambdaQueryWrapper<FollowupPlan> w = new LambdaQueryWrapper<>();
|
||||
w.eq(StringUtils.hasText(status), FollowupPlan::getStatus, status)
|
||||
.eq(StringUtils.hasText(responsibleDoctor), FollowupPlan::getResponsibleDoctor, responsibleDoctor)
|
||||
.orderByDesc(FollowupPlan::getCreateTime);
|
||||
return R.ok(planService.list(w));
|
||||
}
|
||||
|
||||
@GetMapping("/plan/{id}")
|
||||
public R<?> getPlanById(@PathVariable Long id) {
|
||||
return R.ok(planService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/plan/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addPlan(@RequestBody FollowupPlan plan) {
|
||||
plan.setCompletedTimes(0);
|
||||
if (!StringUtils.hasText(plan.getStatus())) {
|
||||
plan.setStatus("ACTIVE");
|
||||
}
|
||||
plan.setCreateTime(new Date());
|
||||
planService.save(plan);
|
||||
// 根据计划自动拆解随访任务
|
||||
generateTasksFromPlan(plan);
|
||||
return R.ok(plan);
|
||||
}
|
||||
|
||||
@PutMapping("/plan/update")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> updatePlan(@RequestBody FollowupPlan plan) {
|
||||
planService.updateById(plan);
|
||||
return R.ok(plan);
|
||||
}
|
||||
|
||||
@DeleteMapping("/plan/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deletePlan(@PathVariable Long id) {
|
||||
// 联动删除该计划下的所有任务和记录
|
||||
LambdaQueryWrapper<FollowupTask> tw = new LambdaQueryWrapper<>();
|
||||
tw.eq(FollowupTask::getPlanId, id);
|
||||
List<FollowupTask> tasks = taskService.list(tw);
|
||||
for (FollowupTask t : tasks) {
|
||||
LambdaQueryWrapper<FollowupRecord> rw = new LambdaQueryWrapper<>();
|
||||
rw.eq(FollowupRecord::getTaskId, t.getId());
|
||||
recordService.remove(rw);
|
||||
}
|
||||
taskService.remove(tw);
|
||||
planService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PutMapping("/plan/complete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> completePlan(@PathVariable Long id) {
|
||||
FollowupPlan plan = planService.getById(id);
|
||||
if (plan != null) {
|
||||
plan.setStatus("COMPLETED");
|
||||
planService.updateById(plan);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据随访计划自动生成随访任务
|
||||
* 业务逻辑: 按频率和总次数计算每次随访的计划日期
|
||||
*/
|
||||
private void generateTasksFromPlan(FollowupPlan plan) {
|
||||
if (plan.getStartDate() == null || plan.getTotalTimes() == null || plan.getTotalTimes() <= 0) {
|
||||
return;
|
||||
}
|
||||
int total = plan.getTotalTimes();
|
||||
LocalDate base = plan.getStartDate();
|
||||
for (int i = 0; i < total; i++) {
|
||||
FollowupTask task = new FollowupTask();
|
||||
task.setPlanId(plan.getId());
|
||||
task.setPatientId(plan.getPatientId());
|
||||
task.setPatientName(plan.getPatientName());
|
||||
task.setScheduledDate(base.plusWeeks(i));
|
||||
task.setContactMethod(plan.getFollowupType());
|
||||
task.setCreateTime(new Date());
|
||||
taskService.save(task);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 随访任务 ====================
|
||||
|
||||
@GetMapping("/task/page")
|
||||
public R<?> getTaskPage(
|
||||
@RequestParam(value = "planId", required = false) Long planId,
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "scheduledDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate scheduledDate,
|
||||
@RequestParam(value = "result", required = false) String result,
|
||||
@RequestParam(value = "abnormalFlag", required = false) Boolean abnormalFlag,
|
||||
@RequestParam(value = "operatorName", required = false) String operatorName,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<FollowupTask> w = new LambdaQueryWrapper<>();
|
||||
w.eq(planId != null, FollowupTask::getPlanId, planId)
|
||||
.like(StringUtils.hasText(patientName), FollowupTask::getPatientName, patientName)
|
||||
.eq(scheduledDate != null, FollowupTask::getScheduledDate, scheduledDate)
|
||||
.eq(StringUtils.hasText(result), FollowupTask::getResult, result)
|
||||
.eq(abnormalFlag != null, FollowupTask::getAbnormalFlag, abnormalFlag)
|
||||
.eq(StringUtils.hasText(operatorName), FollowupTask::getOperatorName, operatorName)
|
||||
.orderByAsc(FollowupTask::getScheduledDate);
|
||||
return R.ok(taskService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/task/today")
|
||||
public R<?> getTodayTasks(
|
||||
@RequestParam(value = "operatorName", required = false) String operatorName) {
|
||||
LocalDate today = LocalDate.now();
|
||||
LambdaQueryWrapper<FollowupTask> w = new LambdaQueryWrapper<>();
|
||||
w.eq(FollowupTask::getScheduledDate, today)
|
||||
.eq(FollowupTask::getResult, null)
|
||||
.eq(StringUtils.hasText(operatorName), FollowupTask::getOperatorName, operatorName)
|
||||
.orderByAsc(FollowupTask::getScheduledDate);
|
||||
return R.ok(taskService.list(w));
|
||||
}
|
||||
|
||||
@GetMapping("/task/{id}")
|
||||
public R<?> getTaskById(@PathVariable Long id) {
|
||||
return R.ok(taskService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/task/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addTask(@RequestBody FollowupTask task) {
|
||||
task.setCreateTime(new Date());
|
||||
taskService.save(task);
|
||||
return R.ok(task);
|
||||
}
|
||||
|
||||
@PutMapping("/task/execute")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> executeTask(@RequestBody FollowupTask task) {
|
||||
task.setActualDate(LocalDate.now());
|
||||
taskService.updateById(task);
|
||||
// 更新随访计划的已完成次数
|
||||
if (task.getPlanId() != null && "SUCCESS".equals(task.getResult())) {
|
||||
FollowupPlan plan = planService.getById(task.getPlanId());
|
||||
if (plan != null) {
|
||||
plan.setCompletedTimes((plan.getCompletedTimes() == null ? 0 : plan.getCompletedTimes()) + 1);
|
||||
planService.updateById(plan);
|
||||
}
|
||||
}
|
||||
return R.ok(task);
|
||||
}
|
||||
|
||||
@PutMapping("/task/{id}/abnormal")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> markAbnormal(@PathVariable Long id, @RequestParam("nextAction") String nextAction) {
|
||||
FollowupTask task = taskService.getById(id);
|
||||
if (task != null) {
|
||||
task.setAbnormalFlag(true);
|
||||
task.setNextAction(nextAction);
|
||||
taskService.updateById(task);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/task/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteTask(@PathVariable Long id) {
|
||||
// 联动删除该任务下的所有记录
|
||||
LambdaQueryWrapper<FollowupRecord> rw = new LambdaQueryWrapper<>();
|
||||
rw.eq(FollowupRecord::getTaskId, id);
|
||||
recordService.remove(rw);
|
||||
taskService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 随访记录 ====================
|
||||
|
||||
@GetMapping("/record/page")
|
||||
public R<?> getRecordPage(
|
||||
@RequestParam(value = "taskId", required = false) Long taskId,
|
||||
@RequestParam(value = "patientId", required = false) Long patientId,
|
||||
@RequestParam(value = "medicationCompliance", required = false) String medicationCompliance,
|
||||
@RequestParam(value = "reappointmentFlag", required = false) Boolean reappointmentFlag,
|
||||
@RequestParam(value = "transferFlag", required = false) Boolean transferFlag,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<FollowupRecord> w = new LambdaQueryWrapper<>();
|
||||
w.eq(taskId != null, FollowupRecord::getTaskId, taskId)
|
||||
.eq(patientId != null, FollowupRecord::getPatientId, patientId)
|
||||
.eq(StringUtils.hasText(medicationCompliance), FollowupRecord::getMedicationCompliance, medicationCompliance)
|
||||
.eq(reappointmentFlag != null, FollowupRecord::getReappointmentFlag, reappointmentFlag)
|
||||
.eq(transferFlag != null, FollowupRecord::getTransferFlag, transferFlag)
|
||||
.orderByDesc(FollowupRecord::getOperateTime);
|
||||
return R.ok(recordService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@PostMapping("/record/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addRecord(@RequestBody FollowupRecord record) {
|
||||
record.setOperateTime(new Date());
|
||||
recordService.save(record);
|
||||
// 联动更新任务状态为SUCCESS
|
||||
if (record.getTaskId() != null) {
|
||||
FollowupTask task = taskService.getById(record.getTaskId());
|
||||
if (task != null) {
|
||||
task.setResult("SUCCESS");
|
||||
task.setActualDate(LocalDate.now());
|
||||
taskService.updateById(task);
|
||||
}
|
||||
}
|
||||
return R.ok(record);
|
||||
}
|
||||
|
||||
@PutMapping("/record/update")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> updateRecord(@RequestBody FollowupRecord record) {
|
||||
recordService.updateById(record);
|
||||
return R.ok(record);
|
||||
}
|
||||
|
||||
@DeleteMapping("/record/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteRecord(@PathVariable Long id) {
|
||||
recordService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 满意度调查 ====================
|
||||
|
||||
@GetMapping("/survey/page")
|
||||
public R<?> getSurveyPage(
|
||||
@RequestParam(value = "departmentName", required = false) String departmentName,
|
||||
@RequestParam(value = "doctorName", required = false) String doctorName,
|
||||
@RequestParam(value = "surveyType", required = false) String surveyType,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<SatisfactionSurvey> w = new LambdaQueryWrapper<>();
|
||||
w.eq(StringUtils.hasText(departmentName), SatisfactionSurvey::getDepartmentName, departmentName)
|
||||
.eq(StringUtils.hasText(doctorName), SatisfactionSurvey::getDoctorName, doctorName)
|
||||
.eq(StringUtils.hasText(surveyType), SatisfactionSurvey::getSurveyType, surveyType)
|
||||
.orderByDesc(SatisfactionSurvey::getSurveyDate);
|
||||
return R.ok(surveyService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/survey/stats")
|
||||
public R<?> getSurveyStats(
|
||||
@RequestParam(value = "departmentName", required = false) String departmentName,
|
||||
@RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
|
||||
@RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
|
||||
LambdaQueryWrapper<SatisfactionSurvey> w = new LambdaQueryWrapper<>();
|
||||
w.eq(StringUtils.hasText(departmentName), SatisfactionSurvey::getDepartmentName, departmentName)
|
||||
.ge(startDate != null, SatisfactionSurvey::getSurveyDate, startDate)
|
||||
.le(endDate != null, SatisfactionSurvey::getSurveyDate, endDate);
|
||||
List<SatisfactionSurvey> list = surveyService.list(w);
|
||||
if (list.isEmpty()) {
|
||||
return R.ok("暂无数据");
|
||||
}
|
||||
double avgOverall = list.stream().mapToInt(SatisfactionSurvey::getOverallScore).average().orElse(0);
|
||||
double avgService = list.stream().mapToInt(SatisfactionSurvey::getServiceScore).average().orElse(0);
|
||||
double avgEnv = list.stream().mapToInt(SatisfactionSurvey::getEnvironmentScore).average().orElse(0);
|
||||
java.util.Map<String, Object> stats = new java.util.HashMap<>();
|
||||
stats.put("total", list.size());
|
||||
stats.put("avgOverall", Math.round(avgOverall * 100) / 100.0);
|
||||
stats.put("avgService", Math.round(avgService * 100) / 100.0);
|
||||
stats.put("avgEnvironment", Math.round(avgEnv * 100) / 100.0);
|
||||
return R.ok(stats);
|
||||
}
|
||||
|
||||
@PostMapping("/survey/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addSurvey(@RequestBody SatisfactionSurvey survey) {
|
||||
survey.setCreateTime(new Date());
|
||||
surveyService.save(survey);
|
||||
return R.ok(survey);
|
||||
}
|
||||
|
||||
@DeleteMapping("/survey/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteSurvey(@PathVariable Long id) {
|
||||
surveyService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 投诉记录 ====================
|
||||
|
||||
@GetMapping("/complaint/page")
|
||||
public R<?> getComplaintPage(
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "complaintType", required = false) String complaintType,
|
||||
@RequestParam(value = "departmentName", required = false) String departmentName,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<ComplaintRecord> w = new LambdaQueryWrapper<>();
|
||||
w.like(StringUtils.hasText(patientName), ComplaintRecord::getPatientName, patientName)
|
||||
.eq(StringUtils.hasText(complaintType), ComplaintRecord::getComplaintType, complaintType)
|
||||
.eq(StringUtils.hasText(departmentName), ComplaintRecord::getDepartmentName, departmentName)
|
||||
.eq(StringUtils.hasText(status), ComplaintRecord::getStatus, status)
|
||||
.orderByDesc(ComplaintRecord::getCreateTime);
|
||||
return R.ok(complaintService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@PostMapping("/complaint/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addComplaint(@RequestBody ComplaintRecord complaint) {
|
||||
if (!StringUtils.hasText(complaint.getStatus())) {
|
||||
complaint.setStatus("PENDING");
|
||||
}
|
||||
complaint.setCreateTime(new Date());
|
||||
complaintService.save(complaint);
|
||||
return R.ok(complaint);
|
||||
}
|
||||
|
||||
@PutMapping("/complaint/handle")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> handleComplaint(@RequestBody ComplaintRecord complaint) {
|
||||
complaint.setHandleTime(new Date());
|
||||
complaint.setStatus("HANDLED");
|
||||
complaintService.updateById(complaint);
|
||||
return R.ok(complaint);
|
||||
}
|
||||
|
||||
@PutMapping("/complaint/close/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> closeComplaint(@PathVariable Long id) {
|
||||
ComplaintRecord c = complaintService.getById(id);
|
||||
if (c != null) {
|
||||
c.setStatus("CLOSED");
|
||||
complaintService.updateById(c);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/complaint/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteComplaint(@PathVariable Long id) {
|
||||
complaintService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
package com.healthlink.his.web.pathology.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.pathology.domain.*;
|
||||
import com.healthlink.his.pathology.service.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 病理管理 Controller — 深度业务逻辑
|
||||
*
|
||||
* 业务说明:
|
||||
* 1. 病理医嘱: 临床医生开具病理检查医嘱,包含标本类型、采集部位、紧急程度
|
||||
* 2. 标本管理: 标本采集→固定→接收→合格性检查→不合格退回,全流程条码追踪
|
||||
* 3. 病理报告: 取材→制片→初诊→审核→签发,三级审核制度(初诊→审核→终审)
|
||||
* 4. 与手术模块联动: 手术中切除标本自动触发病理送检
|
||||
* 5. 与门诊/住院联动: 病理结果回写到电子病历,驱动后续治疗方案
|
||||
*
|
||||
* 调用关系:
|
||||
* PathologyController → IPathologyOrderService → PathologyOrderMapper → DB
|
||||
* → IPathologySpecimenService → 标本全流程追踪
|
||||
* → IPathologyReportService → 三级审核报告签发
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/pathology")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class PathologyController {
|
||||
|
||||
private final IPathologyOrderService orderService;
|
||||
private final IPathologySpecimenService specimenService;
|
||||
private final IPathologyReportService reportService;
|
||||
|
||||
// ==================== 病理医嘱 ====================
|
||||
|
||||
@GetMapping("/order/page")
|
||||
public R<?> getOrderPage(
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "specimenType", required = false) String specimenType,
|
||||
@RequestParam(value = "urgency", required = false) String urgency,
|
||||
@RequestParam(value = "orderStatus", required = false) String orderStatus,
|
||||
@RequestParam(value = "applyDoctor", required = false) String applyDoctor,
|
||||
@RequestParam(value = "encounterId", required = false) Long encounterId,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<PathologyOrder> w = new LambdaQueryWrapper<>();
|
||||
w.like(StringUtils.hasText(patientName), PathologyOrder::getPatientName, patientName)
|
||||
.eq(StringUtils.hasText(specimenType), PathologyOrder::getSpecimenType, specimenType)
|
||||
.eq(StringUtils.hasText(urgency), PathologyOrder::getUrgency, urgency)
|
||||
.eq(StringUtils.hasText(orderStatus), PathologyOrder::getOrderStatus, orderStatus)
|
||||
.eq(StringUtils.hasText(applyDoctor), PathologyOrder::getApplyDoctor, applyDoctor)
|
||||
.eq(encounterId != null, PathologyOrder::getEncounterId, encounterId)
|
||||
.orderByDesc(PathologyOrder::getApplyTime);
|
||||
return R.ok(orderService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/order/{id}")
|
||||
public R<?> getOrderById(@PathVariable Long id) {
|
||||
return R.ok(orderService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/order/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addOrder(@RequestBody PathologyOrder order) {
|
||||
if (!StringUtils.hasText(order.getOrderStatus())) {
|
||||
order.setOrderStatus("PENDING");
|
||||
}
|
||||
order.setApplyTime(new Date());
|
||||
order.setCreateTime(new Date());
|
||||
orderService.save(order);
|
||||
return R.ok(order);
|
||||
}
|
||||
|
||||
@PutMapping("/order/update")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> updateOrder(@RequestBody PathologyOrder order) {
|
||||
orderService.updateById(order);
|
||||
return R.ok(order);
|
||||
}
|
||||
|
||||
@PutMapping("/order/cancel/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> cancelOrder(@PathVariable Long id) {
|
||||
PathologyOrder order = orderService.getById(id);
|
||||
if (order != null) {
|
||||
order.setOrderStatus("CANCELLED");
|
||||
orderService.updateById(order);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/order/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteOrder(@PathVariable Long id) {
|
||||
orderService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 标本管理 ====================
|
||||
|
||||
@GetMapping("/specimen/page")
|
||||
public R<?> getSpecimenPage(
|
||||
@RequestParam(value = "orderId", required = false) Long orderId,
|
||||
@RequestParam(value = "barcode", required = false) String barcode,
|
||||
@RequestParam(value = "isQualified", required = false) Boolean isQualified,
|
||||
@RequestParam(value = "receiver", required = false) String receiver,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<PathologySpecimen> w = new LambdaQueryWrapper<>();
|
||||
w.eq(orderId != null, PathologySpecimen::getOrderId, orderId)
|
||||
.like(StringUtils.hasText(barcode), PathologySpecimen::getBarcode, barcode)
|
||||
.eq(isQualified != null, PathologySpecimen::getIsQualified, isQualified)
|
||||
.eq(StringUtils.hasText(receiver), PathologySpecimen::getReceiver, receiver)
|
||||
.orderByDesc(PathologySpecimen::getReceiveTime);
|
||||
return R.ok(specimenService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/specimen/{id}")
|
||||
public R<?> getSpecimenById(@PathVariable Long id) {
|
||||
return R.ok(specimenService.getById(id));
|
||||
}
|
||||
|
||||
@GetMapping("/specimen/barcode/{barcode}")
|
||||
public R<?> getSpecimenByBarcode(@PathVariable String barcode) {
|
||||
LambdaQueryWrapper<PathologySpecimen> w = new LambdaQueryWrapper<>();
|
||||
w.eq(PathologySpecimen::getBarcode, barcode);
|
||||
return R.ok(specimenService.getOne(w));
|
||||
}
|
||||
|
||||
@PostMapping("/specimen/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addSpecimen(@RequestBody PathologySpecimen specimen) {
|
||||
specimen.setCreateTime(new Date());
|
||||
specimenService.save(specimen);
|
||||
// 联动更新病理医嘱状态为已采集
|
||||
if (specimen.getOrderId() != null) {
|
||||
PathologyOrder order = orderService.getById(specimen.getOrderId());
|
||||
if (order != null && "PENDING".equals(order.getOrderStatus())) {
|
||||
order.setOrderStatus("COLLECTED");
|
||||
orderService.updateById(order);
|
||||
}
|
||||
}
|
||||
return R.ok(specimen);
|
||||
}
|
||||
|
||||
@PutMapping("/specimen/receive")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> receiveSpecimen(@RequestBody PathologySpecimen specimen) {
|
||||
specimen.setReceiveTime(new Date());
|
||||
specimenService.updateById(specimen);
|
||||
// 联动更新病理医嘱状态为已接收
|
||||
if (specimen.getOrderId() != null) {
|
||||
PathologyOrder order = orderService.getById(specimen.getOrderId());
|
||||
if (order != null && "COLLECTED".equals(order.getOrderStatus())) {
|
||||
order.setOrderStatus("RECEIVED");
|
||||
orderService.updateById(order);
|
||||
}
|
||||
}
|
||||
return R.ok(specimen);
|
||||
}
|
||||
|
||||
@PutMapping("/specimen/{id}/qualify")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> qualifySpecimen(@PathVariable Long id, @RequestParam("qualified") boolean qualified,
|
||||
@RequestParam(value = "rejectReason", required = false) String rejectReason) {
|
||||
PathologySpecimen specimen = specimenService.getById(id);
|
||||
if (specimen != null) {
|
||||
specimen.setIsQualified(qualified);
|
||||
if (!qualified) {
|
||||
specimen.setRejectReason(rejectReason);
|
||||
}
|
||||
specimenService.updateById(specimen);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/specimen/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteSpecimen(@PathVariable Long id) {
|
||||
specimenService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 病理报告 ====================
|
||||
|
||||
@GetMapping("/report/page")
|
||||
public R<?> getReportPage(
|
||||
@RequestParam(value = "orderId", required = false) Long orderId,
|
||||
@RequestParam(value = "patientId", required = false) Long patientId,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "reportDoctor", required = false) String reportDoctor,
|
||||
@RequestParam(value = "auditDoctor", required = false) String auditDoctor,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<PathologyReport> w = new LambdaQueryWrapper<>();
|
||||
w.eq(orderId != null, PathologyReport::getOrderId, orderId)
|
||||
.eq(patientId != null, PathologyReport::getPatientId, patientId)
|
||||
.eq(StringUtils.hasText(status), PathologyReport::getStatus, status)
|
||||
.eq(StringUtils.hasText(reportDoctor), PathologyReport::getReportDoctor, reportDoctor)
|
||||
.eq(StringUtils.hasText(auditDoctor), PathologyReport::getAuditDoctor, auditDoctor)
|
||||
.orderByDesc(PathologyReport::getReportTime);
|
||||
return R.ok(reportService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@GetMapping("/report/{id}")
|
||||
public R<?> getReportById(@PathVariable Long id) {
|
||||
return R.ok(reportService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/report/add")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addReport(@RequestBody PathologyReport report) {
|
||||
if (!StringUtils.hasText(report.getStatus())) {
|
||||
report.setStatus("DRAFT");
|
||||
}
|
||||
report.setCreateTime(new Date());
|
||||
reportService.save(report);
|
||||
return R.ok(report);
|
||||
}
|
||||
|
||||
@PutMapping("/report/update")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> updateReport(@RequestBody PathologyReport report) {
|
||||
reportService.updateById(report);
|
||||
return R.ok(report);
|
||||
}
|
||||
|
||||
@PutMapping("/report/{id}/submit")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> submitReport(@PathVariable Long id) {
|
||||
PathologyReport report = reportService.getById(id);
|
||||
if (report != null) {
|
||||
report.setStatus("SUBMITTED");
|
||||
reportService.updateById(report);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PutMapping("/report/{id}/audit")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> auditReport(@PathVariable Long id, @RequestBody PathologyReport auditData) {
|
||||
PathologyReport report = reportService.getById(id);
|
||||
if (report != null) {
|
||||
report.setAuditDoctor(auditData.getAuditDoctor());
|
||||
report.setAuditTime(new Date());
|
||||
report.setStatus("AUDITED");
|
||||
reportService.updateById(report);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PutMapping("/report/{id}/final")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> finalSignReport(@PathVariable Long id, @RequestBody PathologyReport finalData) {
|
||||
PathologyReport report = reportService.getById(id);
|
||||
if (report != null) {
|
||||
report.setFinalDoctor(finalData.getFinalDoctor());
|
||||
report.setFinalTime(new Date());
|
||||
report.setStatus("SIGNED");
|
||||
reportService.updateById(report);
|
||||
// 联动更新病理医嘱状态为已完成
|
||||
if (report.getOrderId() != null) {
|
||||
PathologyOrder order = orderService.getById(report.getOrderId());
|
||||
if (order != null) {
|
||||
order.setOrderStatus("COMPLETED");
|
||||
orderService.updateById(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/report/delete/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> deleteReport(@PathVariable Long id) {
|
||||
reportService.removeById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 统计 ====================
|
||||
|
||||
@GetMapping("/stats/summary")
|
||||
public R<?> getStatsSummary(
|
||||
@RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
|
||||
@RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
|
||||
java.util.Map<String, Object> stats = new java.util.HashMap<>();
|
||||
LambdaQueryWrapper<PathologyOrder> ow = new LambdaQueryWrapper<>();
|
||||
ow.ge(startDate != null, PathologyOrder::getApplyTime, startDate != null ? java.sql.Date.valueOf(startDate) : null)
|
||||
.le(endDate != null, PathologyOrder::getApplyTime, endDate != null ? java.sql.Date.valueOf(endDate) : null);
|
||||
stats.put("totalOrders", orderService.count(ow));
|
||||
|
||||
LambdaQueryWrapper<PathologySpecimen> sw = new LambdaQueryWrapper<>();
|
||||
sw.eq(PathologySpecimen::getIsQualified, true);
|
||||
stats.put("qualifiedSpecimens", specimenService.count(sw));
|
||||
|
||||
LambdaQueryWrapper<PathologySpecimen> sw2 = new LambdaQueryWrapper<>();
|
||||
sw2.eq(PathologySpecimen::getIsQualified, false);
|
||||
stats.put("rejectedSpecimens", specimenService.count(sw2));
|
||||
|
||||
LambdaQueryWrapper<PathologyReport> rw = new LambdaQueryWrapper<>();
|
||||
rw.eq(PathologyReport::getStatus, "SIGNED");
|
||||
stats.put("signedReports", reportService.count(rw));
|
||||
return R.ok(stats);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ CREATE TABLE IF NOT EXISTS structured_emr_template (
|
||||
is_system BOOLEAN DEFAULT FALSE,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
@@ -34,26 +34,17 @@ CREATE TABLE IF NOT EXISTS icd10_diagnosis_code (
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE'
|
||||
);
|
||||
COMMENT ON TABLE icd10_diagnosis_code IS 'ICD-10诊断编码库';
|
||||
CREATE UNIQUE INDEX idx_icd10_code ON icd10_diagnosis_code(code);
|
||||
CREATE INDEX idx_icd10_name ON icd10_diagnosis_code(name);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_icd10_code ON icd10_diagnosis_code(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_icd10_name ON icd10_diagnosis_code(name);
|
||||
|
||||
-- 3. 合理用药审核规则
|
||||
CREATE TABLE IF NOT EXISTS drug_interaction_rule (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
drug_a VARCHAR(200) NOT NULL,
|
||||
drug_b VARCHAR(200),
|
||||
interaction_type VARCHAR(50) NOT NULL,
|
||||
severity VARCHAR(20) NOT NULL,
|
||||
description TEXT,
|
||||
suggestion TEXT,
|
||||
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 drug_interaction_rule IS '合理用药审核规则';
|
||||
-- 3. 合理用药审核规则 (table created in V2, add missing columns)
|
||||
ALTER TABLE drug_interaction_rule ADD COLUMN IF NOT EXISTS interaction_type VARCHAR(50) DEFAULT 'INTERACTION';
|
||||
ALTER TABLE drug_interaction_rule ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'ACTIVE';
|
||||
ALTER TABLE drug_interaction_rule ADD COLUMN IF NOT EXISTS drug_a VARCHAR(200);
|
||||
ALTER TABLE drug_interaction_rule ADD COLUMN IF NOT EXISTS drug_b VARCHAR(200);
|
||||
ALTER TABLE drug_interaction_rule ADD COLUMN IF NOT EXISTS delete_flag VARCHAR(1) DEFAULT '0';
|
||||
ALTER TABLE drug_interaction_rule ADD COLUMN IF NOT EXISTS tenant_id BIGINT DEFAULT 0;
|
||||
COMMENT ON COLUMN drug_interaction_rule.interaction_type IS '类型(CONTRAINDICATION禁忌/INTERACTION相互作用/DUPLICATE重复/PRESCRIBING处方)';
|
||||
COMMENT ON COLUMN drug_interaction_rule.severity IS '严重程度(HIGH/MEDIUM/LOW)';
|
||||
|
||||
-- 4. 出院小结
|
||||
CREATE TABLE IF NOT EXISTS discharge_summary (
|
||||
@@ -74,7 +65,7 @@ CREATE TABLE IF NOT EXISTS discharge_summary (
|
||||
doctor_id BIGINT,
|
||||
status INT NOT NULL DEFAULT 0,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
@@ -82,7 +73,7 @@ CREATE TABLE IF NOT EXISTS discharge_summary (
|
||||
);
|
||||
COMMENT ON TABLE discharge_summary IS '出院小结';
|
||||
COMMENT ON COLUMN discharge_summary.status IS '状态(0草稿 1已完成 2已归档)';
|
||||
CREATE INDEX idx_ds_encounter ON discharge_summary(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ds_encounter ON discharge_summary(encounter_id);
|
||||
|
||||
-- 5. 处方前置拦截记录
|
||||
CREATE TABLE IF NOT EXISTS prescription_intercept_log (
|
||||
|
||||
@@ -22,12 +22,12 @@ CREATE TABLE IF NOT EXISTS nursing_vital_signs_chart (
|
||||
stool_count INT,
|
||||
nurse_name VARCHAR(50),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE nursing_vital_signs_chart IS '体温单数据(三测单)';
|
||||
CREATE INDEX idx_nvs_encounter ON nursing_vital_signs_chart(encounter_id);
|
||||
CREATE INDEX idx_nvs_date ON nursing_vital_signs_chart(record_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_nvs_encounter ON nursing_vital_signs_chart(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_nvs_date ON nursing_vital_signs_chart(record_date);
|
||||
|
||||
-- 2. 术前安全核查
|
||||
CREATE TABLE IF NOT EXISTS surgery_safety_check (
|
||||
@@ -50,13 +50,13 @@ CREATE TABLE IF NOT EXISTS surgery_safety_check (
|
||||
surgeon_name VARCHAR(50),
|
||||
nurse_name VARCHAR(50),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE surgery_safety_check IS '术前安全核查(WS/T 313)';
|
||||
COMMENT ON COLUMN surgery_safety_check.check_phase IS '核查阶段(ANESTHESIA_BEFORE麻醉前/SURGERY_BEFORE手术前/EXIT离室前)';
|
||||
CREATE INDEX idx_ssc_encounter ON surgery_safety_check(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ssc_encounter ON surgery_safety_check(encounter_id);
|
||||
|
||||
-- 3. 标本条码
|
||||
CREATE TABLE IF NOT EXISTS specimen_barcode (
|
||||
@@ -73,12 +73,12 @@ CREATE TABLE IF NOT EXISTS specimen_barcode (
|
||||
scan_confirm_by VARCHAR(50),
|
||||
status VARCHAR(20) DEFAULT 'COLLECTED',
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE specimen_barcode IS '标本条码管理';
|
||||
COMMENT ON COLUMN specimen_barcode.status IS '状态(COLLECTED已采集/SCANNED已扫码/REJECTED已拒收)';
|
||||
CREATE UNIQUE INDEX idx_sb_barcode ON specimen_barcode(barcode);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_sb_barcode ON specimen_barcode(barcode);
|
||||
|
||||
-- 4. 审计日志
|
||||
CREATE TABLE IF NOT EXISTS sys_audit_log (
|
||||
@@ -97,9 +97,9 @@ CREATE TABLE IF NOT EXISTS sys_audit_log (
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE sys_audit_log IS '审计日志(所有接口调用可追溯)';
|
||||
CREATE INDEX idx_audit_time ON sys_audit_log(create_time);
|
||||
CREATE INDEX idx_audit_user ON sys_audit_log(user_id);
|
||||
CREATE INDEX idx_audit_module ON sys_audit_log(module);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_time ON sys_audit_log(create_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_user ON sys_audit_log(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_module ON sys_audit_log(module);
|
||||
|
||||
-- 5. 身份证校验记录
|
||||
CREATE TABLE IF NOT EXISTS empi_id_verification (
|
||||
@@ -113,9 +113,9 @@ CREATE TABLE IF NOT EXISTS empi_id_verification (
|
||||
verify_source VARCHAR(50),
|
||||
verify_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE empi_id_verification IS '身份证校验记录';
|
||||
COMMENT ON COLUMN empi_id_verification.verify_result IS '校验结果(PASS通过/FAIL失败/PENDING待验)';
|
||||
CREATE INDEX idx_idv_patient ON empi_id_verification(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_idv_patient ON empi_id_verification(patient_id);
|
||||
|
||||
@@ -13,14 +13,14 @@ CREATE TABLE IF NOT EXISTS nursing_assessment_intervention (
|
||||
execute_time TIMESTAMP,
|
||||
status VARCHAR(20) DEFAULT 'PENDING',
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE nursing_assessment_intervention IS '评估干预措施';
|
||||
COMMENT ON COLUMN nursing_assessment_intervention.intervention_type IS '干预类型(PRESSURE_ULCER/FALL_RISK/NUTRITION/PAIN/TUBE)';
|
||||
COMMENT ON COLUMN nursing_assessment_intervention.status IS '状态(PENDING/EXECUTED/CANCELLED)';
|
||||
CREATE INDEX idx_nai_encounter ON nursing_assessment_intervention(encounter_id);
|
||||
CREATE INDEX idx_nai_status ON nursing_assessment_intervention(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_nai_encounter ON nursing_assessment_intervention(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_nai_status ON nursing_assessment_intervention(status);
|
||||
|
||||
-- 评估统计视图
|
||||
CREATE OR REPLACE VIEW v_assessment_stats AS
|
||||
|
||||
@@ -18,12 +18,12 @@ CREATE TABLE IF NOT EXISTS emr_archive_record (
|
||||
archived_by VARCHAR(64),
|
||||
file_path VARCHAR(500),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE emr_archive_record IS '病历打印归档记录';
|
||||
COMMENT ON COLUMN emr_archive_record.archive_type IS '归档类型(PRINT打印/ARCHIVE归档/REPRINT补打)';
|
||||
COMMENT ON COLUMN emr_archive_record.archive_status IS '状态(PRINTED已打印/ARCHIVED已归档/LOST遗失)';
|
||||
CREATE INDEX idx_ear_encounter ON emr_archive_record(encounter_id);
|
||||
CREATE INDEX idx_ear_patient ON emr_archive_record(patient_id);
|
||||
CREATE INDEX idx_ear_status ON emr_archive_record(archive_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_ear_encounter ON emr_archive_record(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ear_patient ON emr_archive_record(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ear_status ON emr_archive_record(archive_status);
|
||||
|
||||
@@ -26,14 +26,14 @@ CREATE TABLE IF NOT EXISTS mr_drg_grouping (
|
||||
is_valid BOOLEAN DEFAULT TRUE,
|
||||
invalid_reason VARCHAR(200),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT CURRENT_TIMESTAMP
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE mr_drg_grouping IS 'DRG/DIP分组结果';
|
||||
COMMENT ON COLUMN mr_drg_grouping.grouping_type IS '分组类型(DRG/DIP)';
|
||||
CREATE INDEX idx_drg_encounter ON mr_drg_grouping(encounter_id);
|
||||
CREATE INDEX idx_drg_code ON mr_drg_grouping(drg_code);
|
||||
CREATE INDEX idx_dip_code ON mr_drg_grouping(dip_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_drg_encounter ON mr_drg_grouping(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_drg_code ON mr_drg_grouping(drg_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_dip_code ON mr_drg_grouping(dip_code);
|
||||
|
||||
-- 病案归档统计表
|
||||
CREATE TABLE IF NOT EXISTS mr_archive_stats (
|
||||
@@ -46,7 +46,7 @@ CREATE TABLE IF NOT EXISTS mr_archive_stats (
|
||||
archive_rate DECIMAL(5,2),
|
||||
avg_archive_hours DECIMAL(8,2),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT CURRENT_TIMESTAMP
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE mr_archive_stats IS '病案归档统计(每日)';
|
||||
CREATE UNIQUE INDEX idx_mras_date_dept ON mr_archive_stats(stat_date, department_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_mras_date_dept ON mr_archive_stats(stat_date, department_id);
|
||||
|
||||
@@ -17,13 +17,13 @@ CREATE TABLE IF NOT EXISTS esb_dead_letter (
|
||||
resolved_by VARCHAR(64),
|
||||
resolved_time TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT CURRENT_TIMESTAMP
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE esb_dead_letter IS '死信队列(多次重试失败的消息)';
|
||||
COMMENT ON COLUMN esb_dead_letter.status IS '状态(PENDING待处理/RESOLVED已解决/IGNORED已忽略)';
|
||||
CREATE INDEX idx_dl_status ON esb_dead_letter(status);
|
||||
CREATE INDEX idx_dl_message ON esb_dead_letter(message_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_dl_status ON esb_dead_letter(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_dl_message ON esb_dead_letter(message_id);
|
||||
|
||||
-- ESB监控统计表
|
||||
CREATE TABLE IF NOT EXISTS esb_monitor_stats (
|
||||
@@ -37,7 +37,7 @@ CREATE TABLE IF NOT EXISTS esb_monitor_stats (
|
||||
retry_count INT DEFAULT 0,
|
||||
avg_duration_ms INT DEFAULT 0,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT CURRENT_TIMESTAMP
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE esb_monitor_stats IS 'ESB消息监控统计(每小时)';
|
||||
CREATE UNIQUE INDEX idx_ems_hour ON esb_monitor_stats(stat_hour, source_system, target_system);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_ems_hour ON esb_monitor_stats(stat_hour, source_system, target_system);
|
||||
|
||||
@@ -15,11 +15,11 @@ CREATE TABLE IF NOT EXISTS lab_reference_range (
|
||||
critical_high DECIMAL(10,4),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) 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);
|
||||
CREATE INDEX IF NOT EXISTS idx_lrr_item ON lab_reference_range(item_code);
|
||||
|
||||
-- 2. 检查预约排队
|
||||
CREATE TABLE IF NOT EXISTS exam_appointment (
|
||||
@@ -37,13 +37,13 @@ CREATE TABLE IF NOT EXISTS exam_appointment (
|
||||
room VARCHAR(50),
|
||||
device VARCHAR(100),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) 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);
|
||||
CREATE INDEX IF NOT EXISTS idx_ea_date ON exam_appointment(appoint_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_ea_status ON exam_appointment(status);
|
||||
|
||||
-- 3. 影像图像记录
|
||||
CREATE TABLE IF NOT EXISTS radiology_image (
|
||||
@@ -60,11 +60,11 @@ CREATE TABLE IF NOT EXISTS radiology_image (
|
||||
series_desc VARCHAR(200),
|
||||
instance_number INT,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) 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);
|
||||
CREATE INDEX IF NOT EXISTS idx_ri_apply ON radiology_image(apply_id);
|
||||
|
||||
-- 4. 图文报告
|
||||
CREATE TABLE IF NOT EXISTS radiology_image_report (
|
||||
@@ -84,12 +84,12 @@ CREATE TABLE IF NOT EXISTS radiology_image_report (
|
||||
verify_time TIMESTAMP,
|
||||
status VARCHAR(20) DEFAULT 'DRAFT',
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) 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);
|
||||
CREATE INDEX IF NOT EXISTS idx_rir_apply ON radiology_image_report(apply_id);
|
||||
|
||||
-- 5. DICOM打印记录
|
||||
CREATE TABLE IF NOT EXISTS dicom_print_record (
|
||||
@@ -103,7 +103,7 @@ CREATE TABLE IF NOT EXISTS dicom_print_record (
|
||||
print_by VARCHAR(64),
|
||||
print_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE dicom_print_record IS 'DICOM胶片打印记录';
|
||||
@@ -121,11 +121,11 @@ CREATE TABLE IF NOT EXISTS clinical_pathway (
|
||||
version INT DEFAULT 1,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) 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 INDEX IF NOT EXISTS idx_cp_disease ON clinical_pathway(disease_code);
|
||||
|
||||
-- 临床路径执行记录
|
||||
CREATE TABLE IF NOT EXISTS clinical_pathway_execution (
|
||||
@@ -143,12 +143,12 @@ CREATE TABLE IF NOT EXISTS clinical_pathway_execution (
|
||||
status VARCHAR(20) DEFAULT 'IN_PATH',
|
||||
complete_date DATE,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) 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);
|
||||
CREATE INDEX IF NOT EXISTS idx_cpe_pathway ON clinical_pathway_execution(pathway_id);
|
||||
|
||||
-- 7. DRG/DIP分析统计
|
||||
CREATE TABLE IF NOT EXISTS drg_analysis_stats (
|
||||
@@ -166,7 +166,7 @@ CREATE TABLE IF NOT EXISTS drg_analysis_stats (
|
||||
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);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_das_month ON drg_analysis_stats(stat_month, department_name, drg_code);
|
||||
|
||||
-- 8. ICD-10编码库
|
||||
CREATE TABLE IF NOT EXISTS icd10_code (
|
||||
@@ -182,5 +182,5 @@ CREATE TABLE IF NOT EXISTS icd10_code (
|
||||
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);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_icd10_code_v2 ON icd10_code(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_icd10_pinyin ON icd10_code(pinyin);
|
||||
|
||||
@@ -14,13 +14,13 @@ CREATE TABLE IF NOT EXISTS cssd_tray (
|
||||
last_sterilize_time TIMESTAMP,
|
||||
max_uses INT DEFAULT 100,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE cssd_tray IS 'CSSD器械包';
|
||||
COMMENT ON COLUMN cssd_tray.tray_type IS '类型(OPERATION手术/TUBE管腔/PRECISION精密/COMMON普通)';
|
||||
COMMENT ON COLUMN cssd_tray.status IS '状态(IN_USE在用/WASHING清洗中/STERILIZING灭菌中/STORED储存中/DISTRIBUTED已发放)';
|
||||
CREATE UNIQUE INDEX idx_ct_code ON cssd_tray(tray_code);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_ct_code ON cssd_tray(tray_code);
|
||||
|
||||
-- 2. CSSD追溯记录
|
||||
CREATE TABLE IF NOT EXISTS cssd_trace_record (
|
||||
@@ -41,8 +41,8 @@ CREATE TABLE IF NOT EXISTS cssd_trace_record (
|
||||
);
|
||||
COMMENT ON TABLE cssd_trace_record IS 'CSSD追溯记录';
|
||||
COMMENT ON COLUMN cssd_trace_record.step_type IS '步骤(RECYCLE回收/WASH清洗/DISINFECT消毒/PACK包装/STERILIZE灭菌/STORE储存/DISTRIBUTE发放)';
|
||||
CREATE INDEX idx_ctr_tray ON cssd_trace_record(tray_id);
|
||||
CREATE INDEX idx_ctr_time ON cssd_trace_record(operation_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_ctr_tray ON cssd_trace_record(tray_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ctr_time ON cssd_trace_record(operation_time);
|
||||
|
||||
-- 3. CSSD灭菌批次
|
||||
CREATE TABLE IF NOT EXISTS cssd_sterilize_batch (
|
||||
@@ -63,12 +63,12 @@ CREATE TABLE IF NOT EXISTS cssd_sterilize_batch (
|
||||
release_by VARCHAR(64),
|
||||
release_time TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE cssd_sterilize_batch IS 'CSSD灭菌批次';
|
||||
COMMENT ON COLUMN cssd_sterilize_batch.batch_status IS '状态(RUNNING进行中/COMPLETED已完成/RELEASED已释放/REJECTED已拒绝)';
|
||||
CREATE UNIQUE INDEX idx_csb_code ON cssd_sterilize_batch(batch_code);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_csb_code ON cssd_sterilize_batch(batch_code);
|
||||
|
||||
-- 4. CSSD灭菌包明细
|
||||
CREATE TABLE IF NOT EXISTS cssd_sterilize_item (
|
||||
@@ -81,7 +81,7 @@ CREATE TABLE IF NOT EXISTS cssd_sterilize_item (
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE cssd_sterilize_item IS '灭菌包明细';
|
||||
CREATE INDEX idx_csi_batch ON cssd_sterilize_item(batch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_csi_batch ON cssd_sterilize_item(batch_id);
|
||||
|
||||
-- 5. CSSD过期预警
|
||||
CREATE TABLE IF NOT EXISTS cssd_expiry_alert (
|
||||
@@ -97,7 +97,7 @@ CREATE TABLE IF NOT EXISTS cssd_expiry_alert (
|
||||
);
|
||||
COMMENT ON TABLE cssd_expiry_alert IS 'CSSD过期预警';
|
||||
COMMENT ON COLUMN cssd_expiry_alert.status IS '状态(NORMAL正常/ALERT预警/EXPIRED过期)';
|
||||
CREATE INDEX idx_cea_status ON cssd_expiry_alert(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_cea_status ON cssd_expiry_alert(status);
|
||||
|
||||
-- 6. 影像3D重建任务
|
||||
CREATE TABLE IF NOT EXISTS reconstruction_task (
|
||||
@@ -118,12 +118,12 @@ CREATE TABLE IF NOT EXISTS reconstruction_task (
|
||||
request_doctor VARCHAR(64),
|
||||
complete_time TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE reconstruction_task IS '影像3D重建任务';
|
||||
COMMENT ON COLUMN reconstruction_task.reconstruction_type IS '重建类型(VR容积渲染/MPR多平面/MIP最大密度投影)';
|
||||
CREATE INDEX idx_rt_status ON reconstruction_task(task_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_rt_status ON reconstruction_task(task_status);
|
||||
|
||||
-- 7. 影像3D重建结果
|
||||
CREATE TABLE IF NOT EXISTS reconstruction_result (
|
||||
@@ -137,7 +137,7 @@ CREATE TABLE IF NOT EXISTS reconstruction_result (
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE reconstruction_result IS '3D重建结果';
|
||||
CREATE INDEX idx_rr_task ON reconstruction_result(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_rr_task ON reconstruction_result(task_id);
|
||||
|
||||
-- 8. 影像3D重建报告
|
||||
CREATE TABLE IF NOT EXISTS reconstruction_report (
|
||||
@@ -154,8 +154,8 @@ CREATE TABLE IF NOT EXISTS reconstruction_report (
|
||||
verify_time TIMESTAMP,
|
||||
status VARCHAR(20) DEFAULT 'DRAFT',
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE reconstruction_report IS '3D重建报告';
|
||||
CREATE INDEX idx_rrp_task ON reconstruction_report(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_rrp_task ON reconstruction_report(task_id);
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
-- V32: Phase A 核心模块 — 随访/病理/急诊
|
||||
|
||||
-- ===== 1. 门诊随访管理 =====
|
||||
CREATE TABLE IF NOT EXISTS followup_plan (
|
||||
id BIGSERIAL PRIMARY KEY, patient_id BIGINT NOT NULL, patient_name VARCHAR(50),
|
||||
encounter_id BIGINT, disease_code VARCHAR(20), disease_name VARCHAR(100),
|
||||
followup_type VARCHAR(20) NOT NULL, frequency VARCHAR(20),
|
||||
total_times INT DEFAULT 1, completed_times INT DEFAULT 0,
|
||||
responsible_doctor VARCHAR(64), responsible_nurse VARCHAR(64),
|
||||
start_date DATE, end_date DATE, status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
tenant_id BIGINT DEFAULT 0, delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE followup_plan IS '随访计划';
|
||||
CREATE INDEX IF NOT EXISTS idx_fp_patient ON followup_plan(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_fp_status ON followup_plan(status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS followup_task (
|
||||
id BIGSERIAL PRIMARY KEY, plan_id BIGINT NOT NULL, patient_id BIGINT NOT NULL,
|
||||
patient_name VARCHAR(50), phone VARCHAR(20), scheduled_date DATE NOT NULL,
|
||||
actual_date DATE, contact_method VARCHAR(20), operator_name VARCHAR(64),
|
||||
result VARCHAR(20), abnormal_flag BOOLEAN DEFAULT FALSE, next_action VARCHAR(200),
|
||||
tenant_id BIGINT DEFAULT 0, delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE followup_task IS '随访任务';
|
||||
CREATE INDEX IF NOT EXISTS idx_ft_plan ON followup_task(plan_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ft_date ON followup_task(scheduled_date);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS followup_record (
|
||||
id BIGSERIAL PRIMARY KEY, task_id BIGINT NOT NULL, patient_id BIGINT NOT NULL,
|
||||
contact_content TEXT, patient_condition TEXT, medication_compliance VARCHAR(20),
|
||||
symptoms TEXT, reappointment_flag BOOLEAN DEFAULT FALSE, reappointment_date DATE,
|
||||
transfer_flag BOOLEAN DEFAULT FALSE, transfer_reason TEXT,
|
||||
operator_name VARCHAR(64), operate_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE followup_record IS '随访记录';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS satisfaction_survey (
|
||||
id BIGSERIAL PRIMARY KEY, patient_id BIGINT, patient_name VARCHAR(50),
|
||||
survey_type VARCHAR(20) NOT NULL, department_name VARCHAR(100), doctor_name VARCHAR(64),
|
||||
overall_score INT, service_score INT, environment_score INT, suggestions TEXT,
|
||||
survey_date DATE DEFAULT CURRENT_DATE, tenant_id BIGINT DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE satisfaction_survey IS '满意度调查';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS complaint_record (
|
||||
id BIGSERIAL PRIMARY KEY, patient_id BIGINT, patient_name VARCHAR(50),
|
||||
complaint_type VARCHAR(30) NOT NULL, complaint_content TEXT NOT NULL,
|
||||
department_name VARCHAR(100), handler VARCHAR(64), handle_result TEXT,
|
||||
handle_time TIMESTAMP, status VARCHAR(20) DEFAULT 'PENDING',
|
||||
satisfaction_after INT, tenant_id BIGINT DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE complaint_record IS '投诉记录';
|
||||
|
||||
-- ===== 2. 病理管理 =====
|
||||
CREATE TABLE IF NOT EXISTS pathology_order (
|
||||
id BIGSERIAL PRIMARY KEY, patient_id BIGINT NOT NULL, patient_name VARCHAR(50),
|
||||
encounter_id BIGINT, specimen_type VARCHAR(50), clinical_diagnosis TEXT,
|
||||
sample_site VARCHAR(100), urgency VARCHAR(20) DEFAULT 'NORMAL',
|
||||
order_status VARCHAR(20) DEFAULT 'PENDING', apply_doctor VARCHAR(64),
|
||||
apply_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0, delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE pathology_order IS '病理申请';
|
||||
CREATE INDEX IF NOT EXISTS idx_po_patient ON pathology_order(patient_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pathology_specimen (
|
||||
id BIGSERIAL PRIMARY KEY, order_id BIGINT NOT NULL, barcode VARCHAR(100) NOT NULL,
|
||||
specimen_desc TEXT, collection_site VARCHAR(100), fixative VARCHAR(50),
|
||||
fixative_time TIMESTAMP, receive_time TIMESTAMP, receiver VARCHAR(64),
|
||||
is_qualified BOOLEAN DEFAULT TRUE, reject_reason TEXT,
|
||||
tenant_id BIGINT DEFAULT 0, delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE pathology_specimen IS '病理标本';
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_ps_barcode ON pathology_specimen(barcode);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pathology_process (
|
||||
id BIGSERIAL PRIMARY KEY, specimen_id BIGINT NOT NULL,
|
||||
process_type VARCHAR(30), process_desc TEXT, operator VARCHAR(64),
|
||||
operate_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE pathology_process IS '病理处理记录';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pathology_report (
|
||||
id BIGSERIAL PRIMARY KEY, order_id BIGINT NOT NULL, patient_id BIGINT,
|
||||
specimen_desc TEXT, macroscopic_desc TEXT, microscopic_desc TEXT,
|
||||
diagnosis_result TEXT, suggestion TEXT,
|
||||
report_doctor VARCHAR(64), report_time TIMESTAMP,
|
||||
audit_doctor VARCHAR(64), audit_time TIMESTAMP,
|
||||
final_doctor VARCHAR(64), final_time TIMESTAMP,
|
||||
status VARCHAR(20) DEFAULT 'DRAFT',
|
||||
tenant_id BIGINT DEFAULT 0, delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE pathology_report IS '病理报告';
|
||||
CREATE INDEX IF NOT EXISTS idx_pr_order ON pathology_report(order_id);
|
||||
|
||||
-- ===== 3. 急诊分诊+抢救 =====
|
||||
CREATE TABLE IF NOT EXISTS emergency_triage (
|
||||
id BIGSERIAL PRIMARY KEY, patient_id BIGINT, patient_name VARCHAR(50),
|
||||
triage_level INT NOT NULL, chief_complaint TEXT,
|
||||
temperature DECIMAL(4,1), pulse INT, respiration INT,
|
||||
systolic_bp INT, diastolic_bp INT, spo2 INT, consciousness VARCHAR(20),
|
||||
triage_nurse VARCHAR(64), triage_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
area VARCHAR(20), status VARCHAR(20) DEFAULT 'WAITING',
|
||||
tenant_id BIGINT DEFAULT 0, delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE emergency_triage IS '急诊分诊';
|
||||
CREATE INDEX IF NOT EXISTS idx_et_level ON emergency_triage(triage_level);
|
||||
CREATE INDEX IF NOT EXISTS idx_et_time ON emergency_triage(triage_time);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS emergency_rescue (
|
||||
id BIGSERIAL PRIMARY KEY, patient_id BIGINT NOT NULL, triage_id BIGINT,
|
||||
rescue_start TIMESTAMP, rescue_end TIMESTAMP, rescue_result VARCHAR(20),
|
||||
chief_doctor VARCHAR(64), rescue_team TEXT,
|
||||
procedures TEXT, medications TEXT,
|
||||
outcome VARCHAR(20),
|
||||
tenant_id BIGINT DEFAULT 0, delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE emergency_rescue IS '抢救记录';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS emergency_observation (
|
||||
id BIGSERIAL PRIMARY KEY, patient_id BIGINT NOT NULL, triage_id BIGINT,
|
||||
observation_start TIMESTAMP, observation_end TIMESTAMP, bed_no VARCHAR(20),
|
||||
doctor VARCHAR(64), diagnosis TEXT, disposition VARCHAR(20),
|
||||
observation_hours INT,
|
||||
tenant_id BIGINT DEFAULT 0, delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE emergency_observation IS '留观记录';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS emergency_green_channel (
|
||||
id BIGSERIAL PRIMARY KEY, patient_id BIGINT NOT NULL,
|
||||
disease_type VARCHAR(30),
|
||||
door_to_treatment_time INT, target_time INT, is_achieved BOOLEAN,
|
||||
doctor VARCHAR(64), activate_time TIMESTAMP, complete_time TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0, delete_flag VARCHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE emergency_green_channel IS '绿色通道';
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.healthlink.his.emergency.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("emergency_green_channel")
|
||||
public class EmergencyGreenChannel extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long patientId; private String diseaseType;
|
||||
private Integer doorToTreatmentTime; private Integer targetTime; private Boolean isAchieved;
|
||||
private String doctor; private Date activateTime; private Date completeTime;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.healthlink.his.emergency.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("emergency_observation")
|
||||
public class EmergencyObservation extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long patientId; private Long triageId; private Date observationStart; private Date observationEnd;
|
||||
private String bedNo; private String doctor; private String diagnosis;
|
||||
private String disposition; private Integer observationHours;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.healthlink.his.emergency.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("emergency_rescue")
|
||||
public class EmergencyRescue extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long patientId; private Long triageId; private Date rescueStart; private Date rescueEnd;
|
||||
private String rescueResult; private String chiefDoctor; private String rescueTeam;
|
||||
private String procedures; private String medications; private String outcome;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.healthlink.his.emergency.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.util.Date;
|
||||
@Data @EqualsAndHashCode(callSuper=true) @TableName("emergency_triage")
|
||||
public class EmergencyTriage extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long patientId; private String patientName; private Integer triageLevel;
|
||||
private String chiefComplaint; private BigDecimal temperature; private Integer pulse;
|
||||
private Integer respiration; private Integer systolicBp; private Integer diastolicBp;
|
||||
private Integer spo2; private String consciousness; private String triageNurse;
|
||||
private Date triageTime; private String area; private String status;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.emergency.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.emergency.domain.EmergencyGreenChannel;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface EmergencyGreenChannelMapper extends BaseMapper<EmergencyGreenChannel> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.emergency.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.emergency.domain.EmergencyObservation;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface EmergencyObservationMapper extends BaseMapper<EmergencyObservation> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.emergency.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.emergency.domain.EmergencyRescue;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface EmergencyRescueMapper extends BaseMapper<EmergencyRescue> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.emergency.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.emergency.domain.EmergencyTriage;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface EmergencyTriageMapper extends BaseMapper<EmergencyTriage> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.emergency.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.emergency.domain.EmergencyGreenChannel;
|
||||
public interface IEmergencyGreenChannelService extends IService<EmergencyGreenChannel> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.emergency.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.emergency.domain.EmergencyObservation;
|
||||
public interface IEmergencyObservationService extends IService<EmergencyObservation> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.emergency.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.emergency.domain.EmergencyRescue;
|
||||
public interface IEmergencyRescueService extends IService<EmergencyRescue> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.emergency.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.emergency.domain.EmergencyTriage;
|
||||
public interface IEmergencyTriageService extends IService<EmergencyTriage> {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.emergency.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.emergency.domain.EmergencyGreenChannel;
|
||||
import com.healthlink.his.emergency.mapper.EmergencyGreenChannelMapper;
|
||||
import com.healthlink.his.emergency.service.IEmergencyGreenChannelService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class EmergencyGreenChannelServiceImpl extends ServiceImpl<EmergencyGreenChannelMapper, EmergencyGreenChannel> implements IEmergencyGreenChannelService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.emergency.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.emergency.domain.EmergencyObservation;
|
||||
import com.healthlink.his.emergency.mapper.EmergencyObservationMapper;
|
||||
import com.healthlink.his.emergency.service.IEmergencyObservationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class EmergencyObservationServiceImpl extends ServiceImpl<EmergencyObservationMapper, EmergencyObservation> implements IEmergencyObservationService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.emergency.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.emergency.domain.EmergencyRescue;
|
||||
import com.healthlink.his.emergency.mapper.EmergencyRescueMapper;
|
||||
import com.healthlink.his.emergency.service.IEmergencyRescueService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class EmergencyRescueServiceImpl extends ServiceImpl<EmergencyRescueMapper, EmergencyRescue> implements IEmergencyRescueService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.emergency.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.emergency.domain.EmergencyTriage;
|
||||
import com.healthlink.his.emergency.mapper.EmergencyTriageMapper;
|
||||
import com.healthlink.his.emergency.service.IEmergencyTriageService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class EmergencyTriageServiceImpl extends ServiceImpl<EmergencyTriageMapper, EmergencyTriage> implements IEmergencyTriageService {}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthlink.his.followup.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("complaint_record")
|
||||
public class ComplaintRecord extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long patientId; private String patientName; private String complaintType;
|
||||
private String complaintContent; private String departmentName;
|
||||
private String handler; private String handleResult; private Date handleTime;
|
||||
private String status; private Integer satisfactionAfter;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.healthlink.his.followup.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("followup_plan")
|
||||
public class FollowupPlan extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long patientId; private String patientName; private Long encounterId;
|
||||
private String diseaseCode; private String diseaseName; private String followupType;
|
||||
private String frequency; private Integer totalTimes; private Integer completedTimes;
|
||||
private String responsibleDoctor; private String responsibleNurse;
|
||||
private LocalDate startDate; private LocalDate endDate; private String status;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.healthlink.his.followup.domain;
|
||||
import com.baomidou.mybatisplus.annotation.*;import com.core.common.core.domain.HisBaseEntity;
|
||||
import lombok.Data;import lombok.EqualsAndHashCode;import java.time.LocalDate;import java.util.Date;
|
||||
@Data @EqualsAndHashCode(callSuper=true) @TableName("followup_record")
|
||||
public class FollowupRecord extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long taskId; private Long patientId; private String contactContent;
|
||||
private String patientCondition; private String medicationCompliance; private String symptoms;
|
||||
private Boolean reappointmentFlag; private LocalDate reappointmentDate;
|
||||
private Boolean transferFlag; private String transferReason;
|
||||
private String operatorName; private Date operateTime;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.healthlink.his.followup.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("followup_task")
|
||||
public class FollowupTask extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long planId; private Long patientId; private String patientName; private String phone;
|
||||
private LocalDate scheduledDate; private LocalDate actualDate; private String contactMethod;
|
||||
private String operatorName; private String result; private Boolean abnormalFlag; private String nextAction;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthlink.his.followup.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("satisfaction_survey")
|
||||
public class SatisfactionSurvey extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long patientId; private String patientName; private String surveyType;
|
||||
private String departmentName; private String doctorName;
|
||||
private Integer overallScore; private Integer serviceScore; private Integer environmentScore;
|
||||
private String suggestions; private LocalDate surveyDate;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.followup.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.followup.domain.ComplaintRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface ComplaintRecordMapper extends BaseMapper<ComplaintRecord> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.followup.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.followup.domain.FollowupPlan;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface FollowupPlanMapper extends BaseMapper<FollowupPlan> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.followup.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.followup.domain.FollowupRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface FollowupRecordMapper extends BaseMapper<FollowupRecord> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.followup.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.followup.domain.FollowupTask;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface FollowupTaskMapper extends BaseMapper<FollowupTask> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.followup.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.followup.domain.SatisfactionSurvey;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface SatisfactionSurveyMapper extends BaseMapper<SatisfactionSurvey> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.followup.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.followup.domain.ComplaintRecord;
|
||||
public interface IComplaintRecordService extends IService<ComplaintRecord> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.followup.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.followup.domain.FollowupPlan;
|
||||
public interface IFollowupPlanService extends IService<FollowupPlan> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.followup.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.followup.domain.FollowupRecord;
|
||||
public interface IFollowupRecordService extends IService<FollowupRecord> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.followup.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.followup.domain.FollowupTask;
|
||||
public interface IFollowupTaskService extends IService<FollowupTask> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.followup.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.followup.domain.SatisfactionSurvey;
|
||||
public interface ISatisfactionSurveyService extends IService<SatisfactionSurvey> {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.followup.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.followup.domain.ComplaintRecord;
|
||||
import com.healthlink.his.followup.mapper.ComplaintRecordMapper;
|
||||
import com.healthlink.his.followup.service.IComplaintRecordService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class ComplaintRecordServiceImpl extends ServiceImpl<ComplaintRecordMapper, ComplaintRecord> implements IComplaintRecordService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.followup.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.followup.domain.FollowupPlan;
|
||||
import com.healthlink.his.followup.mapper.FollowupPlanMapper;
|
||||
import com.healthlink.his.followup.service.IFollowupPlanService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class FollowupPlanServiceImpl extends ServiceImpl<FollowupPlanMapper, FollowupPlan> implements IFollowupPlanService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.followup.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.followup.domain.FollowupRecord;
|
||||
import com.healthlink.his.followup.mapper.FollowupRecordMapper;
|
||||
import com.healthlink.his.followup.service.IFollowupRecordService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class FollowupRecordServiceImpl extends ServiceImpl<FollowupRecordMapper, FollowupRecord> implements IFollowupRecordService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.followup.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.followup.domain.FollowupTask;
|
||||
import com.healthlink.his.followup.mapper.FollowupTaskMapper;
|
||||
import com.healthlink.his.followup.service.IFollowupTaskService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class FollowupTaskServiceImpl extends ServiceImpl<FollowupTaskMapper, FollowupTask> implements IFollowupTaskService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.followup.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.followup.domain.SatisfactionSurvey;
|
||||
import com.healthlink.his.followup.mapper.SatisfactionSurveyMapper;
|
||||
import com.healthlink.his.followup.service.ISatisfactionSurveyService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class SatisfactionSurveyServiceImpl extends ServiceImpl<SatisfactionSurveyMapper, SatisfactionSurvey> implements ISatisfactionSurveyService {}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.healthlink.his.pathology.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("pathology_order")
|
||||
public class PathologyOrder extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long patientId; private String patientName; private Long encounterId;
|
||||
private String specimenType; private String clinicalDiagnosis; private String sampleSite;
|
||||
private String urgency; private String orderStatus; private String applyDoctor; private Date applyTime;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.healthlink.his.pathology.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("pathology_report")
|
||||
public class PathologyReport extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long orderId; private Long patientId; private String specimenDesc;
|
||||
private String macroscopicDesc; private String microscopicDesc;
|
||||
private String diagnosisResult; private String suggestion;
|
||||
private String reportDoctor; private Date reportTime;
|
||||
private String auditDoctor; private Date auditTime;
|
||||
private String finalDoctor; private Date finalTime; private String status;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.healthlink.his.pathology.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("pathology_specimen")
|
||||
public class PathologySpecimen extends HisBaseEntity {
|
||||
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
|
||||
private Long orderId; private String barcode; private String specimenDesc;
|
||||
private String collectionSite; private String fixative; private Date fixativeTime;
|
||||
private Date receiveTime; private String receiver; private Boolean isQualified; private String rejectReason;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.pathology.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.pathology.domain.PathologyOrder;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface PathologyOrderMapper extends BaseMapper<PathologyOrder> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.pathology.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.pathology.domain.PathologyReport;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface PathologyReportMapper extends BaseMapper<PathologyReport> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.healthlink.his.pathology.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.pathology.domain.PathologySpecimen;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface PathologySpecimenMapper extends BaseMapper<PathologySpecimen> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.pathology.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.pathology.domain.PathologyOrder;
|
||||
public interface IPathologyOrderService extends IService<PathologyOrder> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.pathology.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.pathology.domain.PathologyReport;
|
||||
public interface IPathologyReportService extends IService<PathologyReport> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.healthlink.his.pathology.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.pathology.domain.PathologySpecimen;
|
||||
public interface IPathologySpecimenService extends IService<PathologySpecimen> {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.pathology.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.pathology.domain.PathologyOrder;
|
||||
import com.healthlink.his.pathology.mapper.PathologyOrderMapper;
|
||||
import com.healthlink.his.pathology.service.IPathologyOrderService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class PathologyOrderServiceImpl extends ServiceImpl<PathologyOrderMapper, PathologyOrder> implements IPathologyOrderService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.pathology.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.pathology.domain.PathologyReport;
|
||||
import com.healthlink.his.pathology.mapper.PathologyReportMapper;
|
||||
import com.healthlink.his.pathology.service.IPathologyReportService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class PathologyReportServiceImpl extends ServiceImpl<PathologyReportMapper, PathologyReport> implements IPathologyReportService {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthlink.his.pathology.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.pathology.domain.PathologySpecimen;
|
||||
import com.healthlink.his.pathology.mapper.PathologySpecimenMapper;
|
||||
import com.healthlink.his.pathology.service.IPathologySpecimenService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class PathologySpecimenServiceImpl extends ServiceImpl<PathologySpecimenMapper, PathologySpecimen> implements IPathologySpecimenService {}
|
||||
7
healthlink-his-ui/src/views/emergency/greentrack/api.js
Normal file
7
healthlink-his-ui/src/views/emergency/greentrack/api.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/emergency/green-channel/page',method:'get',params:p})}
|
||||
export function getById(id){return request({url:'/emergency/green-channel/'+id,method:'get'})}
|
||||
export function activate(d){return request({url:'/emergency/green-channel/activate',method:'post',data:d})}
|
||||
export function complete(id,d){return request({url:'/emergency/green-channel/'+id+'/complete',method:'put',data:d})}
|
||||
export function getStats(p){return request({url:'/emergency/green-channel/stats',method:'get',params:p})}
|
||||
export function del(id){return request({url:'/emergency/green-channel/delete/'+id,method:'delete'})}
|
||||
89
healthlink-his-ui/src/views/emergency/greentrack/index.vue
Normal file
89
healthlink-his-ui/src/views/emergency/greentrack/index.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">绿色通道管理</span></div>
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<div style="display:flex;gap:32px;align-items:center">
|
||||
<div><span style="color:#909399">总例数</span><br/><span style="font-size:24px;font-weight:bold">{{ stats.total||0 }}</span></div>
|
||||
<div><span style="color:#909399">达标例数</span><br/><span style="font-size:24px;font-weight:bold;color:#67C23A">{{ stats.achieved||0 }}</span></div>
|
||||
<div><span style="color:#909399">达标率</span><br/><span style="font-size:24px;font-weight:bold;color:#409EFF">{{ stats.achievementRate||0 }}%</span></div>
|
||||
<div><span style="color:#909399">平均Door-to-Tx时间</span><br/><span style="font-size:24px;font-weight:bold;color:#E6A23C">{{ stats.avgTime||0 }}min</span></div>
|
||||
<el-button type="primary" @click="refreshStats">刷新统计</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.diseaseType" placeholder="疾病类型" clearable style="width:130px"/>
|
||||
<el-select v-model="q.isAchieved" placeholder="是否达标" clearable style="width:110px">
|
||||
<el-option label="达标" :value="true"/><el-option label="未达标" :value="false"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">激活绿色通道</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="patientId" label="患者ID" width="90"/>
|
||||
<el-table-column prop="diseaseType" label="疾病类型" width="120"/>
|
||||
<el-table-column prop="doorToTreatmentTime" label="Door-to-Tx(min)" width="130" align="center">
|
||||
<template #default="{row}"><span :style="{color:row.isAchieved?'#67C23A':'#F56C6C',fontWeight:'bold'}">{{ row.doorToTreatmentTime }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="targetTime" label="目标时间(min)" width="120" align="center"/>
|
||||
<el-table-column prop="isAchieved" label="达标" width="70" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag v-if="row.isAchieved===true" type="success" size="small">达标</el-tag>
|
||||
<el-tag v-else-if="row.isAchieved===false" type="danger" size="small">未达标</el-tag>
|
||||
<el-tag v-else type="info" size="small">待评估</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="doctor" label="医生" width="90"/>
|
||||
<el-table-column prop="activateTime" label="激活时间" width="160"/>
|
||||
<el-table-column prop="completeTime" label="完成时间" width="160"/>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button v-if="!row.completeTime" type="primary" link size="small" @click="showComplete(row)">完成评估</el-button>
|
||||
<el-button type="info" link size="small" @click="showEdit(row)">编辑</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="addVisible" title="激活绿色通道" width="500px">
|
||||
<el-form :model="addForm" label-width="120px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="addForm.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="疾病类型"><el-input v-model="addForm.diseaseType" placeholder="如: 急性心肌梗死、脑卒中"/></el-form-item>
|
||||
<el-form-item label="目标时间(min)"><el-input-number v-model="addForm.targetTime" :min="1"/></el-form-item>
|
||||
<el-form-item label="医生"><el-input v-model="addForm.doctor"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="addVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitAdd">激活</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="completeVisible" title="完成评估" width="400px">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="Door-to-Tx(min)"><el-input-number v-model="completeForm.doorToTreatmentTime" :min="1"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="completeVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="doComplete">提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,activate,complete,getStats,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0);const stats=ref({})
|
||||
const addVisible=ref(false);const completeVisible=ref(false)
|
||||
const addForm=ref({patientId:null,diseaseType:'',targetTime:90,doctor:''})
|
||||
const completeForm=ref({doorToTreatmentTime:60});let currentId=null
|
||||
const q=ref({pageNo:1,pageSize:20,diseaseType:'',isAchieved:null})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const refreshStats=async()=>{const r=await getStats({});stats.value=r.data||{}}
|
||||
const showAdd=()=>{addForm.value={patientId:null,diseaseType:'',targetTime:90,doctor:''};addVisible.value=true}
|
||||
const showComplete=(row)=>{currentId=row.id;completeForm.value={doorToTreatmentTime:60};completeVisible.value=true}
|
||||
const submitAdd=async()=>{await activate(addForm.value);ElMessage.success('绿色通道已激活');addVisible.value=false;loadData();refreshStats()}
|
||||
const doComplete=async()=>{await complete(currentId,completeForm.value);ElMessage.success('评估完成');completeVisible.value=false;loadData();refreshStats()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData();refreshStats()}
|
||||
onMounted(()=>{loadData();refreshStats()})
|
||||
</script>
|
||||
7
healthlink-his-ui/src/views/emergency/observation/api.js
Normal file
7
healthlink-his-ui/src/views/emergency/observation/api.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/emergency/observation/page',method:'get',params:p})}
|
||||
export function getById(id){return request({url:'/emergency/observation/'+id,method:'get'})}
|
||||
export function add(d){return request({url:'/emergency/observation/add',method:'post',data:d})}
|
||||
export function discharge(d){return request({url:'/emergency/observation/discharge',method:'put',data:d})}
|
||||
export function updateDisposition(id,params){return request({url:'/emergency/observation/'+id+'/disposition',method:'put',params})}
|
||||
export function del(id){return request({url:'/emergency/observation/delete/'+id,method:'delete'})}
|
||||
86
healthlink-his-ui/src/views/emergency/observation/index.vue
Normal file
86
healthlink-his-ui/src/views/emergency/observation/index.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<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.doctor" placeholder="医生" clearable style="width:120px"/>
|
||||
<el-input v-model="q.bedNo" placeholder="床位号" clearable style="width:100px"/>
|
||||
<el-select v-model="q.disposition" placeholder="转归" clearable style="width:100px">
|
||||
<el-option label="住院" value="ADMIT"/><el-option label="出院" value="DISCHARGE"/>
|
||||
<el-option label="转科" value="TRANSFER"/><el-option label="死亡" value="DEATH"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">新增留观</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="patientId" label="患者ID" width="90"/>
|
||||
<el-table-column prop="bedNo" label="床位" width="70"/>
|
||||
<el-table-column prop="doctor" label="医生" width="90"/>
|
||||
<el-table-column prop="diagnosis" label="诊断" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="observationStart" label="开始时间" width="160"/>
|
||||
<el-table-column prop="observationEnd" label="结束时间" width="160"/>
|
||||
<el-table-column prop="observationHours" label="观察时长(h)" width="100" align="center"/>
|
||||
<el-table-column prop="disposition" label="转归" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag v-if="row.disposition" :type="row.disposition==='ADMIT'?'warning':row.disposition==='DEATH'?'danger':'success'" size="small">
|
||||
{{ {ADMIT:'住院',DISCHARGE:'出院',TRANSFER:'转科',DEATH:'死亡'}[row.disposition]||row.disposition }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="info" size="small">观察中</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button v-if="!row.observationEnd" type="warning" link size="small" @click="dischargeItem(row)">出院/转归</el-button>
|
||||
<el-button type="info" link size="small" @click="showEdit(row)">编辑</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="550px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="form.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="分诊ID"><el-input-number v-model="form.triageId"/></el-form-item>
|
||||
<el-form-item label="床位号"><el-input v-model="form.bedNo"/></el-form-item>
|
||||
<el-form-item label="医生"><el-input v-model="form.doctor"/></el-form-item>
|
||||
<el-form-item label="诊断"><el-input v-model="form.diagnosis" type="textarea"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dlgVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="dischargeVisible" title="出院/转归" width="400px">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="转归">
|
||||
<el-select v-model="dischargeForm.disposition">
|
||||
<el-option label="住院" value="ADMIT"/><el-option label="出院" value="DISCHARGE"/>
|
||||
<el-option label="转科" value="TRANSFER"/><el-option label="死亡" value="DEATH"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dischargeVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="doDischarge">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,add,discharge,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0);const dlgVisible=ref(false);const isEdit=ref(false)
|
||||
const dischargeVisible=ref(false);const dischargeForm=ref({disposition:'ADMIT'});let currentId=null
|
||||
const q=ref({pageNo:1,pageSize:20,doctor:'',bedNo:'',disposition:''})
|
||||
const form=ref({patientId:null,triageId:null,bedNo:'',doctor:'',diagnosis:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const showAdd=()=>{isEdit.value=false;form.value={patientId:null,triageId:null,bedNo:'',doctor:'',diagnosis:''};dlgVisible.value=true}
|
||||
const showEdit=(row)=>{isEdit.value=true;form.value={...row};dlgVisible.value=true}
|
||||
const submitForm=async()=>{if(isEdit.value){await (await import('./api')).update(form.value||{})}else{await add(form.value)}ElMessage.success('操作成功');dlgVisible.value=false;loadData()}
|
||||
const dischargeItem=(row)=>{currentId=row.id;dischargeForm.value={disposition:'ADMIT'};dischargeVisible.value=true}
|
||||
const doDischarge=async()=>{await discharge({...dischargeForm.value,id:currentId});ElMessage.success('已出院/转归');dischargeVisible.value=false;loadData()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
</script>
|
||||
7
healthlink-his-ui/src/views/emergency/rescue/api.js
Normal file
7
healthlink-his-ui/src/views/emergency/rescue/api.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/emergency/rescue/page',method:'get',params:p})}
|
||||
export function getById(id){return request({url:'/emergency/rescue/'+id,method:'get'})}
|
||||
export function add(d){return request({url:'/emergency/rescue/add',method:'post',data:d})}
|
||||
export function endRescue(d){return request({url:'/emergency/rescue/end',method:'put',data:d})}
|
||||
export function updateResult(id,d){return request({url:'/emergency/rescue/'+id+'/result',method:'put',data:d})}
|
||||
export function del(id){return request({url:'/emergency/rescue/delete/'+id,method:'delete'})}
|
||||
85
healthlink-his-ui/src/views/emergency/rescue/index.vue
Normal file
85
healthlink-his-ui/src/views/emergency/rescue/index.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<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.chiefDoctor" placeholder="主任医师" clearable style="width:120px"/>
|
||||
<el-select v-model="q.rescueResult" placeholder="抢救结果" clearable style="width:110px">
|
||||
<el-option label="成功" value="SUCCESS"/><el-option label="失败" value="FAILED"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="danger" @click="showAdd">开始抢救</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="patientId" label="患者ID" width="90"/>
|
||||
<el-table-column prop="triageId" label="分诊ID" width="80"/>
|
||||
<el-table-column prop="rescueStart" label="开始时间" width="160"/>
|
||||
<el-table-column prop="rescueEnd" label="结束时间" width="160"/>
|
||||
<el-table-column prop="chiefDoctor" label="主任医师" width="100"/>
|
||||
<el-table-column prop="rescueTeam" label="抢救团队" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="procedures" label="抢救操作" min-width="180" show-overflow-tooltip/>
|
||||
<el-table-column prop="medications" label="用药" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="rescueResult" label="结果" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag v-if="row.rescueResult==='SUCCESS'" type="success" size="small">成功</el-tag>
|
||||
<el-tag v-else-if="row.rescueResult==='FAILED'" type="danger" size="small">失败</el-tag>
|
||||
<el-tag v-else type="info" size="small">进行中</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button v-if="!row.rescueEnd" type="warning" link size="small" @click="endRescueItem(row)">结束抢救</el-button>
|
||||
<el-button v-if="row.rescueEnd && !row.rescueResult" type="success" link size="small" @click="showResult(row)">记录结果</el-button>
|
||||
<el-button type="info" link size="small" @click="showEdit(row)">编辑</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="600px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="form.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="分诊ID"><el-input-number v-model="form.triageId"/></el-form-item>
|
||||
<el-form-item label="主任医师"><el-input v-model="form.chiefDoctor"/></el-form-item>
|
||||
<el-form-item label="抢救团队"><el-input v-model="form.rescueTeam" placeholder="团队成员"/></el-form-item>
|
||||
<el-form-item label="抢救操作"><el-input v-model="form.procedures" type="textarea" placeholder="操作记录"/></el-form-item>
|
||||
<el-form-item label="用药"><el-input v-model="form.medications" type="textarea" placeholder="用药记录"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dlgVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="resultVisible" title="记录抢救结果" width="400px">
|
||||
<el-form :model="resultForm" label-width="100px">
|
||||
<el-form-item label="抢救结果">
|
||||
<el-radio-group v-model="resultForm.rescueResult"><el-radio value="SUCCESS">成功</el-radio><el-radio value="FAILED">失败</el-radio></el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="转归"><el-input v-model="resultForm.outcome" type="textarea" placeholder="患者转归"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="resultVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitResult">提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,add,endRescue,updateResult,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0);const dlgVisible=ref(false);const isEdit=ref(false)
|
||||
const resultVisible=ref(false);const resultForm=ref({rescueResult:'SUCCESS',outcome:''});let currentId=null
|
||||
const q=ref({pageNo:1,pageSize:20,chiefDoctor:'',rescueResult:''})
|
||||
const form=ref({patientId:null,triageId:null,chiefDoctor:'',rescueTeam:'',procedures:'',medications:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const showAdd=()=>{isEdit.value=false;form.value={patientId:null,triageId:null,chiefDoctor:'',rescueTeam:'',procedures:'',medications:''};dlgVisible.value=true}
|
||||
const showEdit=(row)=>{isEdit.value=true;form.value={...row};dlgVisible.value=true}
|
||||
const showResult=(row)=>{currentId=row.id;resultForm.value={rescueResult:'SUCCESS',outcome:''};resultVisible.value=true}
|
||||
const submitForm=async()=>{if(isEdit.value){await (await import('./api')).update(form.value||{})}else{await add(form.value)}ElMessage.success('操作成功');dlgVisible.value=false;loadData()}
|
||||
const endRescueItem=async(row)=>{await endRescue(row);ElMessage.success('抢救已结束');loadData()}
|
||||
const submitResult=async()=>{await updateResult(currentId,resultForm.value);ElMessage.success('结果已记录');resultVisible.value=false;loadData()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
</script>
|
||||
9
healthlink-his-ui/src/views/emergency/triage/api.js
Normal file
9
healthlink-his-ui/src/views/emergency/triage/api.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/emergency/triage/page',method:'get',params:p})}
|
||||
export function getRealtime(){return request({url:'/emergency/triage/realtime',method:'get'})}
|
||||
export function getById(id){return request({url:'/emergency/triage/'+id,method:'get'})}
|
||||
export function add(d){return request({url:'/emergency/triage/add',method:'post',data:d})}
|
||||
export function update(d){return request({url:'/emergency/triage/update',method:'put',data:d})}
|
||||
export function startTreatment(id){return request({url:'/emergency/triage/'+id+'/start-treatment',method:'put'})}
|
||||
export function complete(id){return request({url:'/emergency/triage/'+id+'/complete',method:'put'})}
|
||||
export function del(id){return request({url:'/emergency/triage/delete/'+id,method:'delete'})}
|
||||
123
healthlink-his-ui/src/views/emergency/triage/index.vue
Normal file
123
healthlink-his-ui/src/views/emergency/triage/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">急诊分诊管理</span></div>
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<div style="display:flex;gap:24px;align-items:center;flex-wrap:wrap">
|
||||
<div v-for="i in 5" :key="i" style="text-align:center;min-width:80px">
|
||||
<div :style="{background:['#F56C6C','#E6A23C','#409EFF','#67C23A','#909399'][i-1],color:'#fff',borderRadius:'8px',padding:'8px 16px',fontSize:'24px',fontWeight:'bold'}">
|
||||
{{ realtime['level'+i+'Waiting']||0 }}
|
||||
</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:4px">Ⅰ~Ⅴ级</div>
|
||||
</div>
|
||||
<div style="text-align:center;min-width:80px">
|
||||
<div style="background:#303133;color:#fff;borderRadius:'8px';padding:8px 16px;font-size:24px;font-weight:bold">{{ realtime.inTreatment||0 }}</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:4px">就诊中</div>
|
||||
</div>
|
||||
<div style="text-align:center;min-width:80px">
|
||||
<div style="background:#409EFF;color:#fff;borderRadius:'8px';padding:8px 16px;font-size:24px;font-weight:bold">{{ realtime.todayTotal||0 }}</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:4px">今日总量</div>
|
||||
</div>
|
||||
<el-button type="primary" @click="refreshRealtime">刷新</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px"/>
|
||||
<el-select v-model="q.triageLevel" placeholder="分诊级别" clearable style="width:110px">
|
||||
<el-option v-for="i in 5" :key="i" :label="'Ⅰ~Ⅴ级'[i-1]||'级'+i" :value="i"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.status" placeholder="状态" clearable style="width:100px">
|
||||
<el-option label="待诊" value="WAITING"/><el-option label="就诊中" value="IN_TREATMENT"/><el-option label="已完成" value="COMPLETED"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">新增分诊</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe :row-class-name="rowClassName">
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="triageLevel" label="级别" width="60" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="['danger','warning','','success','info'][row.triageLevel-1]" size="small" effect="dark">
|
||||
Ⅰ~Ⅴ级'[row.triageLevel-1]
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="chiefComplaint" label="主诉" min-width="180" show-overflow-tooltip/>
|
||||
<el-table-column prop="temperature" label="体温" width="60" align="center"/>
|
||||
<el-table-column prop="pulse" label="脉搏" width="60" align="center"/>
|
||||
<el-table-column prop="systolicBp" label="收缩压" width="70" align="center"/>
|
||||
<el-table-column prop="spo2" label="血氧" width="60" align="center"/>
|
||||
<el-table-column prop="consciousness" label="意识" width="70" align="center"/>
|
||||
<el-table-column prop="area" label="区域" width="80"/>
|
||||
<el-table-column prop="triageNurse" label="分诊护士" width="90"/>
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status==='WAITING'?'danger':row.status==='IN_TREATMENT'?'warning':'success'" size="small">
|
||||
{{ {WAITING:'待诊',IN_TREATMENT:'就诊中',COMPLETED:'已完成'}[row.status]||row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button v-if="row.status==='WAITING'" type="primary" link size="small" @click="startTreatmentItem(row.id)">叫号</el-button>
|
||||
<el-button v-if="row.status==='IN_TREATMENT'" type="success" link size="small" @click="completeItem(row.id)">完成</el-button>
|
||||
<el-button type="info" link size="small" @click="showEdit(row)">编辑</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="600px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="form.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="患者姓名"><el-input v-model="form.patientName"/></el-form-item>
|
||||
<el-form-item label="分诊级别">
|
||||
<el-radio-group v-model="form.triageLevel">
|
||||
<el-radio :value="1"><span style="color:#F56C6C;font-weight:bold">Ⅰ级 濒死</span></el-radio>
|
||||
<el-radio :value="2"><span style="color:#E6A23C;font-weight:bold">Ⅱ级 危重</span></el-radio>
|
||||
<el-radio :value="3"><span style="color:#409EFF">Ⅲ级 急症</span></el-radio>
|
||||
<el-radio :value="4"><span style="color:#67C23A">Ⅳ级 亚急</span></el-radio>
|
||||
<el-radio :value="5"><span style="color:#909399">Ⅴ级 非紧急</span></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="主诉"><el-input v-model="form.chiefComplaint" type="textarea" placeholder="主要症状描述"/></el-form-item>
|
||||
<el-form-item label="体温"><el-input-number v-model="form.temperature" :precision="1" :step="0.1"/></el-form-item>
|
||||
<el-form-item label="脉搏"><el-input-number v-model="form.pulse" :min="0"/></el-form-item>
|
||||
<el-form-item label="呼吸"><el-input-number v-model="form.respiration" :min="0"/></el-form-item>
|
||||
<el-form-item label="收缩压"><el-input-number v-model="form.systolicBp" :min="0"/></el-form-item>
|
||||
<el-form-item label="舒张压"><el-input-number v-model="form.diastolicBp" :min="0"/></el-form-item>
|
||||
<el-form-item label="血氧"><el-input-number v-model="form.spo2" :min="0" :max="100"/></el-form-item>
|
||||
<el-form-item label="意识状态">
|
||||
<el-select v-model="form.consciousness"><el-option label="清醒" value="清醒"/><el-option label="嗜睡" value="嗜睡"/><el-option label="昏迷" value="昏迷"/></el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分诊区域">
|
||||
<el-select v-model="form.area"><el-option label="抢救区" value="抢救区"/><el-option label="留观区" value="留观区"/><el-option label="诊室" value="诊室"/><el-option label="普通区" value="普通区"/></el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分诊护士"><el-input v-model="form.triageNurse"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dlgVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,getRealtime,add,update,startTreatment,complete,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0);const dlgVisible=ref(false);const isEdit=ref(false);const realtime=ref({})
|
||||
const q=ref({pageNo:1,pageSize:20,patientName:'',triageLevel:null,status:''})
|
||||
const form=ref({patientId:null,patientName:'',triageLevel:3,chiefComplaint:'',temperature:36.5,pulse:80,respiration:20,systolicBp:120,diastolicBp:80,spo2:98,consciousness:'清醒',area:'诊室',triageNurse:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const refreshRealtime=async()=>{const r=await getRealtime();realtime.value=r.data||{}}
|
||||
const showAdd=()=>{isEdit.value=false;form.value={patientId:null,patientName:'',triageLevel:3,chiefComplaint:'',temperature:36.5,pulse:80,respiration:20,systolicBp:120,diastolicBp:80,spo2:98,consciousness:'清醒',area:'诊室',triageNurse:''};dlgVisible.value=true}
|
||||
const showEdit=(row)=>{isEdit.value=true;form.value={...row};dlgVisible.value=true}
|
||||
const submitForm=async()=>{if(isEdit.value){await update(form.value)}else{await add(form.value)}ElMessage.success('操作成功');dlgVisible.value=false;loadData();refreshRealtime()}
|
||||
const startTreatmentItem=async(id)=>{await startTreatment(id);ElMessage.success('已叫号');loadData();refreshRealtime()}
|
||||
const completeItem=async(id)=>{await complete(id);ElMessage.success('已完成');loadData();refreshRealtime()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData();refreshRealtime()}
|
||||
const rowClassName=({row})=>{if(row.triageLevel===1)return 'emergency-row-critical';if(row.triageLevel===2)return 'emergency-row-severe';return ''}
|
||||
onMounted(()=>{loadData();refreshRealtime()})
|
||||
</script>
|
||||
<style scoped>.emergency-row-critical{background-color:#FDE2E2!important}.emergency-row-severe{background-color:#FDF6EC!important}</style>
|
||||
6
healthlink-his-ui/src/views/followup/complaint/api.js
Normal file
6
healthlink-his-ui/src/views/followup/complaint/api.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/followup/complaint/page',method:'get',params:p})}
|
||||
export function add(d){return request({url:'/followup/complaint/add',method:'post',data:d})}
|
||||
export function handle(d){return request({url:'/followup/complaint/handle',method:'put',data:d})}
|
||||
export function close(id){return request({url:'/followup/complaint/close/'+id,method:'put'})}
|
||||
export function del(id){return request({url:'/followup/complaint/delete/'+id,method:'delete'})}
|
||||
91
healthlink-his-ui/src/views/followup/complaint/index.vue
Normal file
91
healthlink-his-ui/src/views/followup/complaint/index.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">投诉记录管理</span></div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px"/>
|
||||
<el-select v-model="q.complaintType" placeholder="投诉类型" clearable style="width:120px">
|
||||
<el-option label="服务态度" value="ATTITUDE"/><el-option label="医疗质量" value="QUALITY"/>
|
||||
<el-option label="等候时间" value="WAITING"/><el-option label="其他" value="OTHER"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.status" placeholder="状态" clearable style="width:100px">
|
||||
<el-option label="待处理" value="PENDING"/><el-option label="已处理" value="HANDLED"/><el-option label="已关闭" value="CLOSED"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">登记投诉</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="complaintType" label="类型" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.complaintType==='QUALITY'?'danger':'warning'" size="small">
|
||||
{{ {ATTITUDE:'服务态度',QUALITY:'医疗质量',WAITING:'等候时间',OTHER:'其他'}[row.complaintType]||row.complaintType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="complaintContent" label="投诉内容" min-width="200" show-overflow-tooltip/>
|
||||
<el-table-column prop="departmentName" label="科室" width="120"/>
|
||||
<el-table-column prop="handler" label="处理人" width="90"/>
|
||||
<el-table-column prop="handleResult" label="处理结果" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status==='PENDING'?'danger':row.status==='HANDLED'?'warning':'success'" size="small">
|
||||
{{ {PENDING:'待处理',HANDLED:'已处理',CLOSED:'已关闭'}[row.status]||row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="satisfactionAfter" label="处理后满意度" width="100" align="center"/>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button v-if="row.status==='PENDING'" type="primary" link size="small" @click="showHandle(row)">处理</el-button>
|
||||
<el-button v-if="row.status==='HANDLED'" type="success" link size="small" @click="closeItem(row.id)">关闭</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="addVisible" title="登记投诉" width="500px">
|
||||
<el-form :model="addForm" label-width="100px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="addForm.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="患者姓名"><el-input v-model="addForm.patientName"/></el-form-item>
|
||||
<el-form-item label="投诉类型"><el-select v-model="addForm.complaintType"><el-option label="服务态度" value="ATTITUDE"/><el-option label="医疗质量" value="QUALITY"/><el-option label="等候时间" value="WAITING"/><el-option label="其他" value="OTHER"/></el-select></el-form-item>
|
||||
<el-form-item label="投诉内容"><el-input v-model="addForm.complaintContent" type="textarea" placeholder="详细描述投诉内容"/></el-form-item>
|
||||
<el-form-item label="科室"><el-input v-model="addForm.departmentName"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="addVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitAdd">提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="handleVisible" title="处理投诉" width="500px">
|
||||
<el-form :model="handleForm" label-width="100px">
|
||||
<el-form-item label="处理人"><el-input v-model="handleForm.handler"/></el-form-item>
|
||||
<el-form-item label="处理结果"><el-input v-model="handleForm.handleResult" type="textarea" placeholder="处理措施和结果"/></el-form-item>
|
||||
<el-form-item label="处理后满意度"><el-rate v-model="handleForm.satisfactionAfter" :max="5"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="handleVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle">确认处理</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,add,handle,close,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,patientName:'',complaintType:'',status:''})
|
||||
const addVisible=ref(false);const handleVisible=ref(false)
|
||||
const addForm=ref({patientId:null,patientName:'',complaintType:'ATTITUDE',complaintContent:'',departmentName:''})
|
||||
const handleForm=ref({id:null,handler:'',handleResult:'',satisfactionAfter:5})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const showAdd=()=>{addForm.value={patientId:null,patientName:'',complaintType:'ATTITUDE',complaintContent:'',departmentName:''};addVisible.value=true}
|
||||
const showHandle=(row)=>{handleForm.value={id:row.id,handler:'',handleResult:'',satisfactionAfter:5};handleVisible.value=true}
|
||||
const submitAdd=async()=>{await add(addForm.value);ElMessage.success('投诉已登记');addVisible.value=false;loadData()}
|
||||
const submitHandle=async()=>{await handle(handleForm.value);ElMessage.success('已处理');handleVisible.value=false;loadData()}
|
||||
const closeItem=async(id)=>{await close(id);ElMessage.success('已关闭');loadData()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
</script>
|
||||
8
healthlink-his-ui/src/views/followup/plan/api.js
Normal file
8
healthlink-his-ui/src/views/followup/plan/api.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/followup/plan/page',method:'get',params:p})}
|
||||
export function getList(p){return request({url:'/followup/plan/list',method:'get',params:p})}
|
||||
export function getById(id){return request({url:'/followup/plan/'+id,method:'get'})}
|
||||
export function add(d){return request({url:'/followup/plan/add',method:'post',data:d})}
|
||||
export function update(d){return request({url:'/followup/plan/update',method:'put',data:d})}
|
||||
export function del(id){return request({url:'/followup/plan/delete/'+id,method:'delete'})}
|
||||
export function complete(id){return request({url:'/followup/plan/complete/'+id,method:'put'})}
|
||||
95
healthlink-his-ui/src/views/followup/plan/index.vue
Normal file
95
healthlink-his-ui/src/views/followup/plan/index.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">随访计划管理</span></div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px"/>
|
||||
<el-input v-model="q.diseaseCode" placeholder="病种编码" clearable style="width:120px"/>
|
||||
<el-select v-model="q.followupType" placeholder="随访方式" clearable style="width:120px">
|
||||
<el-option label="电话" value="PHONE"/><el-option label="短信" value="SMS"/>
|
||||
<el-option label="微信" value="WECHAT"/><el-option label="门诊" value="OUTPATIENT"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.status" placeholder="状态" clearable style="width:100px">
|
||||
<el-option label="进行中" value="ACTIVE"/><el-option label="已完成" value="COMPLETED"/>
|
||||
<el-option label="已取消" value="CANCELLED"/>
|
||||
</el-select>
|
||||
<el-input v-model="q.responsibleDoctor" placeholder="责任医生" clearable style="width:120px"/>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">新建计划</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="diseaseName" label="病种" width="140" show-overflow-tooltip/>
|
||||
<el-table-column prop="followupType" label="随访方式" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small">{{ {PHONE:'电话',SMS:'短信',WECHAT:'微信',OUTPATIENT:'门诊'}[row.followupType]||row.followupType }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="frequency" label="频率" width="90" align="center"/>
|
||||
<el-table-column label="完成度" width="120" align="center">
|
||||
<template #default="{row}">
|
||||
<el-progress :percentage="row.totalTimes?Math.round(row.completedTimes*100/row.totalTimes):0" :stroke-width="14"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="responsibleDoctor" label="责任医生" width="100"/>
|
||||
<el-table-column prop="startDate" label="开始日期" width="110"/>
|
||||
<el-table-column prop="endDate" label="结束日期" width="110"/>
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status==='ACTIVE'?'success':row.status==='COMPLETED'?'info':'danger'" size="small">
|
||||
{{ {ACTIVE:'进行中',COMPLETED:'已完成',CANCELLED:'已取消'}[row.status]||row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button type="primary" link size="small" @click="showEdit(row)">编辑</el-button>
|
||||
<el-button v-if="row.status==='ACTIVE'" type="success" link size="small" @click="completePlan(row.id)">完成</el-button>
|
||||
<el-button type="warning" link size="small" @click="$router.push('/followup-task?planId='+row.id)">任务</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="600px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="form.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="患者姓名"><el-input v-model="form.patientName"/></el-form-item>
|
||||
<el-form-item label="就诊ID"><el-input-number v-model="form.encounterId"/></el-form-item>
|
||||
<el-form-item label="病种编码"><el-input v-model="form.diseaseCode"/></el-form-item>
|
||||
<el-form-item label="病种名称"><el-input v-model="form.diseaseName"/></el-form-item>
|
||||
<el-form-item label="随访方式">
|
||||
<el-select v-model="form.followupType"><el-option label="电话" value="PHONE"/><el-option label="短信" value="SMS"/><el-option label="微信" value="WECHAT"/><el-option label="门诊" value="OUTPATIENT"/></el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="频率">
|
||||
<el-select v-model="form.frequency"><el-option label="每日" value="DAILY"/><el-option label="每周" value="WEEKLY"/><el-option label="每月" value="MONTHLY"/><el-option label="每季度" value="QUARTERLY"/></el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="总次数"><el-input-number v-model="form.totalTimes" :min="1"/></el-form-item>
|
||||
<el-form-item label="责任医生"><el-input v-model="form.responsibleDoctor"/></el-form-item>
|
||||
<el-form-item label="责任护士"><el-input v-model="form.responsibleNurse"/></el-form-item>
|
||||
<el-form-item label="开始日期"><el-date-picker v-model="form.startDate" type="date" value-format="YYYY-MM-DD"/></el-form-item>
|
||||
<el-form-item label="结束日期"><el-date-picker v-model="form.endDate" type="date" value-format="YYYY-MM-DD"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dlgVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,add,update,del,complete} from './api'
|
||||
const tableData=ref([]);const total=ref(0);const dlgVisible=ref(false);const isEdit=ref(false)
|
||||
const q=ref({pageNo:1,pageSize:20,patientName:'',diseaseCode:'',followupType:'',status:'',responsibleDoctor:''})
|
||||
const form=ref({patientId:null,patientName:'',encounterId:null,diseaseCode:'',diseaseName:'',followupType:'PHONE',frequency:'MONTHLY',totalTimes:1,responsibleDoctor:'',responsibleNurse:'',startDate:'',endDate:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const showAdd=()=>{isEdit.value=false;form.value={patientId:null,patientName:'',encounterId:null,diseaseCode:'',diseaseName:'',followupType:'PHONE',frequency:'MONTHLY',totalTimes:1,responsibleDoctor:'',responsibleNurse:'',startDate:'',endDate:''};dlgVisible.value=true}
|
||||
const showEdit=(row)=>{isEdit.value=true;form.value={...row};dlgVisible.value=true}
|
||||
const submitForm=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()}
|
||||
const completePlan=async(id)=>{await complete(id);ElMessage.success('已完成');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
</script>
|
||||
5
healthlink-his-ui/src/views/followup/record/api.js
Normal file
5
healthlink-his-ui/src/views/followup/record/api.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/followup/record/page',method:'get',params:p})}
|
||||
export function add(d){return request({url:'/followup/record/add',method:'post',data:d})}
|
||||
export function update(d){return request({url:'/followup/record/update',method:'put',data:d})}
|
||||
export function del(id){return request({url:'/followup/record/delete/'+id,method:'delete'})}
|
||||
52
healthlink-his-ui/src/views/followup/record/index.vue
Normal file
52
healthlink-his-ui/src/views/followup/record/index.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<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.taskId" placeholder="任务ID" clearable style="width:120px"/>
|
||||
<el-select v-model="q.medicationCompliance" placeholder="用药依从" clearable style="width:110px">
|
||||
<el-option label="完全遵医" value="FULL"/><el-option label="部分遵医" value="PARTIAL"/><el-option label="未遵医" value="NONE"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="taskId" label="任务ID" width="100"/>
|
||||
<el-table-column prop="patientId" label="患者ID" width="90"/>
|
||||
<el-table-column prop="contactContent" label="联系内容" min-width="180" show-overflow-tooltip/>
|
||||
<el-table-column prop="patientCondition" label="患者状况" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="medicationCompliance" label="用药依从" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.medicationCompliance==='FULL'?'success':row.medicationCompliance==='PARTIAL'?'warning':'danger'" size="small">
|
||||
{{ {FULL:'完全遵医',PARTIAL:'部分遵医',NONE:'未遵医'}[row.medicationCompliance]||row.medicationCompliance }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="symptoms" label="症状" width="120" show-overflow-tooltip/>
|
||||
<el-table-column prop="reappointmentFlag" label="复查" width="60" align="center">
|
||||
<template #default="{row}"><el-tag v-if="row.reappointmentFlag" type="warning" size="small">是</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="transferFlag" label="转诊" width="60" align="center">
|
||||
<template #default="{row}"><el-tag v-if="row.transferFlag" type="danger" size="small">是</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operatorName" label="随访人" width="90"/>
|
||||
<el-table-column prop="operateTime" label="时间" width="160"/>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{row}">
|
||||
<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"/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,taskId:'',medicationCompliance:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
</script>
|
||||
5
healthlink-his-ui/src/views/followup/survey/api.js
Normal file
5
healthlink-his-ui/src/views/followup/survey/api.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/followup/survey/page',method:'get',params:p})}
|
||||
export function getStats(p){return request({url:'/followup/survey/stats',method:'get',params:p})}
|
||||
export function add(d){return request({url:'/followup/survey/add',method:'post',data:d})}
|
||||
export function del(id){return request({url:'/followup/survey/delete/'+id,method:'delete'})}
|
||||
75
healthlink-his-ui/src/views/followup/survey/index.vue
Normal file
75
healthlink-his-ui/src/views/followup/survey/index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">满意度调查</span></div>
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<div style="display:flex;gap:32px;align-items:center">
|
||||
<div><span style="color:#909399">调查总数</span><br/><span style="font-size:24px;font-weight:bold">{{ stats.total||0 }}</span></div>
|
||||
<div><span style="color:#909399">总体满意度</span><br/><span style="font-size:24px;font-weight:bold;color:#67C23A">{{ stats.avgOverall||'--' }}</span></div>
|
||||
<div><span style="color:#909399">服务评分</span><br/><span style="font-size:24px;font-weight:bold;color:#409EFF">{{ stats.avgService||'--' }}</span></div>
|
||||
<div><span style="color:#909399">环境评分</span><br/><span style="font-size:24px;font-weight:bold;color:#E6A23C">{{ stats.avgEnvironment||'--' }}</span></div>
|
||||
<el-button type="primary" @click="refreshStats">刷新统计</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.departmentName" placeholder="科室" clearable style="width:130px"/>
|
||||
<el-input v-model="q.doctorName" placeholder="医生" clearable style="width:120px"/>
|
||||
<el-select v-model="q.surveyType" placeholder="调查类型" clearable style="width:120px">
|
||||
<el-option label="出院调查" value="DISCHARGE"/><el-option label="门诊调查" value="OUTPATIENT"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="surveyType" label="类型" width="90" align="center"/>
|
||||
<el-table-column prop="departmentName" label="科室" width="120"/>
|
||||
<el-table-column prop="doctorName" label="医生" width="90"/>
|
||||
<el-table-column prop="overallScore" label="总体" width="60" align="center">
|
||||
<template #default="{row}"><span :style="{color:row.overallScore>=4?'#67C23A':row.overallScore>=3?'#E6A23C':'#F56C6C',fontWeight:'bold'}">{{ row.overallScore }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="serviceScore" label="服务" width="60" align="center"/>
|
||||
<el-table-column prop="environmentScore" label="环境" width="60" align="center"/>
|
||||
<el-table-column prop="suggestions" label="建议" min-width="200" show-overflow-tooltip/>
|
||||
<el-table-column prop="surveyDate" label="日期" width="110"/>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template #default="{row}">
|
||||
<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="新增满意度调查" width="500px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="form.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="患者姓名"><el-input v-model="form.patientName"/></el-form-item>
|
||||
<el-form-item label="调查类型"><el-select v-model="form.surveyType"><el-option label="出院调查" value="DISCHARGE"/><el-option label="门诊调查" value="OUTPATIENT"/></el-select></el-form-item>
|
||||
<el-form-item label="科室"><el-input v-model="form.departmentName"/></el-form-item>
|
||||
<el-form-item label="医生"><el-input v-model="form.doctorName"/></el-form-item>
|
||||
<el-form-item label="总体评分"><el-rate v-model="form.overallScore" :max="5"/></el-form-item>
|
||||
<el-form-item label="服务评分"><el-rate v-model="form.serviceScore" :max="5"/></el-form-item>
|
||||
<el-form-item label="环境评分"><el-rate v-model="form.environmentScore" :max="5"/></el-form-item>
|
||||
<el-form-item label="建议"><el-input v-model="form.suggestions" type="textarea" placeholder="患者建议"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dlgVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,getStats,add,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0);const stats=ref({});const dlgVisible=ref(false)
|
||||
const q=ref({pageNo:1,pageSize:20,departmentName:'',doctorName:'',surveyType:''})
|
||||
const form=ref({patientId:null,patientName:'',surveyType:'DISCHARGE',departmentName:'',doctorName:'',overallScore:5,serviceScore:5,environmentScore:5,suggestions:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const refreshStats=async()=>{const r=await getStats({departmentName:q.value.departmentName});stats.value=r.data||{}}
|
||||
const showAdd=()=>{form.value={patientId:null,patientName:'',surveyType:'DISCHARGE',departmentName:'',doctorName:'',overallScore:5,serviceScore:5,environmentScore:5,suggestions:''};dlgVisible.value=true}
|
||||
const submitForm=async()=>{await add(form.value);ElMessage.success('已提交');dlgVisible.value=false;loadData();refreshStats()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData();refreshStats()}
|
||||
onMounted(()=>{loadData();refreshStats()})
|
||||
</script>
|
||||
8
healthlink-his-ui/src/views/followup/task/api.js
Normal file
8
healthlink-his-ui/src/views/followup/task/api.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/followup/task/page',method:'get',params:p})}
|
||||
export function getToday(p){return request({url:'/followup/task/today',method:'get',params:p})}
|
||||
export function getById(id){return request({url:'/followup/task/'+id,method:'get'})}
|
||||
export function add(d){return request({url:'/followup/task/add',method:'post',data:d})}
|
||||
export function execute(d){return request({url:'/followup/task/execute',method:'put',data:d})}
|
||||
export function markAbnormal(id,params){return request({url:'/followup/task/'+id+'/abnormal',method:'put',params})}
|
||||
export function del(id){return request({url:'/followup/task/delete/'+id,method:'delete'})}
|
||||
92
healthlink-his-ui/src/views/followup/task/index.vue
Normal file
92
healthlink-his-ui/src/views/followup/task/index.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">随访任务管理</span></div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px"/>
|
||||
<el-input v-model="q.operatorName" placeholder="随访人" clearable style="width:120px"/>
|
||||
<el-date-picker v-model="q.scheduledDate" placeholder="计划日期" type="date" value-format="YYYY-MM-DD" clearable style="width:150px"/>
|
||||
<el-select v-model="q.result" placeholder="结果" clearable style="width:100px">
|
||||
<el-option label="成功" value="SUCCESS"/><el-option label="未接" value="NO_ANSWER"/>
|
||||
<el-option label="空号" value="WRONG_NUMBER"/><el-option label="失访" value="LOST"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="warning" @click="loadToday">今日任务</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="phone" label="电话" width="120"/>
|
||||
<el-table-column prop="scheduledDate" label="计划日期" width="110"/>
|
||||
<el-table-column prop="actualDate" label="实际日期" width="110"/>
|
||||
<el-table-column prop="contactMethod" label="联系方式" width="90" align="center"/>
|
||||
<el-table-column prop="operatorName" label="随访人" width="90"/>
|
||||
<el-table-column prop="result" label="结果" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag v-if="row.result==='SUCCESS'" type="success" size="small">成功</el-tag>
|
||||
<el-tag v-else-if="row.result" type="warning" size="small">{{ row.result }}</el-tag>
|
||||
<el-tag v-else type="info" size="small">待随访</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="abnormalFlag" label="异常" width="60" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag v-if="row.abnormalFlag" type="danger" size="small">异常</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="nextAction" label="下一步" min-width="120" show-overflow-tooltip/>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button v-if="!row.result" type="primary" link size="small" @click="showExecute(row)">执行随访</el-button>
|
||||
<el-button v-if="!row.result" type="warning" link size="small" @click="showAbnormal(row)">标记异常</el-button>
|
||||
<el-button type="info" link size="small" @click="showDetail(row)">详情</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="execVisible" title="执行随访" width="500px">
|
||||
<el-form :model="execForm" label-width="100px">
|
||||
<el-form-item label="联系方式"><el-input v-model="execForm.contactContent" placeholder="通话/联系内容"/></el-form-item>
|
||||
<el-form-item label="患者状况"><el-input v-model="execForm.patientCondition" type="textarea" placeholder="患者当前状况"/></el-form-item>
|
||||
<el-form-item label="用药依从性">
|
||||
<el-select v-model="execForm.medicationCompliance"><el-option label="完全遵医" value="FULL"/><el-option label="部分遵医" value="PARTIAL"/><el-option label="未遵医" value="NONE"/></el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="症状"><el-input v-model="execForm.symptoms" placeholder="当前症状"/></el-form-item>
|
||||
<el-form-item label="是否复查"><el-switch v-model="execForm.reappointmentFlag"/></el-form-item>
|
||||
<el-form-item v-if="execForm.reappointmentFlag" label="复查日期"><el-date-picker v-model="execForm.reappointmentDate" type="date" value-format="YYYY-MM-DD"/></el-form-item>
|
||||
<el-form-item label="是否转诊"><el-switch v-model="execForm.transferFlag"/></el-form-item>
|
||||
<el-form-item v-if="execForm.transferFlag" label="转诊原因"><el-input v-model="execForm.transferReason" type="textarea"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="execVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitExecute">提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="abnormalVisible" title="标记异常" width="400px">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="下一步行动"><el-input v-model="abnormalNextAction" type="textarea" placeholder="下一步行动方案"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="abnormalVisible=false">取消</el-button>
|
||||
<el-button type="warning" @click="submitAbnormal">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,getToday,execute,markAbnormal,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,patientName:'',operatorName:'',scheduledDate:'',result:''})
|
||||
const execVisible=ref(false);const execForm=ref({taskId:null,patientId:null,contactContent:'',patientCondition:'',medicationCompliance:'FULL',symptoms:'',reappointmentFlag:false,reappointmentDate:'',transferFlag:false,transferReason:''})
|
||||
const abnormalVisible=ref(false);const abnormalNextAction=ref('');let currentTaskId=null
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const loadToday=async()=>{const r=await getToday({});tableData.value=r.data||[];total.value=r.data?.length||0}
|
||||
const showExecute=(row)=>{execForm.value={taskId:row.id,patientId:row.patientId,contactContent:'',patientCondition:'',medicationCompliance:'FULL',symptoms:'',reappointmentFlag:false,reappointmentDate:'',transferFlag:false,transferReason:''};execVisible.value=true}
|
||||
const submitExecute=async()=>{await execute({id:execForm.value.taskId,planId:null,result:'SUCCESS',patientId:execForm.value.patientId,contactContent:execForm.value.contactContent,patientCondition:execForm.value.patientCondition,medicationCompliance:execForm.value.medicationCompliance,symptoms:execForm.value.symptoms,reappointmentFlag:execForm.value.reappointmentFlag,reappointmentDate:execForm.value.reappointmentDate,transferFlag:execForm.value.transferFlag,transferReason:execForm.value.transferReason,operatorName:''});ElMessage.success('随访完成');execVisible.value=false;loadData()}
|
||||
const showAbnormal=(row)=>{currentTaskId=row.id;abnormalNextAction.value='';abnormalVisible.value=true}
|
||||
const submitAbnormal=async()=>{await markAbnormal(currentTaskId,{nextAction:abnormalNextAction.value});ElMessage.success('已标记异常');abnormalVisible.value=false;loadData()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
</script>
|
||||
8
healthlink-his-ui/src/views/pathology/order/api.js
Normal file
8
healthlink-his-ui/src/views/pathology/order/api.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/pathology/order/page',method:'get',params:p})}
|
||||
export function getById(id){return request({url:'/pathology/order/'+id,method:'get'})}
|
||||
export function add(d){return request({url:'/pathology/order/add',method:'post',data:d})}
|
||||
export function update(d){return request({url:'/pathology/order/update',method:'put',data:d})}
|
||||
export function cancel(id){return request({url:'/pathology/order/cancel/'+id,method:'put'})}
|
||||
export function del(id){return request({url:'/pathology/order/delete/'+id,method:'delete'})}
|
||||
export function getStats(p){return request({url:'/pathology/stats/summary',method:'get',params:p})}
|
||||
85
healthlink-his-ui/src/views/pathology/order/index.vue
Normal file
85
healthlink-his-ui/src/views/pathology/order/index.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">病理医嘱管理</span></div>
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px"/>
|
||||
<el-select v-model="q.specimenType" placeholder="标本类型" clearable style="width:120px">
|
||||
<el-option label="组织" value="TISSUE"/><el-option label="细胞" value="CELL"/>
|
||||
<el-option label="穿刺" value="BIOPSY"/><el-option label="冰冻" value="FROZEN"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.urgency" placeholder="紧急程度" clearable style="width:100px">
|
||||
<el-option label="加急" value="URGENT"/><el-option label="常规" value="NORMAL"/>
|
||||
</el-select>
|
||||
<el-select v-model="q.orderStatus" placeholder="状态" clearable style="width:100px">
|
||||
<el-option label="待采集" value="PENDING"/><el-option label="已采集" value="COLLECTED"/>
|
||||
<el-option label="已接收" value="RECEIVED"/><el-option label="已完成" value="COMPLETED"/>
|
||||
<el-option label="已取消" value="CANCELLED"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">开病理医嘱</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="specimenType" label="标本类型" width="90" align="center"/>
|
||||
<el-table-column prop="clinicalDiagnosis" label="临床诊断" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="sampleSite" label="采样部位" width="100"/>
|
||||
<el-table-column prop="urgency" label="紧急" width="70" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.urgency==='URGENT'?'danger':'info'" size="small">{{ row.urgency==='URGENT'?'加急':'常规' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderStatus" label="状态" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.orderStatus==='COMPLETED'?'success':row.orderStatus==='CANCELLED'?'info':row.orderStatus==='URGENT'?'danger':''" size="small">
|
||||
{{ {PENDING:'待采集',COLLECTED:'已采集',RECEIVED:'已接收',COMPLETED:'已完成',CANCELLED:'已取消'}[row.orderStatus]||row.orderStatus }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="applyDoctor" label="申请医生" width="100"/>
|
||||
<el-table-column prop="applyTime" label="申请时间" width="160"/>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button type="primary" link size="small" @click="showEdit(row)">编辑</el-button>
|
||||
<el-button v-if="row.orderStatus==='PENDING'" type="warning" link size="small" @click="cancelItem(row.id)">取消</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="550px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="form.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="患者姓名"><el-input v-model="form.patientName"/></el-form-item>
|
||||
<el-form-item label="就诊ID"><el-input-number v-model="form.encounterId"/></el-form-item>
|
||||
<el-form-item label="标本类型">
|
||||
<el-select v-model="form.specimenType"><el-option label="组织" value="TISSUE"/><el-option label="细胞" value="CELL"/><el-option label="穿刺" value="BIOPSY"/><el-option label="冰冻" value="FROZEN"/></el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="临床诊断"><el-input v-model="form.clinicalDiagnosis" placeholder="临床诊断描述"/></el-form-item>
|
||||
<el-form-item label="采样部位"><el-input v-model="form.sampleSite"/></el-form-item>
|
||||
<el-form-item label="紧急程度"><el-radio-group v-model="form.urgency"><el-radio value="NORMAL">常规</el-radio><el-radio value="URGENT">加急</el-radio></el-radio-group></el-form-item>
|
||||
<el-form-item label="申请医生"><el-input v-model="form.applyDoctor"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dlgVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,add,update,cancel,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0);const dlgVisible=ref(false);const isEdit=ref(false)
|
||||
const q=ref({pageNo:1,pageSize:20,patientName:'',specimenType:'',urgency:'',orderStatus:''})
|
||||
const form=ref({patientId:null,patientName:'',encounterId:null,specimenType:'TISSUE',clinicalDiagnosis:'',sampleSite:'',urgency:'NORMAL',applyDoctor:''})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const showAdd=()=>{isEdit.value=false;form.value={patientId:null,patientName:'',encounterId:null,specimenType:'TISSUE',clinicalDiagnosis:'',sampleSite:'',urgency:'NORMAL',applyDoctor:''};dlgVisible.value=true}
|
||||
const showEdit=(row)=>{isEdit.value=true;form.value={...row};dlgVisible.value=true}
|
||||
const submitForm=async()=>{if(isEdit.value){await update(form.value)}else{await add(form.value)}ElMessage.success('操作成功');dlgVisible.value=false;loadData()}
|
||||
const cancelItem=async(id)=>{await cancel(id);ElMessage.success('已取消');loadData()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
</script>
|
||||
9
healthlink-his-ui/src/views/pathology/report/api.js
Normal file
9
healthlink-his-ui/src/views/pathology/report/api.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/pathology/report/page',method:'get',params:p})}
|
||||
export function getById(id){return request({url:'/pathology/report/'+id,method:'get'})}
|
||||
export function add(d){return request({url:'/pathology/report/add',method:'post',data:d})}
|
||||
export function update(d){return request({url:'/pathology/report/update',method:'put',data:d})}
|
||||
export function submitReport(id){return request({url:'/pathology/report/'+id+'/submit',method:'put'})}
|
||||
export function auditReport(id,d){return request({url:'/pathology/report/'+id+'/audit',method:'put',data:d})}
|
||||
export function finalSign(id,d){return request({url:'/pathology/report/'+id+'/final',method:'put',data:d})}
|
||||
export function del(id){return request({url:'/pathology/report/delete/'+id,method:'delete'})}
|
||||
97
healthlink-his-ui/src/views/pathology/report/index.vue
Normal file
97
healthlink-his-ui/src/views/pathology/report/index.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<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.reportDoctor" placeholder="报告医生" clearable style="width:120px"/>
|
||||
<el-select v-model="q.status" placeholder="报告状态" clearable style="width:110px">
|
||||
<el-option label="草稿" value="DRAFT"/><el-option label="已提交" value="SUBMITTED"/>
|
||||
<el-option label="已审核" value="AUDITED"/><el-option label="已签发" value="SIGNED"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">新建报告</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="orderId" label="医嘱ID" width="90"/>
|
||||
<el-table-column prop="patientId" label="患者ID" width="90"/>
|
||||
<el-table-column prop="diagnosisResult" label="诊断结果" min-width="200" show-overflow-tooltip/>
|
||||
<el-table-column prop="reportDoctor" label="报告医生" width="100"/>
|
||||
<el-table-column prop="auditDoctor" label="审核医生" width="100"/>
|
||||
<el-table-column prop="finalDoctor" label="终审医生" width="100"/>
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status==='SIGNED'?'success':row.status==='DRAFT'?'info':row.status==='AUDITED'?'warning':''" size="small">
|
||||
{{ {DRAFT:'草稿',SUBMITTED:'已提交',AUDITED:'已审核',SIGNED:'已签发'}[row.status]||row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reportTime" label="报告时间" width="160"/>
|
||||
<el-table-column label="操作" width="280" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button type="primary" link size="small" @click="showEdit(row)">编辑</el-button>
|
||||
<el-button v-if="row.status==='DRAFT'" type="warning" link size="small" @click="submitItem(row.id)">提交</el-button>
|
||||
<el-button v-if="row.status==='SUBMITTED'" type="success" link size="small" @click="showAudit(row)">审核</el-button>
|
||||
<el-button v-if="row.status==='AUDITED'" type="danger" link size="small" @click="showFinal(row)">终审签发</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="700px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="医嘱ID"><el-input-number v-model="form.orderId" :min="1"/></el-form-item>
|
||||
<el-form-item label="患者ID"><el-input-number v-model="form.patientId" :min="1"/></el-form-item>
|
||||
<el-form-item label="标本描述"><el-input v-model="form.specimenDesc" type="textarea" placeholder="标本大体描述"/></el-form-item>
|
||||
<el-form-item label="肉眼所见"><el-input v-model="form.macroscopicDesc" type="textarea" placeholder="大体描述"/></el-form-item>
|
||||
<el-form-item label="镜下所见"><el-input v-model="form.microscopicDesc" type="textarea" placeholder="镜下描述"/></el-form-item>
|
||||
<el-form-item label="诊断结果"><el-input v-model="form.diagnosisResult" type="textarea" placeholder="病理诊断结论"/></el-form-item>
|
||||
<el-form-item label="建议"><el-input v-model="form.suggestion" type="textarea" placeholder="诊疗建议"/></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="submitForm">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="auditVisible" title="审核病理报告" width="400px">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="审核医生"><el-input v-model="auditForm.auditDoctor"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="auditVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="doAudit">确认审核</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="finalVisible" title="终审签发" width="400px">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="终审医生"><el-input v-model="finalForm.finalDoctor"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="finalVisible=false">取消</el-button>
|
||||
<el-button type="danger" @click="doFinal">确认签发</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,add,update,submitReport,auditReport,finalSign,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0);const dlgVisible=ref(false);const isEdit=ref(false)
|
||||
const auditVisible=ref(false);const finalVisible=ref(false)
|
||||
const q=ref({pageNo:1,pageSize:20,reportDoctor:'',status:''})
|
||||
const form=ref({orderId:null,patientId:null,specimenDesc:'',macroscopicDesc:'',microscopicDesc:'',diagnosisResult:'',suggestion:'',reportDoctor:''})
|
||||
const auditForm=ref({auditDoctor:''});const finalForm=ref({finalDoctor:''});let currentId=null
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const showAdd=()=>{isEdit.value=false;form.value={orderId:null,patientId:null,specimenDesc:'',macroscopicDesc:'',microscopicDesc:'',diagnosisResult:'',suggestion:'',reportDoctor:''};dlgVisible.value=true}
|
||||
const showEdit=(row)=>{isEdit.value=true;form.value={...row};dlgVisible.value=true}
|
||||
const showAudit=(row)=>{currentId=row.id;auditForm.value={auditDoctor:''};auditVisible.value=true}
|
||||
const showFinal=(row)=>{currentId=row.id;finalForm.value={finalDoctor:''};finalVisible.value=true}
|
||||
const submitForm=async()=>{if(isEdit.value){await update(form.value)}else{await add(form.value)}ElMessage.success('已保存');dlgVisible.value=false;loadData()}
|
||||
const submitItem=async(id)=>{await submitReport(id);ElMessage.success('已提交');loadData()}
|
||||
const doAudit=async()=>{await auditReport(currentId,auditForm.value);ElMessage.success('已审核');auditVisible.value=false;loadData()}
|
||||
const doFinal=async()=>{await finalSign(currentId,finalForm.value);ElMessage.success('已签发');finalVisible.value=false;loadData()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
</script>
|
||||
8
healthlink-his-ui/src/views/pathology/specimen/api.js
Normal file
8
healthlink-his-ui/src/views/pathology/specimen/api.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPage(p){return request({url:'/pathology/specimen/page',method:'get',params:p})}
|
||||
export function getById(id){return request({url:'/pathology/specimen/'+id,method:'get'})}
|
||||
export function getByBarcode(barcode){return request({url:'/pathology/specimen/barcode/'+barcode,method:'get'})}
|
||||
export function add(d){return request({url:'/pathology/specimen/add',method:'post',data:d})}
|
||||
export function receive(d){return request({url:'/pathology/specimen/receive',method:'put',data:d})}
|
||||
export function qualify(id,params){return request({url:'/pathology/specimen/'+id+'/qualify',method:'put',params})}
|
||||
export function del(id){return request({url:'/pathology/specimen/delete/'+id,method:'delete'})}
|
||||
106
healthlink-his-ui/src/views/pathology/specimen/index.vue
Normal file
106
healthlink-his-ui/src/views/pathology/specimen/index.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<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.barcode" placeholder="条码号" clearable style="width:140px"/>
|
||||
<el-input v-model="q.receiver" placeholder="接收人" clearable style="width:120px"/>
|
||||
<el-select v-model="q.isQualified" placeholder="合格状态" clearable style="width:110px">
|
||||
<el-option label="合格" :value="true"/><el-option label="不合格" :value="false"/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd">登记标本</el-button>
|
||||
<el-button type="warning" @click="showScan">扫码接收</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe>
|
||||
<el-table-column prop="orderId" label="医嘱ID" width="90"/>
|
||||
<el-table-column prop="barcode" label="条码" width="140"/>
|
||||
<el-table-column prop="specimenDesc" label="标本描述" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="collectionSite" label="采集部位" width="100"/>
|
||||
<el-table-column prop="fixative" label="固定液" width="90"/>
|
||||
<el-table-column prop="fixativeTime" label="固定时间" width="160"/>
|
||||
<el-table-column prop="receiver" label="接收人" width="90"/>
|
||||
<el-table-column prop="receiveTime" label="接收时间" width="160"/>
|
||||
<el-table-column prop="isQualified" label="合格" width="70" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag v-if="row.isQualified===true" type="success" size="small">合格</el-tag>
|
||||
<el-tag v-else-if="row.isQualified===false" type="danger" size="small">不合格</el-tag>
|
||||
<el-tag v-else type="info" size="small">待检</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="rejectReason" label="不合格原因" width="120" show-overflow-tooltip/>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button v-if="!row.receiver" type="primary" link size="small" @click="receiveItem(row)">接收</el-button>
|
||||
<el-button v-if="row.receiver && row.isQualified===null" type="warning" link size="small" @click="showQualify(row)">质检</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="addVisible" title="登记标本" width="500px">
|
||||
<el-form :model="addForm" label-width="100px">
|
||||
<el-form-item label="医嘱ID"><el-input-number v-model="addForm.orderId" :min="1"/></el-form-item>
|
||||
<el-form-item label="条码号"><el-input v-model="addForm.barcode" placeholder="唯一条码"/></el-form-item>
|
||||
<el-form-item label="标本描述"><el-input v-model="addForm.specimenDesc" type="textarea"/></el-form-item>
|
||||
<el-form-item label="采集部位"><el-input v-model="addForm.collectionSite"/></el-form-item>
|
||||
<el-form-item label="固定液"><el-input v-model="addForm.fixative"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="addVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="submitAdd">登记</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="scanVisible" title="扫码接收标本" width="400px">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="扫码条码"><el-input v-model="scanBarcode" placeholder="输入或扫描条码" @keyup.enter="doScan"/></el-form-item>
|
||||
<el-form-item v-if="scannedSpecimen" label="标本信息">
|
||||
<el-descriptions :column="1" border size="small">
|
||||
<el-descriptions-item label="条码">{{ scannedSpecimen.barcode }}</el-descriptions-item>
|
||||
<el-descriptions-item label="描述">{{ scannedSpecimen.specimenDesc }}</el-descriptions-item>
|
||||
<el-descriptions-item label="采集部位">{{ scannedSpecimen.collectionSite }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form-item>
|
||||
<el-form-item label="接收人"><el-input v-model="scanReceiver"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="scanVisible=false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!scannedSpecimen" @click="doReceive">确认接收</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="qualifyVisible" title="标本质检" width="400px">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="质检结果">
|
||||
<el-radio-group v-model="qualifyResult"><el-radio :value="true">合格</el-radio><el-radio :value="false">不合格</el-radio></el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!qualifyResult" label="不合格原因"><el-input v-model="rejectReason" type="textarea"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="qualifyVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="doQualify">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getPage,add,getByBarcode,receive,qualify,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0)
|
||||
const q=ref({pageNo:1,pageSize:20,barcode:'',receiver:'',isQualified:null})
|
||||
const addVisible=ref(false);const scanVisible=ref(false);const qualifyVisible=ref(false)
|
||||
const addForm=ref({orderId:null,barcode:'',specimenDesc:'',collectionSite:'',fixative:''})
|
||||
const scanBarcode=ref('');const scanReceiver=ref('');const scannedSpecimen=ref(null)
|
||||
const qualifyResult=ref(true);const rejectReason=ref('');let qualifyId=null
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const showAdd=()=>{addForm.value={orderId:null,barcode:'',specimenDesc:'',collectionSite:'',fixative:''};addVisible.value=true}
|
||||
const showScan=()=>{scanBarcode.value='';scanReceiver.value='';scannedSpecimen.value=null;scanVisible.value=true}
|
||||
const showQualify=(row)=>{qualifyId=row.id;qualifyResult.value=true;rejectReason.value='';qualifyVisible.value=true}
|
||||
const submitAdd=async()=>{await add(addForm.value);ElMessage.success('已登记');addVisible.value=false;loadData()}
|
||||
const doScan=async()=>{const r=await getByBarcode(scanBarcode.value);scannedSpecimen.value=r.data||null;if(!scannedSpecimen.value)ElMessage.warning('未找到标本')}
|
||||
const doReceive=async()=>{await receive({...scannedSpecimen.value,receiver:scanReceiver.value});ElMessage.success('已接收');scanVisible.value=false;loadData()}
|
||||
const doQualify=async()=>{await qualify(qualifyId,{qualified:qualifyResult.value,rejectReason:rejectReason.value});ElMessage.success('质检完成');qualifyVisible.value=false;loadData()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData()}
|
||||
onMounted(()=>loadData())
|
||||
</script>
|
||||
Reference in New Issue
Block a user