Compare commits

...

25 Commits

Author SHA1 Message Date
4693c0d4c0 fix(#762): 汇总领药筛选标签文字丢失且功能失效
根因:
- el-radio-button 使用自闭合标签,Element Plus 需要 slot 传入文字才能渲染
- drugType 未通过 prop 传递给子组件,西药/中药筛选功能断裂
- 子组件 API 调用缺少 tcmFlag 参数,后端无法区分西药/中药
- provide 未从 vue 导入

修复:
- index.vue: 4个 radio-button 补充中文标签(西药/中药/明细/汇总)
- index.vue: drugType radio-group 添加 @change 触发刷新
- index.vue: 子组件添加 :drug-type prop 传递
- index.vue: 补全 provide 到 vue import
- prescriptionList.vue: defineProps 新增 drugType prop,API 添加 tcmFlag
- summaryMedicineList.vue: defineProps 新增 drugType prop,API 添加 tcmFlag
- 清理三个文件的 UTF-8 BOM 字符
2026-06-13 00:57:34 +08:00
750efffa8b fix(#670): 中医处方煎药方式下拉框数据为空 — 添加 method_of_decocting_medicine 字典加载 2026-06-10 23:54:44 +08:00
fa23375050 fix(#718): 停嘱流程优化 — 医嘱状态流转与护士站同步
- AdviceProcessAppServiceImpl: 停嘱逻辑增加护士确认环节
- AdviceManageAppServiceImpl: 医嘱状态查询优化
- RequestStatus: 增加停嘱待确认状态枚举
- medicalConstants: 前端状态常量同步
- OrderForm/prescriptionList: 前端展示优化

由 AI Agent (zhaoyun) 自动修复
2026-06-10 09:00:03 +08:00
8164cec57a fix(#719): 频次/用法和停嘱医生字段为空
根因1: frequencyUsage 由 DictAspect 延迟翻译导致 _dictText 为空
- 改用 DictUtils.getDictLabel 直接翻译,避免 AOP 时序问题

根因2: AdviceProcessAppMapper.xml UNION 分支硬编码 NULL AS stopper_name
- 改为读取 T1.update_by

相关修复: 多个前端组件和后端 mapper 改进

由 AI Agent (zhaoyun) 自动修复
2026-06-10 08:31:37 +08:00
babf62083a fix: 切换账户后路由权限校验 — 防止通过旧标签/URL访问无权限页面
根因: router.beforeEach 在角色加载后 return true,不检查目标路由
是否在当前用户已注册的路由列表中。导致切换账户后,通过旧标签
或直接输入 URL 可访问前一个用户的页面。

修复: 在 return true 前增加 router.resolve() 检查,若目标路由
未注册(matched.length === 0)则拦截并提示无权访问。

数据库验证: 护士角色(role_id=201)确实没有住院医生工作站
(menu_id=288)的 sys_role_menu 权限,后端 getRouters 返回
正确。问题纯粹在前端路由守卫。
2026-06-09 16:55:17 +08:00
68cfa48820 refactor(config): 重构应用配置中的Jackson序列化设置
- 将Jackson2ObjectMapperBuilderCustomizer替换为直接配置ObjectMapper实例
- 移除未使用的Logger导入和日志变量声明
- 统一日期时间序列化配置方式,禁用时间戳格式
- 更新反序列化上下文参数命名以保持一致性
- 简化泛型类型声明,使用钻石操作符

fix(patient): 修复患者管理中的数据库查询语法错误

- 移除PatientManageMapper.xml中多余的逗号导致的SQL语法问题
- 确保字段列表格式正确以避免数据库解析错误

fix(rationaldrug): 修正合理用药模块API端点路径

- 移除API路径中的healthlink-his前缀,统一使用/api/v1基础路径
- 保持所有处方审核相关接口的一致性

feat(patient): 在患者添加对话框中增加联系人信息字段

- 添加联系人姓名、关系和电话号码输入表单
- 为新字段提供相应的验证规则和占位符提示
- 保持与现有监护人信息字段的界面布局一致
2026-06-09 16:37:59 +08:00
Ranyunqiao
d47c83eec5 bug 699 2026-06-09 15:46:52 +08:00
Ranyunqiao
2915915881 bug 573 588 2026-06-09 13:16:36 +08:00
68b92dfe31 fix: Bug#705 死亡时间日期格式兼容 — DTO改String+Service层解析
问题:前端el-date-picker发送yyyy/MM/dd格式,后端Jackson无法解析
根因:Jackson全局simpleDateFormat覆盖字段级@JsonDeserialize,SimpleModule注册Date反序列化器在Spring Boot 4.x中不生效

修复:
- PatientBaseInfoDto.deceasedDate: Date → String(绕过Jackson日期解析)
- PatientInformationServiceImpl.handlePatientInfo: 手动解析String→Date,兼容yyyy-MM-dd和yyyy/MM/dd格式
- ApplicationConfig: 恢复干净状态,移除无效的自定义Date反序列化器
- systemd service: 修正jar路径 openhis → healthlink-his
2026-06-08 22:12:14 +08:00
c9e8729d07 fix: Bug#704 文化程度字典key修正 + 死亡时间日期格式兼容
问题:
1. 修改患者弹窗文化程度下拉无数据
   根因:前端查询字典key为education_level,数据库实际为educational_level
2. 填写死亡时间保存时JSON解析报错
   根因:el-date-picker用YYYY/MM/DD格式,后端期望yyyy-MM-dd HH:mm:ss

修复:
- 前端:字典key修正为educational_level,降级数据与数据库对齐
- 前端:el-date-picker value-format改为YYYY-MM-DD HH:mm:ss
- 前端:submitForm增加deceasedDate格式标准化兜底
- 后端:PatientBaseInfoDto deceasedDate改用FlexibleDateDeserializer兼容多格式
- 新增FlexibleDateDeserializer支持yyyy-MM-dd和yyyy/MM/dd等格式
2026-06-08 16:36:57 +08:00
207640f4ef fix: Bug#705 患者编辑字段不持久化修复
根因分析:
1. Patient实体/PatientBaseInfoDto缺少postalCode,hukouAddress,guardian*,patientDerived,companyAddress字段
2. PatientManageMapper.xml外层SELECT缺少这些字段导致查询不返回
3. handlePatientInfo使用updateById默认NOT_NULL策略导致null字段不更新
4. patientAddDialog.vue的reset()未初始化这些字段

修复内容:
- Patient.java: 补全缺失字段定义
- PatientBaseInfoDto.java: 补全缺失DTO字段
- PatientManageMapper.xml: SQL补全SELECT字段
- PatientInformationServiceImpl.java: updateById改为LambdaUpdateWrapper显式set所有字段
- patientAddDialog.vue: reset()/show()补全字段初始化
- V2026_0608_1: Flyway迁移脚本确保数据库字段存在
2026-06-08 15:24:26 +08:00
566ce61293 fix: EMR模块Schema修复 + 时效统计参数可选化
- V40 Flyway迁移: 修复emr_archive_record/emr_search_index/emr_revision表缺失列和NOT NULL约束
- StructuredEmrController: timeliness/statistics的startDate/endDate参数改为可选
- EMR模块全API连通性验证通过(200)
- 测试数据已填充: 归档21条/修订15条/索引20条/待写病历72条
2026-06-08 15:24:26 +08:00
wangjian963
a04fa368b1 fix(clinic): 修复门诊手术安排计费弹窗vxe-table布局与项目选择问题
问题:
  1. vxe-table expand列40px切换格中渲染复杂编辑表单,内容溢出导致表头表体列错位
  2. adviceBaseList clickRow未解构vxe-table 4.x cell-click事件对象{row},导致selectAdviceBase数据错误
  3. prescriptionList数组元素替换(arr[i]={})不被vxe-table变更检测,选中项目后数据未填入input
  4. 保存按钮调用formRef{index}但表单已迁出expand列,运行时抛undefined.validate异常
2026-06-08 14:42:54 +08:00
f940078208 Merge remote-tracking branch 'origin/develop' into develop 2026-06-08 13:06:50 +08:00
06363ec191 fix(user): 解决切换账户时标签页状态残留问题
- 导入 tagsView 模块以管理标签页状态
- 在用户登出时清除标签页内存状态
- 添加异常处理避免标签页清理失败影响登出流程
- 修复切换账户时页面标签残留的安全风险
- 在检查清单文档开头添加空行以符合格式规范
2026-06-08 13:06:38 +08:00
wangjian963
3c8d5e94a3 598 【住院医生工作站-临床医嘱】临床医嘱列表缺少“开嘱医生”列,无法追溯责任医生 2026-06-08 13:05:58 +08:00
wangjian963
6f7f6dc9f5 Merge remote-tracking branch 'origin/develop' into develop 2026-06-08 12:52:19 +08:00
wangjian963
376ddd46ff 595 【住院护士站-医嘱校对】医嘱校对模块列表字段缺失严重,与医生站医嘱要素不一致,存在核对安全隐患 2026-06-08 12:51:54 +08:00
e1ab9fba23 Merge remote-tracking branch 'origin/develop' into develop 2026-06-08 12:11:54 +08:00
f458835183 fix(viewer): 修复3D渲染器初始化和纹理配置问题
- 将体积纹理从DataTexture改为Data3DTexture以支持三维数据
- 分别设置纹理格式和类型属性避免构造函数参数错误
- 使用ResizeObserver替代nextTick和setTimeout实现容器尺寸检测
- 添加最小高度约束确保渲染器正确初始化
- 优化样式定义增强组件布局稳定性
2026-06-08 12:11:46 +08:00
wangjian963
57f591e1c0 Merge remote-tracking branch 'origin/develop' into develop 2026-06-08 11:41:23 +08:00
wangjian963
a98a03e00a fix: 替换 eslint-plugin-import 为 eslint-plugin-import-x,解决与 ESLint 10 的依赖冲突
- eslint-plugin-import@2.32.0 peerDependency 仅支持 ESLint ^2-^9,与项目 eslint@10.4.1 不兼容
- eslint-import-resolver-alias 依赖链会间接拉回旧版 eslint-plugin-import,形成连锁冲突
- 移除 eslint-plugin-import 和 eslint-import-resolver-alias,改用 eslint-plugin-import-x@^4.16.1
- eslint.config.js 使用内置 createNodeResolver() 替代外部 resolver,@ 别名改用绝对路径解析
2026-06-08 11:40:55 +08:00
fddf1c2d03 fix: 医生下拉关联真实用户 + 清理脏数据 + 3D查看器
修复:
- 医生下拉改为调用/system/user/list获取所有活跃用户
- 新建任务表单filterable选择真实医生
- 清理测试产生的脏数据(7个CANCELLED任务+5个测试报告)
- 修复卡住的PROCESSING任务(改为CANCELLED)

医生列表:
- 显示所有活跃用户的nickName+userName
- 支持搜索过滤
2026-06-08 11:35:56 +08:00
wangjian963
c7f85ff20d Merge remote-tracking branch 'origin/develop' into develop 2026-06-08 11:20:28 +08:00
wangjian963
72ab38f5d0 594 【住院医生工作站-临床医嘱】开立需皮试药物时系统未弹出皮试确认框,且医嘱输入行“皮试”字段置灰只读无法手动编辑 2026-06-08 11:19:34 +08:00
57 changed files with 1758 additions and 572 deletions

View File

@@ -1,3 +1,4 @@
# 前端发布前检查清单
> **文档类型**: 技术规范

View File

@@ -1,5 +1,5 @@
{
"test_time": "2026-06-08T09:11:33.934379",
"test_time": "2026-06-08T11:20:49.248056",
"environment": "http://localhost:18082/healthlink-his",
"total": 125,
"passed": 125,
@@ -167,7 +167,7 @@
"id": "OP-PHARM",
"name": "待发药列表",
"ok": true,
"detail": "待发药=534"
"detail": "待发药=532"
},
{
"id": "OP-WEST",
@@ -377,13 +377,13 @@
"id": "INS-3D",
"name": "3D重建任务",
"ok": true,
"detail": "任务=0"
"detail": "任务=14"
},
{
"id": "INS-3D-RPT",
"name": "3D重建报告",
"ok": true,
"detail": "报告=0"
"detail": "报告=11"
},
{
"id": "INS-RAD-RPT",
@@ -695,7 +695,7 @@
"id": "MR-05-PHARM",
"name": "药师→待发药",
"ok": true,
"detail": "待发药=534"
"detail": "待发药=532"
},
{
"id": "MR-06-CHARGE",

View File

@@ -1,83 +1,56 @@
package com.core.framework.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.jackson2.autoconfigure.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
/**
* 程序注解配置
*
* @author system
*/
@Configuration
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
// 指定要扫描的Mapper类的包的路径
@MapperScan({"com.core.**.mapper", "com.healthlink.his.**.mapper"})
public class ApplicationConfig {
private static final Logger log = LoggerFactory.getLogger(ApplicationConfig.class);
/** 支持多种日期格式的反序列化器 */
private static final JsonDeserializer<LocalDateTime> LOCAL_DATE_TIME_DESERIALIZER = new JsonDeserializer<LocalDateTime>() {
private static final JsonDeserializer<LocalDateTime> LOCAL_DATE_TIME_DESERIALIZER = new JsonDeserializer<>() {
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
private static final DateTimeFormatter SIMPLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter SLASH_FORMATTER = DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss");
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
public LocalDateTime deserialize(JsonParser p, DeserializationContext context) throws IOException {
String text = p.getText();
if (text == null || text.isEmpty()) {
return null;
}
// 去除时区后缀 Z/z 和偏移量 +HH:MM/+HHMMLocalDateTime 不含时区信息)
if (text == null || text.isEmpty()) return null;
String cleaned = text.replaceAll("[Zz]$", "").replaceAll("[+-]\\d{2}:?\\d{2}$", "");
// 尝试 ISO 8601 格式yyyy-MM-ddTHH:mm:ss.SSS
try {
return LocalDateTime.parse(cleaned, ISO_FORMATTER);
} catch (Exception ignored) {
// intentionally ignored
}
// 尝试简单格式yyyy-MM-dd HH:mm:ss
try {
return LocalDateTime.parse(cleaned, SIMPLE_FORMATTER);
} catch (Exception ignored) {
// intentionally ignored
}
// 尝试斜杠格式yyyy/M/d HH:mm:ss
try { return LocalDateTime.parse(cleaned, ISO_FORMATTER); } catch (Exception ignored) {}
try { return LocalDateTime.parse(cleaned, SIMPLE_FORMATTER); } catch (Exception ignored) {}
return LocalDateTime.parse(cleaned, SLASH_FORMATTER);
}
};
/**
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return builder -> {
// 设置默认时区
builder.timeZone(TimeZone.getDefault());
// 设置日期格式为 yyyy/M/d HH:mm:ss支持多种格式反序列化
builder.simpleDateFormat("yyyy/M/d HH:mm:ss");
// 添加JavaTimeModule支持用于LocalDateTime
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class, LOCAL_DATE_TIME_DESERIALIZER);
builder.modules(javaTimeModule);
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss")));
};
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setTimeZone(TimeZone.getDefault());
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class, LOCAL_DATE_TIME_DESERIALIZER);
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
mapper.registerModule(javaTimeModule);
return mapper;
}
}
}

View File

@@ -72,8 +72,8 @@ public class StructuredEmrController {
@GetMapping("/timeliness/statistics")
@Operation(summary = "完成率统计")
public R<Map<String, Object>> getCompletionStatistics(
@RequestParam String startDate,
@RequestParam String endDate) {
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
return R.ok(structuredEmrAppService.getCompletionStatistics(startDate, endDate));
}

View File

@@ -99,4 +99,20 @@ public interface IInHospitalRegisterAppService {
* @return 病区列表
*/
List<LocationDto> getWardList(Long orgId);
/**
* 修改住院登记信息
*
* @param inHospitalInfoDto 登记dto
* @return 结果
*/
R<?> updateRegistration(InHospitalInfoDto inHospitalInfoDto);
/**
* 作废住院登记
*
* @param encounterId 住院就诊id
* @return 结果
*/
R<?> voidRegistration(Long encounterId);
}

View File

@@ -84,6 +84,9 @@ public class InHospitalRegisterAppServiceImpl implements IInHospitalRegisterAppS
@Resource
private YbManager ybManager;
@Resource
private IChargeItemService iChargeItemService;
/**
* 门诊医生开住院申请
*
@@ -362,6 +365,183 @@ public class InHospitalRegisterAppServiceImpl implements IInHospitalRegisterAppS
return locationDtoList;
}
/**
* 修改住院登记信息
*
* @param inHospitalInfoDto 登记dto
* @return 结果
*/
@Override
public R<?> updateRegistration(InHospitalInfoDto inHospitalInfoDto) {
Long encounterId = inHospitalInfoDto.getEncounterId();
if (encounterId == null) {
throw new ServiceException("就诊ID不能为空");
}
Encounter encounter = iEncounterService.getById(encounterId);
if (encounter == null) {
throw new ServiceException("未找到该住院登记记录");
}
// 仅"待入科"状态可修改
if (!EncounterZyStatus.REGISTERED.getValue().equals(encounter.getStatusEnum())) {
throw new ServiceException("患者已入科接收,无法修改登记信息");
}
// 更新就诊信息
encounter.setOrganizationId(inHospitalInfoDto.getInHospitalOrgId()); // 住院科室
encounter.setPriorityEnum(inHospitalInfoDto.getPriorityEnum()); // 优先级(患者病情)
encounter.setAdmitSourceCode(inHospitalInfoDto.getAdmitSourceCode()); // 入院类型
encounter.setInWayCode(inHospitalInfoDto.getInWayCode()); // 入院方式
encounter.setStartTime(inHospitalInfoDto.getStartTime()); // 入院日期
encounter.setRegistrarId(SecurityUtils.getLoginUser().getPractitionerId()); // 登记员
iEncounterService.saveOrUpdate(encounter);
// 更新病区信息
EncounterLocation encounterLocation = iEncounterLocationService.getOne(
new LambdaQueryWrapper<EncounterLocation>()
.eq(EncounterLocation::getEncounterId, encounterId)
.eq(EncounterLocation::getFormEnum, LocationForm.WARD.getValue()));
if (inHospitalInfoDto.getWardLocationId() != null) {
EncounterLocation encounterLocationReg = new EncounterLocation();
if (encounterLocation != null) {
encounterLocationReg.setId(encounterLocation.getId());
}
encounterLocationReg.setEncounterId(encounterId);
encounterLocationReg.setLocationId(inHospitalInfoDto.getWardLocationId());
encounterLocationReg.setFormEnum(LocationForm.WARD.getValue());
iEncounterLocationService.saveOrUpdate(encounterLocationReg);
}
// 更新费用性质(contractNo)对应的账户信息
if (inHospitalInfoDto.getContractNo() != null) {
boolean selfFunded = CommonConstants.BusinessName.DEFAULT_CONTRACT_NO.equals(inHospitalInfoDto.getContractNo());
// 查找自费账户type_code='04'
Account cashAccount = iAccountService.getOne(
new LambdaQueryWrapper<Account>()
.eq(Account::getEncounterId, encounterId)
.eq(Account::getTypeCode, AccountType.PERSONAL_CASH_ACCOUNT.getCode()));
// 查找非自费账户
Account contractAccount = iAccountService.getOne(
new LambdaQueryWrapper<Account>()
.eq(Account::getEncounterId, encounterId)
.ne(Account::getTypeCode, AccountType.PERSONAL_CASH_ACCOUNT.getCode()));
if (selfFunded) {
// 改为自费
if (cashAccount != null) {
cashAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
cashAccount.setEncounterFlag(Whether.YES.getValue());
iAccountService.saveOrUpdate(cashAccount);
} else {
// 不存在自费账户时,创建一个
Account newCashAccount = new Account();
newCashAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newCashAccount.setPatientId(encounter.getPatientId());
newCashAccount.setEncounterId(encounterId);
newCashAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newCashAccount.setEncounterFlag(Whether.YES.getValue());
newCashAccount.setBalanceAmount(BigDecimal.ZERO);
iAccountService.save(newCashAccount);
}
// 删除非自费账户
if (contractAccount != null) {
iAccountService.removeById(contractAccount.getId());
}
} else {
// 改为非自费
if (cashAccount != null) {
cashAccount.setEncounterFlag(Whether.NO.getValue());
iAccountService.saveOrUpdate(cashAccount);
} else {
// 不存在自费账户时,创建一个
Account newCashAccount = new Account();
newCashAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newCashAccount.setPatientId(encounter.getPatientId());
newCashAccount.setEncounterId(encounterId);
newCashAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newCashAccount.setEncounterFlag(Whether.NO.getValue());
newCashAccount.setBalanceAmount(BigDecimal.ZERO);
iAccountService.save(newCashAccount);
}
// 更新或创建非自费账户
String typeCode = StringUtils.isNotEmpty(inHospitalInfoDto.getTypeCoce())
? inHospitalInfoDto.getTypeCoce()
: AccountType.PERSONAL_CASH_ACCOUNT.getCode();
if (contractAccount != null) {
contractAccount.setContractNo(inHospitalInfoDto.getContractNo());
iAccountService.saveOrUpdate(contractAccount);
} else {
Account newAccount = new Account();
newAccount.setTypeCode(typeCode);
newAccount.setPatientId(encounter.getPatientId());
newAccount.setEncounterId(encounterId);
newAccount.setContractNo(inHospitalInfoDto.getContractNo());
newAccount.setEncounterFlag(Whether.YES.getValue());
iAccountService.save(newAccount);
}
}
}
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"住院登记"}));
}
/**
* 作废住院登记
*
* @param encounterId 住院就诊id
* @return 结果
*/
@Override
public R<?> voidRegistration(Long encounterId) {
if (encounterId == null) {
throw new ServiceException("就诊ID不能为空");
}
Encounter encounter = iEncounterService.getById(encounterId);
if (encounter == null) {
throw new ServiceException("未找到该住院登记记录");
}
// 仅"待入科"状态可作废
if (!EncounterZyStatus.REGISTERED.getValue().equals(encounter.getStatusEnum())) {
throw new ServiceException("该患者已入科,请先通知护士站办理退科处理!");
}
// 检查预交金余额
Account cashAccount = iAccountService.getOne(
new LambdaQueryWrapper<Account>()
.eq(Account::getEncounterId, encounterId)
.eq(Account::getTypeCode, AccountType.PERSONAL_CASH_ACCOUNT.getCode()));
if (cashAccount != null && cashAccount.getBalanceAmount() != null
&& cashAccount.getBalanceAmount().compareTo(BigDecimal.ZERO) > 0) {
throw new ServiceException("该患者存在未退清的预交金,请先前往预交金页面办理退款!");
}
// 检查是否已产生计费
long chargeCount = iChargeItemService.count(
new LambdaQueryWrapper<ChargeItem>()
.eq(ChargeItem::getEncounterId, encounterId));
if (chargeCount > 0) {
throw new ServiceException("该患者已产生计费记录,无法作废登记!");
}
// 设置状态为已作废
encounter.setStatusEnum(EncounterZyStatus.VOIDED.getValue());
encounter.setRegistrarId(SecurityUtils.getLoginUser().getPractitionerId()); // 作废操作人
iEncounterService.saveOrUpdate(encounter);
// 清理账户记录
if (cashAccount != null) {
iAccountService.removeById(cashAccount.getId());
}
// 清理非自费账户
iAccountService.remove(
new LambdaQueryWrapper<Account>()
.eq(Account::getEncounterId, encounterId)
.ne(Account::getTypeCode, AccountType.PERSONAL_CASH_ACCOUNT.getCode()));
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"作废操作"}));
}
/**
* 处理入院登记信息
*

View File

@@ -142,4 +142,26 @@ public class InHospitalRegisterController {
return R.ok(iInHospitalRegisterAppService.getWardList(orgId));
}
/**
* 修改住院登记信息
*
* @param inHospitalInfoDto 登记dto
* @return 结果
*/
@PutMapping(value = "/update-registration")
public R<?> updateRegistration(@RequestBody InHospitalInfoDto inHospitalInfoDto) {
return iInHospitalRegisterAppService.updateRegistration(inHospitalInfoDto);
}
/**
* 作废住院登记
*
* @param encounterId 住院就诊id
* @return 结果
*/
@PutMapping(value = "/void-registration")
public R<?> voidRegistration(@RequestParam(value = "encounterId") Long encounterId) {
return iInHospitalRegisterAppService.voidRegistration(encounterId);
}
}

