Merge remote-tracking branch 'origin/develop' into zhaoyun

This commit is contained in:
2026-06-18 16:58:59 +08:00
76 changed files with 3123 additions and 2 deletions

View File

@@ -0,0 +1,157 @@
# Phase 3 里程碑评审报告
> **文档类型**: 里程碑评审
> **版本**: v1.0
> **日期**: 2026-06-18
> **状态**: Phase 3 完成
---
## 1. Phase 3 完成统计
### Sprint 9: EMPI合并/拆分 + 重复检测 + 质控指标 + 随访管理
| 任务 | 状态 | Commit |
|------|------|--------|
| T9.1 患者身份合并/拆分 | ✅ 完成 | `0c0fd3315` |
| T9.2 重复检测+跨系统同步 | ✅ 完成 | `4c3f7e406` |
| T9.3 质控指标自动采集 | ✅ 完成 | `d863e54ff` |
| T9.4 随访管理 | ✅ 完成 | `0e27b9f8d` |
### Sprint 10: 药品追溯 + CSSD + 术前讨论 + 3D重建
> Sprint 10 的功能在更早阶段已有完整实现V31__cssd_3d_reconstruction.sql, V36__drug_traceability.sql 等),无需额外开发。
### Sprint 11: DRG/DIP分析 + 经营分析 + 仪表盘 + 肝肾功能调量
| 任务 | 状态 | Commit |
|------|------|--------|
| T11.1 DRG/DIP分析模块 | ✅ 完成 | `965418dc4` |
| T11.2 经营分析+数据导出 | ✅ 完成 | `abafd4b2a` |
| T11.3 可视化仪表盘 | ✅ 完成 | `632d0828b` |
| T11.4 肝肾功能自动调量 | ✅ 完成 | `c004badf3` |
### Sprint 12: 集成测试
| 任务 | 状态 | Commit |
|------|------|--------|
| Phase 3 集成测试 | ✅ 通过 | `cb8a67cb3` |
| 质量API修复 | ✅ 完成 | `7f315175a` |
---
## 2. 三甲能力覆盖统计
| Phase | Sprint | 覆盖项数 | 说明 |
|-------|--------|---------|------|
| Phase 1 | Sprint 1-4 | 17项 | P0核心达标病历、护理、院感、药品等 |
| Phase 2 | Sprint 5-8 | 15项 | P1评审保障质控、护理增强、实验室、ESB等 |
| Phase 3 | Sprint 9-11 | 12项 | 空壳补全+其他EMPI、质控指标、随访、DRG/DIP等 |
| **合计** | | **44/84项** | **52.4% 覆盖率** |
### Phase 3 新增能力清单
| # | 能力项 | Sprint | 优先级 |
|---|--------|--------|--------|
| 1 | 患者身份合并/拆分 | 9 | P1 |
| 2 | 重复检测+跨系统同步 | 9 | P1 |
| 3 | 质控指标自动采集 | 9 | P2 |
| 4 | 随访管理 | 9 | P1 |
| 5 | DRG/DIP分析模块 | 11 | P1 |
| 6 | 经营分析+数据导出 | 11 | P2 |
| 7 | 可视化仪表盘 | 11 | P1 |
| 8 | 肝肾功能自动调量 | 11 | P2 |
---
## 3. 新增文件统计
### Flyway 数据库迁移
Phase 3 新增迁移脚本V57-V65
| 版本 | 文件 | 说明 |
|------|------|------|
| V57 | blood_transfusion.sql | 输血管理 |
| V58 | clinical_pathway_variance.sql | 临床路径变异 |
| V59 | fix_clinical_pathway_variance_delete_flag.sql | 变异删除标记修复 |
| V60 | critical_value_handle_record.sql | 危急值处理记录 |
| V61 | fix_critical_value_handle_record_columns.sql | 危急值字段修复 |
| V62 | anes_asa_assessment.sql | 麻醉ASA评估 |
| V63 | anes_summary.sql | 麻醉小结 |
| V64 | emr_version_management.sql | 病历版本管理 |
| V65 | mr_hqms_report.sql | 病案质量报告 |
**合计**: 9个迁移脚本V57-V65
### Java 文件
Phase 3 新增 Java 文件约 **60个**,分布在以下模块:
| 模块 | 主要文件 |
|------|---------|
| empi | EmpiMergeController, EmpiMergeService, EmpiDuplicateDetectionService, EmpiSyncService |
| quality | QualityIndicatorController, QualityIndicatorAppService |
| followup | FollowupController, FollowupAppService, FollowupPlanService |
| reportmanage | DrAnalysisController, BusinessAnalysisController, DashboardController |
| rationaldrug | DoseAdjustmentService, DoseAdjustmentController |
| anes | AnesAsaAssessment, AnesSummary |
| emr | EmrVersionManagement |
### Vue 文件
Phase 3 新增 Vue 文件约 **25个**,主要包括:
- `empi/MergeManagement.vue` — 患者合并/拆分管理
- `empi/DuplicateDetection.vue` — 重复检测tab
- `quality/QualityIndicatorPage.vue` — 质控指标页面
- `followup/FollowupManagement.vue` — 随访管理页面
- `reportmanage/DrAnalysisPage.vue` — DRG/DIP分析
- `reportmanage/BusinessAnalysisPage.vue` — 经营分析
- `reportmanage/DashboardPage.vue` — 可视化仪表盘
- `anes/AnesAsaAssessment.vue` — ASA评估
- `anes/AnesSummary.vue` — 麻醉小结
- `emr/EmrVersionManagement.vue` — 病历版本管理
### Mapper XML
Phase 3 新增 Mapper XML 约 **12个**,覆盖 EMPI、质控、随访、报表、麻醉等模块的数据访问层。
---
## 4. 剩余工作Phase 4
### Phase 4: 广西地方特色 (5项)
| # | 功能 | 优先级 | 说明 |
|---|------|--------|------|
| 1 | 壮医/中医 | P1 | 广西壮族自治区特色中医诊疗 |
| 2 | 传染病直报 | P1 | 疾控中心传染病网络直报 |
| 3 | 电子健康卡 | P2 | 居民电子健康卡对接 |
| 4 | 电子票据 | P2 | 财政电子票据系统对接 |
| 5 | DRG/DIP深化 | P2 | DRG/DIP付费精细化管理 |
---
## 5. 风险与建议
### 已识别风险
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| EMPI数据质量 | 合并/拆分依赖高质量患者数据 | 上线前需完成历史数据清洗 |
| DRG/DIP规则时效性 | 分组规则随政策调整 | 预留规则配置接口,支持动态更新 |
| 随访管理覆盖率 | 随访计划需科室配合执行 | 结合绩效考核推动执行 |
| 仪表盘性能 | 大数据量聚合查询可能较慢 | 建议引入Redis缓存热数据 |
### 建议
1. **Phase 4 优先级建议**:传染病直报 > 壮医/中医 > 电子健康卡 > 电子票据 > DRG/DIP深化
2. **性能优化**:对报表类接口增加缓存层,避免实时计算
3. **数据治理**EMPI上线前完成患者数据去重和标准化
4. **培训计划**:质控指标、随访管理模块需对临床科室进行操作培训
---
> 📅 报告生成时间: 2026-06-18
> 📊 基于 git log 统计,共 3816 条 commit 记录

View File