View File

@@ -95,4 +95,9 @@ public class InHospitalRegisterQueryDto {
private String admitSourceCode;
private String admitSourceCode_dictText;
/**
* 住院状态
*/
private Integer statusEnum;
}

View File

@@ -186,13 +186,18 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
QueryWrapper<InpatientAdviceParam> queryWrapper
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
// 手动拼接requestStatus条件COMPLETED(3)时同时包含CHECK_VERIFIED(10)和PENDING_RECEIVE(11)
// 手动拼接requestStatus条件
// COMPLETED(3)时同时包含CHECK_VERIFIED(10)和PENDING_RECEIVE(11)
// ACTIVE(2)时同时包含PENDING_STOP(13),以便护士核对停嘱医嘱
// UNION查询外层列名为request_statusT1.status_enum AS request_status不是status_enum
if (requestStatus != null) {
if (RequestStatus.COMPLETED.getValue().equals(requestStatus)) {
queryWrapper.in("request_status",
RequestStatus.COMPLETED.getValue(), RequestStatus.CHECK_VERIFIED.getValue(),
RequestStatus.PENDING_RECEIVE.getValue());
} else if (RequestStatus.ACTIVE.getValue().equals(requestStatus)) {
queryWrapper.in("request_status",
RequestStatus.ACTIVE.getValue(), RequestStatus.PENDING_STOP.getValue());
} else {
queryWrapper.eq("request_status", requestStatus);
}
@@ -230,6 +235,70 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
if (e.getBirthDate() != null) {
e.setAge(AgeCalculatorUtil.getAge(e.getBirthDate()));
}
// ---------- Bug #595: 医嘱校对列表计算字段 ----------
// 单次剂量:剂量 + 单位
if (e.getDose() != null) {
String doseStr = e.getDose().stripTrailingZeros().toPlainString();
String unitStr = e.getDoseUnitCode_dictText() != null ? e.getDoseUnitCode_dictText() : "";
e.setSingleDose(doseStr + unitStr);
}
// 总量:剂量 × 数量 + 单位(仅药品医嘱)
if (e.getDose() != null && e.getQuantity() != null) {
BigDecimal total = e.getDose().multiply(BigDecimal.valueOf(e.getQuantity()));
String totalStr = total.stripTrailingZeros().toPlainString();
String unitStr = e.getUnitCode_dictText() != null ? e.getUnitCode_dictText() : "";
e.setTotalAmount(totalStr + unitStr);
} else if (e.getQuantity() != null) {
String unitStr = e.getUnitCode_dictText() != null ? e.getUnitCode_dictText() : "";
e.setTotalAmount(e.getQuantity() + unitStr);
}
// 频次/用法组合直接使用DictUtils翻译避免DictAspect延迟导致_dictText为空
String rateLabel = e.getRateCode() != null ? DictUtils.getDictLabel("rate_code", e.getRateCode()) : null;
String methodLabel = e.getMethodCode() != null ? DictUtils.getDictLabel("method_code", e.getMethodCode()) : null;
StringBuilder freqBuilder = new StringBuilder();
if (rateLabel != null && !rateLabel.isEmpty()) {
freqBuilder.append(rateLabel);
}
if (methodLabel != null && !methodLabel.isEmpty()) {
if (freqBuilder.length() > 0) freqBuilder.append(" ");
freqBuilder.append(methodLabel);
}
e.setFrequencyUsage(freqBuilder.length() > 0 ? freqBuilder.toString() : null);
// 开嘱医生
e.setOrderingDoctor(e.getRequesterId_dictText() != null
? e.getRequesterId_dictText() : e.getAdmittingDoctorName());
// 皮试状态 + 高亮
if (e.getSkinTestFlag() != null && e.getSkinTestFlag() == 1) {
e.setSkinTestStatus("需皮试");
e.setSkinTestHighlight(true);
} else if (e.getSkinTestFlag() != null && e.getSkinTestFlag() == 2) {
e.setSkinTestStatus("皮试通过");
e.setSkinTestHighlight(false);
} else {
e.setSkinTestStatus("无需");
e.setSkinTestHighlight(false);
}
// 注射药品标记
e.setIsInjection(e.getInjectFlag() != null && e.getInjectFlag() == 1);
// 停嘱医生
e.setStopperName(e.getStopperName());
// 停嘱时间
e.setStopTime(e.getEndTime());
// 医嘱号
e.setOrderNo(e.getRequestId() != null ? e.getRequestId().toString() : null);
// 诊断(行级)
e.setDiagnosis(e.getConditionNames());
});
// 获取医嘱列表
@@ -333,10 +402,29 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
List<Long> serviceReqIds = serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList();
// 先查询服务请求,按 categoryEnum 分流:检查类(23)走 CHECK_VERIFIED其余走 COMPLETED
List<ServiceRequest> allServiceRequests = serviceRequestService.listByIds(serviceReqIds);
List<Long> checkReqIds = allServiceRequests.stream()
// 分离已停嘱(PENDING_STOP)的订单,护士核对后变为已停止(STOPPED)
List<ServiceRequest> pendingStopRequests = allServiceRequests.stream()
.filter(sr -> RequestStatus.PENDING_STOP.getValue().equals(sr.getStatusEnum()))
.toList();
List<ServiceRequest> normalRequests = allServiceRequests.stream()
.filter(sr -> !RequestStatus.PENDING_STOP.getValue().equals(sr.getStatusEnum()))
.toList();
// 已停嘱订单 → 已停止(STOPPED)
if (!pendingStopRequests.isEmpty()) {
List<Long> pendingStopIds = pendingStopRequests.stream().map(ServiceRequest::getId).toList();
serviceRequestService.update(new LambdaUpdateWrapper<ServiceRequest>()
.in(ServiceRequest::getId, pendingStopIds)
.set(ServiceRequest::getStatusEnum, RequestStatus.STOPPED.getValue())
.set(ServiceRequest::getUpdateBy, SecurityUtils.getNickName()));
}
// 正常订单:检查类 → 已校对CHECK_VERIFIED=10其余 → 已完成COMPLETED=3
List<Long> checkReqIds = normalRequests.stream()
.filter(sr -> ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum()))
.map(ServiceRequest::getId).toList();
List<Long> otherReqIds = allServiceRequests.stream()
List<Long> otherReqIds = normalRequests.stream()
.filter(sr -> !ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum()))
.map(ServiceRequest::getId).toList();
// 检查类 → 已校对CHECK_VERIFIED=10
@@ -348,7 +436,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
serviceRequestService.updateCompleteRequestStatus(otherReqIds, practitionerId, checkDate);
}
// 处理转科/出院等特殊医嘱
for (ServiceRequest serviceRequest : allServiceRequests) {
for (ServiceRequest serviceRequest : normalRequests) {
if (ActivityDefCategory.TRANSFER.getValue().equals(serviceRequest.getCategoryEnum())) {
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
EncounterZyStatus.PENDING_TRANSFER.getValue());
@@ -359,9 +447,30 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
}
}
if (!medRequestList.isEmpty()) {
// 更新药品请求状态已完成
medicationRequestService.updateCompletedStatusBatch(
medRequestList.stream().map(PerformInfoDto::getRequestId).toList(), practitionerId, checkDate);
List<Long> medReqIds = medRequestList.stream().map(PerformInfoDto::getRequestId).toList();
// 查询药品请求,分离已停嘱(PENDING_STOP)的订单
List<MedicationRequest> allMedRequests = medicationRequestService.list(
new LambdaQueryWrapper<MedicationRequest>()
.select(MedicationRequest::getId, MedicationRequest::getStatusEnum)
.in(MedicationRequest::getId, medReqIds));
List<Long> pendingStopMedIds = allMedRequests.stream()
.filter(mr -> RequestStatus.PENDING_STOP.getValue().equals(mr.getStatusEnum()))
.map(MedicationRequest::getId).toList();
List<Long> normalMedIds = allMedRequests.stream()
.filter(mr -> !RequestStatus.PENDING_STOP.getValue().equals(mr.getStatusEnum()))
.map(MedicationRequest::getId).toList();
// 已停嘱订单 → 已停止(STOPPED)
if (!pendingStopMedIds.isEmpty()) {
medicationRequestService.update(new LambdaUpdateWrapper<MedicationRequest>()
.in(MedicationRequest::getId, pendingStopMedIds)
.set(MedicationRequest::getStatusEnum, RequestStatus.STOPPED.getValue())
.set(MedicationRequest::getUpdateBy, SecurityUtils.getNickName()));
}
// 正常订单 → 已完成(COMPLETED)
if (!normalMedIds.isEmpty()) {
medicationRequestService.updateCompletedStatusBatch(normalMedIds, practitionerId, checkDate);
}
}
return R.ok(null, "校对成功");
}

View File

@@ -190,8 +190,9 @@ public class MedicineSummaryAppServiceImpl implements IMedicineSummaryAppService
}
// 领药人
Long receiverId = medicineSummaryParamList.get(0).getReceiverId();
// 申请时间
Date now = DateUtils.getNowDate();
// 申请时间(优先使用前端传递的操作时间,确保与实际操作时间一致)
Date now = medicineSummaryParamList.get(0).getDispenseTime() != null
? medicineSummaryParamList.get(0).getDispenseTime() : DateUtils.getNowDate();
// 申请人
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
// 药品发放id

View File

@@ -259,4 +259,40 @@ public class InpatientAdviceDto {
/** 发药状态 */
private Integer dispenseStatus;
private String dispenseStatus_enumText;
// ========== Bug #595 计算展示字段 ==========
/** 单次剂量(格式化:剂量+单位) */
private String singleDose;
/** 总量(格式化:剂量×数量+单位) */
private String totalAmount;
/** 频次/用法组合tid 静滴) */
private String frequencyUsage;
/** 开嘱医生 */
private String orderingDoctor;
/** 皮试状态文本(需皮试/皮试通过/无需) */
private String skinTestStatus;
/** 皮试高亮标记需皮试时为true */
private Boolean skinTestHighlight;
/** 是否注射药品 */
private Boolean isInjection;
/** 停嘱医生 */
private String stopperName;
/** 停嘱时间 */
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date stopTime;
/** 医嘱号 */
private String orderNo;
/** 诊断(行级展示) */
private String diagnosis;
}

View File

@@ -36,6 +36,8 @@ import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -310,13 +312,73 @@ public class PatientInformationServiceImpl implements IPatientInformationService
patient.setBloodAbo(patientInfoDto.getBloodAbo()); // 血型ABO
patient.setBloodRh(patientInfoDto.getBloodRh()); // 血型RH
patient.setMaritalStatusEnum(patientInfoDto.getMaritalStatusEnum()); // 婚姻状态
patient.setDeceasedDate(patientInfoDto.getDeceasedDate()); // 死亡时间
// 死亡时间:支持 yyyy-MM-dd HH:mm:ss 和 yyyy/MM/dd HH:mm:ss 格式
if (patientInfoDto.getDeceasedDate() != null && !patientInfoDto.getDeceasedDate().isEmpty()) {
String dateStr = patientInfoDto.getDeceasedDate();
String[] patterns = {"yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss", "yyyy/M/d HH:mm:ss"};
Date parsedDate = null;
for (String pat : patterns) {
try { parsedDate = new SimpleDateFormat(pat).parse(dateStr); break; } catch (Exception ignored) {}
}
patient.setDeceasedDate(parsedDate);
} else {
patient.setDeceasedDate(null);
}
patient.setNationalityCode(patientInfoDto.getNationalityCode());// 民族
patient.setActiveFlag(patientInfoDto.getActiveFlag());// 活动标识
patient.setCountryCode(patientInfoDto.getCountryCode());// 国家编码
patient.setPostalCode(patientInfoDto.getPostalCode());// 邮政编码
patient.setHukouAddress(patientInfoDto.getHukouAddress());// 户籍地址
patient.setGuardianName(patientInfoDto.getGuardianName());// 监护人姓名
patient.setGuardianRelation(patientInfoDto.getGuardianRelation());// 监护人关系
patient.setGuardianPhone(patientInfoDto.getGuardianPhone());// 监护人电话
patient.setGuardianIdType(patientInfoDto.getGuardianIdType());// 监护人证件类型
patient.setGuardianIdNo(patientInfoDto.getGuardianIdNo());// 监护人证件号码
patient.setGuardianAddress(patientInfoDto.getGuardianAddress());// 监护人地址
patient.setPatientDerived(patientInfoDto.getPatientDerived());// 患者来源
patient.setEducationLevel(patientInfoDto.getEducationLevel());// 文化程度
patient.setCompanyAddress(patientInfoDto.getCompanyAddress());// 单位地址
if (patientInfoDto.getId() != null) {
// 更新操作
patientService.updateById(patient);
// 更新操作 - 使用 LambdaUpdateWrapper 显式设置所有字段,确保 null 值也能正确更新
LambdaUpdateWrapper<Patient> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Patient::getId, patient.getId())
.set(Patient::getName, patient.getName())
.set(Patient::getPyStr, patient.getPyStr())
.set(Patient::getWbStr, patient.getWbStr())
.set(Patient::getIdCard, patient.getIdCard())
.set(Patient::getBirthDate, patient.getBirthDate())
.set(Patient::getGenderEnum, patient.getGenderEnum())
.set(Patient::getPhone, patient.getPhone())
.set(Patient::getPrfsEnum, patient.getPrfsEnum())
.set(Patient::getWorkCompany, patient.getWorkCompany())
.set(Patient::getLinkName, patient.getLinkName())
.set(Patient::getLinkRelationCode, patient.getLinkRelationCode())
.set(Patient::getLinkTelcom, patient.getLinkTelcom())
.set(Patient::getAddress, patient.getAddress())
.set(Patient::getAddressProvince, patient.getAddressProvince())
.set(Patient::getAddressCity, patient.getAddressCity())
.set(Patient::getAddressDistrict, patient.getAddressDistrict())
.set(Patient::getAddressStreet, patient.getAddressStreet())
.set(Patient::getBloodAbo, patient.getBloodAbo())
.set(Patient::getBloodRh, patient.getBloodRh())
.set(Patient::getMaritalStatusEnum, patient.getMaritalStatusEnum())
.set(Patient::getDeceasedDate, patient.getDeceasedDate())
.set(Patient::getNationalityCode, patient.getNationalityCode())
.set(Patient::getActiveFlag, patient.getActiveFlag())
.set(Patient::getCountryCode, patient.getCountryCode())
.set(Patient::getPostalCode, patient.getPostalCode())
.set(Patient::getHukouAddress, patient.getHukouAddress())
.set(Patient::getGuardianName, patient.getGuardianName())
.set(Patient::getGuardianRelation, patient.getGuardianRelation())
.set(Patient::getGuardianPhone, patient.getGuardianPhone())
.set(Patient::getGuardianIdType, patient.getGuardianIdType())
.set(Patient::getGuardianIdNo, patient.getGuardianIdNo())
.set(Patient::getGuardianAddress, patient.getGuardianAddress())
.set(Patient::getPatientDerived, patient.getPatientDerived())
.set(Patient::getEducationLevel, patient.getEducationLevel())
.set(Patient::getCompanyAddress, patient.getCompanyAddress());
patientService.update(updateWrapper);
} else {
// 新增操作
patientService.save(patient);

View File

@@ -0,0 +1,42 @@
package com.healthlink.his.web.patientmanage.dto;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 支持多种日期格式的反序列化器
* 兼容 yyyy-MM-dd HH:mm:ss 和 yyyy/MM/dd HH:mm:ss 等格式
*/
public class FlexibleDateDeserializer extends JsonDeserializer<Date> {
private static final String[] DATE_FORMATS = {
"yyyy-MM-dd HH:mm:ss",
"yyyy/MM/dd HH:mm:ss",
"yyyy/M/d HH:mm:ss",
"yyyy-MM-dd'T'HH:mm:ss",
"yyyy-MM-dd'T'HH:mm:ss.SSS",
"yyyy-MM-dd",
"yyyy/MM/dd"
};
@Override
public Date deserialize(JsonParser p, DeserializationContext context) throws IOException {
String dateStr = p.getValueAsString();
if (dateStr == null || dateStr.isEmpty()) {
return null;
}
for (String pattern : DATE_FORMATS) {
try {
return new SimpleDateFormat(pattern).parse(dateStr);
} catch (ParseException ignored) {
}
}
throw new IOException("无法解析日期: " + dateStr + ",支持格式: yyyy-MM-dd HH:mm:ss 或 yyyy/MM/dd HH:mm:ss");
}
}

View File

@@ -1,6 +1,7 @@
package com.healthlink.his.web.patientmanage.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.healthlink.his.common.annotation.Dict;
@@ -144,8 +145,7 @@ public class PatientBaseInfoDto {
/**
* 死亡时间
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date deceasedDate;
private String deceasedDate;
/**
* 病人证件信息集合
@@ -168,4 +168,65 @@ public class PatientBaseInfoDto {
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 邮政编码
*/
private String postalCode;
/**
* 户籍地址
*/
private String hukouAddress;
/**
* 监护人姓名
*/
private String guardianName;
/**
* 监护人关系
*/
private Integer guardianRelation;
private String guardianRelation_enumText;
/**
* 监护人电话
*/
private String guardianPhone;
/**
* 监护人证件类型
*/
private String guardianIdType;
/**
* 监护人证件号码
*/
private String guardianIdNo;
/**
* 监护人地址
*/
private String guardianAddress;
/**
* 患者来源
*/
private String patientDerived;
/**
* 文化程度
*/
private String educationLevel;
/**
* 单位地址
*/
private String companyAddress;
/**
* 国家编码
*/
private String countryCode;
}

View File

@@ -155,6 +155,8 @@ public class WesternMedicineDispenseAppServiceImpl implements IWesternMedicineDi
// 发药状态
List<DispenseStatusOption> dispenseStatusOptions = new ArrayList<>();
dispenseStatusOptions.add(new DispenseStatusOption(DispenseStatus.SUBMITTED.getValue(),
DispenseStatus.SUBMITTED.getInfo()));
dispenseStatusOptions.add(new DispenseStatusOption(DispenseStatus.IN_PROGRESS.getValue(),
DispenseStatus.IN_PROGRESS.getInfo()));
dispenseStatusOptions.add(new DispenseStatusOption(DispenseStatus.COMPLETED.getValue(),

View File

@@ -4,6 +4,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.healthlink.his.reconstruction.domain.*;
import com.healthlink.his.reconstruction.service.*;
import com.core.system.service.ISysUserService;
import com.core.system.service.ISysRoleService;
import com.core.common.core.domain.entity.SysRole;
import com.core.common.core.domain.entity.SysUser;
import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.*;
@@ -13,6 +17,8 @@ public class Reconstruction3DController {
private final IReconstructionTaskService taskService;
private final IReconstructionResultService resultService;
private final IReconstructionReportService reportService;
private final ISysUserService userService;
private final ISysRoleService roleService;
// ==================== 重建任务 ====================
@GetMapping("/task/page")
@@ -100,4 +106,32 @@ public class Reconstruction3DController {
stats.put("totalReports", reportService.count());
return R.ok(stats);
}
// ==================== 医生列表 ====================
@GetMapping("/doctors")
public R<?> getDoctors() {
SysUser query = new SysUser();
query.setStatus("0");
query.setDelFlag("0");
List<SysUser> allUsers = userService.selectUserList(query);
// For each user, check if they have role 200 (doctor)
List<Map<String, Object>> doctors = new ArrayList<>();
for (SysUser user : allUsers) {
List<SysRole> roles = roleService.selectRolesByUserId(user.getUserId());
if (roles != null) {
for (SysRole role : roles) {
if (role.getRoleId() != null && role.getRoleId() == 200L) {
Map<String, Object> doc = new HashMap<>();
doc.put("userId", user.getUserId());
doc.put("userName", user.getUserName());
doc.put("nickName", user.getNickName());
doctors.add(doc);
break;
}
}
}
}
return R.ok(doctors);
}
}

View File

@@ -1122,14 +1122,14 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
iMedicationRequestService.update(new LambdaUpdateWrapper<MedicationRequest>()
.in(MedicationRequest::getId, medicineRequestIds)
.set(MedicationRequest::getEffectiveDoseEnd, stopTime)
.set(MedicationRequest::getStatusEnum, RequestStatus.STOPPED.getValue())
.set(MedicationRequest::getStatusEnum, RequestStatus.PENDING_STOP.getValue())
.set(MedicationRequest::getUpdateBy, stopUserName));
}
if (!activityRequestIds.isEmpty()) {
iServiceRequestService.update(new LambdaUpdateWrapper<ServiceRequest>()
.in(ServiceRequest::getId, activityRequestIds)
.set(ServiceRequest::getOccurrenceEndTime, stopTime)
.set(ServiceRequest::getStatusEnum, RequestStatus.STOPPED.getValue())
.set(ServiceRequest::getStatusEnum, RequestStatus.PENDING_STOP.getValue())
.set(ServiceRequest::getUpdateBy, stopUserName));
}
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"医嘱停止"}));
@@ -1173,7 +1173,8 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
.select(MedicationRequest::getId, MedicationRequest::getStatusEnum)
.in(MedicationRequest::getId, medicineRequestIds));
for (MedicationRequest mr : medicineRequestList) {
if (!RequestStatus.STOPPED.getValue().equals(mr.getStatusEnum())) {
if (!RequestStatus.STOPPED.getValue().equals(mr.getStatusEnum())
&& !RequestStatus.PENDING_STOP.getValue().equals(mr.getStatusEnum())) {
throw new ServiceException("护士站已确认停止该医嘱,无法取消停嘱!");
}
}
@@ -1184,7 +1185,8 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
.select(ServiceRequest::getId, ServiceRequest::getStatusEnum)
.in(ServiceRequest::getId, activityRequestIds));
for (ServiceRequest sr : activityRequestList) {
if (!RequestStatus.STOPPED.getValue().equals(sr.getStatusEnum())) {
if (!RequestStatus.STOPPED.getValue().equals(sr.getStatusEnum())
&& !RequestStatus.PENDING_STOP.getValue().equals(sr.getStatusEnum())) {
throw new ServiceException("护士站已确认停止该医嘱,无法取消停嘱!");
}
}

View File

@@ -0,0 +1,25 @@
-- Bug #705: 患者管理修改后字段回显丢失修复
-- 添加邮政编码、户籍地址、监护人信息、患者来源等缺失字段到adm_patient表
-- 邮政编码
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS postal_code VARCHAR(10);
-- 户籍地址
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS hukou_address VARCHAR(500);
-- 监护人信息
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS guardian_name VARCHAR(50);
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS guardian_relation INTEGER;
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS guardian_phone VARCHAR(20);
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS guardian_id_type VARCHAR(10);
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS guardian_id_no VARCHAR(30);
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS guardian_address VARCHAR(500);
-- 患者来源
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS patient_derived VARCHAR(50);
-- 文化程度
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS education_level VARCHAR(20);
-- 单位地址
ALTER TABLE adm_patient ADD COLUMN IF NOT EXISTS company_address VARCHAR(500);

View File

@@ -0,0 +1,79 @@
-- V40: 修复EMR模块数据库表结构问题
-- 1. emr_archive_record表添加缺失的审计字段修复NOT NULL约束
-- 2. emr_search_index表修复NOT NULL约束
-- 3. emr_revision表添加缺失的审计字段
-- ==========================================
-- 1. emr_archive_record 表修复
-- ==========================================
-- 添加缺失的审计字段(如果不存在)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'emr_archive_record' AND column_name = 'create_by') THEN
ALTER TABLE emr_archive_record ADD COLUMN create_by character varying;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'emr_archive_record' AND column_name = 'update_by') THEN
ALTER TABLE emr_archive_record ADD COLUMN update_by character varying;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'emr_archive_record' AND column_name = 'update_time') THEN
ALTER TABLE emr_archive_record ADD COLUMN update_time timestamp without time zone;
END IF;
END $$;
-- 修复NOT NULL约束
ALTER TABLE emr_archive_record ALTER COLUMN emr_id DROP NOT NULL;
ALTER TABLE emr_archive_record ALTER COLUMN patient_id DROP NOT NULL;
ALTER TABLE emr_archive_record ALTER COLUMN archive_type DROP NOT NULL;
-- ==========================================
-- 2. emr_search_index 表修复
-- ==========================================
-- 修复NOT NULL约束
ALTER TABLE emr_search_index ALTER COLUMN emr_id DROP NOT NULL;
ALTER TABLE emr_search_index ALTER COLUMN encounter_id DROP NOT NULL;
ALTER TABLE emr_search_index ALTER COLUMN patient_id DROP NOT NULL;
-- ==========================================
-- 3. emr_revision 表修复
-- ==========================================
-- 添加缺失的审计字段
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'emr_revision' AND column_name = 'create_by') THEN
ALTER TABLE emr_revision ADD COLUMN create_by character varying;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'emr_revision' AND column_name = 'tenant_id') THEN
ALTER TABLE emr_revision ADD COLUMN tenant_id bigint;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'emr_revision' AND column_name = 'update_by') THEN
ALTER TABLE emr_revision ADD COLUMN update_by character varying;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'emr_revision' AND column_name = 'update_time') THEN
ALTER TABLE emr_revision ADD COLUMN update_time timestamp without time zone;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'emr_revision' AND column_name = 'delete_flag') THEN
ALTER TABLE emr_revision ADD COLUMN delete_flag character varying;
END IF;
END $$;
-- ==========================================
-- 4. 添加索引以优化查询性能
-- ==========================================
-- emr_archive_record 索引
CREATE INDEX IF NOT EXISTS idx_emr_archive_patient_name ON emr_archive_record(patient_name);
CREATE INDEX IF NOT EXISTS idx_emr_archive_status ON emr_archive_record(archive_status);
CREATE INDEX IF NOT EXISTS idx_emr_archive_encounter ON emr_archive_record(encounter_id);
-- emr_search_index 索引
CREATE INDEX IF NOT EXISTS idx_emr_search_patient ON emr_search_index(patient_name);
CREATE INDEX IF NOT EXISTS idx_emr_search_type ON emr_search_index(emr_type);
CREATE INDEX IF NOT EXISTS idx_emr_search_doctor ON emr_search_index(doctor_name);
CREATE INDEX IF NOT EXISTS idx_emr_search_dept ON emr_search_index(department_name);
-- emr_revision 索引
CREATE INDEX IF NOT EXISTS idx_emr_revision_emr ON emr_revision(emr_id);
CREATE INDEX IF NOT EXISTS idx_emr_revision_operator ON emr_revision(operator_name);

View File