@@ -7,6 +7,7 @@ import com.healthlink.his.check.domain.*;
import com.healthlink.his.check.service.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@@ -24,6 +25,7 @@ public class RadiologyEnhancedController {
// ==================== 紧急报告 ====================
@GetMapping("/urgent-report/page")
@PreAuthorize("@ss.hasPermi('infection:check:list')")
public R<?> getUrgentReportPage(
@RequestParam(value = "patientName", required = false) String patientName,
@RequestParam(value = "notifyStatus", required = false) Integer status,
@@ -37,6 +39,7 @@ public class RadiologyEnhancedController {
}
@PostMapping("/urgent-report/add")
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> addUrgentReport(@RequestBody RadiologyUrgentReport r) {
r.setNotifyStatus(0);
@@ -47,6 +50,7 @@ public class RadiologyEnhancedController {
}
@PostMapping("/urgent-report/notify")
@PreAuthorize("@ss.hasPermi('infection:check:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> notifyReport(@RequestParam Long id) {
RadiologyUrgentReport r = urgentReportService.getById(id);
@@ -60,6 +64,7 @@ public class RadiologyEnhancedController {
// ==================== 检查统计 ====================
@GetMapping("/statistics/page")
@PreAuthorize("@ss.hasPermi('infection:check:list')")
public R<?> getStatisticsPage(
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.web.ehcard.appservice;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.healthlink.his.ehcard.domain.EhcardCard;
public interface IEhcardAppService {
void apply(EhcardCard card);
IPage<EhcardCard> page(String status, String patientName, Integer pageNum, Integer pageSize);
void lock(Long id, String reason);
void unlock(Long id);
}

View File

@@ -0,0 +1,99 @@
package com.healthlink.his.web.ehcard.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.healthlink.his.ehcard.domain.EhcardCard;
import com.healthlink.his.ehcard.domain.EhcardUsageLog;
import com.healthlink.his.ehcard.service.IEhcardCardService;
import com.healthlink.his.ehcard.service.IEhcardUsageLogService;
import com.healthlink.his.web.ehcard.appservice.IEhcardAppService;
import com.core.common.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.UUID;
@Service
public class EhcardAppServiceImpl implements IEhcardAppService {
@Autowired
private IEhcardCardService cardService;
@Autowired
private IEhcardUsageLogService usageLogService;
@Override
public void apply(EhcardCard card) {
card.setCardNo("EHC" + System.currentTimeMillis());
card.setCardType("HEALTH");
card.setStatus("ACTIVE");
card.setApplyTime(new Date());
cardService.save(card);
EhcardUsageLog log = new EhcardUsageLog();
log.setCardId(card.getId());
log.setPatientId(card.getPatientId());
log.setUsageType("APPLY");
log.setUsageDesc("申请电子健康卡");
log.setOperatorName(SecurityUtils.getUsername());
log.setUsageTime(new Date());
usageLogService.save(log);
}
@Override
public IPage<EhcardCard> page(String status, String patientName, Integer pageNum, Integer pageSize) {
LambdaQueryWrapper<EhcardCard> w = new LambdaQueryWrapper<>();
if (StringUtils.hasText(status)) {
w.eq(EhcardCard::getStatus, status);
}
if (StringUtils.hasText(patientName)) {
w.like(EhcardCard::getPatientName, patientName);
}
w.orderByDesc(EhcardCard::getCreateTime);
return cardService.page(new Page<>(pageNum, pageSize), w);
}
@Override
public void lock(Long id, String reason) {
EhcardCard card = cardService.getById(id);
if (card == null) {
throw new RuntimeException("电子健康卡不存在");
}
card.setStatus("LOCKED");
card.setLockTime(new Date());
card.setLockReason(reason);
cardService.updateById(card);
EhcardUsageLog log = new EhcardUsageLog();
log.setCardId(id);
log.setPatientId(card.getPatientId());
log.setUsageType("LOCK");
log.setUsageDesc("锁定: " + reason);
log.setOperatorName(SecurityUtils.getUsername());
log.setUsageTime(new Date());
usageLogService.save(log);
}
@Override
public void unlock(Long id) {
EhcardCard card = cardService.getById(id);
if (card == null) {
throw new RuntimeException("电子健康卡不存在");
}
card.setStatus("ACTIVE");
card.setUnlockTime(new Date());
cardService.updateById(card);
EhcardUsageLog log = new EhcardUsageLog();
log.setCardId(id);
log.setPatientId(card.getPatientId());
log.setUsageType("UNLOCK");
log.setUsageDesc("解锁");
log.setOperatorName(SecurityUtils.getUsername());
log.setUsageTime(new Date());
usageLogService.save(log);
}
}

View File

@@ -0,0 +1,53 @@
package com.healthlink.his.web.ehcard.controller;
import com.core.common.core.domain.AjaxResult;
import com.healthlink.his.ehcard.domain.EhcardCard;
import com.healthlink.his.web.ehcard.appservice.IEhcardAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@Tag(name = "电子健康卡管理")
@RestController
@RequestMapping("/api/v1/ehcard")
public class EhcardController {
@Autowired
private IEhcardAppService ehcardAppService;
@Operation(summary = "申请电子健康卡")
@PreAuthorize("@ss.hasPermi('basicmanage:ehcard:edit')")
@PostMapping("/apply")
public AjaxResult apply(@RequestBody EhcardCard card) {
ehcardAppService.apply(card);
return AjaxResult.success();
}
@Operation(summary = "电子健康卡分页")
@PreAuthorize("@ss.hasPermi('basicmanage:ehcard:list')")
@GetMapping("/page")
public AjaxResult page(@RequestParam(required = false) String status,
@RequestParam(required = false) String patientName,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return AjaxResult.success(ehcardAppService.page(status, patientName, pageNum, pageSize));
}
@Operation(summary = "锁定电子健康卡")
@PreAuthorize("@ss.hasPermi('basicmanage:ehcard:edit')")
@PostMapping("/lock")
public AjaxResult lock(@RequestParam Long id, @RequestParam(required = false) String reason) {
ehcardAppService.lock(id, reason);
return AjaxResult.success();
}
@Operation(summary = "解锁电子健康卡")
@PreAuthorize("@ss.hasPermi('basicmanage:ehcard:edit')")
@PostMapping("/unlock")
public AjaxResult unlock(@RequestParam Long id) {
ehcardAppService.unlock(id);
return AjaxResult.success();
}
}

View File

@@ -0,0 +1,13 @@
package com.healthlink.his.web.einvoice.appservice;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
import java.util.Map;
public interface IEinvoiceAppService {
EinvoiceHeader generate(EinvoiceHeader header);
IPage<EinvoiceHeader> page(String invoiceStatus, String patientName, Integer pageNum, Integer pageSize);
void voidInvoice(Long id, String reason);
Map<String, Object> getReconciliation(Integer pageNum, Integer pageSize);
}

View File

@@ -0,0 +1,82 @@
package com.healthlink.his.web.einvoice.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
import com.healthlink.his.einvoice.service.IEinvoiceHeaderService;
import com.healthlink.his.einvoice.service.IEinvoiceReconciliationService;
import com.healthlink.his.web.einvoice.appservice.IEinvoiceAppService;
import com.core.common.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
public class EinvoiceAppServiceImpl implements IEinvoiceAppService {
@Autowired
private IEinvoiceHeaderService headerService;
@Autowired
private IEinvoiceReconciliationService reconciliationService;
@Override
public EinvoiceHeader generate(EinvoiceHeader header) {
header.setInvoiceNo("EINV" + System.currentTimeMillis());
header.setInvoiceType("ELECTRONIC");
header.setInvoiceStatus("ISSUED");
header.setIssueTime(new Date());
header.setIssuerName(SecurityUtils.getUsername());
headerService.save(header);
return header;
}
@Override
public IPage<EinvoiceHeader> page(String invoiceStatus, String patientName, Integer pageNum, Integer pageSize) {
LambdaQueryWrapper<EinvoiceHeader> w = new LambdaQueryWrapper<>();
if (StringUtils.hasText(invoiceStatus)) {
w.eq(EinvoiceHeader::getInvoiceStatus, invoiceStatus);
}
if (StringUtils.hasText(patientName)) {
w.like(EinvoiceHeader::getPatientName, patientName);
}
w.orderByDesc(EinvoiceHeader::getCreateTime);
return headerService.page(new Page<>(pageNum, pageSize), w);
}
@Override
public void voidInvoice(Long id, String reason) {
EinvoiceHeader header = headerService.getById(id);
if (header == null) {
throw new RuntimeException("发票不存在");
}
header.setInvoiceStatus("VOID");
header.setVoidTime(new Date());
header.setVoidReason(reason);
headerService.updateById(header);
}
@Override
public Map<String, Object> getReconciliation(Integer pageNum, Integer pageSize) {
LambdaQueryWrapper<EinvoiceHeader> w = new LambdaQueryWrapper<>();
w.eq(EinvoiceHeader::getInvoiceStatus, "ISSUED");
w.orderByDesc(EinvoiceHeader::getIssueTime);
IPage<EinvoiceHeader> page = headerService.page(new Page<>(pageNum, pageSize), w);
Map<String, Object> result = new HashMap<>();
result.put("records", page.getRecords());
result.put("total", page.getTotal());
LambdaQueryWrapper<EinvoiceHeader> totalW = new LambdaQueryWrapper<>();
totalW.eq(EinvoiceHeader::getInvoiceStatus, "ISSUED");
long totalCount = headerService.count(totalW);
result.put("totalCount", totalCount);
return result;
}
}

View File

@@ -0,0 +1,54 @@
package com.healthlink.his.web.einvoice.controller;
import com.core.common.core.domain.AjaxResult;
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
import com.healthlink.his.web.einvoice.appservice.IEinvoiceAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "电子票据管理")
@RestController
@RequestMapping("/invoice")
public class EinvoiceController {
@Autowired
private IEinvoiceAppService einvoiceAppService;
@Operation(summary = "生成电子票据")
@PreAuthorize("@ss.hasPermi('basicmanage:invoice:edit')")
@PostMapping("/generate")
public AjaxResult generate(@RequestBody EinvoiceHeader header) {
return AjaxResult.success(einvoiceAppService.generate(header));
}
@Operation(summary = "电子票据分页")
@PreAuthorize("@ss.hasPermi('basicmanage:invoice:list')")
@GetMapping("/page")
public AjaxResult page(@RequestParam(required = false) String invoiceStatus,
@RequestParam(required = false) String patientName,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return AjaxResult.success(einvoiceAppService.page(invoiceStatus, patientName, pageNum, pageSize));
}
@Operation(summary = "作废电子票据")
@PreAuthorize("@ss.hasPermi('basicmanage:invoice:edit')")
@PostMapping("/void")
public AjaxResult voidInvoice(@RequestParam Long id, @RequestParam(required = false) String reason) {
einvoiceAppService.voidInvoice(id, reason);
return AjaxResult.success();
}
@Operation(summary = "票据对账")
@PreAuthorize("@ss.hasPermi('basicmanage:invoice:list')")
@GetMapping("/reconciliation")
public AjaxResult reconciliation(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return AjaxResult.success(einvoiceAppService.getReconciliation(pageNum, pageSize));
}
}

View File

@@ -7,4 +7,7 @@ public interface IEpidemicAppService {
void confirmReport(Long id, String cdcNo);
List<EpidemicReport> getReports(String status);
Map<String, Object> getStatistics(String startDate, String endDate);
EpidemicReport autoScreen(EpidemicReport r);
EpidemicReport saveReport(EpidemicReport r);
Map<String, Object> getReportStats(String startDate, String endDate);
}

View File

@@ -9,6 +9,18 @@ import java.util.*;
@Service
public class EpidemicAppServiceImpl implements IEpidemicAppService {
@Autowired private IEpidemicReportService reportService;
private static final Set<String> NOTIFIABLE_DISEASES = Set.of(
"鼠疫", "霍乱", "传染性非典型肺炎", "艾滋病", "病毒性肝炎", "脊髓灰质炎",
"人感染高致病性禽流感", "麻疹", "流行性出血热", "狂犬病", "流行性乙型脑炎",
"登革热", "炭疽", "细菌性和阿米巴性痢疾", "肺结核", "伤寒和副伤寒",
"流行性脑脊髓膜炎", "百日咳", "白喉", "新生儿破伤风", "猩红热",
"布鲁氏菌病", "淋病", "梅毒", "钩端螺旋体病", "血吸虫病", "疟疾",
"手足口病", "流行性感冒", "流行性腮腺炎", "风疹", "急性出血性结膜炎",
"麻风病", "流行性和地方性斑疹伤寒", "黑热病", "包虫病", "丝虫病",
"感染性腹泻", "甲型H1N1流感", "新型冠状病毒肺炎"
);
@Override
public EpidemicReport report(EpidemicReport r) { r.setStatus("PENDING"); r.setDelFlag("0"); r.setReportDate(new Date()); reportService.save(r); return r; }
@Override
@@ -27,4 +39,40 @@ public class EpidemicAppServiceImpl implements IEpidemicAppService {
r.put("confirmed", reportService.count(new LambdaQueryWrapper<EpidemicReport>().eq(EpidemicReport::getStatus, "CONFIRMED")));
return r;
}
@Override
public EpidemicReport autoScreen(EpidemicReport r) {
boolean match = r.getDiseaseName() != null && NOTIFIABLE_DISEASES.stream()
.anyMatch(d -> r.getDiseaseName().contains(d));
r.setScreenResult(match ? "MATCHED" : "NOT_MATCHED");
r.setScreenLevel(match ? "LEVEL_A" : "NORMAL");
r.setScreenTime(new Date());
if (match && (r.getStatus() == null || "DRAFT".equals(r.getStatus()))) {
r.setStatus("PENDING");
}
r.setDelFlag("0");
r.setReportDate(new Date());
reportService.save(r);
return r;
}
@Override
public EpidemicReport saveReport(EpidemicReport r) {
if (r.getId() == null) {
r.setStatus("DRAFT"); r.setDelFlag("0"); r.setReportDate(new Date());
reportService.save(r);
} else {
reportService.updateById(r);
}
return r;
}
@Override
public Map<String, Object> getReportStats(String startDate, String endDate) {
Map<String, Object> r = new HashMap<>();
LambdaQueryWrapper<EpidemicReport> base = new LambdaQueryWrapper<EpidemicReport>().eq(EpidemicReport::getDelFlag, "0");
r.put("total", reportService.count(base));
r.put("pending", reportService.count(new LambdaQueryWrapper<EpidemicReport>().eq(EpidemicReport::getStatus, "PENDING").eq(EpidemicReport::getDelFlag, "0")));
r.put("confirmed", reportService.count(new LambdaQueryWrapper<EpidemicReport>().eq(EpidemicReport::getStatus, "CONFIRMED").eq(EpidemicReport::getDelFlag, "0")));
r.put("screenMatched", reportService.count(new LambdaQueryWrapper<EpidemicReport>().eq(EpidemicReport::getScreenResult, "MATCHED").eq(EpidemicReport::getDelFlag, "0")));
r.put("screenNormal", reportService.count(new LambdaQueryWrapper<EpidemicReport>().eq(EpidemicReport::getScreenResult, "NOT_MATCHED").eq(EpidemicReport::getDelFlag, "0")));
return r;
}
}

View File

@@ -5,16 +5,32 @@ import com.healthlink.his.web.epidemic.appservice.IEpidemicAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@Tag(name = "传染病直报") @RestController @RequestMapping("/api/v1/epidemic")
public class EpidemicController {
@Autowired private IEpidemicAppService epidemicAppService;
@Operation(summary = "上报") @PostMapping("/report")
@PreAuthorize("hasAuthority('epidemic:edit')")
public AjaxResult report(@RequestBody EpidemicReport r) { return AjaxResult.success(epidemicAppService.report(r)); }
@Operation(summary = "确认") @PutMapping("/confirm/{id}")
@PreAuthorize("hasAuthority('epidemic:edit')")
public AjaxResult confirm(@PathVariable Long id, @RequestParam String cdcNo) { epidemicAppService.confirmReport(id, cdcNo); return AjaxResult.success(); }
@Operation(summary = "列表") @GetMapping("/list")
@PreAuthorize("hasAuthority('epidemic:list')")
public AjaxResult list(@RequestParam(required = false) String status) { return AjaxResult.success(epidemicAppService.getReports(status)); }
@Operation(summary = "统计") @GetMapping("/statistics")
@PreAuthorize("hasAuthority('epidemic:list')")
public AjaxResult statistics(@RequestParam(required = false) String s, @RequestParam(required = false) String e) { return AjaxResult.success(epidemicAppService.getStatistics(s, e)); }
@Operation(summary = "自动筛查") @PostMapping("/auto-screen")
@PreAuthorize("hasAuthority('epidemic:edit')")
public AjaxResult autoScreen(@RequestBody EpidemicReport r) { return AjaxResult.success(epidemicAppService.autoScreen(r)); }
@Operation(summary = "保存报告") @PostMapping("/save")
@PreAuthorize("hasAuthority('epidemic:edit')")
public AjaxResult saveReport(@RequestBody EpidemicReport r) { return AjaxResult.success(epidemicAppService.saveReport(r)); }
@Operation(summary = "报告统计") @GetMapping("/report-stats")
@PreAuthorize("hasAuthority('epidemic:list')")
public AjaxResult reportStats(@RequestParam(required = false) String s, @RequestParam(required = false) String e) { return AjaxResult.success(epidemicAppService.getReportStats(s, e)); }
}

View File

@@ -7,6 +7,7 @@ import com.healthlink.his.lab.domain.LabResultComparison;
import com.healthlink.his.lab.service.ILabResultComparisonService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@@ -20,6 +21,7 @@ public class LabHistoryController {
private final ILabResultComparisonService comparisonService;
@GetMapping("/compare")
@PreAuthorize("@ss.hasPermi('infection:lab:list')")
public R<?> compareResults(
@RequestParam Long patientId,
@RequestParam(required = false) String testItem) {
@@ -31,6 +33,7 @@ public class LabHistoryController {
}
@PostMapping("/add")
@PreAuthorize("@ss.hasPermi('infection:lab:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> addResult(@RequestBody LabResultComparison result) {
result.setCreateTime(new java.util.Date());
@@ -39,6 +42,7 @@ public class LabHistoryController {
}
@GetMapping("/trend")
@PreAuthorize("@ss.hasPermi('infection:lab:list')")
public R<?> getTrend(
@RequestParam Long patientId,
@RequestParam String testItem) {

View File

@@ -15,6 +15,7 @@ import com.healthlink.his.prescription.domain.PrescriptionInterceptLog;
import com.healthlink.his.prescription.service.IPrescriptionInterceptLogService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@@ -105,6 +106,7 @@ public class OutpatientEnhancedController {
// ==================== 出院小结 ====================
@GetMapping("/discharge/page")
@PreAuthorize("hasAuthority('outpatient:discharge:list')")
public R<?> getDischargePage(
@RequestParam(value = "status", required = false) Integer status,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@@ -116,6 +118,7 @@ public class OutpatientEnhancedController {
}
@PostMapping("/discharge/add")
@PreAuthorize("hasAuthority('outpatient:discharge:add')")
@Transactional(rollbackFor = Exception.class)
public R<?> addDischarge(@RequestBody DischargeSummary summary) {
summary.setStatus(0);
@@ -125,6 +128,7 @@ public class OutpatientEnhancedController {
}
@PostMapping("/discharge/complete")
@PreAuthorize("hasAuthority('outpatient:discharge:edit')")
@Transactional(rollbackFor = Exception.class)
public R<?> completeDischarge(@RequestParam Long id) {
DischargeSummary s = dischargeService.getById(id);

View File

@@ -8,4 +8,7 @@ public interface ITcmAppService {
TcmConstitutionAssessment assess(TcmConstitutionAssessment a);
List<TcmConstitutionAssessment> getAssessmentsByEncounter(Long encounterId);
Map<String, Object> getStatistics();
TcmDiagnosis saveDiagnosis(TcmDiagnosis d);
List<TcmDiagnosis> getDiagnosesByEncounter(Long encounterId);
TcmConstitutionAssessment saveConstitution(TcmConstitutionAssessment a);
}

View File

@@ -10,6 +10,7 @@ import java.util.*;
public class TcmAppServiceImpl implements ITcmAppService {
@Autowired private ITcmPrescriptionService prescriptionService;
@Autowired private ITcmConstitutionAssessmentService assessmentService;
@Autowired private ITcmDiagnosisService diagnosisService;
@Override
public List<TcmPrescription> getPrescriptions(String type) {
@@ -33,4 +34,13 @@ public class TcmAppServiceImpl implements ITcmAppService {
r.put("totalAssessments", assessmentService.count(new LambdaQueryWrapper<TcmConstitutionAssessment>().eq(TcmConstitutionAssessment::getDeleteFlag, "0")));
return r;
}
@Override
public TcmDiagnosis saveDiagnosis(TcmDiagnosis d) { d.setDeleteFlag("0"); d.setEnabled("1"); diagnosisService.save(d); return d; }
@Override
public List<TcmDiagnosis> getDiagnosesByEncounter(Long encounterId) {
return diagnosisService.list(new LambdaQueryWrapper<TcmDiagnosis>()
.eq(TcmDiagnosis::getEncounterId, encounterId).eq(TcmDiagnosis::getDeleteFlag, "0"));
}
@Override
public TcmConstitutionAssessment saveConstitution(TcmConstitutionAssessment a) { a.setDeleteFlag("0"); assessmentService.save(a); return a; }
}

View File

@@ -5,19 +5,37 @@ import com.healthlink.his.web.tcm.appservice.ITcmAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "壮医中医特色") @RestController @RequestMapping("/api/v1/tcm")
public class TcmController {
@Autowired private ITcmAppService tcmAppService;
@Operation(summary = "中医方剂列表") @GetMapping("/prescriptions")
@PreAuthorize("hasAuthority('tcm:edit')")
public AjaxResult prescriptions(@RequestParam(required = false) String type) { return AjaxResult.success(tcmAppService.getPrescriptions(type)); }
@Operation(summary = "新增方剂") @PostMapping("/prescription")
@PreAuthorize("hasAuthority('tcm:edit')")
public AjaxResult savePrescription(@RequestBody TcmPrescription p) { return AjaxResult.success(tcmAppService.savePrescription(p)); }
@Operation(summary = "统计") @GetMapping("/statistics")
@PreAuthorize("hasAuthority('tcm:list')")
public AjaxResult statistics() { return AjaxResult.success(tcmAppService.getStatistics()); }
@Operation(summary = "体质辨识") @PostMapping("/constitution")
@PreAuthorize("hasAuthority('tcm:edit')")
public AjaxResult assess(@RequestBody TcmConstitutionAssessment a) { return AjaxResult.success(tcmAppService.assess(a)); }
@Operation(summary = "体质辨识记录") @GetMapping("/constitution/encounter/{encounterId}")
@PreAuthorize("hasAuthority('tcm:list')")
public AjaxResult getAssessments(@PathVariable Long encounterId) { return AjaxResult.success(tcmAppService.getAssessmentsByEncounter(encounterId)); }
@Operation(summary = "统计") @GetMapping("/statistics")
public AjaxResult statistics() { return AjaxResult.success(tcmAppService.getStatistics()); }
@Operation(summary = "保存体质辨识") @PostMapping("/constitution/save")
@PreAuthorize("hasAuthority('tcm:edit')")
public AjaxResult saveConstitution(@RequestBody TcmConstitutionAssessment a) { return AjaxResult.success(tcmAppService.saveConstitution(a)); }
@Operation(summary = "保存诊断") @PostMapping("/diagnosis")
@PreAuthorize("hasAuthority('tcm:edit')")
public AjaxResult saveDiagnosis(@RequestBody TcmDiagnosis d) { return AjaxResult.success(tcmAppService.saveDiagnosis(d)); }
@Operation(summary = "诊断记录") @GetMapping("/diagnosis/encounter/{encounterId}")
@PreAuthorize("hasAuthority('tcm:list')")
public AjaxResult getDiagnoses(@PathVariable Long encounterId) { return AjaxResult.success(tcmAppService.getDiagnosesByEncounter(encounterId)); }
}

View File

@@ -0,0 +1,10 @@
package com.healthlink.his.web.ybmanage.appservice;
import com.core.common.core.domain.R;
import java.util.Map;
public interface IDrgDeepAppService {
R<?> analyzeDrg(Map<String, Object> params);
R<?> getCostAlert(Map<String, Object> params);
R<?> getOptimization(Map<String, Object> params);
}

View File

@@ -0,0 +1,160 @@
package com.healthlink.his.web.ybmanage.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R;
import com.healthlink.his.crossmodule.domain.DrgCostAlert;
import com.healthlink.his.crossmodule.domain.DrgPerformance;
import com.healthlink.his.crossmodule.service.IDrgCostAlertService;
import com.healthlink.his.crossmodule.service.IDrgPerformanceService;
import com.healthlink.his.mrhomepage.domain.MrDrgGrouping;
import com.healthlink.his.mrhomepage.service.IMrDrgGroupingService;
import com.healthlink.his.web.ybmanage.appservice.IDrgDeepAppService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
public class DrgDeepAppServiceImpl implements IDrgDeepAppService {
private final IMrDrgGroupingService drgGroupingService;
private final IDrgPerformanceService drgPerformanceService;
private final IDrgCostAlertService drgCostAlertService;
@Override
public R<?> analyzeDrg(Map<String, Object> params) {
String startDate = (String) params.get("startDate");
String endDate = (String) params.get("endDate");
String groupingType = (String) params.get("groupingType");
LambdaQueryWrapper<MrDrgGrouping> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(groupingType), MrDrgGrouping::getGroupingType, groupingType)
.eq(MrDrgGrouping::getIsValid, true);
if (StringUtils.hasText(startDate)) {
w.ge(MrDrgGrouping::getDischargeDate, startDate);
}
if (StringUtils.hasText(endDate)) {
w.le(MrDrgGrouping::getDischargeDate, endDate);
}
List<MrDrgGrouping> list = drgGroupingService.list(w);
Map<String, Object> result = new HashMap<>();
result.put("totalCases", list.size());
BigDecimal totalCost = BigDecimal.ZERO;
BigDecimal totalInsurance = BigDecimal.ZERO;
int totalLos = 0;
for (MrDrgGrouping g : list) {
if (g.getTotalCost() != null) totalCost = totalCost.add(g.getTotalCost());
if (g.getInsurancePayment() != null) totalInsurance = totalInsurance.add(g.getInsurancePayment());
if (g.getLosDays() != null) totalLos += g.getLosDays();
}
int count = list.size();
result.put("totalCost", totalCost);
result.put("totalInsurance", totalInsurance);
result.put("avgCost", count > 0 ? totalCost.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
result.put("avgLos", count > 0 ? BigDecimal.valueOf(totalLos).divide(BigDecimal.valueOf(count), 1, RoundingMode.HALF_UP) : BigDecimal.ZERO);
result.put("insuranceRate", totalCost.compareTo(BigDecimal.ZERO) > 0
? totalInsurance.multiply(BigDecimal.valueOf(100)).divide(totalCost, 2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
Map<String, Integer> typeCount = list.stream()
.collect(Collectors.groupingBy(
g -> g.getGroupingType() != null ? g.getGroupingType() : "UNKNOWN",
Collectors.summingInt(g -> 1)));
result.put("typeDistribution", typeCount);
Map<String, BigDecimal> drgCostMap = new LinkedHashMap<>();
Map<String, Integer> drgCountMap = new LinkedHashMap<>();
for (MrDrgGrouping g : list) {
String code = g.getDrgCode();
if (StringUtils.hasText(code)) {
drgCostMap.merge(code, g.getTotalCost() != null ? g.getTotalCost() : BigDecimal.ZERO, BigDecimal::add);
drgCountMap.merge(code, 1, Integer::sum);
}
}
List<Map<String, Object>> topDrg = drgCostMap.entrySet().stream()
.sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
.limit(10)
.map(e -> {
Map<String, Object> item = new HashMap<>();
item.put("drgCode", e.getKey());
item.put("count", drgCountMap.getOrDefault(e.getKey(), 0));
item.put("totalCost", e.getValue());
return item;
})
.collect(Collectors.toList());
result.put("topDrgByCost", topDrg);
return R.ok(result);
}
@Override
public R<?> getCostAlert(Map<String, Object> params) {
String alertLevel = (String) params.get("alertLevel");
String handleStatus = (String) params.get("handleStatus");
LambdaQueryWrapper<DrgCostAlert> w = new LambdaQueryWrapper<>();
if (StringUtils.hasText(alertLevel)) {
w.eq(DrgCostAlert::getAlertLevel, alertLevel);
}
if (StringUtils.hasText(handleStatus)) {
w.eq(DrgCostAlert::getHandleStatus, handleStatus);
}
w.orderByDesc(DrgCostAlert::getCreateTime);
List<DrgCostAlert> list = drgCostAlertService.list(w);
Map<String, Object> result = new HashMap<>();
result.put("total", list.size());
long unhandled = list.stream().filter(a -> "PENDING".equals(a.getHandleStatus())).count();
long highLevel = list.stream().filter(a -> "HIGH".equals(a.getAlertLevel())).count();
result.put("unhandledCount", unhandled);
result.put("highLevelCount", highLevel);
result.put("records", list);
return R.ok(result);
}
@Override
public R<?> getOptimization(Map<String, Object> params) {
LambdaQueryWrapper<DrgPerformance> w = new LambdaQueryWrapper<>();
w.orderByDesc(DrgPerformance::getStatMonth);
List<DrgPerformance> perfList = drgPerformanceService.list(w);
Map<String, Object> result = new HashMap<>();
if (!perfList.isEmpty()) {
DrgPerformance latest = perfList.get(0);
result.put("latestMonth", latest.getStatMonth());
result.put("totalCases", latest.getTotalCases());
result.put("drgCoveredRate", latest.getDrgCoveredRate());
result.put("avgWeight", latest.getAvgWeight());
result.put("avgCost", latest.getAvgCost());
result.put("costControlRate", latest.getCostControlRate());
result.put("cmiValue", latest.getCmiValue());
result.put("avgLos", latest.getAvgLos());
}
List<Map<String, Object>> trend = new ArrayList<>();
for (DrgPerformance p : perfList) {
Map<String, Object> item = new HashMap<>();
item.put("month", p.getStatMonth());
item.put("totalCases", p.getTotalCases());
item.put("avgWeight", p.getAvgWeight());
item.put("avgCost", p.getAvgCost());
item.put("costControlRate", p.getCostControlRate());
item.put("cmiValue", p.getCmiValue());
trend.add(item);
}
result.put("trend", trend);
return R.ok(result);
}
}

View File

@@ -0,0 +1,41 @@
package com.healthlink.his.web.ybmanage.controller;
import com.core.common.core.domain.AjaxResult;
import com.healthlink.his.web.ybmanage.appservice.IDrgDeepAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "DRG/DIP深化分析")
@RestController
@RequestMapping("/ybmanage/drg-deep")
public class DrgDeepController {
@Autowired
private IDrgDeepAppService drgDeepAppService;
@Operation(summary = "DRG分组分析")
@PreAuthorize("@ss.hasPermi('ybmanage:drgdeep:edit')")
@PostMapping("/analyze")
public AjaxResult analyzeDrg(@RequestBody Map<String, Object> params) {
return AjaxResult.success(drgDeepAppService.analyzeDrg(params).getData());
}
@Operation(summary = "费用预警查询")
@PreAuthorize("@ss.hasPermi('ybmanage:drgdeep:list')")
@PostMapping("/cost-alert")
public AjaxResult getCostAlert(@RequestBody Map<String, Object> params) {
return AjaxResult.success(drgDeepAppService.getCostAlert(params).getData());
}
@Operation(summary = "优化建议")
@PreAuthorize("@ss.hasPermi('ybmanage:drgdeep:list')")
@PostMapping("/optimization")
public AjaxResult getOptimization(@RequestBody Map<String, Object> params) {
return AjaxResult.success(drgDeepAppService.getOptimization(params).getData());
}
}

View File

@@ -0,0 +1,338 @@
-- V66__update_menu_icons.sql
-- 更新菜单图标 - 根据菜单功能名称匹配合适的图标
-- 仅使用 src/assets/icons/svg/ 目录下实际存在的图标
SET search_path TO healthlink_his;
-- ========== 一级菜单(顶级目录)==========
UPDATE sys_menu SET icon = 'system' WHERE menu_id = 1; -- 系统管理
UPDATE sys_menu SET icon = 'monitor' WHERE menu_id = 2; -- 系统监控
UPDATE sys_menu SET icon = 'tool' WHERE menu_id = 3; -- 系统工具
UPDATE sys_menu SET icon = 'log' WHERE menu_id = 108; -- 日志管理
UPDATE sys_menu SET icon = 'inpatient' WHERE menu_id = 235; -- 住院管理
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 268; -- 药库管理
UPDATE sys_menu SET icon = 'outpatient' WHERE menu_id = 270; -- 门诊管理
UPDATE sys_menu SET icon = 'inventory' WHERE menu_id = 313; -- 库房管理
UPDATE sys_menu SET icon = 'drug-dispensing' WHERE menu_id = 342; -- 发药管理
UPDATE sys_menu SET icon = 'finance' WHERE menu_id = 350; -- 财务管理
UPDATE sys_menu SET icon = 'report' WHERE menu_id = 360; -- 报表管理
UPDATE sys_menu SET icon = 'insurance' WHERE menu_id = 386; -- 医保管理
UPDATE sys_menu SET icon = 'connection' WHERE menu_id = 418; -- 接口管理
UPDATE sys_menu SET icon = 'workflow' WHERE menu_id = 2020; -- 流程管理
UPDATE sys_menu SET icon = 'task' WHERE menu_id = 2023; -- 任务管理
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 2078; -- 收费工作站
UPDATE sys_menu SET icon = 'pharmacy' WHERE menu_id = 2079; -- 药房管理
UPDATE sys_menu SET icon = 'tool' WHERE menu_id = 2083; -- 维护系统
UPDATE sys_menu SET icon = 'doctor' WHERE menu_id = 2084; -- 门诊医生工作站
UPDATE sys_menu SET icon = 'appointment' WHERE menu_id = 2092; -- 预约管理
UPDATE sys_menu SET icon = 'triage' WHERE menu_id = 2110; -- 分诊排队管理
UPDATE sys_menu SET icon = 'surgery' WHERE menu_id = 2119; -- 手术管理
UPDATE sys_menu SET icon = 'user' WHERE menu_id = 2140; -- 患者管理
UPDATE sys_menu SET icon = 'consultation' WHERE menu_id = 2147; -- 会诊管理
UPDATE sys_menu SET icon = 'report' WHERE menu_id = 2159; -- 疾病报告管理
UPDATE sys_MENU SET icon = 'infection' WHERE menu_id = 10001; -- 院感管理
UPDATE sys_menu SET icon = 'log' WHERE menu_id = 10011; -- 药品追溯管理
UPDATE sys_menu SET icon = 'edit' WHERE menu_id = 10021; -- 电子签名管理
UPDATE sys_menu SET icon = 'alert' WHERE menu_id = 10031; -- 危急值管理
UPDATE sys_menu SET icon = 'peoples' WHERE menu_id = 10041; -- 患者主索引(EMPI)
UPDATE sys_menu SET icon = 'peoples' WHERE menu_id = 10051; -- 质量管理
UPDATE sys_menu SET icon = 'shopping' WHERE menu_id = 10061; -- 手术安全核查
UPDATE sys_menu SET icon = 'guide' WHERE menu_id = 10071; -- 临床路径
UPDATE sys_menu SET icon = 'anesthesia' WHERE menu_id = 20001; -- 麻醉管理
UPDATE sys_menu SET icon = 'medication' WHERE menu_id = 20011; -- 合理用药
UPDATE sys_menu SET icon = 'emergency' WHERE menu_id = 20021; -- 急诊管理
UPDATE sys_menu SET icon = 'nursing' WHERE menu_id = 20031; -- 护理管理
UPDATE sys_menu SET icon = 'pathology' WHERE menu_id = 20041; -- 病理管理
UPDATE sys_menu SET icon = 'medical-record' WHERE menu_id = 20051; -- 病案管理
UPDATE sys_menu SET icon = 'radiology' WHERE menu_id = 20061; -- 影像管理
UPDATE sys_menu SET icon = 'follow-up' WHERE menu_id = 20071; -- 随访管理
UPDATE sys_menu SET icon = 'monitor' WHERE menu_id = 20081; -- ESB管理
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20091; -- 检查检验
UPDATE sys_menu SET icon = 'consent' WHERE menu_id = 20101; -- 知情同意管理
UPDATE sys_menu SET icon = 'order' WHERE menu_id = 20111; -- 医嘱闭环
UPDATE sys_menu SET icon = 'audit' WHERE menu_id = 20131; -- 审核管理
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20141; -- 跨模块协同
UPDATE sys_menu SET icon = 'sample' WHERE menu_id = 20151; -- 标本管理
UPDATE sys_menu SET icon = 'standard' WHERE menu_id = 20161; -- FHIR/CDA标准
UPDATE sys_menu SET icon = 'emr' WHERE menu_id = 20201; -- 电子病历管理
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20211; -- 检验增强
-- ========== 二级菜单图标更新 ==========
-- 系统管理子菜单
UPDATE sys_menu SET icon = 'user' WHERE menu_id = 100; -- 用户管理
UPDATE sys_menu SET icon = 'peoples' WHERE menu_id = 101; -- 角色管理
UPDATE sys_menu SET icon = 'post' WHERE menu_id = 104; -- 岗位管理
UPDATE sys_menu SET icon = 'system' WHERE menu_id = 102; -- 菜单管理
UPDATE sys_menu SET icon = 'tree-table' WHERE menu_id = 103; -- 部门管理
UPDATE sys_menu SET icon = 'dict' WHERE menu_id = 105; -- 字典管理
UPDATE sys_menu SET icon = 'edit' WHERE menu_id = 106; -- 参数设置
UPDATE sys_menu SET icon = 'message' WHERE menu_id = 107; -- 通知公告
-- 系统监控子菜单
UPDATE sys_menu SET icon = 'online' WHERE menu_id = 109; -- 在线用户
UPDATE sys_menu SET icon = 'job' WHERE menu_id = 110; -- 定时任务
UPDATE sys_menu SET icon = 'druid' WHERE menu_id = 111; -- 数据监控
UPDATE sys_menu SET icon = 'server' WHERE menu_id = 112; -- 服务监控
UPDATE sys_menu SET icon = 'monitor' WHERE menu_id = 113; -- 缓存监控
UPDATE sys_menu SET icon = 'log' WHERE menu_id = 2104; -- 监控日志
-- 系统工具子菜单
UPDATE sys_menu SET icon = 'form' WHERE menu_id = 115; -- 表单构建
UPDATE sys_menu SET icon = 'code' WHERE menu_id = 116; -- 代码生成
UPDATE sys_menu SET icon = 'swagger' WHERE menu_id = 117; -- 系统接口
-- 日志管理子菜单
UPDATE sys_menu SET icon = 'log' WHERE menu_id = 500; -- 操作日志
UPDATE sys_menu SET icon = 'log' WHERE menu_id = 501; -- 登录日志
-- 住院管理子菜单
UPDATE sys_menu SET icon = 'doctor' WHERE menu_id = 288; -- 住院医生工作站
UPDATE sys_menu SET icon = 'nurse' WHERE menu_id = 295; -- 住院护士站
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 307; -- 住院收费工作站
UPDATE sys_menu SET icon = 'doctor' WHERE menu_id = 20171; -- 住院医生增强
UPDATE sys_menu SET icon = 'nurse' WHERE menu_id = 20181; -- 住院护士增强
UPDATE sys_menu SET icon = 'hospital' WHERE menu_id = 20221; -- 住院辅助
-- 药库管理子菜单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 2049; -- 药库订货单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 2050; -- 药库退货单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 2051; -- 药库进货单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 2053; -- 药库出库单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 2054; -- 药库退库单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 2055; -- 药库损益单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 2056; -- 药库盘点单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 2058; -- 库房单据管理
-- 门诊管理子菜单
UPDATE sys_menu SET icon = 'doctor' WHERE menu_id = 269; -- 门诊工作站
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 277; -- 医技工作站
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 282; -- 门诊收费工作站
-- 门诊工作站子菜单
UPDATE sys_MENU SET icon = 'drug' WHERE menu_id = 272; -- 门诊退药
UPDATE sys_menu SET icon = 'registration' WHERE menu_id = 274; -- 门诊退号
-- 门诊收费工作站子菜单
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 283; -- 门诊收费
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 284; -- 门诊退费
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 285; -- 门诊划价
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 287; -- 收费详情查询
-- 药房管理子菜单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 343; -- 门诊发药
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 344; -- 门诊退药
-- 住院护士站子菜单
UPDATE sys_menu SET icon = 'nurse' WHERE menu_id = 296; -- 门户
UPDATE sys_menu SET icon = 'admission' WHERE menu_id = 297; -- 入出转管理
UPDATE sys_menu SET icon = 'nursing' WHERE menu_id = 298; -- 护理记录
UPDATE sys_menu SET icon = 'vital-signs' WHERE menu_id = 299; -- 三测单
UPDATE sys_menu SET icon = 'order' WHERE menu_id = 2061; -- 医嘱执行
UPDATE sys_menu SET icon = 'order' WHERE menu_id = 2064; -- 医嘱校对
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 301; -- 汇总发药申请
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 302; -- 住院记账
UPDATE sys_menu SET icon = 'allergy' WHERE menu_id = 303; -- 皮试管理
UPDATE sys_menu SET icon = 'order' WHERE menu_id = 276; -- 医嘱查看与打印
UPDATE sys_menu SET icon = 'discharge' WHERE menu_id = 304; -- 出院管理
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 305; -- 退药管理
UPDATE sys_menu SET icon = 'surgery' WHERE menu_id = 306; -- 手术记录
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 2062; -- 住院领药
-- 住院收费工作站子菜单
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 308; -- 费用管理
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 309; -- 住院费用结算
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 311; -- 住院收费详情
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 312; -- 中途结算
-- 财务管理子菜单
UPDATE sys_menu SET icon = 'finance' WHERE menu_id = 354; -- 日结结算单管理
UPDATE sys_menu SET icon = 'finance' WHERE menu_id = 356; -- 药房对账
UPDATE sys_menu SET icon = 'finance' WHERE menu_id = 357; -- 库房审批
UPDATE sys_menu SET icon = 'insurance' WHERE menu_id = 358; -- 医保结算
UPDATE sys_menu SET icon = 'insurance' WHERE menu_id = 359; -- 医保对账
-- 报表管理子菜单
UPDATE sys_menu SET icon = 'report' WHERE menu_id = 213; -- 门诊就诊记录
UPDATE sys_menu SET icon = 'report' WHERE menu_id = 208; -- 项目定价
UPDATE sys_menu SET icon = 'report' WHERE menu_id = 262; -- 门诊收费报表
UPDATE sys_menu SET icon = 'report' WHERE menu_id = 363; -- 费用整体查询
-- 医保管理子菜单
UPDATE sys_menu SET icon = 'insurance' WHERE menu_id = 390; -- 省医保
UPDATE sys_menu SET icon = 'insurance' WHERE menu_id = 391; -- 市医保
UPDATE sys_menu SET icon = 'insurance' WHERE menu_id = 392; -- 医保对账
UPDATE sys_menu SET icon = 'insurance' WHERE menu_id = 394; -- 医保结算
-- 检查检验子菜单
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20092; -- 检查申请
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20093; -- 检查仪器
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20094; -- LIS配置
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20095; -- 检验组套
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20096; -- 标本采集
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20097; -- 标本类型
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20098; -- 观察项目
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20099; -- 检验增强
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20100; -- 检验ICD10
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20102; -- 检验预约
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20103; -- 检验路径
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20104; -- 参考范围
UPDATE sys_menu SET icon = 'radiology' WHERE menu_id = 20105; -- 放射报告
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 20106; -- 检验历史
-- 院感管理子菜单
UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10002; -- 感染病例监测
UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10003; -- 手卫生监测
UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10004; -- 环境监测
UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10005; -- 抗菌药物使用
UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10006; -- 多重耐药菌
UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10007; -- 职业暴露
UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10008; -- 疫情预警
UPDATE sys_menu SET icon = 'infection' WHERE menu_id = 10009; -- 目标性监测
-- 药品追溯管理子菜单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 10012; -- 追溯码管理
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 10013; -- 批次管理
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 10014; -- 扫码记录
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 10015; -- 追溯预警
-- 麻醉管理子菜单
UPDATE sys_menu SET icon = 'anesthesia' WHERE menu_id = 20002; -- 麻醉记录
UPDATE sys_menu SET icon = 'anesthesia' WHERE menu_id = 20003; -- 麻醉增强
-- 合理用药子菜单
UPDATE sys_menu SET icon = 'medication' WHERE menu_id = 20012; -- 抗菌药物规则
UPDATE sys_menu SET icon = 'medication' WHERE menu_id = 20013; -- 药物相互作用
UPDATE sys_menu SET icon = 'medication' WHERE menu_id = 20014; -- 审核日志
UPDATE sys_menu SET icon = 'medication' WHERE menu_id = 20015; -- 合理用药统计
-- 急诊管理子菜单
UPDATE sys_menu SET icon = 'emergency' WHERE menu_id = 20022; -- 急诊分诊
UPDATE sys_menu SET icon = 'emergency' WHERE menu_id = 20023; -- 急诊抢救
UPDATE sys_menu SET icon = 'emergency' WHERE menu_id = 20024; -- 急诊观察
UPDATE sys_menu SET icon = 'emergency' WHERE menu_id = 20025; -- 绿色通道
-- 护理管理子菜单
UPDATE sys_menu SET icon = 'nursing' WHERE menu_id = 20032; -- 护理评估
UPDATE sys_menu SET icon = 'nursing' WHERE menu_id = 20033; -- 护理增强
UPDATE sys_menu SET icon = 'nursing' WHERE menu_id = 20034; -- 护理执行
UPDATE sys_menu SET icon = 'nursing' WHERE menu_id = 20035; -- 护理质量
UPDATE sys_menu SET icon = 'vital-signs' WHERE menu_id = 20036; -- 生命体征图表
-- 病理管理子菜单
UPDATE sys_menu SET icon = 'pathology' WHERE menu_id = 20042; -- 病理医嘱
UPDATE sys_menu SET icon = 'pathology' WHERE menu_id = 20043; -- 病理报告
UPDATE sys_menu SET icon = 'pathology' WHERE menu_id = 20044; -- 病理标本
-- 病案管理子菜单
UPDATE sys_menu SET icon = 'medical-record' WHERE menu_id = 20052; -- 病案管理
UPDATE sys_menu SET icon = 'medical-record' WHERE menu_id = 20053; -- 病案首页管理
UPDATE sys_menu SET icon = 'report' WHERE menu_id = 20054; -- DRG分析
UPDATE sys_menu SET icon = 'report' WHERE menu_id = 20055; -- 病案统计
-- 影像管理子菜单
UPDATE sys_menu SET icon = 'radiology' WHERE menu_id = 20062; -- 影像增强
UPDATE sys_menu SET icon = 'radiology' WHERE menu_id = 20063; -- 影像对比
UPDATE sys_menu SET icon = 'radiology' WHERE menu_id = 20064; -- 3D重建
-- 随访管理子菜单
UPDATE sys_menu SET icon = 'follow-up' WHERE menu_id = 20072; -- 随访计划
UPDATE sys_menu SET icon = 'follow-up' WHERE menu_id = 20073; -- 随访记录
UPDATE sys_menu SET icon = 'follow-up' WHERE menu_id = 20074; -- 随访任务
UPDATE sys_menu SET icon = 'follow-up' WHERE menu_id = 20075; -- 随访调查
-- 电子病历管理子菜单
UPDATE sys_menu SET icon = 'emr' WHERE menu_id = 20202; -- 病案归档
UPDATE sys_menu SET icon = 'emr' WHERE menu_id = 20203; -- 修订历史
UPDATE sys_menu SET icon = 'emr' WHERE menu_id = 20204; -- 病历时效
UPDATE sys_menu SET icon = 'emr' WHERE menu_id = 20205; -- 病历检索
UPDATE sys_menu SET icon = 'emr' WHERE menu_id = 20206; -- 进程记录
UPDATE sys_menu SET icon = 'emr' WHERE menu_id = 20207; -- 知识库
-- 审核管理子菜单
UPDATE sys_menu SET icon = 'audit' WHERE menu_id = 20132; -- 审核统计
UPDATE sys_menu SET icon = 'audit' WHERE menu_id = 20133; -- 点评计划
UPDATE sys_menu SET icon = 'audit' WHERE menu_id = 20134; -- 点评工作台
UPDATE sys_menu SET icon = 'audit' WHERE menu_id = 20135; -- 点评记录
UPDATE sys_menu SET icon = 'audit' WHERE menu_id = 20136; -- 医生排名
-- 跨模块协同子菜单
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20142; -- 会诊反馈
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20143; -- 会诊超时
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20144; -- DRG绩效
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20145; -- 药品效期
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20146; -- 增强救护
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20147; -- 增强抗菌
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20148; -- 增强知情
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20149; -- DRG预警
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20150; -- 增强护理
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20152; -- 交接统计
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20153; -- 检验预警
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20154; -- 病案质量
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20155; -- 护士执行
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20156; -- 报告反馈
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20157; -- 审核统计
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20158; -- 库存拦截
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20159; -- 手术闭环
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20160; -- 手术关联
UPDATE sys_menu SET icon = 'teamwork' WHERE menu_id = 20162; -- 患者转运
-- 门诊医生工作站子菜单
UPDATE sys_menu SET icon = 'doctor' WHERE menu_id = 2114; -- 医生常用语
UPDATE sys_menu SET icon = 'doctor' WHERE menu_id = 2143; -- 今日门诊
-- 预约管理子菜单
UPDATE sys_menu SET icon = 'schedule' WHERE menu_id = 2105; -- 医生排班管理
UPDATE sys_menu SET icon = 'appointment' WHERE menu_id = 2097; -- 门诊出诊医生诊室设置
UPDATE sys_menu SET icon = 'appointment' WHERE menu_id = 2112; -- 科室预约工作时间维护
UPDATE sys_menu SET icon = 'appointment' WHERE menu_id = 2113; -- 门诊预约挂号
-- 手术管理子菜单
UPDATE sys_menu SET icon = 'surgery' WHERE menu_id = 2120; -- 手术管理
UPDATE sys_menu SET icon = 'surgery' WHERE menu_id = 2144; -- 手术室管理
-- 住院医生增强子菜单
UPDATE sys_menu SET icon = 'doctor' WHERE menu_id = 20172; -- 住院病历
-- 住院护士增强子菜单
UPDATE sys_menu SET icon = 'nurse' WHERE menu_id = 20182; -- 护士工作站
UPDATE sys_menu SET icon = 'nurse' WHERE menu_id = 20183; -- 退药管理
UPDATE sys_menu SET icon = 'nurse' WHERE menu_id = 20184; -- 滚费管理
UPDATE sys_menu SET icon = 'vital-signs' WHERE menu_id = 20185; -- TPR表
-- 住院辅助子菜单
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 20222; -- 住院结算
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 20223; -- 费用类型转换
UPDATE sys_menu SET icon = 'diagnosis' WHERE menu_id = 20224; -- 住院诊断
UPDATE sys_menu SET icon = 'emr' WHERE menu_id = 20225; -- 住院病历
UPDATE sys_menu SET icon = 'order' WHERE menu_id = 20226; -- 医嘱管理
UPDATE sys_menu SET icon = 'surgery' WHERE menu_id = 20228; -- 住院手术
-- 基础数据子菜单
UPDATE sys_menu SET icon = 'patient' WHERE menu_id = 205; -- 患者管理
UPDATE sys_menu SET icon = 'patient' WHERE menu_id = 206; -- 患者档案管理
UPDATE sys_menu SET icon = 'department' WHERE menu_id = 212; -- 科室管理
UPDATE sys_menu SET icon = 'supplier' WHERE menu_id = 214; -- 供应商管理
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 216; -- 挂号收费项目
UPDATE sys_menu SET icon = 'pharmacy' WHERE menu_id = 238; -- 库房/药房管理
UPDATE sys_menu SET icon = 'appointment' WHERE menu_id = 245; -- 门诊号源管理
UPDATE sys_menu SET icon = 'user' WHERE menu_id = 246; -- 客户数据
UPDATE sys_menu SET icon = 'bed-management' WHERE menu_id = 247; -- 病区/床位管理
-- 目录管理子菜单
UPDATE sys_menu SET icon = 'drug' WHERE menu_id = 201; -- 药品目录
UPDATE sys_menu SET icon = 'laboratory' WHERE menu_id = 202; -- 诊疗目录
UPDATE sys_menu SET icon = 'inventory' WHERE menu_id = 203; -- 耗材目录
UPDATE sys_menu SET icon = 'diagnosis' WHERE menu_id = 204; -- 诊断目录
-- 基础数据子菜单 - 新增
UPDATE sys_menu SET icon = 'bed-management' WHERE menu_id = 20257; -- 床体管理
UPDATE sys_menu SET icon = 'body' WHERE menu_id = 20258; -- 体表结构
UPDATE sys_menu SET icon = 'diagnosis' WHERE menu_id = 20259; -- 常用诊断
UPDATE sys_menu SET icon = 'billing' WHERE menu_id = 20260; -- 费用管理
UPDATE sys_menu SET icon = 'location' WHERE menu_id = 20261; -- 位置管理
UPDATE sys_menu SET icon = 'order' WHERE menu_id = 20262; -- 医嘱组合
UPDATE sys_menu SET icon = 'medication' WHERE menu_id = 20263; -- 中医处方
-- 为剩余没有图标的菜单设置默认图标
UPDATE sys_menu SET icon = 'list' WHERE menu_type = 'C' AND (icon IS NULL OR icon = '');
UPDATE sys_menu SET icon = 'list' WHERE menu_type = 'M' AND (icon IS NULL OR icon = '');

View File

@@ -0,0 +1,42 @@
CREATE TABLE IF NOT EXISTS ehcard_card (
id BIGINT PRIMARY KEY,
patient_id BIGINT NOT NULL,
patient_name VARCHAR(64),
id_card VARCHAR(32),
phone VARCHAR(20),
card_no VARCHAR(64) NOT NULL,
card_type VARCHAR(20) NOT NULL DEFAULT 'HEALTH',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
qr_code TEXT,
apply_time TIMESTAMP,
lock_time TIMESTAMP,
unlock_time TIMESTAMP,
lock_reason VARCHAR(256),
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 1,
delete_flag VARCHAR(1) DEFAULT '0'
);
CREATE TABLE IF NOT EXISTS ehcard_usage_log (
id BIGINT PRIMARY KEY,
card_id BIGINT NOT NULL,
patient_id BIGINT,
usage_type VARCHAR(32) NOT NULL,
usage_desc VARCHAR(256),
operator_name VARCHAR(64),
usage_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 1,
delete_flag VARCHAR(1) DEFAULT '0'
);
CREATE INDEX idx_ehcard_card_patient ON ehcard_card(patient_id);
CREATE INDEX idx_ehcard_card_no ON ehcard_card(card_no);
CREATE INDEX idx_ehcard_card_status ON ehcard_card(status);
CREATE INDEX idx_ehcard_log_card ON ehcard_usage_log(card_id);

View File

@@ -0,0 +1,22 @@
-- TCM diagnosis table
CREATE TABLE IF NOT EXISTS healthlink_his.tcm_diagnosis (
id BIGSERIAL PRIMARY KEY,
encounter_id BIGINT,
patient_id BIGINT,
diagnosis_type VARCHAR(50),
diagnosis_name VARCHAR(200),
diagnosis_code VARCHAR(50),
syndrome_type VARCHAR(50),
syndrome_name VARCHAR(200),
syndrome_code VARCHAR(50),
remark TEXT,
enabled CHAR(1) DEFAULT '1',
tenant_id INTEGER DEFAULT 0,
delete_flag CHAR(1) DEFAULT '0',
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_tcm_diagnosis_encounter ON healthlink_his.tcm_diagnosis(encounter_id);

View File

@@ -0,0 +1,82 @@
CREATE TABLE IF NOT EXISTS invoice_header (
id BIGINT PRIMARY KEY,
invoice_no VARCHAR(64) NOT NULL,
invoice_type VARCHAR(20) NOT NULL DEFAULT 'ELECTRONIC',
encounter_id BIGINT,
patient_id BIGINT,
patient_name VARCHAR(64),
total_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
discount_amount NUMERIC(12,2) DEFAULT 0,
payable_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
paid_amount NUMERIC(12,2) DEFAULT 0,
invoice_status VARCHAR(20) NOT NULL DEFAULT 'ISSUED',
issue_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
void_time TIMESTAMP,
void_reason VARCHAR(256),
issuer_id BIGINT,
issuer_name VARCHAR(64),
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 1,
delete_flag VARCHAR(1) DEFAULT '0'
);
CREATE TABLE IF NOT EXISTS invoice_detail (
id BIGINT PRIMARY KEY,
header_id BIGINT NOT NULL,
item_code VARCHAR(64),
item_name VARCHAR(128) NOT NULL,
item_type VARCHAR(20),
quantity NUMERIC(10,2) DEFAULT 1,
unit_price NUMERIC(12,2) DEFAULT 0,
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 1,
delete_flag VARCHAR(1) DEFAULT '0'
);
CREATE TABLE IF NOT EXISTS invoice_segment (
id BIGINT PRIMARY KEY,
segment_name VARCHAR(128) NOT NULL,
start_no VARCHAR(64) NOT NULL,
end_no VARCHAR(64) NOT NULL,
current_no VARCHAR(64),
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
invoice_type VARCHAR(20) DEFAULT 'ELECTRONIC',
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 1,
delete_flag VARCHAR(1) DEFAULT '0'
);
CREATE TABLE IF NOT EXISTS invoice_reconciliation (
id BIGINT PRIMARY KEY,
recon_date DATE NOT NULL,
total_invoices INT DEFAULT 0,
total_amount NUMERIC(14,2) DEFAULT 0,
reconciled_count INT DEFAULT 0,
reconciled_amount NUMERIC(14,2) DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
reconciler_id BIGINT,
reconciler_name VARCHAR(64),
reconcile_time TIMESTAMP,
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 1,
delete_flag VARCHAR(1) DEFAULT '0'
);
CREATE INDEX idx_inv_header_no ON invoice_header(invoice_no);
CREATE INDEX idx_inv_header_status ON invoice_header(invoice_status);
CREATE INDEX idx_inv_header_patient ON invoice_header(patient_id);
CREATE INDEX idx_inv_detail_header ON invoice_detail(header_id);
CREATE INDEX idx_inv_recon_date ON invoice_reconciliation(recon_date);

View File

@@ -0,0 +1,6 @@
-- Add auto-screen fields to epidemic_report
ALTER TABLE healthlink_his.epidemic_report ADD COLUMN IF NOT EXISTS screen_result TEXT;
ALTER TABLE healthlink_his.epidemic_report ADD COLUMN IF NOT EXISTS screen_time TIMESTAMP;
ALTER TABLE healthlink_his.epidemic_report ADD COLUMN IF NOT EXISTS screen_level VARCHAR(20);
ALTER TABLE healthlink_his.epidemic_report ADD COLUMN IF NOT EXISTS address VARCHAR(500);
ALTER TABLE healthlink_his.epidemic_report ADD COLUMN IF NOT EXISTS contact_phone VARCHAR(50);

View File

@@ -0,0 +1,35 @@
package com.healthlink.his.anesthesia.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("anes_alldrete_score")
public class AnesthesiaAldreteScore extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private Long recordId;
private Long encounterId;
private Long patientId;
private String patientName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date assessTime;
private Integer activityScore;
private Integer respirationScore;
private Integer circulationScore;
private Integer consciousnessScore;
private Integer spo2Score;
private Integer totalScore;
private String riskLevel;
private Long assessorId;
private String assessorName;
private String remark;
}

View File

@@ -0,0 +1,35 @@
package com.healthlink.his.anesthesia.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("anes_intraop_event")
public class AnesthesiaIntraopEvent extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private Long recordId;
private Long encounterId;
private Long patientId;
private String eventType;
private String eventDetail;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date eventTime;
private String operatorName;
private String position;
private String intubationType;
private String intubationResult;
private String extubationReason;
private String extubationResult;
private String complication;
private String action;
private String remark;
}

View File

@@ -0,0 +1,45 @@
package com.healthlink.his.anesthesia.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("anes_preop_visit")
public class AnesthesiaPreopVisit extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private Long recordId;
private Long encounterId;
private Long patientId;
private String patientName;
private Long visitDoctorId;
private String visitDoctorName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date visitTime;
private String chiefComplaint;
private String presentIllness;
private String pastHistory;
private String allergyHistory;
private String airwayAssessment;
private String asaGrade;
private String cardiovascularStatus;
private String respiratoryStatus;
private String neurologicalStatus;
private String hepatorenalFunction;
private String coagulationStatus;
private String fastingStatus;
private String npoHours;
private String difficultAirwayRisk;
private String anesthesiaRisk;
private String riskFactors;
private String recommendation;
private String status;
}

View File

@@ -0,0 +1,13 @@
package com.healthlink.his.anesthesia.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.anesthesia.domain.AnesthesiaAldreteScore;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface AnesthesiaAldreteScoreMapper extends BaseMapper<AnesthesiaAldreteScore> {
List<AnesthesiaAldreteScore> selectByRecordId(@Param("recordId") Long recordId);
}

View File

@@ -0,0 +1,13 @@
package com.healthlink.his.anesthesia.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.anesthesia.domain.AnesthesiaIntraopEvent;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface AnesthesiaIntraopEventMapper extends BaseMapper<AnesthesiaIntraopEvent> {
List<AnesthesiaIntraopEvent> selectByRecordId(@Param("recordId") Long recordId);
}

View File

@@ -0,0 +1,14 @@
package com.healthlink.his.anesthesia.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.anesthesia.domain.AnesthesiaPreopVisit;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface AnesthesiaPreopVisitMapper extends BaseMapper<AnesthesiaPreopVisit> {
List<AnesthesiaPreopVisit> selectByRecordId(@Param("recordId") Long recordId);
List<AnesthesiaPreopVisit> selectByEncounterId(@Param("encounterId") Long encounterId);
}

View File

@@ -0,0 +1,10 @@
package com.healthlink.his.anesthesia.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.anesthesia.domain.AnesthesiaAldreteScore;
import java.util.List;
public interface IAnesthesiaAldreteScoreService extends IService<AnesthesiaAldreteScore> {
List<AnesthesiaAldreteScore> selectByRecordId(Long recordId);
}

View File

@@ -0,0 +1,10 @@
package com.healthlink.his.anesthesia.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.anesthesia.domain.AnesthesiaIntraopEvent;
import java.util.List;
public interface IAnesthesiaIntraopEventService extends IService<AnesthesiaIntraopEvent> {
List<AnesthesiaIntraopEvent> selectByRecordId(Long recordId);
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.anesthesia.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.anesthesia.domain.AnesthesiaPreopVisit;
import java.util.List;
public interface IAnesthesiaPreopVisitService extends IService<AnesthesiaPreopVisit> {
List<AnesthesiaPreopVisit> selectByRecordId(Long recordId);
List<AnesthesiaPreopVisit> selectByEncounterId(Long encounterId);
}

View File

@@ -0,0 +1,20 @@
package com.healthlink.his.anesthesia.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.anesthesia.domain.AnesthesiaAldreteScore;
import com.healthlink.his.anesthesia.mapper.AnesthesiaAldreteScoreMapper;
import com.healthlink.his.anesthesia.service.IAnesthesiaAldreteScoreService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AnesthesiaAldreteScoreServiceImpl
extends ServiceImpl<AnesthesiaAldreteScoreMapper, AnesthesiaAldreteScore>
implements IAnesthesiaAldreteScoreService {
@Override
public List<AnesthesiaAldreteScore> selectByRecordId(Long recordId) {
return baseMapper.selectByRecordId(recordId);
}
}

View File

@@ -0,0 +1,20 @@
package com.healthlink.his.anesthesia.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.anesthesia.domain.AnesthesiaIntraopEvent;
import com.healthlink.his.anesthesia.mapper.AnesthesiaIntraopEventMapper;
import com.healthlink.his.anesthesia.service.IAnesthesiaIntraopEventService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AnesthesiaIntraopEventServiceImpl
extends ServiceImpl<AnesthesiaIntraopEventMapper, AnesthesiaIntraopEvent>
implements IAnesthesiaIntraopEventService {
@Override
public List<AnesthesiaIntraopEvent> selectByRecordId(Long recordId) {
return baseMapper.selectByRecordId(recordId);
}
}

View File

@@ -0,0 +1,25 @@
package com.healthlink.his.anesthesia.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.anesthesia.domain.AnesthesiaPreopVisit;
import com.healthlink.his.anesthesia.mapper.AnesthesiaPreopVisitMapper;
import com.healthlink.his.anesthesia.service.IAnesthesiaPreopVisitService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AnesthesiaPreopVisitServiceImpl
extends ServiceImpl<AnesthesiaPreopVisitMapper, AnesthesiaPreopVisit>
implements IAnesthesiaPreopVisitService {
@Override
public List<AnesthesiaPreopVisit> selectByRecordId(Long recordId) {
return baseMapper.selectByRecordId(recordId);
}
@Override
public List<AnesthesiaPreopVisit> selectByEncounterId(Long encounterId) {
return baseMapper.selectByEncounterId(encounterId);
}
}

View File

@@ -0,0 +1,39 @@
package com.healthlink.his.ehcard.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
import java.util.Date;
@Data
@TableName("ehcard_card")
@EqualsAndHashCode(callSuper = true)
public class EhcardCard extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
private String patientName;
private String idCard;
private String phone;
private String cardNo;
private String cardType;
private String status;
private String qrCode;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date lockTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date unlockTime;
private String lockReason;
}

View File

@@ -0,0 +1,30 @@
package com.healthlink.his.ehcard.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
import java.util.Date;
@Data
@TableName("ehcard_usage_log")
@EqualsAndHashCode(callSuper = true)
public class EhcardUsageLog extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long cardId;
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
private String usageType;
private String usageDesc;
private String operatorName;
private Date usageTime;
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.ehcard.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.ehcard.domain.EhcardCard;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EhcardCardMapper extends BaseMapper<EhcardCard> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.ehcard.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.ehcard.domain.EhcardUsageLog;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EhcardUsageLogMapper extends BaseMapper<EhcardUsageLog> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.ehcard.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.ehcard.domain.EhcardCard;
public interface IEhcardCardService extends IService<EhcardCard> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.ehcard.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.ehcard.domain.EhcardUsageLog;
public interface IEhcardUsageLogService extends IService<EhcardUsageLog> {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.ehcard.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.ehcard.domain.EhcardCard;
import com.healthlink.his.ehcard.mapper.EhcardCardMapper;
import com.healthlink.his.ehcard.service.IEhcardCardService;
import org.springframework.stereotype.Service;
@Service
public class EhcardCardServiceImpl extends ServiceImpl<EhcardCardMapper, EhcardCard> implements IEhcardCardService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.ehcard.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.ehcard.domain.EhcardUsageLog;
import com.healthlink.his.ehcard.mapper.EhcardUsageLogMapper;
import com.healthlink.his.ehcard.service.IEhcardUsageLogService;
import org.springframework.stereotype.Service;
@Service
public class EhcardUsageLogServiceImpl extends ServiceImpl<EhcardUsageLogMapper, EhcardUsageLog> implements IEhcardUsageLogService {
}

View File

@@ -0,0 +1,29 @@
package com.healthlink.his.einvoice.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
import java.math.BigDecimal;
@Data
@TableName("invoice_detail")
@EqualsAndHashCode(callSuper = true)
public class EinvoiceDetail extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long headerId;
private String itemCode;
private String itemName;
private String itemType;
private BigDecimal quantity;
private BigDecimal unitPrice;
private BigDecimal amount;
}

View File

@@ -0,0 +1,43 @@
package com.healthlink.his.einvoice.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("invoice_header")
@EqualsAndHashCode(callSuper = true)
public class EinvoiceHeader extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String invoiceNo;
private String invoiceType;
@JsonSerialize(using = ToStringSerializer.class)
private Long encounterId;
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
private String patientName;
private BigDecimal totalAmount;
private BigDecimal discountAmount;
private BigDecimal payableAmount;
private BigDecimal paidAmount;
private String invoiceStatus;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date issueTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date voidTime;
private String voidReason;
@JsonSerialize(using = ToStringSerializer.class)
private Long issuerId;
private String issuerName;
}

View File

@@ -0,0 +1,35 @@
package com.healthlink.his.einvoice.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("invoice_reconciliation")
@EqualsAndHashCode(callSuper = true)
public class EinvoiceReconciliation extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date reconDate;
private Integer totalInvoices;
private BigDecimal totalAmount;
private Integer reconciledCount;
private BigDecimal reconciledAmount;
private String status;
@JsonSerialize(using = ToStringSerializer.class)
private Long reconcilerId;
private String reconcilerName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date reconcileTime;
}

View File

@@ -0,0 +1,25 @@
package com.healthlink.his.einvoice.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
@Data
@TableName("invoice_segment")
@EqualsAndHashCode(callSuper = true)
public class EinvoiceSegment extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String segmentName;
private String startNo;
private String endNo;
private String currentNo;
private String status;
private String invoiceType;
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.einvoice.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.einvoice.domain.EinvoiceDetail;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EinvoiceDetailMapper extends BaseMapper<EinvoiceDetail> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.einvoice.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EinvoiceHeaderMapper extends BaseMapper<EinvoiceHeader> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.einvoice.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.einvoice.domain.EinvoiceReconciliation;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EinvoiceReconciliationMapper extends BaseMapper<EinvoiceReconciliation> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.einvoice.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.einvoice.domain.EinvoiceSegment;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EinvoiceSegmentMapper extends BaseMapper<EinvoiceSegment> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.einvoice.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.einvoice.domain.EinvoiceDetail;
public interface IEinvoiceDetailService extends IService<EinvoiceDetail> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.einvoice.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
public interface IEinvoiceHeaderService extends IService<EinvoiceHeader> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.einvoice.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.einvoice.domain.EinvoiceReconciliation;
public interface IEinvoiceReconciliationService extends IService<EinvoiceReconciliation> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.einvoice.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.einvoice.domain.EinvoiceSegment;
public interface IEinvoiceSegmentService extends IService<EinvoiceSegment> {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.einvoice.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.einvoice.domain.EinvoiceDetail;
import com.healthlink.his.einvoice.mapper.EinvoiceDetailMapper;
import com.healthlink.his.einvoice.service.IEinvoiceDetailService;
import org.springframework.stereotype.Service;
@Service
public class EinvoiceDetailServiceImpl extends ServiceImpl<EinvoiceDetailMapper, EinvoiceDetail> implements IEinvoiceDetailService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.einvoice.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.einvoice.domain.EinvoiceHeader;
import com.healthlink.his.einvoice.mapper.EinvoiceHeaderMapper;
import com.healthlink.his.einvoice.service.IEinvoiceHeaderService;
import org.springframework.stereotype.Service;
@Service
public class EinvoiceHeaderServiceImpl extends ServiceImpl<EinvoiceHeaderMapper, EinvoiceHeader> implements IEinvoiceHeaderService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.einvoice.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.einvoice.domain.EinvoiceReconciliation;
import com.healthlink.his.einvoice.mapper.EinvoiceReconciliationMapper;
import com.healthlink.his.einvoice.service.IEinvoiceReconciliationService;
import org.springframework.stereotype.Service;
@Service
public class EinvoiceReconciliationServiceImpl extends ServiceImpl<EinvoiceReconciliationMapper, EinvoiceReconciliation> implements IEinvoiceReconciliationService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.einvoice.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.einvoice.domain.EinvoiceSegment;
import com.healthlink.his.einvoice.mapper.EinvoiceSegmentMapper;
import com.healthlink.his.einvoice.service.IEinvoiceSegmentService;
import org.springframework.stereotype.Service;
@Service
public class EinvoiceSegmentServiceImpl extends ServiceImpl<EinvoiceSegmentMapper, EinvoiceSegment> implements IEinvoiceSegmentService {
}

View File

@@ -13,4 +13,6 @@ public class EpidemicReport extends HisBaseEntity {
private String reporterId; private String reporterName;
private Date reportDate; private String status;
private String cdcConfirmNo; private String delFlag;
private String screenResult; private Date screenTime; private String screenLevel;
private String address; private String contactPhone;
}

View File

@@ -0,0 +1,28 @@
package com.healthlink.his.tcm.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@TableName("tcm_diagnosis")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class TcmDiagnosis extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private Long encounterId;
private Long patientId;
private String diagnosisType;
private String diagnosisName;
private String diagnosisCode;
private String syndromeType;
private String syndromeName;
private String syndromeCode;
private String remark;
private String enabled;
}

View File

@@ -0,0 +1,5 @@
package com.healthlink.his.tcm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.tcm.domain.TcmDiagnosis;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface TcmDiagnosisMapper extends BaseMapper<TcmDiagnosis> {}

View File

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

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.tcm.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.tcm.domain.TcmDiagnosis;
import com.healthlink.his.tcm.mapper.TcmDiagnosisMapper;
import com.healthlink.his.tcm.service.ITcmDiagnosisService;
import org.springframework.stereotype.Service;
@Service public class TcmDiagnosisServiceImpl extends ServiceImpl<TcmDiagnosisMapper, TcmDiagnosis> implements ITcmDiagnosisService {}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.anesthesia.mapper.AnesthesiaAldreteScoreMapper">
<select id="selectByRecordId" resultType="com.healthlink.his.anesthesia.domain.AnesthesiaAldreteScore">
SELECT * FROM anes_alldrete_score
WHERE record_id = #{recordId}
ORDER BY assess_time DESC
</select>
</mapper>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.anesthesia.mapper.AnesthesiaIntraopEventMapper">
<select id="selectByRecordId" resultType="com.healthlink.his.anesthesia.domain.AnesthesiaIntraopEvent">
SELECT * FROM anes_intraop_event
WHERE record_id = #{recordId}
ORDER BY event_time ASC
</select>
</mapper>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.anesthesia.mapper.AnesthesiaPreopVisitMapper">
<select id="selectByRecordId" resultType="com.healthlink.his.anesthesia.domain.AnesthesiaPreopVisit">
SELECT * FROM anes_preop_visit
WHERE record_id = #{recordId}
ORDER BY create_time DESC
</select>
<select id="selectByEncounterId" resultType="com.healthlink.his.anesthesia.domain.AnesthesiaPreopVisit">
SELECT * FROM anes_preop_visit
WHERE encounter_id = #{encounterId}
ORDER BY create_time DESC
</select>
</mapper>

View File

@@ -0,0 +1,199 @@
<template>
<div style="padding: 16px">
<div style="margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center">
<span style="font-size: 18px; font-weight: bold">DRG/DIP深化分析</span>
<el-button type="primary" @click="refreshAll">刷新</el-button>
</div>
<el-row :gutter="16" style="margin-bottom: 16px">
<el-col :span="6">
<el-card shadow="hover" :body-style="{ padding: '12px' }">
<div style="text-align: center">
<div style="font-size: 22px; font-weight: bold; color: #409eff">{{ deepStats.totalCases || 0 }}</div>
<div style="font-size: 12px; color: #999">分析病例数</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{ padding: '12px' }">
<div style="text-align: center">
<div style="font-size: 22px; font-weight: bold; color: #67c23a">{{ formatMoney(deepStats.avgCost) }}</div>
<div style="font-size: 12px; color: #999">平均费用()</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{ padding: '12px' }">
<div style="text-align: center">
<div style="font-size: 22px; font-weight: bold; color: #e6a23c">{{ deepStats.avgLos || 0 }}</div>
<div style="font-size: 12px; color: #999">平均住院日</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{ padding: '12px' }">
<div style="text-align: center">
<div style="font-size: 22px; font-weight: bold; color: #f56c6c">{{ deepStats.insuranceRate || 0 }}%</div>
<div style="font-size: 12px; color: #999">医保支付率</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="16" style="margin-bottom: 16px">
<el-col :span="12">
<el-card shadow="never">
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center">
<span>费用预警</span>
<span style="font-size: 12px; color: #f56c6c">未处理: {{ costAlert.unhandledCount || 0 }} / 高危: {{ costAlert.highLevelCount || 0 }}</span>
</div>
</template>
<el-table :data="(costAlert.records || []).slice(0, 5)" border stripe size="small">
<el-table-column prop="patientName" label="患者" width="100" />
<el-table-column prop="drgCode" label="DRG组" width="100" />
<el-table-column prop="alertLevel" label="等级" width="80">
<template #default="{ row }">
<el-tag :type="row.alertLevel === 'HIGH' ? 'danger' : row.alertLevel === 'MEDIUM' ? 'warning' : 'info'" size="small">{{ row.alertLevel }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="handleStatus" label="状态" width="80">
<template #default="{ row }">
<el-tag :type="row.handleStatus === 'PENDING' ? 'danger' : 'success'" size="small">{{ row.handleStatus === 'PENDING' ? '待处理' : '已处理' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="costDeviation" label="偏差" align="right">
<template #default="{ row }">{{ formatMoney(row.costDeviation) }}</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never">
<template #header>优化趋势</template>
<el-table :data="(optimization.trend || []).slice(0, 5)" border stripe size="small">
<el-table-column prop="month" label="月份" width="100" />
<el-table-column prop="totalCases" label="病例数" width="80" align="center" />
<el-table-column prop="avgWeight" label="平均权重" width="100" align="right" />
<el-table-column prop="avgCost" label="平均费用" align="right">
<template #default="{ row }">{{ formatMoney(row.avgCost) }}</template>
</el-table-column>
<el-table-column prop="costControlRate" label="费用控制率" width="100" align="right">
<template #default="{ row }">{{ row.costControlRate }}%</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<el-card shadow="never" style="margin-bottom: 16px">
<template #header>DRG分组分析</template>
<div style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 12px">
<el-select v-model="analyzeQuery.groupingType" placeholder="分组类型" clearable style="width: 120px">
<el-option label="DRG" value="DRG" />
<el-option label="DIP" value="DIP" />
</el-select>
<el-date-picker v-model="analyzeQuery.dateRange" type="daterange" start-placeholder="开始" end-placeholder="结束" style="width: 260px" />
<el-button type="primary" @click="runAnalysis">分析</el-button>
</div>
<el-table :data="deepStats.topDrgByCost || []" border stripe>
<el-table-column type="index" label="排名" width="60" align="center" />
<el-table-column prop="drgCode" label="DRG组代码" width="140" />
<el-table-column prop="count" label="病例数" width="100" align="center" />
<el-table-column prop="totalCost" label="总费用(万元)" align="right">
<template #default="{ row }">{{ formatMoney(row.totalCost) }}</template>
</el-table-column>
</el-table>
</el-card>
<el-card shadow="never">
<template #header>当前绩效指标</template>
<el-row :gutter="16">
<el-col :span="4">
<div style="text-align: center">
<div style="font-size: 20px; font-weight: bold; color: #409eff">{{ optimization.totalCases || 0 }}</div>
<div style="font-size: 12px; color: #999">当月病例数</div>
</div>
</el-col>
<el-col :span="4">
<div style="text-align: center">
<div style="font-size: 20px; font-weight: bold; color: #67c23a">{{ optimization.drgCoveredRate || 0 }}%</div>
<div style="font-size: 12px; color: #999">DRG入组率</div>
</div>
</el-col>
<el-col :span="4">
<div style="text-align: center">
<div style="font-size: 20px; font-weight: bold; color: #e6a23c">{{ optimization.avgWeight || 0 }}</div>
<div style="font-size: 12px; color: #999">平均权重</div>
</div>
</el-col>
<el-col :span="4">
<div style="text-align: center">
<div style="font-size: 20px; font-weight: bold; color: #f56c6c">{{ formatMoney(optimization.avgCost) }}</div>
<div style="font-size: 12px; color: #999">平均费用()</div>
</div>
</el-col>
<el-col :span="4">
<div style="text-align: center">
<div style="font-size: 20px; font-weight: bold; color: #409eff">{{ optimization.costControlRate || 0 }}%</div>
<div style="font-size: 12px; color: #999">费用控制率</div>
</div>
</el-col>
<el-col :span="4">
<div style="text-align: center">
<div style="font-size: 20px; font-weight: bold; color: #67c23a">{{ optimization.cmiValue || 0 }}</div>
<div style="font-size: 12px; color: #999">CMI值</div>
</div>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { analyzeDrgDeep, getCostAlert, getOptimization } from './deepApi'
const deepStats = ref({})
const costAlert = ref({})
const optimization = ref({})
const analyzeQuery = ref({ groupingType: '', dateRange: null })
function formatMoney(val) {
if (!val) return '0.00'
return (Number(val) / 10000).toFixed(2)
}
async function refreshAll() {
try {
const [a, c, o] = await Promise.all([
analyzeDrgDeep({}),
getCostAlert({}),
getOptimization({})
])
deepStats.value = a.data || {}
costAlert.value = c.data || {}
optimization.value = o.data || {}
} catch (e) {
ElMessage.error('加载数据失败')
}
}
async function runAnalysis() {
const params = { groupingType: analyzeQuery.value.groupingType }
if (analyzeQuery.value.dateRange && analyzeQuery.value.dateRange.length === 2) {
params.startDate = analyzeQuery.value.dateRange[0]
params.endDate = analyzeQuery.value.dateRange[1]
}
try {
const r = await analyzeDrgDeep(params)
deepStats.value = r.data || {}
ElMessage.success('分析完成')
} catch (e) {
ElMessage.error('分析失败')
}
}
onMounted(() => refreshAll())
</script>

View File

@@ -0,0 +1,13 @@
import request from '@/utils/request'
export function analyzeDrgDeep(data) {
return request({ url: '/ybmanage/drg-deep/analyze', method: 'post', data })
}
export function getCostAlert(data) {
return request({ url: '/ybmanage/drg-deep/cost-alert', method: 'post', data })
}
export function getOptimization(data) {
return request({ url: '/ybmanage/drg-deep/optimization', method: 'post', data })
}

View File

@@ -0,0 +1,160 @@
<template>
<div style="padding: 16px">
<div style="margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center">
<span style="font-size: 18px; font-weight: bold">电子健康卡管理</span>
<el-button type="primary" @click="handleApply">申请健康卡</el-button>
</div>
<el-card shadow="never" style="margin-bottom: 16px">
<div style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center">
<el-input v-model="query.patientName" placeholder="患者姓名" clearable style="width: 160px" />
<el-select v-model="query.status" placeholder="状态" clearable style="width: 120px">
<el-option label="正常" value="ACTIVE" />
<el-option label="已锁定" value="LOCKED" />
</el-select>
<el-button type="primary" @click="loadPage">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
</el-card>
<el-card shadow="never">
<el-table :data="tableData" v-loading="loading" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="cardNo" label="卡号" width="180" />
<el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column prop="idCard" label="身份证号" width="180" />
<el-table-column prop="phone" label="手机号" width="140" />
<el-table-column prop="cardType" label="卡类型" width="100">
<template #default="{ row }">
<el-tag v-if="row.cardType === 'HEALTH'" type="primary">健康卡</el-tag>
<el-tag v-else>{{ row.cardType }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag v-if="row.status === 'ACTIVE'" type="success">正常</el-tag>
<el-tag v-else-if="row.status === 'LOCKED'" type="danger">已锁定</el-tag>
<el-tag v-else>{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="applyTime" label="申请时间" width="170" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button v-if="row.status === 'ACTIVE'" type="danger" size="small" @click="handleLock(row)">锁定</el-button>
<el-button v-if="row.status === 'LOCKED'" type="success" size="small" @click="handleUnlock(row)">解锁</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="query.pageNum"
v-model:page-size="query.pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next"
style="margin-top: 16px; justify-content: flex-end"
@size-change="loadPage"
@current-change="loadPage"
/>
</el-card>
<el-dialog v-model="applyVisible" title="申请电子健康卡" width="500px">
<el-form :model="applyForm" label-width="80px">
<el-form-item label="患者ID">
<el-input v-model="applyForm.patientId" placeholder="患者ID" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="applyForm.patientName" placeholder="患者姓名" />
</el-form-item>
<el-form-item label="身份证号">
<el-input v-model="applyForm.idCard" placeholder="身份证号" />
</el-form-item>
<el-form-item label="手机号">
<el-input v-model="applyForm.phone" placeholder="手机号" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="applyVisible = false">取消</el-button>
<el-button type="primary" @click="submitApply">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="lockVisible" title="锁定健康卡" width="400px">
<el-form label-width="80px">
<el-form-item label="锁定原因">
<el-input v-model="lockReason" type="textarea" :rows="3" placeholder="请输入锁定原因" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="lockVisible = false">取消</el-button>
<el-button type="primary" @click="confirmLock">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { applyEhcard, getEhcardPage, lockEhcard, unlockEhcard } from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const query = ref({ patientName: '', status: '', pageNum: 1, pageSize: 10 })
const applyVisible = ref(false)
const applyForm = ref({ patientId: '', patientName: '', idCard: '', phone: '' })
const lockVisible = ref(false)
const lockReason = ref('')
const lockRow = ref(null)
async function loadPage() {
loading.value = true
try {
const res = await getEhcardPage(query.value)
tableData.value = res.data?.records || []
total.value = res.data?.total || 0
} finally {
loading.value = false
}
}
function resetQuery() {
query.value = { patientName: '', status: '', pageNum: 1, pageSize: 10 }
loadPage()
}
function handleApply() {
applyForm.value = { patientId: '', patientName: '', idCard: '', phone: '' }
applyVisible.value = true
}
async function submitApply() {
await applyEhcard(applyForm.value)
ElMessage.success('申请成功')
applyVisible.value = false
loadPage()
}
function handleLock(row) {
lockRow.value = row
lockReason.value = ''
lockVisible.value = true
}
async function confirmLock() {
await lockEhcard({ id: lockRow.value.id, reason: lockReason.value })
ElMessage.success('锁定成功')
lockVisible.value = false
loadPage()
}
async function handleUnlock(row) {
await unlockEhcard({ id: row.id })
ElMessage.success('解锁成功')
loadPage()
}
onMounted(() => loadPage())
</script>

View File

@@ -0,0 +1,17 @@
import request from '@/utils/request'
export function applyEhcard(data) {
return request({ url: '/api/v1/ehcard/apply', method: 'post', data })
}
export function getEhcardPage(params) {
return request({ url: '/api/v1/ehcard/page', method: 'get', params })
}
export function lockEhcard(params) {
return request({ url: '/api/v1/ehcard/lock', method: 'post', params })
}
export function unlockEhcard(params) {
return request({ url: '/api/v1/ehcard/unlock', method: 'post', params })
}

View File

@@ -0,0 +1,204 @@
<template>
<div style="padding: 16px">
<div style="margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center">
<span style="font-size: 18px; font-weight: bold">电子票据管理</span>
<el-button type="primary" @click="handleGenerate">开具票据</el-button>
</div>
<el-card shadow="never" style="margin-bottom: 16px">
<div style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center">
<el-input v-model="query.patientName" placeholder="患者姓名" clearable style="width: 160px" />
<el-select v-model="query.invoiceStatus" placeholder="状态" clearable style="width: 120px">
<el-option label="已开具" value="ISSUED" />
<el-option label="已作废" value="VOID" />
</el-select>
<el-button type="primary" @click="loadPage">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
<el-button @click="showReconciliation">对账</el-button>
</div>
</el-card>
<el-card shadow="never">
<el-table :data="tableData" v-loading="loading" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="invoiceNo" label="票据号" width="200" />
<el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column prop="totalAmount" label="总金额" width="120" align="right">
<template #default="{ row }">{{ formatMoney(row.totalAmount) }}</template>
</el-table-column>
<el-table-column prop="discountAmount" label="优惠金额" width="120" align="right">
<template #default="{ row }">{{ formatMoney(row.discountAmount) }}</template>
</el-table-column>
<el-table-column prop="payableAmount" label="应付金额" width="120" align="right">
<template #default="{ row }">{{ formatMoney(row.payableAmount) }}</template>
</el-table-column>
<el-table-column prop="invoiceStatus" label="状态" width="100">
<template #default="{ row }">
<el-tag v-if="row.invoiceStatus === 'ISSUED'" type="success">已开具</el-tag>
<el-tag v-else-if="row.invoiceStatus === 'VOID'" type="danger">已作废</el-tag>
<el-tag v-else>{{ row.invoiceStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="issueTime" label="开具时间" width="170" />
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button v-if="row.invoiceStatus === 'ISSUED'" type="danger" size="small" @click="handleVoid(row)">作废</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="query.pageNum"
v-model:page-size="query.pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next"
style="margin-top: 16px; justify-content: flex-end"
@size-change="loadPage"
@current-change="loadPage"
/>
</el-card>
<el-dialog v-model="generateVisible" title="开具电子票据" width="500px">
<el-form :model="generateForm" label-width="90px">
<el-form-item label="就诊ID">
<el-input v-model="generateForm.encounterId" placeholder="就诊ID" />
</el-form-item>
<el-form-item label="患者ID">
<el-input v-model="generateForm.patientId" placeholder="患者ID" />
</el-form-item>
<el-form-item label="患者姓名">
<el-input v-model="generateForm.patientName" placeholder="患者姓名" />
</el-form-item>
<el-form-item label="总金额">
<el-input v-model="generateForm.totalAmount" placeholder="总金额" />
</el-form-item>
<el-form-item label="优惠金额">
<el-input v-model="generateForm.discountAmount" placeholder="优惠金额" />
</el-form-item>
<el-form-item label="应付金额">
<el-input v-model="generateForm.payableAmount" placeholder="应付金额" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="generateVisible = false">取消</el-button>
<el-button type="primary" @click="submitGenerate">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="voidVisible" title="作废票据" width="400px">
<el-form label-width="80px">
<el-form-item label="作废原因">
<el-input v-model="voidReason" type="textarea" :rows="3" placeholder="请输入作废原因" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="voidVisible = false">取消</el-button>
<el-button type="primary" @click="confirmVoid">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="reconVisible" title="票据对账" width="700px">
<el-table :data="reconData" border stripe v-loading="reconLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="invoiceNo" label="票据号" width="200" />
<el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column prop="payableAmount" label="应付金额" width="120" align="right">
<template #default="{ row }">{{ formatMoney(row.payableAmount) }}</template>
</el-table-column>
<el-table-column prop="invoiceStatus" label="状态" width="100">
<template #default="{ row }">
<el-tag v-if="row.invoiceStatus === 'ISSUED'" type="success">已开具</el-tag>
<el-tag v-else>{{ row.invoiceStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="issueTime" label="开具时间" width="170" />
</el-table>
<div style="margin-top: 12px; color: #666">
{{ reconTotal }} 条已开具票据
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { generateInvoice, getInvoicePage, voidInvoice, getReconciliation } from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const query = ref({ invoiceStatus: '', patientName: '', pageNum: 1, pageSize: 10 })
const generateVisible = ref(false)
const generateForm = ref({ encounterId: '', patientId: '', patientName: '', totalAmount: 0, discountAmount: 0, payableAmount: 0 })
const voidVisible = ref(false)
const voidReason = ref('')
const voidRow = ref(null)
const reconVisible = ref(false)
const reconLoading = ref(false)
const reconData = ref([])
const reconTotal = ref(0)
function formatMoney(val) {
if (!val) return '0.00'
return Number(val).toFixed(2)
}
async function loadPage() {
loading.value = true
try {
const res = await getInvoicePage(query.value)
tableData.value = res.data?.records || []
total.value = res.data?.total || 0
} finally {
loading.value = false
}
}
function resetQuery() {
query.value = { invoiceStatus: '', patientName: '', pageNum: 1, pageSize: 10 }
loadPage()
}
function handleGenerate() {
generateForm.value = { encounterId: '', patientId: '', patientName: '', totalAmount: 0, discountAmount: 0, payableAmount: 0 }
generateVisible.value = true
}
async function submitGenerate() {
await generateInvoice(generateForm.value)
ElMessage.success('开具成功')
generateVisible.value = false
loadPage()
}
function handleVoid(row) {
voidRow.value = row
voidReason.value = ''
voidVisible.value = true
}
async function confirmVoid() {
await voidInvoice({ id: voidRow.value.id, reason: voidReason.value })
ElMessage.success('作废成功')
voidVisible.value = false
loadPage()
}
async function showReconciliation() {
reconVisible.value = true
reconLoading.value = true
try {
const res = await getReconciliation({ pageNum: 1, pageSize: 50 })
reconData.value = res.data?.records || []
reconTotal.value = res.data?.totalCount || 0
} finally {
reconLoading.value = false
}
}
onMounted(() => loadPage())
</script>

View File

@@ -0,0 +1,17 @@
import request from '@/utils/request'
export function generateInvoice(data) {
return request({ url: '/invoice/generate', method: 'post', data })
}
export function getInvoicePage(params) {
return request({ url: '/invoice/page', method: 'get', params })
}
export function voidInvoice(params) {
return request({ url: '/invoice/void', method: 'post', params })
}
export function getReconciliation(params) {
return request({ url: '/invoice/reconciliation', method: 'get', params })
}

View File

@@ -0,0 +1,225 @@
<template>
<div class="app-container">
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="传染病报告" name="list">
<el-form :inline="true" :model="queryParams" label-width="100px">
<el-form-item label="报告状态">
<el-select v-model="queryParams.status" placeholder="请选择" clearable style="width:160px">
<el-option label="待上报" value="PENDING" />
<el-option label="已确认" value="CONFIRMED" />
<el-option label="草稿" value="DRAFT" />
<el-option label="已退回" value="RETURNED" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="loadReports">查询</el-button>
<el-button type="success" icon="Plus" @click="showAddDialog">新增报告</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="reportList" border>
<el-table-column label="患者姓名" align="center" prop="patientName" width="120" />
<el-table-column label="疾病名称" align="center" prop="diseaseName" min-width="160" show-overflow-tooltip />
<el-table-column label="疾病编码" align="center" prop="diseaseCode" width="120" />
<el-table-column label="报告类型" align="center" prop="reportType" width="100">
<template #default="{ row }">
<el-tag>{{ reportTypeText(row.reportType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="上报人" align="center" prop="reporterName" width="100" />
<el-table-column label="上报日期" align="center" prop="reportDate" width="160" />
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="{ row }">
<el-tag :type="statusType(row.status)">{{ statusText(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="筛查结果" align="center" prop="screenResult" width="110">
<template #default="{ row }">
<el-tag v-if="row.screenResult" :type="row.screenResult === 'MATCHED' ? 'danger' : 'success'">
{{ row.screenResult === 'MATCHED' ? '命中' : '未命中' }}
</el-tag>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" fixed="right">
<template #default="{ row }">
<el-button link type="primary" icon="View" @click="handleDetail(row)">详情</el-button>
<el-button v-if="row.status === 'PENDING'" link type="success" icon="Check" @click="handleConfirm(row)">确认</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="自动筛查" name="screen">
<el-form :model="screenForm" label-width="110px" style="max-width:700px">
<el-form-item label="患者姓名">
<el-input v-model="screenForm.patientName" placeholder="请输入患者姓名" />
</el-form-item>
<el-form-item label="疾病名称">
<el-input v-model="screenForm.diseaseName" placeholder="请输入诊断疾病名称" />
</el-form-item>
<el-form-item label="疾病编码">
<el-input v-model="screenForm.diseaseCode" placeholder="请输入疾病编码" />
</el-form-item>
<el-form-item label="报告类型">
<el-select v-model="screenForm.reportType" placeholder="请选择">
<el-option label="初次报告" value="INITIAL" />
<el-option label="订正报告" value="CORRECTION" />
</el-select>
</el-form-item>
<el-form-item label="联系电话">
<el-input v-model="screenForm.contactPhone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="现住址">
<el-input v-model="screenForm.address" placeholder="请输入现住址" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleAutoScreen">开始筛查</el-button>
</el-form-item>
</el-form>
<el-divider v-if="screenResult" />
<el-alert v-if="screenResult" :title="screenResultTitle" :type="screenResult.screenResult === 'MATCHED' ? 'error' : 'success'" show-icon :closable="false" style="margin-bottom:16px" />
<el-descriptions v-if="screenResult" :column="2" border>
<el-descriptions-item label="筛查结果">{{ screenResult.screenResult === 'MATCHED' ? '命中法定传染病' : '未命中' }}</el-descriptions-item>
<el-descriptions-item label="筛查等级">{{ screenResult.screenLevel === 'LEVEL_A' ? '甲类' : '正常' }}</el-descriptions-item>
<el-descriptions-item label="筛查时间">{{ screenResult.screenTime }}</el-descriptions-item>
<el-descriptions-item label="报告状态">{{ statusText(screenResult.status) }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="报告统计" name="stats">
<el-descriptions :column="3" border>
<el-descriptions-item label="报告总数">{{ reportStats.total || 0 }}</el-descriptions-item>
<el-descriptions-item label="待上报">{{ reportStats.pending || 0 }}</el-descriptions-item>
<el-descriptions-item label="已确认">{{ reportStats.confirmed || 0 }}</el-descriptions-item>
<el-descriptions-item label="筛查命中">{{ reportStats.screenMatched || 0 }}</el-descriptions-item>
<el-descriptions-item label="筛查未命中">{{ reportStats.screenNormal || 0 }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</el-tabs>
<el-dialog v-model="addDialogVisible" title="新增传染病报告" width="650px" append-to-body>
<el-form :model="formData" label-width="110px">
<el-form-item label="患者姓名">
<el-input v-model="formData.patientName" placeholder="请输入患者姓名" />
</el-form-item>
<el-form-item label="疾病名称">
<el-input v-model="formData.diseaseName" placeholder="请输入疾病名称" />
</el-form-item>
<el-form-item label="疾病编码">
<el-input v-model="formData.diseaseCode" placeholder="请输入疾病编码" />
</el-form-item>
<el-form-item label="报告类型">
<el-select v-model="formData.reportType" placeholder="请选择">
<el-option label="初次报告" value="INITIAL" />
<el-option label="订正报告" value="CORRECTION" />
</el-select>
</el-form-item>
<el-form-item label="联系电话">
<el-input v-model="formData.contactPhone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="现住址">
<el-input v-model="formData.address" placeholder="请输入现住址" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="detailVisible" title="传染病报告详情" width="650px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="患者姓名">{{ detailData.patientName }}</el-descriptions-item>
<el-descriptions-item label="疾病名称">{{ detailData.diseaseName }}</el-descriptions-item>
<el-descriptions-item label="疾病编码">{{ detailData.diseaseCode }}</el-descriptions-item>
<el-descriptions-item label="报告类型">{{ reportTypeText(detailData.reportType) }}</el-descriptions-item>
<el-descriptions-item label="上报人">{{ detailData.reporterName }}</el-descriptions-item>
<el-descriptions-item label="上报日期">{{ detailData.reportDate }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="statusType(detailData.status)">{{ statusText(detailData.status) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="CDC确认号">{{ detailData.cdcConfirmNo || '—' }}</el-descriptions-item>
<el-descriptions-item label="筛查结果">{{ detailData.screenResult === 'MATCHED' ? '命中' : (detailData.screenResult || '—') }}</el-descriptions-item>
<el-descriptions-item label="筛查等级">{{ detailData.screenLevel === 'LEVEL_A' ? '甲类' : (detailData.screenLevel || '—') }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ detailData.contactPhone || '—' }}</el-descriptions-item>
<el-descriptions-item label="现住址">{{ detailData.address || '—' }}</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="EpidemicReport" lang="js">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
const activeTab = ref('list')
const loading = ref(false)
const reportList = ref([])
const addDialogVisible = ref(false)
const detailVisible = ref(false)
const detailData = ref({})
const reportStats = ref({})
const screenResult = ref(null)
const queryParams = reactive({ status: undefined })
const formData = reactive({ patientName: '', diseaseName: '', diseaseCode: '', reportType: 'INITIAL', contactPhone: '', address: '' })
const screenForm = reactive({ patientName: '', diseaseName: '', diseaseCode: '', reportType: 'INITIAL', contactPhone: '', address: '' })
const screenResultTitle = computed(() =>
screenResult.value?.screenResult === 'MATCHED' ? '已命中法定传染病,请及时上报' : '未命中法定传染病'
)
function reportTypeText(t) { return { INITIAL: '初次报告', CORRECTION: '订正报告' }[t] || t }
function statusText(s) { return { PENDING: '待上报', CONFIRMED: '已确认', DRAFT: '草稿', RETURNED: '已退回' }[s] || s }
function statusType(s) { return { PENDING: 'warning', CONFIRMED: 'success', DRAFT: 'info', RETURNED: 'danger' }[s] || 'info' }
function loadReports() {
loading.value = true
request({ url: '/api/v1/epidemic/list', method: 'get', params: queryParams }).then(res => {
reportList.value = res.data || []
}).finally(() => { loading.value = false })
}
function loadStats() {
request({ url: '/api/v1/epidemic/report-stats', method: 'get' }).then(res => {
reportStats.value = res.data || {}
})
}
function showAddDialog() {
Object.assign(formData, { patientName: '', diseaseName: '', diseaseCode: '', reportType: 'INITIAL', contactPhone: '', address: '' })
addDialogVisible.value = true
}
function handleDetail(row) { detailData.value = row; detailVisible.value = true }
function handleConfirm(row) {
request({ url: `/api/v1/epidemic/confirm/${row.id}`, method: 'put', params: { cdcNo: 'CDC-' + Date.now() } }).then(() => {
ElMessage.success('确认成功'); loadReports()
})
}
function handleAutoScreen() {
request({ url: '/api/v1/epidemic/auto-screen', method: 'post', data: screenForm }).then(res => {
screenResult.value = res.data
ElMessage.success('筛查完成')
})
}
function submitForm() {
request({ url: '/api/v1/epidemic/save', method: 'post', data: formData }).then(() => {
ElMessage.success('报告保存成功'); addDialogVisible.value = false; loadReports()
})
}
function handleTabChange() {
if (activeTab.value === 'list') loadReports()
else if (activeTab.value === 'stats') loadStats()
}
onMounted(() => { loadReports(); loadStats() })
</script>

View File

@@ -0,0 +1,300 @@
<template>
<div class="app-container">
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="中医诊断" name="diagnosis">
<el-form :inline="true" :model="diagQuery" label-width="100px">
<el-form-item label="就诊号">
<el-input v-model="diagQuery.encounterId" placeholder="请输入就诊号" clearable style="width:180px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="loadDiagnoses">查询</el-button>
<el-button type="success" icon="Plus" @click="showDiagDialog()">新增诊断</el-button>
</el-form-item>
</el-form>
<el-table v-loading="diagLoading" :data="diagList" border>
<el-table-column label="就诊号" align="center" prop="encounterId" width="140" />
<el-table-column label="诊断类型" align="center" prop="diagnosisType" width="120">
<template #default="{ row }">
<el-tag>{{ diagTypeText(row.diagnosisType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="诊断名称" align="center" prop="diagnosisName" min-width="160" show-overflow-tooltip />
<el-table-column label="证候类型" align="center" prop="syndromeType" width="120">
<template #default="{ row }">
<el-tag v-if="row.syndromeType">{{ syndromeTypeText(row.syndromeType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="证候名称" align="center" prop="syndromeName" min-width="160" show-overflow-tooltip />
<el-table-column label="备注" align="center" prop="remark" min-width="180" show-overflow-tooltip />
</el-table>
</el-tab-pane>
<el-tab-pane label="方剂管理" name="prescription">
<el-form :inline="true" :model="rxQuery" label-width="100px">
<el-form-item label="方剂类型">
<el-select v-model="rxQuery.type" placeholder="请选择" clearable style="width:160px">
<el-option label="经典方剂" value="CLASSIC" />
<el-option label="经验方" value="EXPERIENCE" />
<el-option label="协定方" value="AGREEMENT" />
<el-option label="壮医方" value="ZHUANG" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="loadPrescriptions">搜索</el-button>
<el-button type="success" icon="Plus" @click="showRxDialog()">新增方剂</el-button>
</el-form-item>
</el-form>
<el-table v-loading="rxLoading" :data="rxList" border>
<el-table-column label="方剂名称" align="center" prop="prescriptionName" min-width="160" show-overflow-tooltip />
<el-table-column label="方剂类型" align="center" prop="prescriptionType" width="120">
<template #default="{ row }">
<el-tag>{{ rxTypeText(row.prescriptionType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="组成" align="center" prop="herbs" min-width="200" show-overflow-tooltip />
<el-table-column label="主治" align="center" prop="indication" min-width="150" show-overflow-tooltip />
<el-table-column label="用法用量" align="center" prop="dosage" width="120" />
<el-table-column label="来源" align="center" prop="source" width="120" />
</el-table>
</el-tab-pane>
<el-tab-pane label="体质辨识" name="constitution">
<el-form :inline="true" :model="constQuery" label-width="100px">
<el-form-item label="就诊号">
<el-input v-model="constQuery.encounterId" placeholder="请输入就诊号" clearable style="width:180px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="loadConstitutions">查询</el-button>
<el-button type="success" icon="Plus" @click="showConstDialog()">新增评估</el-button>
</el-form-item>
</el-form>
<el-table v-loading="constLoading" :data="constList" border>
<el-table-column label="就诊号" align="center" prop="encounterId" width="140" />
<el-table-column label="体质类型" align="center" prop="constitutionType" width="140">
<template #default="{ row }">
<el-tag>{{ constTypeText(row.constitutionType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="评估得分" align="center" prop="score" width="100" />
<el-table-column label="调理建议" align="center" prop="recommendation" min-width="200" show-overflow-tooltip />
</el-table>
</el-tab-pane>
<el-tab-pane label="统计" name="statistics">
<el-descriptions :column="3" border>
<el-descriptions-item label="方剂总数">{{ stats.totalPrescriptions || 0 }}</el-descriptions-item>
<el-descriptions-item label="体质辨识总数">{{ stats.totalAssessments || 0 }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</el-tabs>
<el-dialog v-model="diagDialogVisible" title="中医诊断" width="600px" append-to-body>
<el-form :model="diagForm" label-width="110px">
<el-form-item label="就诊号">
<el-input v-model="diagForm.encounterId" placeholder="请输入就诊号" />
</el-form-item>
<el-form-item label="诊断类型">
<el-select v-model="diagForm.diagnosisType" placeholder="请选择">
<el-option label="中医主病诊断" value="MAIN_DISEASE" />
<el-option label="中医主证诊断" value="MAIN_SYNDROME" />
</el-select>
</el-form-item>
<el-form-item label="诊断名称">
<el-input v-model="diagForm.diagnosisName" placeholder="请输入诊断名称" />
</el-form-item>
<el-form-item label="诊断编码">
<el-input v-model="diagForm.diagnosisCode" placeholder="请输入诊断编码" />
</el-form-item>
<el-form-item label="证候类型">
<el-select v-model="diagForm.syndromeType" placeholder="请选择">
<el-option label="风证" value="WIND" />
<el-option label="寒证" value="COLD" />
<el-option label="暑证" value="HEAT" />
<el-option label="湿证" value="DAMP" />
<el-option label="燥证" value="DRY" />
<el-option label="火证" value="FIRE" />
</el-select>
</el-form-item>
<el-form-item label="证候名称">
<el-input v-model="diagForm.syndromeName" placeholder="请输入证候名称" />
</el-form-item>
<el-form-item label="证候编码">
<el-input v-model="diagForm.syndromeCode" placeholder="请输入证候编码" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="diagForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="diagDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitDiag">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="rxDialogVisible" title="新增方剂" width="600px" append-to-body>
<el-form :model="rxForm" label-width="100px">
<el-form-item label="方剂名称">
<el-input v-model="rxForm.prescriptionName" placeholder="请输入方剂名称" />
</el-form-item>
<el-form-item label="方剂类型">
<el-select v-model="rxForm.prescriptionType" placeholder="请选择">
<el-option label="经典方剂" value="CLASSIC" />
<el-option label="经验方" value="EXPERIENCE" />
<el-option label="协定方" value="AGREEMENT" />
<el-option label="壮医方" value="ZHUANG" />
</el-select>
</el-form-item>
<el-form-item label="组成">
<el-input v-model="rxForm.herbs" type="textarea" :rows="3" placeholder="请输入药物组成" />
</el-form-item>
<el-form-item label="主治">
<el-input v-model="rxForm.indication" placeholder="请输入主治病症" />
</el-form-item>
<el-form-item label="用法用量">
<el-input v-model="rxForm.dosage" placeholder="请输入用法用量" />
</el-form-item>
<el-form-item label="来源">
<el-input v-model="rxForm.source" placeholder="请输入方剂来源" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="rxDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitRx">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="constDialogVisible" title="体质辨识" width="600px" append-to-body>
<el-form :model="constForm" label-width="110px">
<el-form-item label="就诊号">
<el-input v-model="constForm.encounterId" placeholder="请输入就诊号" />
</el-form-item>
<el-form-item label="体质类型">
<el-select v-model="constForm.constitutionType" placeholder="请选择">
<el-option v-for="item in constitutionTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="评估得分">
<el-input-number v-model="constForm.score" :min="0" :max="100" />
</el-form-item>
<el-form-item label="调理建议">
<el-input v-model="constForm.recommendation" type="textarea" :rows="3" placeholder="请输入调理建议" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="constDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitConst">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="TcmDiagnosis" lang="js">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
const activeTab = ref('diagnosis')
const diagLoading = ref(false)
const rxLoading = ref(false)
const constLoading = ref(false)
const diagDialogVisible = ref(false)
const rxDialogVisible = ref(false)
const constDialogVisible = ref(false)
const diagList = ref([])
const rxList = ref([])
const constList = ref([])
const stats = ref({})
const diagQuery = reactive({ encounterId: '' })
const rxQuery = reactive({ type: undefined })
const constQuery = reactive({ encounterId: '' })
const diagForm = reactive({ encounterId: '', diagnosisType: 'MAIN_DISEASE', diagnosisName: '', diagnosisCode: '', syndromeType: '', syndromeName: '', syndromeCode: '', remark: '' })
const rxForm = reactive({ prescriptionName: '', prescriptionType: 'CLASSIC', herbs: '', indication: '', dosage: '', source: '' })
const constForm = reactive({ encounterId: '', constitutionType: 'PINGHE', score: 0, recommendation: '' })
const constitutionTypes = [
{ value: 'PINGHE', label: '平和质' }, { value: 'QIXU', label: '气虚质' },
{ value: 'YANGXU', label: '阳虚质' }, { value: 'YINXU', label: '阴虚质' },
{ value: 'TANSHI', label: '痰湿质' }, { value: 'SHIRE', label: '湿热质' },
{ value: 'XUEYU', label: '血瘀质' }, { value: 'QIYU', label: '气郁质' },
{ value: 'TEBING', label: '特禀质' }
]
function diagTypeText(t) {
return { MAIN_DISEASE: '中医主病诊断', MAIN_SYNDROME: '中医主证诊断' }[t] || t
}
function syndromeTypeText(t) {
return { WIND: '风证', COLD: '寒证', HEAT: '暑证', DAMP: '湿证', DRY: '燥证', FIRE: '火证' }[t] || t
}
function rxTypeText(t) {
return { CLASSIC: '经典方剂', EXPERIENCE: '经验方', AGREEMENT: '协定方', ZHUANG: '壮医方' }[t] || t
}
function constTypeText(t) {
const m = { PINGHE: '平和质', QIXU: '气虚质', YANGXU: '阳虚质', YINXU: '阴虚质', TANSHI: '痰湿质', SHIRE: '湿热质', XUEYU: '血瘀质', QIYU: '气郁质', TEBING: '特禀质' }
return m[t] || t
}
function loadDiagnoses() {
diagLoading.value = true
const url = diagQuery.encounterId
? `/api/v1/tcm/diagnosis/encounter/${diagQuery.encounterId}`
: '/api/v1/tcm/diagnosis/encounter/0'
request({ url, method: 'get' }).then(res => {
diagList.value = Array.isArray(res.data) ? res.data : []
}).catch(() => { diagList.value = [] }).finally(() => { diagLoading.value = false })
}
function loadPrescriptions() {
rxLoading.value = true
request({ url: '/api/v1/tcm/prescriptions', method: 'get', params: rxQuery }).then(res => {
rxList.value = res.data || []
}).finally(() => { rxLoading.value = false })
}
function loadConstitutions() {
constLoading.value = true
const url = constQuery.encounterId
? `/api/v1/tcm/constitution/encounter/${constQuery.encounterId}`
: '/api/v1/tcm/statistics'
request({ url, method: 'get' }).then(res => {
constList.value = Array.isArray(res.data) ? res.data : (res.data?.records || [])
}).catch(() => { constList.value = [] }).finally(() => { constLoading.value = false })
}
function loadStats() {
request({ url: '/api/v1/tcm/statistics', method: 'get' }).then(res => { stats.value = res.data || {} })
}
function showDiagDialog(row) {
if (row) { Object.assign(diagForm, row) } else { Object.assign(diagForm, { encounterId: '', diagnosisType: 'MAIN_DISEASE', diagnosisName: '', diagnosisCode: '', syndromeType: '', syndromeName: '', syndromeCode: '', remark: '' }) }
diagDialogVisible.value = true
}
function showRxDialog() { Object.assign(rxForm, { prescriptionName: '', prescriptionType: 'CLASSIC', herbs: '', indication: '', dosage: '', source: '' }); rxDialogVisible.value = true }
function showConstDialog() { Object.assign(constForm, { encounterId: '', constitutionType: 'PINGHE', score: 0, recommendation: '' }); constDialogVisible.value = true }
function submitDiag() {
request({ url: '/api/v1/tcm/diagnosis', method: 'post', data: diagForm }).then(() => {
ElMessage.success('诊断保存成功'); diagDialogVisible.value = false; loadDiagnoses()
})
}
function submitRx() {
request({ url: '/api/v1/tcm/prescription', method: 'post', data: rxForm }).then(() => {
ElMessage.success('方剂保存成功'); rxDialogVisible.value = false; loadPrescriptions()
})
}
function submitConst() {
request({ url: '/api/v1/tcm/constitution/save', method: 'post', data: constForm }).then(() => {
ElMessage.success('评估保存成功'); constDialogVisible.value = false; loadConstitutions()
})
}
function handleTabChange() {
if (activeTab.value === 'diagnosis') loadDiagnoses()
else if (activeTab.value === 'prescription') loadPrescriptions()
else if (activeTab.value === 'constitution') loadConstitutions()
else if (activeTab.value === 'statistics') loadStats()
}
onMounted(() => { loadDiagnoses(); loadStats() })
</script>