@@ -18,7 +18,8 @@
ihri.ward_name,
ihri.contract_no,
ihri.bus_no,
ihri.admit_source_code
ihri.admit_source_code,
ihri.status_enum
from (SELECT ae.tenant_id,
ae.ID AS encounter_id,
ae.amb_encounter_id AS amb_encounter_id,
@@ -32,7 +33,8 @@
al.NAME AS ward_name,
aa.contract_no,
ae.bus_no,
ae.admit_source_code
ae.admit_source_code,
ae.status_enum
FROM adm_encounter AS ae
LEFT JOIN adm_encounter AS ambae ON ae.amb_encounter_id = ambae.
ID

View File

@@ -154,7 +154,11 @@
ii.account_id AS account_id,
ii.performer_check_id,
ii.category_code,
ii.dispense_status
ii.dispense_status,
ii.unit_price,
ii.total_price,
ii.stopper_id,
ii.stopper_name
FROM (( SELECT DISTINCT T1.encounter_id,
T1.tenant_id,
#{medMedicationRequest} AS advice_table,
@@ -199,7 +203,11 @@
personal_account.balance_amount,
personal_account.id AS account_id,
T2.category_code,
mmd.status_enum AS dispense_status
mmd.status_enum AS dispense_status,
NULL::numeric AS unit_price,
NULL::numeric AS total_price,
NULL::bigint AS stopper_id,
T1.update_by AS stopper_name
FROM med_medication_request AS T1
LEFT JOIN med_medication_definition AS T2
ON T2.id = T1.medication_id
@@ -341,7 +349,11 @@
personal_account.balance_amount,
personal_account.id AS account_id,
T2.category_code,
NULL::integer AS dispense_status
NULL::integer AS dispense_status,
NULL::numeric AS unit_price,
NULL::numeric AS total_price,
NULL::bigint AS stopper_id,
T1.update_by AS stopper_name
FROM wor_service_request AS T1
LEFT JOIN wor_activity_definition AS T2
ON T2.id = T1.activity_id

View File

@@ -38,7 +38,18 @@
pt.link_telcom,
pt.link_jsons,
pt.organization_id,
pt.create_time
pt.create_time,
pt.postal_code,
pt.hukou_address,
pt.guardian_name,
pt.guardian_relation,
pt.guardian_phone,
pt.guardian_id_type,
pt.guardian_id_no,
pt.guardian_address,
pt.patient_derived,
pt.education_level,
pt.company_address
FROM (
SELECT
(
@@ -81,7 +92,18 @@
p.link_telcom,
p.link_jsons,
p.organization_id,
p.create_time
p.create_time,
p.postal_code,
p.hukou_address,
p.guardian_name,
p.guardian_relation,
p.guardian_phone,
p.guardian_id_type,
p.guardian_id_no,
p.guardian_address,
p.patient_derived,
p.education_level,
p.company_address
FROM adm_patient p
where p.delete_flag = '0'
) AS pt

View File

@@ -189,6 +189,7 @@
T1.id AS request_id,
T1.id || '-1' AS unique_key,
T1.practitioner_id AS requester_id,
ap.name AS requester_id_dict_text,
T1.create_time AS request_time,
CASE WHEN T1.practitioner_id = #{practitionerId} THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
@@ -223,6 +224,7 @@
T1.effective_dose_end AS stop_time,
T1.update_by AS stop_user_name
FROM med_medication_request AS T1
LEFT JOIN adm_practitioner AS ap ON ap.id = T1.practitioner_id AND ap.delete_flag = '0'
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
AND T2.delete_flag = '0'
LEFT JOIN med_medication AS T3 ON T3.medication_def_id = T2.ID
@@ -246,6 +248,7 @@
T1.id AS request_id,
T1.id || '-2' AS unique_key,
T1.requester_id AS requester_id,
ap.name AS requester_id_dict_text,
T1.create_time AS request_time,
CASE WHEN T1.requester_id = #{practitionerId} THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
@@ -280,6 +283,7 @@
NULL::timestamp AS stop_time,
'' AS stop_user_name
FROM wor_device_request AS T1
LEFT JOIN adm_practitioner AS ap ON ap.id = T1.requester_id AND ap.delete_flag = '0'
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item AS T3
@@ -300,6 +304,7 @@
T1.id AS request_id,
T1.id || '-3' AS unique_key,
T1.requester_id AS requester_id,
ap.name AS requester_id_dict_text,
T1.create_time AS request_time,
CASE WHEN T1.requester_id = #{practitionerId} THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
@@ -334,6 +339,7 @@
T1.occurrence_end_time AS stop_time,
T1.update_by AS stop_user_name
FROM wor_service_request AS T1
LEFT JOIN adm_practitioner AS ap ON ap.id = T1.requester_id AND ap.delete_flag = '0'
LEFT JOIN wor_activity_definition AS T2
ON T2.ID = T1.activity_id
AND T2.delete_flag = '0'

View File

@@ -22,7 +22,9 @@ public enum EncounterZyStatus implements HisEnumInterface {
PENDING_TRANSFER(6, "pending-transfer", "待转科"),
ALREADY_SETTLED(7, "already-settled", "已结算出院");
ALREADY_SETTLED(7, "already-settled", "已结算出院"),
VOIDED(8, "voided", "已作废");
@EnumValue
private final Integer value;

View File

@@ -74,6 +74,11 @@ public enum RequestStatus implements HisEnumInterface {
*/
CHECK_RECEIVED(12, "check_received", "已接收"),
/**
* 已停嘱(医生停嘱,等待护士核对)
*/
PENDING_STOP(13, "pending_stop", "已停嘱"),
/**
* 已完成(药品发药完成)
*/

View File

@@ -130,4 +130,37 @@ public class Patient extends HisBaseEntity {
/** 机构Id */
private Long organizationId;
/** 邮政编码 */
private String postalCode;
/** 户籍地址 */
private String hukouAddress;
/** 监护人姓名 */
private String guardianName;
/** 监护人关系 */
private Integer guardianRelation;
/** 监护人电话 */
private String guardianPhone;
/** 监护人证件类型 */
private String guardianIdType;
/** 监护人证件号码 */
private String guardianIdNo;
/** 监护人地址 */
private String guardianAddress;
/** 患者来源 */
private String patientDerived;
/** 文化程度 */
private String educationLevel;
/** 单位地址 */
private String companyAddress;
}

View File

@@ -1,8 +1,12 @@
/* eslint-env node */
import path from "node:path";
import { fileURLToPath } from "node:url";
import globals from "globals";
import pluginVue from "eslint-plugin-vue";
import parserVue from "vue-eslint-parser";
import importPlugin from "eslint-plugin-import";
import importPlugin, { createNodeResolver } from "eslint-plugin-import-x";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default [
{
@@ -29,31 +33,31 @@ export default [
},
plugins: {
import: importPlugin,
"import-x": importPlugin,
},
rules: {
// 确保导入的模块实际存在(核心规则,防止构建失败)
"import/no-unresolved": "error",
"import-x/no-unresolved": "error",
// 确保导入的命名导出实际存在
"import/named": "error",
"import-x/named": "error",
// 确保默认导出存在
"import/default": "error",
"import-x/default": "error",
// 确保命名空间导出存在
"import/namespace": "error",
"import-x/namespace": "error",
// Vue 相关规则
"vue/multi-word-component-names": "off",
},
settings: {
"import/resolver": {
alias: {
map: [
["@", "./src"],
],
extensions: [".js", ".jsx", ".vue"],
},
},
"import-x/resolver-next": [
createNodeResolver({
alias: {
"@": [path.join(__dirname, "src")],
},
extensions: [".mjs", ".cjs", ".js", ".jsx", ".vue", ".json", ".node"],
}),
],
},
},
];

View File

@@ -75,8 +75,7 @@
"@vitejs/plugin-vue": "^5.2.4",
"@vue/test-utils": "^2.4.6",
"eslint": "^10.4.1",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-vue": "^10.9.1",
"globals": "^17.5.0",
"happy-dom": "^20.8.3",

View File

@@ -2,51 +2,51 @@ import request from '@/utils/request'
// ==================== 处方审核 ====================
export function auditPrescription(data) {
return request({ url: '/healthlink-his/api/v1/rational-drug/audit', method: 'post', data })
return request({ url: '/api/v1/rational-drug/audit', method: 'post', data })
}
export function batchAudit(prescriptionIds) {
return request({ url: '/healthlink-his/api/v1/rational-drug/batch-audit', method: 'post', data: prescriptionIds })
return request({ url: '/api/v1/rational-drug/batch-audit', method: 'post', data: prescriptionIds })
}
export function getAuditStatistics() {
return request({ url: '/healthlink-his/api/v1/rational-drug/statistics', method: 'get' })
return request({ url: '/api/v1/rational-drug/statistics', method: 'get' })
}
export function getAuditTrend(startDate) {
return request({ url: '/healthlink-his/api/v1/rational-drug/trend', method: 'get', params: { startDate } })
return request({ url: '/api/v1/rational-drug/trend', method: 'get', params: { startDate } })
}
export function getAuditLog(encounterId) {
return request({ url: `/healthlink-his/api/v1/rational-drug/audit-log/${encounterId}`, method: 'get' })
return request({ url: `/api/v1/rational-drug/audit-log/${encounterId}`, method: 'get' })
}
// ==================== 配伍禁忌 ====================
export function checkInteraction(drugCodes) {
return request({ url: '/healthlink-his/api/v1/rational-drug/check-interaction', method: 'post', data: drugCodes })
return request({ url: '/api/v1/rational-drug/check-interaction', method: 'post', data: drugCodes })
}
export function listInteractionRules(params) {
return request({ url: '/healthlink-his/api/v1/rational-drug/interaction-rules', method: 'get', params })
return request({ url: '/api/v1/rational-drug/interaction-rules', method: 'get', params })
}
export function addInteractionRule(data) {
return request({ url: '/healthlink-his/api/v1/rational-drug/interaction-rules', method: 'post', data })
return request({ url: '/api/v1/rational-drug/interaction-rules', method: 'post', data })
}
export function updateInteractionRule(data) {
return request({ url: '/healthlink-his/api/v1/rational-drug/interaction-rules', method: 'put', data })
return request({ url: '/api/v1/rational-drug/interaction-rules', method: 'put', data })
}
export function delInteractionRule(id) {
return request({ url: `/healthlink-his/api/v1/rational-drug/interaction-rules/${id}`, method: 'delete' })
return request({ url: `/api/v1/rational-drug/interaction-rules/${id}`, method: 'delete' })
}
// ==================== 剂量规则 ====================
export function listDosageRules(params) {
return request({ url: '/healthlink-his/api/v1/rational-drug/dosage-rules', method: 'get', params })
return request({ url: '/api/v1/rational-drug/dosage-rules', method: 'get', params })
}
export function checkDosage(drugCode, dosage, population) {
return request({ url: '/healthlink-his/api/v1/rational-drug/check-dosage', method: 'get', params: { drugCode, dosage, population } })
return request({ url: '/api/v1/rational-drug/check-dosage', method: 'get', params: { drugCode, dosage, population } })
}

View File

@@ -3,7 +3,7 @@ import request from '@/utils/request'
// 处方审核
export function auditPrescription(data) {
return request({
url: '/healthlink-his/api/v1/rational-drug/audit',
url: '/api/v1/rational-drug/audit',
method: 'post',
data: data
})
@@ -12,7 +12,7 @@ export function auditPrescription(data) {
// 批量审核
export function batchAudit(data) {
return request({
url: '/healthlink-his/api/v1/rational-drug/batch-audit',
url: '/api/v1/rational-drug/batch-audit',
method: 'post',
data: data
})
@@ -21,7 +21,7 @@ export function batchAudit(data) {
// 审核统计
export function getAuditStatistics() {
return request({
url: '/healthlink-his/api/v1/rational-drug/statistics',
url: '/api/v1/rational-drug/statistics',
method: 'get'
})
}
@@ -29,7 +29,7 @@ export function getAuditStatistics() {
// 审核趋势
export function getAuditTrend(params) {
return request({
url: '/healthlink-his/api/v1/rational-drug/trend',
url: '/api/v1/rational-drug/trend',
method: 'get',
params: params
})
@@ -38,7 +38,7 @@ export function getAuditTrend(params) {
// 审核记录
export function getAuditLog(encounterId) {
return request({
url: '/healthlink-his/api/v1/rational-drug/audit-log/' + encounterId,
url: '/api/v1/rational-drug/audit-log/' + encounterId,
method: 'get'
})
}
@@ -46,7 +46,7 @@ export function getAuditLog(encounterId) {
// 配伍禁忌检查
export function checkInteraction(data) {
return request({
url: '/healthlink-his/api/v1/rational-drug/check-interaction',
url: '/api/v1/rational-drug/check-interaction',
method: 'post',
data: data
})
@@ -55,7 +55,7 @@ export function checkInteraction(data) {
// 配伍禁忌规则列表
export function listInteractionRules(params) {
return request({
url: '/healthlink-his/api/v1/rational-drug/interaction-rules',
url: '/api/v1/rational-drug/interaction-rules',
method: 'get',
params: params
})
@@ -64,7 +64,7 @@ export function listInteractionRules(params) {
// 新增配伍禁忌规则
export function addInteractionRule(data) {
return request({
url: '/healthlink-his/api/v1/rational-drug/interaction-rules',
url: '/api/v1/rational-drug/interaction-rules',
method: 'post',
data: data
})
@@ -73,7 +73,7 @@ export function addInteractionRule(data) {
// 修改配伍禁忌规则
export function updateInteractionRule(data) {
return request({
url: '/healthlink-his/api/v1/rational-drug/interaction-rules',
url: '/api/v1/rational-drug/interaction-rules',
method: 'put',
data: data
})
@@ -82,7 +82,7 @@ export function updateInteractionRule(data) {
// 删除配伍禁忌规则
export function delInteractionRule(id) {
return request({
url: '/healthlink-his/api/v1/rational-drug/interaction-rules/' + id,
url: '/api/v1/rational-drug/interaction-rules/' + id,
method: 'delete'
})
}
@@ -90,7 +90,7 @@ export function delInteractionRule(id) {
// 剂量规则列表
export function listDosageRules(params) {
return request({
url: '/healthlink-his/api/v1/rational-drug/dosage-rules',
url: '/api/v1/rational-drug/dosage-rules',
method: 'get',
params: params
})

View File

@@ -71,6 +71,14 @@ router.beforeEach(async (to, from) => {
return { path: '/login' }
}
}
// 铁律: 路由权限校验 — 目标路由必须在已注册的路由中存在
// 防止切换账户后,通过旧标签或直接输入 URL 访问无权限页面
const resolved = router.resolve(to)
if (resolved.matched.length === 0 || resolved.name === 'NotFound') {
// 路由不存在(未注册),拒绝导航
ElMessage.warning('无权访问该页面')
return { path: '/' }
}
return true
} else {
if (isWhiteList(to.path)) {

View File

@@ -1,4 +1,5 @@
import {getInfo, login, logout} from '@/api/login'
import useTagsViewStore from '@/store/modules/tagsView'
import {getToken, removeToken, setToken} from '@/utils/auth'
import defAva from '@/assets/images/user.png'
import {defineStore} from 'pinia'
@@ -84,6 +85,8 @@ const useUserStore = defineStore(
this.permissions = []
this.tenantId = ''
removeToken()
// 清除标签页内存状态,防止切换账户时残留
try { useTagsViewStore().delAllViews() } catch(e) {}
resolve()
}).catch(error => {
reject(error)

View File

@@ -40,6 +40,8 @@ export const RequestStatus = {
PENDING_RECEIVE: 11,
/** 已接收(检查申请:医技科室已接单) */
CHECK_RECEIVED: 12,
/** 已停嘱(医生停嘱,等待护士核对) */
PENDING_STOP: 13,
/** 已完成(药品发药完成) */
DISPENSE_COMPLETED: 20,
};
@@ -59,6 +61,7 @@ export const RequestStatusDescriptions = {
10: '已校对',
11: '待接收',
12: '已接收',
13: '已停嘱',
20: '已完成',
};

View File

@@ -92,6 +92,7 @@
placeholder="请选"
class="inline-select"
:disabled="!isEditMode"
clearable
>
<el-option
v-for="doctor in getDoctorOptions(filterParams.appointmentType)"
@@ -113,6 +114,7 @@
placeholder="请选"
class="inline-select"
:disabled="!isEditMode"
clearable
>
<el-option
label="诊室1"
@@ -206,6 +208,7 @@
placeholder="请选"
class="inline-select"
:disabled="!isEditMode"
clearable
@change="handleAppointmentItemChange(scope.row)"
>
<el-option
@@ -247,6 +250,7 @@
placeholder="请选择诊查项目"
class="inline-select"
:disabled="!isEditMode"
clearable
@change="handleClinicItemChange(scope.row)"
>
<el-option

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="appoinmentmanage-wrapper">
<div class="appoinmentmanage-container">
<div class="appoinmentmanage-header">
@@ -335,6 +335,7 @@
v-model="scope.row.doctorId"
placeholder="请选"
class="inline-select"
clearable
:disabled="!isEditMode"
@change="(selectedId) => handleDoctorChange(selectedId, scope.row)"
>
@@ -359,6 +360,7 @@
filterable
:remote-method="searchClinicRooms"
class="inline-select"
clearable
:disabled="!isEditMode"
>
<el-option
@@ -443,6 +445,7 @@
v-model="scope.row.appointmentItem"
placeholder="请选"
class="inline-select"
clearable
:disabled="!isEditMode"
@change="handleAppointmentItemChange(scope.row)"
>
@@ -474,6 +477,7 @@
v-model="scope.row.clinicItem"
placeholder="请选择诊查项目"
class="inline-select"
clearable
:disabled="!isEditMode"
@change="handleClinicItemChange(scope.row)"
>

View File

@@ -447,15 +447,65 @@
v-model="form.deceasedDate"
type="datetime"
placeholder="请选择时间"
format="YYYY/MM/DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
:disabled="isViewMode"
value-format="YYYY/MM/DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 第八行监护监护人关系监护人电话 -->
<!-- 第八行联系联系人关系联系人电话 -->
<el-row>
<el-col :span="8">
<el-form-item
label="联系人"
prop="linkName"
>
<el-input
v-model="form.linkName"
clearable
:disabled="isViewMode"
placeholder="请输入联系人"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="联系人关系"
prop="linkRelationCode"
>
<el-select
v-model="form.linkRelationCode"
placeholder="联系人关系"
clearable
:disabled="isViewMode"
>
<el-option
v-for="item in link_relation_code"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="联系人电话"
prop="linkTelcom"
>
<el-input
v-model="form.linkTelcom"
clearable
:disabled="isViewMode"
placeholder="请输入联系人电话"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 第九行监护人监护人关系监护人电话 -->
<el-row>
<el-col :span="8">
<el-form-item
@@ -702,12 +752,26 @@ const getGenderOptions = async () => {
const getEducationLevelOptions = async () => {
try {
// 从字典管理获取文化程度数据
const response = await getDicts('文化程度');
const response = await getDicts('educational_level');
console.log('获取到的文化程度原始数据:', response);
// 确保数据是数组
if (!response || response.code !== 200 || !Array.isArray(response.data)) {
console.error('文化程度数据格式错误:', response);
// 确保数据是数组且非空
if (!response || response.code !== 200 || !Array.isArray(response.data) || response.data.length === 0) {
console.warn('文化程度数据为空或格式错误,使用默认数据:', response);
// 降级方案:使用默认的文化程度选项,按编码顺序排列
educationLevelList.value = [
{ value: '1', info: '大学本科' },
{ value: '2', info: '硕士研究生' },
{ value: '3', info: '博士研究生' },
{ value: '4', info: '初中毕业' },
{ value: '5', info: '大学专科结业' },
{ value: '6', info: '技工学院结业' },
{ value: '7', info: '职业高中结业' },
{ value: '8', info: '小学毕业' },
{ value: '9', info: '普通高中结业' },
{ value: '10', info: '中等专科结业' },
{ value: '99', info: '其他' }
];
return;
}
const educationDict = response.data;
@@ -743,17 +807,18 @@ const getEducationLevelOptions = async () => {
console.error('获取文化程度字典数据失败:', error);
// 降级方案:使用默认的文化程度选项,按编码顺序排列
educationLevelList.value = [
{ value: '3912', info: '大学本科' },
{ value: '3913', info: '硕士研究生' },
{ value: '3914', info: '博士研究生' },
{ value: '3915', info: '初中毕业' },
{ value: '3916', info: '大学业' },
{ value: '3917', info: '技工学校毕业' },
{ value: '3918', info: '职业高中业' },
{ value: '3919', info: '小学毕业' },
{ value: '3920', info: '普通高中业' },
{ value: '3921', info: '中等专科业' }
].sort((a, b) => parseInt(a.value) - parseInt(b.value)); // 确保默认选项也按编码排序
{ value: '1', info: '大学本科' },
{ value: '2', info: '硕士研究生' },
{ value: '3', info: '博士研究生' },
{ value: '4', info: '初中毕业' },
{ value: '5', info: '大学专科结业' },
{ value: '6', info: '技工学院结业' },
{ value: '7', info: '职业高中业' },
{ value: '8', info: '小学毕业' },
{ value: '9', info: '普通高中业' },
{ value: '10', info: '中等专科业' },
{ value: '99', info: '其他' }
];
}
};
const options = ref(pcas); // 地区数据
@@ -1403,10 +1468,8 @@ const getCountryCodeOptions = async () => {
// 显示弹框
function show() {
// 重置为新增模式
isEditMode.value = false;
title.value = '新增患者';
originalFormData.value = {};
// 重置表单为初始状态
reset();
// queryParams.roleId = props.roleId;
getList();
@@ -1452,6 +1515,22 @@ function reset() {
busNo: undefined,
organizationId: undefined,
birthDate: undefined,
postalCode: undefined,
hukouAddress: undefined,
hukouAddressSelect: undefined,
hukouAddressProvince: undefined,
hukouAddressCity: undefined,
hukouAddressDistrict: undefined,
hukouAddressStreet: undefined,
companyAddress: undefined,
patientDerived: undefined,
educationLevel: undefined,
guardianName: undefined,
guardianRelation: undefined,
guardianPhone: undefined,
guardianIdType: undefined,
guardianIdNo: undefined,
guardianAddress: undefined,
};
// 重置编辑模式状态
isEditMode.value = false;
@@ -1509,6 +1588,18 @@ function submitForm() {
if (!form.value.identifierNo) {
form.value.typeCode = undefined;
}
// 修复死亡时间日期格式:确保 yyyy-MM-dd HH:mm:ss 格式
if (form.value.deceasedDate) {
const d = form.value.deceasedDate;
if (d instanceof Date) {
const pad = (n) => String(n).padStart(2, '0');
form.value.deceasedDate = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
} else if (typeof d === 'string') {
// 将任何非标准格式转为 yyyy-MM-dd HH:mm:ss
const normalized = d.replace(/\//g, '-').replace(/T/, ' ').replace(/\.\d+Z?$/, '').trim();
form.value.deceasedDate = normalized;
}
}
// 拼接完整地址用于提交,但不覆写表单字段(避免弹窗关闭前显示全地址)
const submitData = { ...form.value, address: getAddress(form) };

View File

@@ -263,9 +263,8 @@ const handleCurrentChange = (currentRow) => {
currentSelectRow.value = currentRow;
};
function clickRow(row, column, cell, event) {
// cell-click 事件会传递 row, column, cell, event 四个参数
// 确保传递的是完整的行数据
function clickRow({ row }) {
// vxe-table 4.x cell-click 事件参数是 { row, column, ... } 对象,需解构取 row
if (row) {
emit('selectAdviceBase', row);
}

View File

@@ -38,170 +38,15 @@
max-height="650"
:data="prescriptionList"
:row-config="{ keyField: 'uniqueKey', expandRowKeys: expandOrder }"
:column-config="{ resizable: true }"
border
auto-resize
@cell-dblclick="clickRowDb"
>
<vxe-column
type="expand"
width="40"
>
<template #default="scope">
<el-form
:ref="'formRef' + scope.rowIndex"
:model="scope.row"
:rules="rowRules"
>
<div style="padding: 16px; background: #f8f9fa; border-radius: 8px">
<!-- 药品类型adviceType == 1和耗材类型adviceType == 2使用相同的界面 -->
<template v-if="scope.row.adviceType == 1 || scope.row.adviceType == 2">
<div style="display: flex; align-items: center; margin-bottom: 16px; gap: 16px">
<span style="font-size: 16px; font-weight: 600">
{{
scope.row.adviceName +
' ' +
(scope.row.volume ? scope.row.volume + ' ' : '') +
(scope.row.unitPrice ? scope.row.unitPrice + ' 元/' : '') +
(scope.row.unitCode_dictText || '')
}}
</span>
<div class="form-group">
<!-- 库存不为空时显示批号选择 -->
<el-select
v-if="scope.row.stockList && scope.row.stockList.length > 0"
v-model="scope.row.lotNumber"
style="width: 180px; margin-right: 20px"
placeholder="选择批号"
>
<el-option
v-for="item in scope.row.stockList"
:key="item.lotNumber"
:value="item.lotNumber"
:label="
item.locationName +
' ' +
'批次号: ' +
item.lotNumber +
' ' +
' 库存:' +
(item.quantity / scope.row.partPercent).toFixed(2) +
item.unitCode_dictText +
' 单价:' +
item.price.toFixed(2) +
'/' +
item.unitCode_dictText
"
@click="handleNumberClick(item, scope.rowIndex)"
/>
</el-select>
<!-- 库存为空时显示提示 -->
<span
v-else
style="color: #EF4444; margin-right: 20px; font-size: 14px;"
>
无可用库存
</span>
<el-form-item
label="数量:"
prop="quantity"
class="required-field"
data-prop="quantity"
>
<el-input-number
v-model="scope.row.quantity"
placeholder="数量"
style="width: 70px"
controls-position="right"
:controls="false"
@keyup.enter.prevent="handleEnter('quantity', scope.row, scope.rowIndex)"
@input="calculateTotalPrice(scope.row, scope.rowIndex)"
/>
</el-form-item>
<el-select
v-if="scope.row.unitCodeList && scope.row.unitCodeList.length > 0"
v-model="scope.row.unitCode"
style="width: 70px; margin-right: 20px"
placeholder="单位"
@change="calculateTotalAmount(scope.row, scope.rowIndex)"
>
<template
v-for="item in scope.row.unitCodeList"
:key="item.value"
>
<el-option
v-if="item.type != unitMap['dose']"
:value="item.value"
:label="item.label"
/>
</template>
</el-select>
<span class="total-amount">
总金额{{ scope.row.totalPrice ? scope.row.totalPrice + ' 元' : '0.00 元' }}
</span>
</div>
<el-button
type="primary"
@click="handleSaveSign(scope.row, scope.rowIndex)"
>
保存
</el-button>
</div>
</template>
<template v-else>
<div style="display: flex; align-items: center; margin-bottom: 16px; gap: 16px">
<span style="font-size: 16px; font-weight: 600">
{{
scope.row.adviceName + ' ' + scope.row.unitPrice
? Number(scope.row.unitPrice).toFixed(2)
: '-' + '元'
}}
</span>
<div class="form-group">
<el-form-item
label="执行次数:"
prop="quantity"
class="required-field"
data-prop="quantity"
>
<el-input-number
v-model="scope.row.quantity"
placeholder="执行次数"
style="width: 100px; margin: 0 20px"
controls-position="right"
:controls="false"
@keyup.enter.prevent="handleEnter('quantity', scope.row, scope.rowIndex)"
@input="calculateTotalPrice(scope.row, scope.rowIndex)"
/>
</el-form-item>
<el-tree-select
v-model="scope.row.orgId"
clearable
:data="organization"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
check-strictly
placeholder="请选择执行科室"
style="min-width: 150px; width: auto;"
class="org-select"
/>
<span class="total-amount">
总金额{{ scope.row.totalPrice ? scope.row.totalPrice + ' 元' : '0.00 元' }}
</span>
<span style="font-size: 16px; font-weight: 600">
<!-- 金额: {{ scope.row.priceList[0].price }} -->
</span>
</div>
<el-button
type="primary"
@click="handleSaveSign(scope.row, scope.rowIndex)"
>
保存
</el-button>
</div>
</template>
</div>
</el-form>
</template>
</vxe-column>
/>
<vxe-column
title=""
align="center"
@@ -370,6 +215,7 @@
<vxe-column
title="总量"
align="center"
width="100"
field=""
>
<template #default="scope">
@@ -383,6 +229,7 @@
align="right"
field=""
header-align="center"
width="130"
>
<template #default="scope">
<span
@@ -430,6 +277,151 @@
</template>
</vxe-column>
</vxe-table>
<!-- 编辑表单卡片:独立于表格,选中项目后显示在表格下方 -->
<div
v-if="editingRow"
class="edit-form-card"
>
<el-form
:ref="'editFormRef'"
:model="editingRow"
:rules="rowRules"
>
<div style="padding: 16px; background: #f8f9fa; border-radius: 8px">
<template v-if="editingRow.adviceType == 1 || editingRow.adviceType == 2">
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<span style="font-size: 16px; font-weight: 600">
{{
editingRow.adviceName +
' ' +
(editingRow.volume ? editingRow.volume + ' ' : '') +
(editingRow.unitPrice ? editingRow.unitPrice + ' 元/' : '') +
(editingRow.unitCode_dictText || '')
}}
</span>
<div class="form-group">
<el-select
v-if="editingRow.stockList && editingRow.stockList.length > 0"
v-model="editingRow.lotNumber"
style="width: 180px; margin-right: 20px"
placeholder="选择批号"
>
<el-option
v-for="item in editingRow.stockList"
:key="item.lotNumber"
:value="item.lotNumber"
:label="
item.locationName +
' 批次号: ' + item.lotNumber +
' 库存:' + (item.quantity / editingRow.partPercent).toFixed(2) +
item.unitCode_dictText +
' 单价:' + item.price.toFixed(2) + '/' + item.unitCode_dictText
"
@click="handleNumberClick(item, editingRowIndex)"
/>
</el-select>
<span
v-else
style="color: #EF4444; margin-right: 20px; font-size: 14px;"
>无可用库存</span>
<el-form-item
label="数量"
prop="quantity"
class="required-field"
data-prop="quantity"
>
<el-input-number
v-model="editingRow.quantity"
placeholder="数量"
style="width: 70px"
controls-position="right"
:controls="false"
@keyup.enter.prevent="handleEnter('quantity', editingRow, editingRowIndex)"
@input="calculateTotalPrice(editingRow, editingRowIndex)"
/>
</el-form-item>
<el-select
v-if="editingRow.unitCodeList && editingRow.unitCodeList.length > 0"
v-model="editingRow.unitCode"
style="width: 70px; margin-right: 20px"
placeholder="单位"
@change="calculateTotalAmount(editingRow, editingRowIndex)"
>
<template
v-for="item in editingRow.unitCodeList"
:key="item.value"
>
<el-option
v-if="item.type != unitMap['dose']"
:value="item.value"
:label="item.label"
/>
</template>
</el-select>
<span class="total-amount">
总金额:{{ editingRow.totalPrice ? editingRow.totalPrice + ' 元' : '0.00 元' }}
</span>
</div>
<el-button
type="primary"
@click="handleSaveSign(editingRow, editingRowIndex)"
>
保存
</el-button>
</div>
</template>
<template v-else>
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<span style="font-size: 16px; font-weight: 600">
{{
editingRow.adviceName + ' ' + editingRow.unitPrice
? Number(editingRow.unitPrice).toFixed(2)
: '-' + '元'
}}
</span>
<div class="form-group">
<el-form-item
label="执行次数"
prop="quantity"
class="required-field"
data-prop="quantity"
>
<el-input-number
v-model="editingRow.quantity"
placeholder="执行次数"
style="width: 100px; margin: 0 20px"
controls-position="right"
:controls="false"
@keyup.enter.prevent="handleEnter('quantity', editingRow, editingRowIndex)"
@input="calculateTotalPrice(editingRow, editingRowIndex)"
/>
</el-form-item>
<el-tree-select
v-model="editingRow.orgId"
clearable
:data="organization"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
check-strictly
placeholder="请选择执行科室"
style="min-width: 150px; width: auto;"
class="org-select"
/>
<span class="total-amount">
总金额:{{ editingRow.totalPrice ? editingRow.totalPrice + ' 元' : '0.00 元' }}
</span>
</div>
<el-button
type="primary"
@click="handleSaveSign(editingRow, editingRowIndex)"
>
保存
</el-button>
</div>
</template>
</div>
</el-form>
</div>
</div>
</template>
@@ -444,7 +436,7 @@ import {
getEncounterDiagnosis,
} from './api';
import adviceBaseList from './adviceBaseList';
import {getCurrentInstance, nextTick, ref, watch} from 'vue';
import {getCurrentInstance, nextTick, ref, watch, computed} from 'vue';
const emit = defineEmits(['selectDiagnosis']);
const prescriptionList = ref([]);
@@ -493,6 +485,14 @@ const isAdding = ref(false);
const isSaving = ref(false); // #437 防重复提交锁
const prescriptionRef = ref();
const expandOrder = ref([]); //目前的展开行
const editingRow = computed(() => {
if (expandOrder.value.length === 0) return null;
return prescriptionList.value.find(r => r.uniqueKey === expandOrder.value[0]) || null;
});
const editingRowIndex = computed(() => {
if (expandOrder.value.length === 0) return -1;
return prescriptionList.value.findIndex(r => r.uniqueKey === expandOrder.value[0]);
});
const stockList = ref([]);
const groupList = ref([])
const { proxy } = getCurrentInstance();
@@ -546,8 +546,11 @@ watch(
nextTick(() => {
const index = prescriptionList.value.findIndex((row) => row.uniqueKey === newValue[0]);
const items = proxy.$refs['formRef' + index]?.$el?.querySelectorAll('[data-prop]');
requiredProps.value = Array.from(items).map((item) => item.dataset.prop);
const formEl = proxy.$refs['editFormRef'];
if (formEl) {
const items = formEl.$el?.querySelectorAll('[data-prop]') || formEl.querySelectorAll?.('[data-prop]');
if (items) requiredProps.value = Array.from(items).map((item) => item.dataset.prop);
}
});
} else {
requiredProps.value = {};
@@ -831,11 +834,9 @@ async function selectAdviceBase(key, row) {
});
}
// 将选中的基础项“覆盖”到当前处方行(这是之前正常工作的核心逻辑)
prescriptionList.value[rowIndex.value] = {
...prescriptionList.value[rowIndex.value],
...JSON.parse(JSON.stringify(row)),
};
// 将选中的基础项“覆盖”到当前处方行
// 用 Object.assign 原地修改,确保 vxe-table 能检测到变更重新渲染
Object.assign(prescriptionList.value[rowIndex.value], JSON.parse(JSON.stringify(row)));
// 后续字段处理保持原样
// 🔧 修复执行科室逻辑:诊疗项目优先使用项目维护的所属科室(row.orgId)
@@ -1271,7 +1272,7 @@ function handleSaveSign(row, index) {
return;
}
isSaving.value = true; // #437 立即加锁,消除 TOCTOU 竞态
proxy.$refs['formRef' + index].validate((valid) => {
proxy.$refs['editFormRef'].validate((valid) => {
if (!valid) {
isSaving.value = false; // 验证失败释放锁
return;
@@ -1391,6 +1392,14 @@ defineExpose({ getListInfo, closeAllPopovers });
:deep(.vxe-table--expand-btn) {
display: none !important;
}
// 编辑表单卡片:独立于表格,显示在表格下方
.edit-form-card {
margin-top: 12px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #fff;
}
.medicine-title {
font-size: 16px;
font-weight: 600;

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div>
<el-row :gutter="24">
<el-col
@@ -873,90 +873,66 @@ function handleInfectiousDiseaseReport() {
// 疾病名称到报卡编码的映射(根据传染病报告卡弹窗中的疾病列表)
const diseaseNameToCode = {
// 甲类
'鼠疫': '0101',
'霍乱': '0102',
'鼠疫': '0101', '霍乱': '0102',
// 乙类
'传染性非典型肺炎': '0201',
'艾滋病': '0202',
'病毒性肝炎': '0203',
'脊髓灰质炎': '0204',
'人感染高致病性禽流感': '0205',
'麻疹': '0206',
'流行性出血热': '0207',
'狂犬病': '0208',
'流行性乙型脑炎': '0209',
'登革热': '0210',
'炭疽': '0211',
'细菌性和阿米巴性痢疾': '0212',
'肺结核': '0213',
'伤寒和副伤寒': '0214',
'流行性脑脊髓膜炎': '0215',
'百日咳': '0216',
'白喉': '0217',
'新生儿破伤风': '0218',
'猩红热': '0219',
'布鲁氏菌病': '0220',
'淋病': '0221',
'梅毒': '0222',
'钩端螺旋体病': '0223',
'血吸虫病': '0224',
'疟疾': '0225',
'新型冠状病毒肺炎': '0226',
'甲型H1N1流感': '0227',
'传染性非典型肺炎': '0201', '艾滋病': '0202', '病毒性肝炎': '0203',
'脊髓灰质炎': '0204', '人感染高致病性禽流感': '0205', '麻疹': '0206',
'流行性出血热': '0207', '狂犬病': '0208', '流行性乙型脑炎': '0209',
'登革热': '0210', '炭疽': '0211', '细菌性和阿米巴性痢疾': '0212',
'肺结核': '0213', '伤寒和副伤寒': '0214', '流行性脑脊髓膜炎': '0215',
'百日咳': '0216', '白喉': '0217', '新生儿破伤风': '0218',
'猩红热': '0219', '布鲁氏菌病': '0220', '淋病': '0221',
'梅毒': '0222', '钩端螺旋体病': '0223', '血吸虫病': '0224',
'疟疾': '0225', '新型冠状病毒肺炎': '0226', '甲型H1N1流感': '0227',
'人感染H7N9禽流感': '0228',
// 丙类
'流行性感冒': '0301',
'流行性腮腺炎': '0302',
'风疹': '0303',
'急性出血性结膜炎': '0304',
'麻风病': '0305',
'流行性和地方性斑疹伤寒': '0306',
'黑热病': '0307',
'包虫病': '0308',
'丝虫病': '0309',
'流行性感冒': '0301', '流行性腮腺炎': '0302', '风疹': '0303',
'急性出血性结膜炎': '0304', '麻风病': '0305',
'流行性和地方性斑疹伤寒': '0306', '黑热病': '0307',
'包虫病': '0308', '丝虫病': '0309',
'除霍乱/菌痢/伤寒副伤寒以外的感染性腹泻病': '0310',
'其它感染性腹泻病': '0310',
'手足口病': '0311',
'其它感染性腹泻病': '0310', '手足口病': '0311',
};
// 获取所有需要触发传染病报卡的诊断,跳过已有已提交报卡的诊断
// 判断依据1) 硬编码名称匹配2) 后端配置了 reportTypeCode报卡类型
const infectiousDiagnoses = form.value.diagnosisList
.map(d => {
// 跳过已有已提交报卡的诊断
if (d.hasInfectiousReport === 1) return null;
// 获取所有需要触发传染病报卡的诊断,跳过已有已提交报卡的诊断
const infectiousDiagnoses = [];
let diseaseCode = null;
for (const d of form.value.diagnosisList) {
// 跳过已有已提交报卡的诊断
if (d.hasInfectiousReport === 1) continue;
// 1. 尝试精确名称匹配
if (d.name && diseaseNameToCode[d.name]) {
diseaseCode = diseaseNameToCode[d.name];
}
// 2. 尝试部分名称匹配(如"古典生物型霍乱"包含"霍乱"
else if (d.name && d.reportTypeCode) {
const match = Object.entries(diseaseNameToCode).find(([name]) =>
name && d.name.includes(name)
);
if (match) {
diseaseCode = match[1];
}
}
// 3. 配置了 reportTypeCode 但无名称匹配,仍触发弹窗(不预选疾病)
else if (d.reportTypeCode) {
let diseaseCode = null;
// 1. 精确名称匹配硬编码映射表
if (d.name && diseaseNameToCode[d.name]) {
diseaseCode = diseaseNameToCode[d.name];
}
// 2. 部分名称匹配双向诊断名包含映射key或映射key包含诊断名
else if (d.name && d.reportTypeCode) {
const match = Object.entries(diseaseNameToCode).find(([name]) =>
name && (d.name.includes(name) || name.includes(d.name))
);
if (match) {
diseaseCode = match[1];
} else {
// 3. 诊断目录中配置了报卡类型(reportTypeCode)但无名称匹配,仍触发弹窗
diseaseCode = 'OTHER';
}
}
// 4. 仅有reportTypeCode但name为空
else if (d.reportTypeCode) {
diseaseCode = 'OTHER';
}
if (!diseaseCode) return null;
return { diagnosis: d, diseaseCode };
})
.filter(item => item !== null);
if (diseaseCode) {
infectiousDiagnoses.push({ diagnosis: d, diseaseCode });
}
}
if (infectiousDiagnoses.length === 0) return;
const allSelectedDiseases = infectiousDiagnoses.map(item => item.diseaseCode);
if (allSelectedDiseases.length === 0) {
return;
}
// 优先使用命中传染病映射的主诊断,否则使用第一条命中的传染病诊断
const mainInfectiousDiagnosis = infectiousDiagnoses.find(item => item.diagnosis.maindiseFlag === 1)?.diagnosis;
const firstInfectiousDiagnosis = infectiousDiagnoses[0].diagnosis;

View File

@@ -1738,7 +1738,7 @@ const loadCategoryItems = async (categoryKey, loadMore = false) => {
const mappedItems = records.map(item => {
// 套餐项目处理:需同时满足 feePackageId 有效且 packageName 非空
// BugFix#556: 增加 packageName 联合判断,避免普通项目因 feePackageId 有值被误标为套餐
const isPackage = item.feePackageId != null && item.feePackageId !== '' && item.feePackageId !== 'null' && item.packageName
const isPackage = Boolean(item.feePackageId != null && item.feePackageId !== '' && item.feePackageId !== 'null' && item.packageName)
const itemPrice = isPackage
? (parseFloat(item.packageAmount || 0) || parseFloat(item.retailPrice || 0) || parseFloat(item.price || 0))
: (parseFloat(item.retailPrice || 0) || parseFloat(item.price || 0))
@@ -2226,7 +2226,7 @@ const handleSave = () => {
// Bug #326修复: 传入 activityId后端直接使用 ID 关联,避免用名称反查
activityId: item.activityId || item.itemId || null,
feePackageId: item.feePackageId || null,
isPackage: item.isPackage || false,
isPackage: Boolean(item.isPackage),
sampleType: item.sampleType || '',
unit: item.unit || ''
}))

View File

@@ -636,13 +636,14 @@ const inputRefs = ref({}); // 存储输入框实例
const requiredProps = ref([]); // 存储必填项 prop 顺序
const totalAmount = ref(0);
const tcmDianosis = ref();
const { method_code, unit_code, rate_code, distribution_category_code, dosage_instruction } =
const { method_code, unit_code, rate_code, distribution_category_code, dosage_instruction, method_of_decocting_medicine } =
proxy.useDict(
'method_code',
'unit_code',
'rate_code',
'distribution_category_code',
'dosage_instruction'
'dosage_instruction',
'method_of_decocting_medicine'
);
onMounted(() => {

View File

@@ -198,11 +198,23 @@
align="center"
min-width="100"
/>
<!-- <vxe-column title="操作" min-width="100" align="center" fixed="right">
<vxe-column
title="操作"
min-width="100"
align="center"
fixed="right"
>
<template #default="scope">
<el-button link type="primary" size="small">补打</el-button>
<el-button
link
type="primary"
size="small"
@click="handlePrint(scope.row)"
>
打印
</el-button>
</template>
</vxe-column> -->
</vxe-column>
</vxe-table>
<pagination
v-show="total > 0"
@@ -238,6 +250,7 @@
<script setup>
import {nextTick, ref} from 'vue';
import {simplePrint, PRINT_TEMPLATE} from '@/utils/printUtils';
import {getDepositInfo, getDepositInfoPage} from './components/api';
import PatientList from './components/patientList.vue';
import ChargeDialog from './components/chargeDialog.vue';
@@ -319,6 +332,36 @@ function refund() {
}
}
/**
* 打印预交金收据
*
* @param {Object} row 当前行数据
*/
async function handlePrint(row) {
if (!patientInfo.value.patientId) {
proxy.$modal.msgError("请先选择病人!");
return;
}
try {
const printData = {
patientName: patientInfo.value.patientName || "",
encounterNosd: patientInfo.value.busNo || "",
inHospitalOrgName: patientInfo.value.inHospitalOrgName || "",
patientId: patientInfo.value.patientId || "",
contractName: patientInfo.value.contractName || "",
currentTime: row.operateTime || new Date().toLocaleString(),
balanceAmount: row.tenderedAmount ? row.tenderedAmount.toFixed(2) : "0.00",
amountInWords: "",
paymentDetails: "收据号: " + (row.paymentNo || ""),
};
await simplePrint(PRINT_TEMPLATE.ADVANCE_PAYMENT, printData);
proxy.$modal.msgSuccess("打印成功");
} catch (error) {
console.error("预交金打印失败:", error);
proxy.$modal.msgError("打印失败: " + (error.message || "未知错误"));
}
}
/** 选择病人 */
function handlePatientSelected(row) {
// console.log(row, 'rowwwwwwwwhandlePatientSelected');

View File

@@ -18,17 +18,13 @@
查询
</el-button>
</el-space>
<el-space>
<!-- <el-button>读卡</el-button> -->
<!-- <el-button type="primary">无档登记</el-button> -->
</el-space>
</div>
<div class="table-container">
<vxe-table
:data="treatHospitalizedData"
style="width: 100%"
height="100%"
show-overflow
show-overflow="title"
>
<vxe-column
type="seq"
@@ -104,20 +100,68 @@
align="center"
title="登记员"
/>
<vxe-column
align="center"
title="登记状态"
width="100"
>
<template #default="scope">
<span :style="{ color: scope.row.statusEnum == 8 ? '#F56C6C' : '#67C23A' }">
{{ scope.row.statusEnum == 8 ? '已作废' : '已登记' }}
</span>
</template>
</vxe-column>
<vxe-column
fixed="right"
align="center"
title="操作"
width="88"
width="280"
>
<template #default="scope">
<el-button
type="primary"
text
@click="doEdit(scope.row)"
@click="doView(scope.row)"
>
查看
</el-button>
<el-tooltip
v-if="scope.row.statusEnum == 5"
content="患者已入科接收,无法修改登记信息"
placement="top"
>
<el-button
type="primary"
text
disabled
>
修改
</el-button>
</el-tooltip>
<el-button
v-else-if="scope.row.statusEnum == 8"
type="primary"
text
disabled
>
修改
</el-button>
<el-button
v-else
type="primary"
text
@click="doModify(scope.row)"
>
修改
</el-button>
<el-button
type="danger"
text
:disabled="scope.row.statusEnum == 5 || scope.row.statusEnum == 8"
@click="doVoid(scope.row)"
>
作废
</el-button>
</template>
</vxe-column>
</vxe-table>
@@ -134,35 +178,32 @@
v-model:dialog-visible="patientRegisterVisible"
:patient-info="patient"
:in-hospital-info="inHospitalInfo"
title="登记"
:title="dialogTitle"
:registration-type="registrationType"
:already-edit="alreadyEdit"
:no-file="noFile"
:is-registered="true"
:is-registered="!isEditMode"
:is-edit-mode="isEditMode"
@ok-act="patientRegisterOK"
@cancel-act="cancelAct"
/>
</template>
<script setup>
import PatientRegister from './patientRegister.vue';
import {getAdmissionPage, getContractList, getInHospitalInfo, getPatientBasicInfo} from './api';
import {ElMessage, ElMessageBox} from 'element-plus';
import {getAdmissionPage, getContractList, getInHospitalInfo, getPatientBasicInfo, voidRegistration} from './api';
const { proxy } = getCurrentInstance();
const { admit_source_code } = proxy.useDict('admit_source_code');
//const { proxy } = getCurrentInstance();
const emits = defineEmits([]);
// const props = defineProps({});
const searchForm = reactive({
searchType: 'name',
searchKey: '',
});
const total = ref();
const inHospitalInfo = ref({});
const alreadyEdit = ref(true);
const isEditMode = ref(false);
const dialogTitle = ref('登记');
const queryParams = ref({
pageNo: 1,
pageSize: 10,
searchKey: undefined,
registeredFlag: '1',
searchKey: '',
});
@@ -173,22 +214,58 @@ const noFile = ref(false);
const registrationType = ref(true);
const patient = ref({});
const priceTypeList = ref({});
const doEdit = (row) => {
getPatientBasicInfo(row.patientId).then((res) => {
patient.value = res.data;
});
getInHospitalInfo(row.encounterId).then((res) => {
inHospitalInfo.value = res.data;
patientRegisterVisible.value = true;
noFile.value = false;
});
const doView = async (row) => {
isEditMode.value = false;
dialogTitle.value = '查看';
const [patientRes, hospitalRes] = await Promise.all([
getPatientBasicInfo(row.patientId),
getInHospitalInfo(row.encounterId),
]);
patient.value = patientRes.data;
inHospitalInfo.value = hospitalRes.data;
patientRegisterVisible.value = true;
noFile.value = false;
};
const doModify = async (row) => {
isEditMode.value = true;
dialogTitle.value = '修改登记';
const [patientRes, hospitalRes] = await Promise.all([
getPatientBasicInfo(row.patientId),
getInHospitalInfo(row.encounterId),
]);
patient.value = patientRes.data;
inHospitalInfo.value = hospitalRes.data;
patientRegisterVisible.value = true;
noFile.value = false;
};
const doVoid = (row) => {
ElMessageBox.confirm(
'确认作废该患者的住院登记信息吗?作废后不可撤销',
'作废确认',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
voidRegistration(row.encounterId).then((res) => {
if (res.code == 200) {
ElMessage.success('作废成功');
getList();
} else {
ElMessage.error(res.msg || '作废失败');
}
});
})
.catch(() => {});
};
onBeforeMount(() => {});
getContract();
onMounted(() => {
getList();
});
const activeName = ref('first');
const patientRegisterOK = () => {
patientRegisterVisible.value = false;
@@ -211,7 +288,6 @@ function resetQuery() {
queryParams.value = {
pageNo: 1,
pageSize: 10,
searchKey: undefined,
registeredFlag: '1',
searchKey: '',
};
@@ -262,7 +338,6 @@ const getList = () => {
}
treatHospitalizedData.value = dataList;
// treatHospitalizedData.value = res.data.records;
total.value = res.data.total;
});
};

View File

@@ -132,6 +132,23 @@ export function registerInHospital(data) {
});
}
// 修改住院登记
export function updateRegistration(data) {
return request({
url: '/inhospital-charge/register/update-registration',
method: 'put',
data: data,
});
}
// 作废住院登记
export function voidRegistration(encounterId) {
return request({
url: `/inhospital-charge/register/void-registration?encounterId=${encounterId}`,
method: 'put',
});
}
// 无档登记
export function noFilesRegister(data) {
return request({

View File

@@ -39,7 +39,10 @@
/>
</el-scrollbar>
<template #footer>
<div class="advance-container">
<div
v-if="!props.isEditMode"
class="advance-container"
>
<div
v-if="currentFeeType !== 'hipCash'"
class="payment-item"
@@ -110,13 +113,21 @@
取消
</el-button>
<el-button
v-if="!props.isRegistered"
v-if="!props.isRegistered && !props.isEditMode"
size="fixed"
type="primary"
@click="handleSubmit"
>
登记
</el-button>
<el-button
v-if="props.isEditMode"
size="fixed"
type="primary"
@click="handleEditSubmit"
>
保存
</el-button>
</template>
</el-dialog>
</template>
@@ -125,7 +136,7 @@ const { proxy } = getCurrentInstance();
import {ElMessage, ElMessageBox} from 'element-plus';
import PatientInfoComp from './patientInfo.vue';
import RegisterForm from './registerForm.vue';
import {noFilesRegister, registerInHospital} from './api';
import {noFilesRegister, registerInHospital, updateRegistration} from './api';
import {getInit} from '@/views/doctorstation/components/api';
import {useRouter} from 'vue-router';
import {wxPay, WxPayResult} from '../../../../charge/cliniccharge/components/api';
@@ -161,6 +172,10 @@ const props = defineProps({
type: Boolean,
default: false, // false 表示待登记true 表示已登记
},
isEditMode: {
type: Boolean,
default: false, // true 表示修改已登记的记录
},
});
watch(
@@ -283,6 +298,7 @@ const handleSubmit = () => {
ElMessage.success(res.msg);
// 打印预交金收据
printDepositReceipt(props.patientInfo, params.inHospitalInfo);
emits('okAct');
cancelAct();
// 询问是否需要医保登记
// ElMessageBox.confirm('是否需要进行医保登记?', '医保登记确认', {
@@ -392,19 +408,25 @@ const handleSubmit = () => {
})
.then(() => {
console.log('路由跳转成功');
emits('okAct');
})
.catch((error) => {
console.error('路由跳转失败:', error);
ElMessage.error('跳转到医保登记页面失败');
emits('okAct');
});
} catch (error) {
console.error('跳转异常:', error);
emits('okAct');
}
})
.catch(() => {
// 用户取消医保登记,关闭当前弹窗
emits('okAct');
});
} else {
// 自费患者,直接通知刷新列表
emits('okAct');
}
cancelAct();
} else {
@@ -430,6 +452,34 @@ const handleSubmit = () => {
}
};
/* 修改登记 */
const handleEditSubmit = () => {
RegisterFormRef.value.validateData(async () => {
const formData = RegisterFormRef.value.submitForm;
const params = {
encounterId: props.inHospitalInfo.encounterId,
patientId: props.patientInfo.patientId,
inHospitalOrgId: formData.inHospitalOrgId,
wardLocationId: formData.wardLocationId,
priorityEnum: formData.priorityEnum,
admitSourceCode: formData.admitSourceCode,
inWayCode: formData.inWayCode,
startTime: formData.startTime,
contractNo: formData.contractNo,
typeCoce: formData.typeCoce,
};
updateRegistration(params).then((res) => {
if (res.code == 200) {
ElMessage.success(res.msg);
emits('okAct');
cancelAct();
} else {
ElMessage.error(res.msg);
}
});
});
};
const openAct = () => {
console.log(props.patientInfo, 'patientRegister.vue');
console.log(props.inHospitalInfo, 'inHospitalInfo.vue');

View File

@@ -364,7 +364,7 @@ const medicalInsuranceTitle = ref('');
// });
const getProvincesAndCitiesInfo = async () => {
try {
if (inHospitalInfo.encounterId) {
if (props.inHospitalInfo.encounterId) {
const res = await getProvincesAndCities(props.inHospitalInfo.encounterId);
// console.log('获取省市医保字符串', res);
if (res && res.code == 200) {
@@ -388,8 +388,7 @@ watch(
if (newEncounterId) {
getProvincesAndCitiesInfo();
}
},
{ immediate: true }
}
);
/* 提交表单 */
@@ -463,8 +462,8 @@ watch(
}
);
onMounted(() => {
getInitOptions();
onMounted(async () => {
await getInitOptions();
setValue();
setDefaultAdmitSource();
if (submitForm.inHospitalOrgId) {
@@ -505,7 +504,7 @@ function getInitOptions() {
// 获取所有病区
const wardPromise = getPractitionerWard();
Promise.all([orgPromise, wardPromise]).then(([orgRes, wardRes]) => {
const initPromise = Promise.all([orgPromise, wardPromise]).then(([orgRes, wardRes]) => {
// 入院科室:展示所有 typeEnum=2(科室) + classEnum含"2"(住院) 的科室
organization.value = orgRes.data.records.filter(
(record) => record.typeEnum === 2 && checkClassEnumValue(record.classEnum, 2)
@@ -529,19 +528,15 @@ function getInitOptions() {
}
});
// if (!props.noFile) {
// wardList().then((res) => {
// wardListOptions.value = res.data;
// });
// }
diagnosisInit().then((res) => {
verificationStatusOptions.value = res.data.verificationStatusOptions;
});
getContractList().then((response) => {
contractList.value = response.data;
setValue();
});
getDiagnosisInfo(undefined);
return initPromise;
}
function getDiagnosisInfo(value) {
getDiagnosisDefinitionList({ pageSize: 500, pageNo: 1, searchKey: value }).then((res) => {
@@ -550,6 +545,7 @@ function getDiagnosisInfo(value) {
}
function handleNodeClick(orgInfo) {
const savedWardId = props.inHospitalInfo?.wardLocationId; // 保存原始病区ID用于编辑模式恢复
submitForm.wardLocationId = undefined; // 切换科室时,先清空原有病区
submitForm.totalBedsNum = undefined;
submitForm.idleBedsNum = undefined;
@@ -558,13 +554,20 @@ function handleNodeClick(orgInfo) {
wardList({ orgId: orgInfo.id })
.then((res) => {
wardListOptions.value = res.data || [];
if (wardListOptions.value.length > 0 && !props.inHospitalInfo.wardLocationId) {
submitForm.wardLocationId = wardListOptions.value[0].id;
const defaultWard = wardListOptions.value.find(
(item) => item.id === submitForm.wardLocationId
);
if (defaultWard) {
handleWardClick(defaultWard);
if (wardListOptions.value.length > 0) {
// 编辑模式:尝试恢复之前保存的病区
if (savedWardId) {
const savedWard = wardListOptions.value.find((item) => String(item.id) === String(savedWardId));
if (savedWard) {
submitForm.wardLocationId = savedWardId;
handleWardClick(savedWard);
return;
}
}
// 新增模式 或 原病区不在新科室下:自动选中第一个病区
if (!submitForm.wardLocationId) {
submitForm.wardLocationId = wardListOptions.value[0].id;
handleWardClick(wardListOptions.value[0]);
}
}
})
@@ -619,6 +622,19 @@ function setValue() {
submitForm.inWayCode_dictText = props.inHospitalInfo?.inWayCode_dictText;
}
// 编辑模式下API 数据异步到达后重新赋值表单字段并加载病区
watch(
() => props.inHospitalInfo.encounterId,
(newEncounterId, oldEncounterId) => {
if (newEncounterId && newEncounterId !== oldEncounterId) {
setValue();
if (submitForm.inHospitalOrgId) {
handleNodeClick({ id: submitForm.inHospitalOrgId });
}
}
}
);
const registerRef = ref();
/* 登记 */
const validateData = async (callback) => {

View File

@@ -1,4 +1,4 @@
<template>
<template>
<el-form :model="row" :rules="rules" :ref="(el) => (formRef = el)" :label-width="100">
<div class="expend_div" style="padding: 16px; background: #f8f9fa; border-radius: 8px">
<template v-if="row.adviceType == 1">
@@ -71,7 +71,17 @@
/>
</el-form-item>
<span class="medicine-info"> 诊断{{ config.diagnosisName }} </span>
<span class="medicine-info"> 皮试{{ row.skinTestFlag_enumText }} </span>
<span class="medicine-info">
皮试
<el-select
v-model="row.skinTestFlag"
style="width: 70px"
size="small"
>
<el-option :value="1" label="是" />
<el-option :value="0" label="否" />
</el-select>
</span>
<span class="medicine-info"> 注射药品{{ row.injectFlag_enumText }} </span>
<span class="total-amount">
总金额{{ row.totalPrice ? Number(row.totalPrice).toFixed(2) + ' 元' : '0.00 元' }}
@@ -467,7 +477,7 @@
</el-form-item>
<div class="form-group">
<el-select
v-model="row.lotNumber"
v-model="row.inventoryId"
style="width: 180px; margin-right: 20px"
placeholder="药房"
>

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="inpatientDoctor-order-container" style="width: 100%">
<div style="margin-bottom: 5px" class="order-operate-btn">
<div style="height: 44px; display: flex; align-items: center; flex: none">
@@ -142,9 +142,9 @@
</span>
</template>
</vxe-column>
<vxe-column title="开嘱医生" align="center" field="createdStaffName" width="120">
<vxe-column title="开嘱医生" align="center" field="requesterId_dictText" width="120">
<template #default="scope">
{{ scope.row.createdStaffName || '-' }}
{{ scope.row.requesterId_dictText || '-' }}
</template>
</vxe-column>
<vxe-column title="开始时间" align="center" field="startTime" width="200">
@@ -268,6 +268,7 @@
<el-tag v-else-if="scope.row.statusEnum == 10" type="primary">已校对</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 11" type="primary">待接收</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 3" type="success">已校对</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 13" type="warning">已停嘱</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 6" type="danger">停止</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 20" type="success">已完成</el-tag>
<el-tag v-else type="info">{{ scope.row.chargeStatus_enumText }}</el-tag>
@@ -332,14 +333,16 @@
<span v-if="!scope.row.isEdit">
{{ scope.row.skinTestFlag_enumText || '-' }}
</span>
<el-checkbox
<el-select
v-else
v-model="scope.row.skinTestFlag"
:true-value="true"
:false-value="0"
style="width: 80px"
size="small"
@click.stop
>
</el-checkbox>
<el-option :value="1" label="是" />
<el-option :value="0" label="否" />
</el-select>
</template>
</vxe-column>
<vxe-column title="停嘱医生" align="center" field="stopUserName" width="120">
@@ -445,12 +448,13 @@ import {
} from '../api';
import adviceBaseList from '../adviceBaseList';
import {calculateQuantityByDays} from '@/utils/his';
import {localPatientInfo as patientInfo} from '../../store/localPatient.js';
import {localPatientInfo as localPatientInfoRef} from '../../store/localPatient.js';
import OrderGroupDrawer from '@/views/doctorstation/components/prescription/orderGroupDrawer.vue';
import PrescriptionHistory from '@/views/doctorstation/components/prescription/prescriptionHistory.vue';
import Decimal from 'decimal.js';
import {ElMessage, ElMessageBox} from 'element-plus';
import useUserStore from '@/store/modules/user';
import {RequestStatus} from '@/utils/medicalConstants';
import ApplicationFormBottomBtn from './applicationForm/applicationFormBottomBtn.vue';
import LeaveHospitalDialog from './applicationForm/leaveHospitalDialog.vue';
import TransferOrganizationDialog from './applicationForm/transferOrganizationDialog.vue';
@@ -515,10 +519,10 @@ const unitMap = ref({
unit: 'unit',
});
const buttonDisabled = computed(() => {
return !patientInfo.value;
return !localPatientInfoRef.value;
});
const isSaveDisabled = computed(() => {
return !patientInfo.value || prescriptionList.value.length === 0;
return !localPatientInfoRef.value || prescriptionList.value.length === 0;
});
const props = defineProps({
patientInfo: {
@@ -579,9 +583,10 @@ const adviceTypeList = computed(() => {
hasShownPharmacyConfigWarning.value = true;
}
// 只返回不需要取药科室配置的类别(诊疗、手术、出院带药)
// 只返回不需要取药科室配置的类别(诊疗、耗材、手术、出院带药)
return [
{ label: '诊疗', value: 3, adviceType: 3, categoryCode: '' },
{ label: '耗材', value: 2, adviceType: 2, categoryCode: '' },
{ label: '手术', value: 6, adviceType: 6, categoryCode: '' },
{ label: '出院带药', value: 7, adviceType: 7, categoryCode: '' },
{ label: '文字', value: 8, adviceType: 8, categoryCode: '' },
@@ -608,8 +613,9 @@ const adviceTypeList = computed(() => {
typeList.push({ label: '中草药', value: '1-4', adviceType: 1, categoryCode: '4' });
}
// 始终添加诊疗和手术(它们不受取药配置限制)
// 始终添加诊疗、耗材和手术(它们不受取药配置限制)
typeList.push({ label: '诊疗', value: 3, adviceType: 3, categoryCode: '' });
typeList.push({ label: '耗材', value: 2, adviceType: 2, categoryCode: '' });
typeList.push({ label: '手术', value: 6, adviceType: 6, categoryCode: '' });
// 始终添加"出院带药"选项(不需要取药科室配置)
@@ -633,6 +639,10 @@ const statusOption = [
label: '已签发',
value: 2,
},
{
label: '已停嘱',
value: 13,
},
{
label: '停止',
value: 6,
@@ -706,7 +716,7 @@ function getListInfo(addNewRow) {
const orgTreePromise = getOrgTree().then((res) => {
organization.value = res?.data?.records ?? res?.data ?? [];
});
getPrescriptionList(patientInfo.value.encounterId).then((res) => {
getPrescriptionList(localPatientInfoRef.value.encounterId).then((res) => {
// 等待科室树加载完成后再处理处方数据,确保 resolveOrgId 能正确匹配
orgTreePromise.then(() => {
loading.value = false;
@@ -722,6 +732,11 @@ function getListInfo(addNewRow) {
return {
...parsedContent,
...item,
// 🔧 修复:确保开嘱医生名称正确显示
// 优先使用后端返回的 requesterId_dictText其次从 userStore 获取
requesterId_dictText: item.requesterId_dictText || item.requesterId_dict_text
|| (String(item.requesterId) === String(userStore.practitionerId) ? userStore.name : '')
|| parsedContent?.requesterId_dictText || '-',
// 🔧 修复contentJson 中的 totalPrice 优先于 charge_item 表的 totalPrice
// charge_item.totalPrice 可能为 0 或 null新建医嘱时导致总金额显示为 "-"
totalPrice: parsedContent?.totalPrice || item.totalPrice,
@@ -770,10 +785,10 @@ function getListInfo(addNewRow) {
}
});
}).catch(() => { loading.value = false; });
getContract({ encounterId: patientInfo.value.encounterId }).then((res) => {
getContract({ encounterId: localPatientInfoRef.value.encounterId }).then((res) => {
contractList.value = res.data;
});
accountId.value = patientInfo.value.accountId;
accountId.value = localPatientInfoRef.value.accountId;
// 加载已配置的药品类别
loadConfiguredCategories();
@@ -783,7 +798,7 @@ function getListInfo(addNewRow) {
* 加载已配置的药品类别
*/
function loadConfiguredCategories() {
const orgId = patientInfo.value?.inHospitalOrgId;
const orgId = localPatientInfoRef.value?.inHospitalOrgId;
if (!orgId) {
isCategoryLoaded.value = true; // 标记已加载即使没有科室ID
return;
@@ -857,7 +872,7 @@ const filterPrescriptionList = computed(() => {
});
function getDiagnosisInfo() {
getEncounterDiagnosis(patientInfo.value.encounterId).then((res) => {
getEncounterDiagnosis(localPatientInfoRef.value.encounterId).then((res) => {
diagnosisList.value = res.data;
let diagnosisInfo = diagnosisList.value.filter((item) => {
return item.maindiseFlag == 1;
@@ -885,13 +900,14 @@ function getRowSelectValue(row) {
if (row.adviceType == 7) {
return 7;
}
// 耗材(adviceType=2)直接返回数字值,与 adviceTypeList 中的 value 匹配
return row.adviceType;
}
// 新增医嘱
function handleAddPrescription() {
// 校验是否选中患者
if (!patientInfo.value || !patientInfo.value.encounterId) {
if (!localPatientInfoRef.value || !localPatientInfoRef.value.encounterId) {
proxy.$modal.msgWarning('请先选择患者');
return;
}
@@ -940,9 +956,9 @@ function checkUnit(item, row) {
* @returns {boolean} true=通过, false=不通过
*/
function validateStartTime(startTime) {
if (!startTime || !patientInfo.value?.inHospitalTime) return true;
if (!startTime || !localPatientInfoRef.value?.inHospitalTime) return true;
const startDate = new Date(startTime);
const inHospitalDate = new Date(patientInfo.value.inHospitalTime);
const inHospitalDate = new Date(localPatientInfoRef.value.inHospitalTime);
if (startDate < inHospitalDate) {
const pad = (n) => String(n).padStart(2, '0');
const d = inHospitalDate;
@@ -989,6 +1005,10 @@ function clickRowDb({ row, column, event }) {
prescriptionList.value[index] = row;
}
expandOrder.value = [row.uniqueKey];
// VXE Table v4: clearRowExpand 后 expandRowKeys 不再自动响应,需手动调 API 展开
if (prescriptionRef.value?.setRowExpand) {
prescriptionRef.value.setRowExpand([row], true);
}
} else {
proxy.$modal.msgWarning('仅待保存或待签发医嘱允许编辑');
}
@@ -1181,12 +1201,22 @@ function selectAdviceBase(key, row) {
// 用户点击"是",设置皮试标志为"是"
prescriptionList.value[rowIndex.value].skinTestFlag = 1;
prescriptionList.value[rowIndex.value].skinTestFlag_enumText = '是';
expandOrder.value = [currentUniqueKey];
const expandRow = filterPrescriptionList.value.find(item => item.uniqueKey === currentUniqueKey);
if (expandRow && prescriptionRef.value?.setRowExpand) {
prescriptionRef.value.setRowExpand([expandRow], true);
}
expandOrderAndFocus(currentUniqueKey, row);
})
.catch((action) => {
// 用户点击"否",设置皮试标志为"否"
prescriptionList.value[rowIndex.value].skinTestFlag = 0;
prescriptionList.value[rowIndex.value].skinTestFlag_enumText = '否';
expandOrder.value = [currentUniqueKey];
const expandRow = filterPrescriptionList.value.find(item => item.uniqueKey === currentUniqueKey);
if (expandRow && prescriptionRef.value?.setRowExpand) {
prescriptionRef.value.setRowExpand([expandRow], true);
}
expandOrderAndFocus(currentUniqueKey, row);
});
} else {
@@ -1432,9 +1462,9 @@ function handleSave() {
// 签发处理逻辑
function executeSaveLogic() {
saveList.forEach((item) => {
item.patientId = patientInfo.value.patientId;
item.encounterId = patientInfo.value.encounterId;
item.accountId = patientInfo.value.accountId;
item.patientId = localPatientInfoRef.value.patientId;
item.encounterId = localPatientInfoRef.value.encounterId;
item.accountId = localPatientInfoRef.value.accountId;
item.dbOpType = '1';
// Bug #589: 出院带药保存时转为药品类型
if (item.adviceType == 7) {
@@ -1463,11 +1493,11 @@ function handleSave() {
// 保存签发按钮
isSaving.value = true;
console.log('签发处方参数:', {
organizationId: patientInfo.value.inHospitalOrgId,
organizationId: localPatientInfoRef.value.inHospitalOrgId,
adviceSaveList: list,
});
savePrescriptionSign({
organizationId: patientInfo.value.inHospitalOrgId,
organizationId: localPatientInfoRef.value.inHospitalOrgId,
regAdviceSaveList: list,
})
.then((res) => {
@@ -1529,8 +1559,8 @@ function handleOrderBindInfo(bindIdInfo) {
const newRow = {
...prescriptionList.value[rowIndex.value],
uniqueKey: nextId.value++,
patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId,
patientId: localPatientInfoRef.value.patientId,
encounterId: localPatientInfoRef.value.encounterId,
accountId: accountId.value,
quantity: item.quantity,
methodCode: item.methodCode,
@@ -1633,8 +1663,8 @@ function handleSaveSign(row, index) {
// 执行保存
row.contentJson = undefined;
row.patientId = patientInfo.value.patientId;
row.encounterId = patientInfo.value.encounterId;
row.patientId = localPatientInfoRef.value.patientId;
row.encounterId = localPatientInfoRef.value.encounterId;
row.accountId = accountId.value;
// 🔧 文字医嘱(type=8)跳过计费逻辑总金额为0
@@ -1679,6 +1709,7 @@ function handleSaveSign(row, index) {
const originalAdviceType = row.adviceType;
if (row.adviceType == 7) {
row.adviceType = 1;
row.prescriptionCategory = 3; // 出院带药标记,与 handleSaveBatch/handleSave 保持一致
}
row.conditionId = conditionId.value;
// 处理总量为小单位情况,需要把单价也保存成小单位的
@@ -1705,6 +1736,10 @@ function handleSaveSign(row, index) {
: 0;
row.skinTestFlag_enumText = row.skinTestFlag == 1 ? '是' : '否';
row.contentJson = JSON.stringify(row);
// Bug #589: contentJson 已序列化(含 adviceType=1恢复内存中的出院带药类型显示
if (originalAdviceType == 7) {
row.adviceType = 7;
}
if (row.requestId) {
row.dbOpType = '2';
savePrescription({ regAdviceSaveList: [row] }).then((res) => {
@@ -1870,14 +1905,16 @@ function setValue(row) {
...prevRow,
...baseRow,
uniqueKey: currentUniqueKey, // 确保 uniqueKey 不被覆盖
// Bug #589: 出院带药在 baseRow 中被药品的 adviceType=1 覆盖,此处恢复
adviceType: prevRow.dischargeFlag ? 7 : baseRow.adviceType,
// 🔧 修复执行科室逻辑:
// 1. 非诊疗类型(adviceType!=3)不需要执行科室设为undefined
// 2. 诊疗类型优先使用项目维护的所属科室(row.orgId)其次positionId
// 3. 如果都为空,回退到患者当前所在科室(patientInfo.orgId)
// 4. 使用 resolveOrgId 从组织树中匹配正确的 String id解决大 Long 精度丢失问题
orgId: row.adviceType != 3 ? undefined : (resolveOrgId(row.orgId || row.positionId || patientInfo.value?.inHospitalOrgId) || ''),
orgId: row.adviceType != 3 ? undefined : (resolveOrgId(row.orgId || row.positionId || localPatientInfoRef.value?.inHospitalOrgId) || ''),
// 🔧 修复:同时保存 orgName当 orgId 在科室树中匹配不到时作为兜底显示
orgName: row.adviceType != 3 ? undefined : (findOrgName(row.orgId || row.positionId || patientInfo.value?.inHospitalOrgId) || row.orgName || patientInfo.value?.inHospitalOrgName || ''),
orgName: row.adviceType != 3 ? undefined : (findOrgName(row.orgId || row.positionId || localPatientInfoRef.value?.inHospitalOrgId) || row.orgName || localPatientInfoRef.value?.inHospitalOrgName || ''),
// dose: undefined, Removed to preserve dose value from group package
unitCodeList: unitCodeList.value,
doseUnitCode: row.doseUnitCode,
@@ -1981,8 +2018,8 @@ function handleSaveGroup(orderGroupList) {
// 创建新的处方项目
const newRow = {
...prescriptionList.value[tempIndex],
patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId,
patientId: localPatientInfoRef.value.patientId,
encounterId: localPatientInfoRef.value.encounterId,
accountId: accountId.value,
// 🔧 修复 Bug #403从 mergedDetail 读取明细字段,而非直接从 item 取
// item.dose 等字段可能为 nullmergedDetail 已做 ?? 兜底
@@ -1996,9 +2033,9 @@ function handleSaveGroup(orderGroupList) {
unitCode: mergedDetail.unitCode ?? item.unitCode,
unitCode_dictText: item.unitCodeName || mergedDetail.unitCodeName || '',
statusEnum: 1,
orgId: resolveOrgId(mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || '',
orgId: resolveOrgId(mergedDetail.orgId || localPatientInfoRef.value?.inHospitalOrgId) || '',
// 🔧 修复:同时存储 orgName确保树匹配不到时仍有中文名称可显示
orgName: findOrgName(mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || mergedDetail.orgName || patientInfo.value?.inHospitalOrgName || '',
orgName: findOrgName(mergedDetail.orgId || localPatientInfoRef.value?.inHospitalOrgId) || mergedDetail.orgName || localPatientInfoRef.value?.inHospitalOrgName || '',
startTime: mergedDetail.startTime || defaultStartTimeFn(),
dbOpType: prescriptionList.value[tempIndex].requestId ? '2' : '1',
conditionId: conditionId.value,
@@ -2040,8 +2077,8 @@ function handleSaveGroup(orderGroupList) {
function handleSaveHistory(value) {
let saveRow = {
...value,
patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId,
patientId: localPatientInfoRef.value.patientId,
encounterId: localPatientInfoRef.value.encounterId,
accountId: accountId.value,
uniqueKey: undefined,
startTime: defaultStartTimeFn(),
@@ -2203,10 +2240,10 @@ function handleStopAdvice() {
break;
}
}
// 找出停嘱的
// 找出停嘱的(已停止=6 或 已停嘱=13 的不允许再停)
for (let index = 0; index < selectRows.length; index++) {
const item = selectRows[index];
if (item.statusEnum == 6) {
if (item.statusEnum == 6 || item.statusEnum == 13) {
isStop = false;
break;
}
@@ -2265,9 +2302,9 @@ function confirmStopAdvice() {
return;
}
// 校验:停嘱时间不能早于患者入院时间
if (patientInfo.value?.inHospitalTime) {
if (localPatientInfoRef.value?.inHospitalTime) {
const stopDate = new Date(stopForm.stopTime);
const inHospitalDate = new Date(patientInfo.value.inHospitalTime);
const inHospitalDate = new Date(localPatientInfoRef.value.inHospitalTime);
if (stopDate < inHospitalDate) {
const pad = (n) => String(n).padStart(2, '0');
const d = inHospitalDate;
@@ -2310,11 +2347,11 @@ function handleResumeAdvice() {
});
return;
}
// 校验:只有状态为"停止"(statusEnum=6)的医嘱才能恢复
// 校验:只有状态为"停止"(statusEnum=6)或"已停嘱"(statusEnum=13)的医嘱才能恢复
let hasStopOrder = false;
for (let index = 0; index < selectRows.length; index++) {
const item = selectRows[index];
if (item.statusEnum == 6) {
if (item.statusEnum == 6 || item.statusEnum == 13) {
hasStopOrder = true;
break;
}
@@ -2322,7 +2359,7 @@ function handleResumeAdvice() {
if (!hasStopOrder) {
ElMessage({
type: 'error',
message: '请选择已停止的医嘱进行恢复',
message: '请选择已停止或已停嘱的医嘱进行恢复',
});
return;
}
@@ -2330,7 +2367,7 @@ function handleResumeAdvice() {
let allStop = true;
for (let index = 0; index < selectRows.length; index++) {
const item = selectRows[index];
if (item.statusEnum != 6) {
if (item.statusEnum != 6 && item.statusEnum != 13) {
allStop = false;
break;
}
@@ -2338,7 +2375,7 @@ function handleResumeAdvice() {
if (!allStop) {
ElMessage({
type: 'error',
message: '恢复操作只能选择已停止的医嘱,请重新选择',
message: '恢复操作只能选择已停止或已停嘱的医嘱,请重新选择',
});
return;
}
@@ -2384,10 +2421,10 @@ function combination() {
break;
}
}
// 找出停嘱的
// 找出停嘱的(已停止或已停嘱的不允许组合)
for (let index = 0; index < selectRows.length; index++) {
const item = selectRows[index];
if (item.statusEnum == 6) {
if (item.statusEnum == RequestStatus.STOPPED || item.statusEnum == RequestStatus.PENDING_STOP) {
isStop = false;
break;
}
@@ -2484,10 +2521,10 @@ function split() {
break;
}
}
// 找出停嘱的
// 找出停嘱的(已停止或已停嘱的不允许拆组)
for (let index = 0; index < selectRows.length; index++) {
const item = selectRows[index];
if (item.statusEnum == 6) {
if (item.statusEnum == RequestStatus.STOPPED || item.statusEnum == RequestStatus.PENDING_STOP) {
isStop = false;
break;
}
@@ -2782,7 +2819,7 @@ function sortPrescriptionList() {
}
function handleLeaveHospital() {
if (!patientInfo.value) {
if (!localPatientInfoRef.value) {
proxy.$modal.msgWarning('请先选择患者');
return;
}

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div style="height: calc(100vh - 176px)">
<div
v-loading="loading"
@@ -272,6 +272,10 @@ const props = defineProps({
type: Number,
default: undefined,
},
drugType: {
type: String,
default: '1',
},
});
function handleGetPrescription() {
@@ -285,6 +289,7 @@ function handleGetPrescription() {
therapyEnum: props.therapyEnum,
exeStatus: props.exeStatus,
requestStatus: props.requestStatus,
tcmFlag: props.drugType === '1' ? 0 : 1,
})
.then((res) => {
// try {
@@ -359,8 +364,11 @@ function handleMedicineSummary() {
return;
}
let ids = [];
const now = proxy.formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss');
paramList.forEach((item) => {
ids.push(...item.dispenseIds);
item.dispenseIds.forEach((d) => {
ids.push({ ...d, dispenseTime: now });
});
});
if (ids.length === 0) {
proxy.$message.warning('所选药品未关联发放信息');

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div style="height: calc(100vh - 176px)">
<div
v-loading="loading"
@@ -178,6 +178,10 @@ const props = defineProps({
type: Number,
default: undefined,
},
drugType: {
type: String,
default: '1',
},
});
handleGetPrescription();
@@ -188,6 +192,7 @@ function handleGetPrescription() {
if (props.therapyEnum !== undefined) {
params.therapyEnum = props.therapyEnum;
}
params.tcmFlag = props.drugType === '1' ? 0 : 1;
getMedicineSummary(params).then((res) => {
medicineSummaryFormList.value = res.data.records;
loading.value = false;

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div style="display: flex; justify-content: space-between">
<div style="width: 20%; height: 90vh; border-right: solid 2px #e4e7ed">
<div
@@ -56,25 +56,26 @@
<el-radio-group
v-model="drugType"
class="ml10"
@change="handleGetPrescription"
>
<el-radio-button
value="1"
/>
<el-radio-button
value="2"
/>
<el-radio-button value="1">
西药
</el-radio-button>
<el-radio-button value="2">
中药
</el-radio-button>
</el-radio-group>
<el-radio-group
v-model="isDetails"
class="ml20"
@change="handleRadioChange"
>
<el-radio-button
value="1"
/>
<el-radio-button
value="2"
/>
<el-radio-button value="1">
明细
</el-radio-button>
<el-radio-button value="2">
汇总
</el-radio-button>
</el-radio-group>
<span class="descriptions-item-label">截止时间</span>
<el-date-picker
@@ -132,11 +133,13 @@
:request-status="requestStatus"
:deadline="deadline"
:therapy-enum="therapyEnum"
:drug-type="drugType"
/>
<SummaryMedicineList
v-else
ref="summaryMedicineRefs"
:therapy-enum="therapyEnum"
:drug-type="drugType"
/>
<!-- <el-tabs v-model="activeName" class="demo-tabs centered-tabs" @tab-change="handleClick">
<el-tab-pane
@@ -158,7 +161,7 @@
</template>
<script setup>
import {getCurrentInstance, nextTick, ref} from 'vue';
import {getCurrentInstance, nextTick, provide, ref} from 'vue';
import {useRouter} from 'vue-router';
import PatientList from '../components/patientList.vue';
import PrescriptionList from './components/prescriptionList.vue';

View File

@@ -1,21 +1,22 @@
<template>
<div style="height: calc(100vh - 126px)">
<template>
<div style="height: calc(100vh - 126px); display: flex; flex-direction: column; overflow: hidden">
<div
style="
height: 51px;
border-bottom: 2px solid #e4e7ed;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-shrink: 0;
overflow: hidden;
"
>
<div>
<div style="display: flex; align-items: center; gap: 12px; flex-shrink: 1; min-width: 0;">
<el-radio-group
v-model="type"
class="ml20"
@change="handleRadioChange"
>
<el-radio :value="null">
<el-radio :value="THERAPY_TYPE_ALL">
全部
</el-radio>
<el-radio :value="1">
@@ -25,8 +26,17 @@
临时
</el-radio>
</el-radio-group>
<span style="flex-shrink: 0;">截止时间</span>
<el-date-picker
v-model="deadline"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
placeholder="选择截止时间"
value-format="YYYY-MM-DD HH:mm:ss"
:clearable="false"
style="width: 200px;"
/>
<el-button
class="ml20"
type="primary"
plain
@click="handleGetPrescription"
@@ -34,21 +44,20 @@
查询
</el-button>
</div>
<div>
<span class="descriptions-item-label">全选</span>
<div style="flex: 1; min-width: 0;"></div>
<div style="display: flex; align-items: center; gap: 12px; flex-shrink: 1; min-width: 0;">
<span class="descriptions-item-label" style="flex-shrink: 0;">全选</span>
<el-switch
v-model="chooseAll"
@change="handelSwitchChange"
/>
<el-button
class="ml20"
type="primary"
@click="handleCheck"
>
核对通过
</el-button>
<el-button
class="ml20 mr20"
type="danger"
:disabled="hasDispensedSelected"
@click="handleCancel"
@@ -59,7 +68,7 @@
</div>
<div
v-loading="loading"
style="padding: 10px; background-color: #eef9fd; height: 100%; overflow-y: auto"
style="padding: 10px; background-color: #eef9fd; flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden"
>
<el-collapse
v-if="prescriptionList.length > 0"
@@ -69,8 +78,6 @@
border-bottom: 0px;
border-top: 0px;
border-radius: 0px;
overflow-y: auto;
max-height: calc(100vh - 200px);
"
>
<el-collapse-item
@@ -81,12 +88,11 @@
border: 2px solid #e4e7ed;
border-radius: 8px;
margin-bottom: 10px;
overflow: hidden;
"
>
<template #title>
<div style="display: flex; justify-content: space-between; align-items: center">
<div>
<div style="display: flex; gap: 16px; align-items: center; width: 100%; min-width: 0; overflow: hidden">
<div style="flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding-right: 12px;">
<span>{{ item[0].bedName + '【' + item[0].busNo + '】' }}</span>
<span>
{{
@@ -103,17 +109,18 @@
<div
style="
display: flex;
justify-content: space-between;
gap: 20px;
align-items: center;
margin-right: 20px;
flex-shrink: 1;
min-width: 0;
overflow: hidden;
"
>
<div style="display: inline-block; margin-right: 10px">
<div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;">
<span class="descriptions-item-label">住院医生</span>
<span>{{ item[0].requesterId_dictText }}</span>
</div>
<div style="display: inline-block; margin-right: 10px">
<div style="white-space: nowrap; flex-shrink: 0;">
<div
class="descriptions-item-label"
style="height: 20px; line-height: 20px; text-align: center; margin: 0"
@@ -135,6 +142,7 @@
</div>
</div>
</template>
<div style="overflow-x: auto">
<vxe-table
:ref="'tableRef' + index"
:data="item"
@@ -168,6 +176,7 @@
<vxe-column
title="医嘱内容"
field="adviceName"
min-width="220"
>
<template #default="scope">
<span>
@@ -217,7 +226,88 @@
field="requestTime"
width="230"
/>
<vxe-column
title="开始时间"
field="startTime"
width="150"
align="center"
/>
<vxe-column
title="单次剂量"
field="singleDose"
width="100"
align="center"
/>
<vxe-column
title="总量"
field="totalAmount"
width="100"
align="center"
/>
<vxe-column
title="频次/用法"
field="frequencyUsage"
width="110"
align="center"
/>
<vxe-column
title="皮试"
field="skinTestStatus"
width="90"
align="center"
>
<template #default="scope">
<el-tag
v-if="scope.row.skinTestStatus"
:type="scope.row.skinTestHighlight ? 'danger' : 'info'"
:class="{ 'skin-test-warning': scope.row.skinTestHighlight }"
size="small"
>
{{ scope.row.skinTestStatus }}
</el-tag>
</template>
</vxe-column>
<vxe-column
title="注射药品"
field="isInjection"
width="90"
align="center"
>
<template #default="scope">
<el-tag
v-if="scope.row.isInjection"
type="warning"
size="small"
>
注射
</el-tag>
</template>
</vxe-column>
<vxe-column
title="开嘱医生"
field="orderingDoctor"
width="100"
align="center"
/>
<vxe-column
title="诊断"
field="diagnosis"
min-width="150"
/>
<vxe-column
title="停嘱时间"
field="stopTime"
width="150"
align="center"
/>
<vxe-column
title="停嘱医生"
field="stopperName"
width="100"
align="center"
/>
</vxe-table>
</div>
</el-collapse-item>
</el-collapse>
<el-empty
@@ -256,7 +346,8 @@ import {RequestStatus} from '@/utils/medicalConstants';
const activeNames = ref([]);
const prescriptionList = ref([]);
const deadline = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
const type = ref(null);
const THERAPY_TYPE_ALL = 0;
const type = ref(THERAPY_TYPE_ALL);
const backReasonVisible = ref(false);
const backReasonForm = ref({ reason: '' });
const backReasonFormRef = ref(null);
@@ -311,13 +402,14 @@ const getStatusDisplayText = (row) => {
const getStatusType = (status) => {
const map = {
1: 'info',
2: 'primary',
3: 'success',
4: 'warning',
5: 'danger',
6: 'danger',
7: 'info',
[RequestStatus.DRAFT]: 'info',
[RequestStatus.ACTIVE]: 'primary',
[RequestStatus.COMPLETED]: 'success',
[RequestStatus.ON_HOLD]: 'warning',
[RequestStatus.CANCELLED]: 'danger',
[RequestStatus.PENDING_STOP]: 'warning',
[RequestStatus.STOPPED]: 'danger',
[RequestStatus.ENDED]: 'info',
};
return map[status] || 'info';
};
@@ -355,7 +447,8 @@ function handleGetPrescription() {
getPrescriptionList({
encounterIds: encounterIds,
requestStatus: props.requestStatus,
...(type.value != null ? { therapyEnum: type.value } : {}),
...(type.value !== THERAPY_TYPE_ALL ? { therapyEnum: type.value } : {}),
deadline: deadline.value,
pageSize: 10000,
pageNo: 1,
}).then((res) => {
@@ -375,8 +468,6 @@ function handleGetPrescription() {
// 将分组结果转换为数组形式
prescriptionList.value = Object.values(groupedPrescriptions);
console.log(prescriptionList.value, '1111');
console.log('@@@@@=======>', JSON.stringify(prescriptionList.value));
loading.value = false;
getGroupMarkers();
});
@@ -394,7 +485,6 @@ function getGroupMarkers() {
prescription.groupIcon = null;
});
});
console.log('prescriptionList====>', JSON.stringify(prescriptionList.value));
// 创建一个映射来存储每个 groupId 对应的行索引
const groupMap = {};
@@ -432,7 +522,6 @@ function getGroupMarkers() {
});
}
});
console.log('prescriptionList====>', JSON.stringify(prescriptionList.value));
}
// 选择框改变时的处理
@@ -452,7 +541,6 @@ function handleCheck() {
handleGetPrescription();
}
});
console.log(list, 'list');
} else {
proxy.$message.warning('请先选择医嘱信息');
}

View File

@@ -1,6 +1,6 @@
<template>
<div style="display: flex; justify-content: space-between">
<div style="width: 20%; height: 90vh; border-right: solid 2px #e4e7ed">
<div style="display: flex; height: 90vh; overflow: hidden">
<div style="width: 20%; flex-shrink: 0; border-right: solid 2px #e4e7ed; overflow: hidden">
<div
style="
height: 40px;
@@ -37,7 +37,7 @@
-->
</el-tabs>
</div>
<div style="width: 100%">
<div style="flex: 1; min-width: 0; overflow: hidden">
<el-tabs
v-model="activeName"
class="centered-tabs"

View File

@@ -10,5 +10,5 @@ export function addReport(d){return request({url:'/reconstruction/report/add',me
export function submitReport(id){return request({url:'/reconstruction/report/submit/'+id,method:'put'})}
export function verifyReport(id,doctor){return request({url:'/reconstruction/report/verify/'+id,method:'put',params:{doctor}})}
export function getStats(){return request({url:'/reconstruction/stats',method:'get'})}
// 获取医生列表(有医生角色的用户)
export function getDoctorList(){return request({url:'/system/user/list',method:'get',params:{pageSize:200}})}
// Get all active users for doctor selection
export function getDoctorList(){return request({url:'/system/user/list',method:'get',params:{status:'0',pageSize:200}})}

View File

@@ -230,7 +230,9 @@ function init(){
// Generate volume
const volData=genVolume()
const volTex=new THREE.DataTexture(volData,SZ,SZ,SZ,THREE.RedFormat,THREE.FloatType)
const volTex=new THREE.Data3DTexture(volData,SZ,SZ,SZ)
volTex.format=THREE.RedFormat
volTex.type=THREE.FloatType
volTex.needsUpdate=true
volTex.minFilter=THREE.LinearFilter
volTex.magFilter=THREE.LinearFilter
@@ -321,9 +323,19 @@ watch(mode,(val)=>{
})
onMounted(()=>{
nextTick(()=>{
setTimeout(init,100)
// Use ResizeObserver to init only when container has dimensions
const el=containerRef.value
if(!el)return
const ro=new ResizeObserver(entries=>{
for(const e of entries){
if(e.contentRect.width>0&&e.contentRect.height>0&&!renderer){
init()
ro.disconnect()
break
}
}
})
ro.observe(el)
window.addEventListener('resize',onResize)
})
@@ -336,9 +348,9 @@ onUnmounted(()=>{
</script>
<style scoped>
.viewer-wrap{display:flex;flex-direction:column;height:100%;background:#0a0a1a;color:#fff}
.viewer-wrap{display:flex;flex-direction:column;min-height:600px;height:100%;background:#0a0a1a;color:#fff}
.vbar{display:flex;gap:8px;padding:8px 12px;background:#1a1a2e;border-bottom:1px solid #333;align-items:center;flex-wrap:wrap}
.vmain{flex:1;position:relative;overflow:hidden;background:#0a0a1a}
.vmain{flex:1;position:relative;overflow:hidden;background:#0a0a1a;min-height:500px}
.gl-canvas{width:100%;height:100%;display:block}
.ov-tl{position:absolute;top:8px;left:8px;padding:6px 10px;background:rgba(0,0,0,.7);border-radius:4px;font-size:11px;font-family:'Courier New',monospace;color:#0f0;line-height:1.5;pointer-events:none;z-index:10}
.ov-bl{position:absolute;bottom:8px;left:8px;padding:6px 10px;background:rgba(0,0,0,.7);border-radius:4px;font-size:11px;font-family:'Courier New',monospace;color:#0f0;pointer-events:none;z-index:10}

View File

@@ -1,8 +0,0 @@
{
"hash": "5905b5e1",
"configHash": "4d078017",
"lockfileHash": "9e11ee45",
"browserHash": "09dcaa6f",
"optimized": {},
"chunks": {}
}

View File

@@ -1,3 +0,0 @@
{
"type": "module"
}