2 Commits

Author SHA1 Message Date
8a7d2abb4a feat(medicalOrderSet): 优化医嘱组套功能实现
- 实现医嘱基础列表的分页功能,添加loading状态和缓存机制
- 添加防抖处理和组织机构ID参数支持,优化性能表现
- 实现医嘱组套的完整编辑功能,包括增删改查操作界面
- 添加医嘱组套预览、应用和管理功能模块
- 实现西医组套筛选功能,确保tcmFlag参数正确传递
- 优化医嘱组套数据结构,完善明细项信息处理逻辑
- 添加表单验证和错误处理,提升用户体验和系统稳定性
- 重构代码结构,采用响应式设计提高可维护性
2026-03-17 09:57:21 +08:00
03939fb232 feat(basicmanage): 新增医嘱组套对话框组件和相关API
- 实现 MedicalOrderSetDialog.vue 组件,支持医嘱组套的新增、编辑功能
- 添加中药医嘱基础列表组件 tcmMedicineList.vue,支持键盘导航选择
- 创建医嘱组套相关API接口文件,包含个人、科室、全院组套的增删改查功能
- 实现医嘱组套的组合拆组功能,支持批量操作
- 集成分页、搜索、缓存等优化功能提升用户体验
- 添加表单验证和数据校验机制确保数据完整性
2026-03-17 09:35:07 +08:00
34 changed files with 4169 additions and 543 deletions

View File

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.core.common.core.redis.RedisCache;
import com.core.common.enums.TenantOptionDict;
import com.core.common.exception.ServiceException;
import com.core.common.utils.AssignSeqUtil;
@@ -99,6 +100,14 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
@Resource
DoctorStationSendApplyUtil doctorStationSendApplyUtil;
@Resource
RedisCache redisCache;
// 缓存 key 前缀
private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:";
// 缓存过期时间(小时)
private static final long CACHE_EXPIRE_HOURS = 24;
/**
* 查询医嘱信息
@@ -132,14 +141,40 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
String safePageNo = pageNo != null ? pageNo.toString() : "";
String safePageSize = pageSize != null ? pageSize.toString() : "";
log.info("从数据库查询医嘱基础信息");
// 设置默认科室:仅当前端/调用方未传 organizationId 时才回退到登录人科室
// 否则会导致门诊划价等场景(按患者挂号科室查询)返回空
if (organizationId == null) {
organizationId = SecurityUtils.getLoginUser().getOrgId();
}
// 只有在没有搜索关键字时才尝试使用缓存
boolean useCache = (searchKey == null || searchKey.trim().isEmpty())
&& (adviceDefinitionIdParamList == null || adviceDefinitionIdParamList.isEmpty());
String cacheKey = null;
if (useCache) {
// 生成缓存 key无搜索关键字时按科室缓存
cacheKey = ADVICE_BASE_INFO_CACHE_PREFIX + organizationId + ":" + safeAdviceTypesStr + ":" + safePageNo + ":" + safePageSize;
// 先清除可能存在的无效缓存JSONObject类型
if (redisCache.hasKey(cacheKey)) {
Object cachedObj = redisCache.getCacheObject(cacheKey);
if (cachedObj instanceof com.alibaba.fastjson2.JSONObject) {
redisCache.deleteObject(cacheKey);
log.info("清除无效缓存, key: {}", cacheKey);
} else if (cachedObj instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
log.info("从缓存获取医嘱基础信息, key: {}, records: {}", cacheKey, ((IPage<?>)cachedObj).getRecords().size());
return (IPage<AdviceBaseDto>) cachedObj;
}
}
log.info("缓存未命中,准备查询数据库, key: {}", cacheKey);
} else {
log.info("不使用缓存条件: searchKey={}, adviceDefinitionIdParamList={}", searchKey, adviceDefinitionIdParamList);
}
log.info("从数据库查询医嘱基础信息");
// 医嘱定价来源
String orderPricingSource = TenantOptionUtil.getOptionContent(TenantOptionDict.ORDER_PRICING_SOURCE);
if (StringUtils.isEmpty(orderPricingSource) && StringUtils.isEmpty(orderPricing)) {
@@ -160,7 +195,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
new Page<>(pageNo, pageSize), PublicationStatus.ACTIVE.getValue(), organizationId,
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, pricingFlag, adviceDefinitionIdParamList,
adviceTypes,
adviceTypes, searchKey,
queryWrapper);
List<AdviceBaseDto> adviceBaseDtoList = adviceBaseInfo.getRecords();
@@ -390,6 +425,15 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
}
}
// 缓存结果(只有无搜索关键字时才缓存)
if (useCache && cacheKey != null && adviceBaseInfo != null) {
// 确保是 IPage 类型再缓存,避免缓存无效数据
if (adviceBaseInfo instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
redisCache.setCacheObject(cacheKey, adviceBaseInfo, (int) CACHE_EXPIRE_HOURS, java.util.concurrent.TimeUnit.HOURS);
log.info("缓存医嘱基础信息, key: {}, 过期时间: {} 小时", cacheKey, CACHE_EXPIRE_HOURS);
}
}
return adviceBaseInfo;
}

View File

@@ -4,6 +4,7 @@
package com.openhis.web.doctorstation.controller;
import com.core.common.core.domain.R;
import com.core.common.utils.SecurityUtils;
import com.openhis.common.enums.AdviceOpType;
import com.openhis.common.enums.Whether;
import com.openhis.web.doctorstation.appservice.IDoctorStationAdviceAppService;
@@ -109,9 +110,13 @@ public class DoctorStationChineseMedicalController {
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "locationId", required = false) Long locationId,
@RequestParam(value = "adviceDefinitionIdParamList", required = false) List<Long> adviceDefinitionIdParamList,
@RequestParam(value = "organizationId") Long organizationId,
@RequestParam(value = "organizationId", required = false) Long organizationId,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
// 从当前登录用户获取科室 ID 作为默认值
if (organizationId == null) {
organizationId = SecurityUtils.getLoginUser().getOrgId();
}
return R.ok(iDoctorStationChineseMedicalAppService.getTcmAdviceBaseInfo(adviceBaseDto, searchKey, locationId,
adviceDefinitionIdParamList, organizationId, pageNo, pageSize, Whether.NO.getValue()));
}

View File

@@ -37,6 +37,7 @@ public interface DoctorStationAdviceAppMapper {
@Param("activityTableName") String activityTableName, @Param("pricingFlag") Integer pricingFlag,
@Param("adviceDefinitionIdParamList") List<Long> adviceDefinitionIdParamList,
@Param("adviceTypes") List<Integer> adviceTypes,
@Param("searchKey") String searchKey,
@Param(Constants.WRAPPER) QueryWrapper<AdviceBaseDto> queryWrapper);
/**

View File

@@ -30,9 +30,10 @@ public interface IOrdersGroupPackageAppService {
*
* @param packageTypeEnum 类型枚举
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 组合套餐
*/
List<OrdersGroupPackageQueryDto> getGroupPackage(Integer packageTypeEnum, String searchKey);
List<OrdersGroupPackageQueryDto> getGroupPackage(Integer packageTypeEnum, String searchKey, Integer tcmFlag);
/**
* 查询组合套餐明细
@@ -54,8 +55,9 @@ public interface IOrdersGroupPackageAppService {
* 查询组合套餐,供开立医嘱使用
*
* @param organizationId 患者挂号对应的科室id
* @param tcmFlag 中医标识0-西医 1-中医
* @return 组合套餐
*/
OrdersGroupPackageUseDto getGroupPackageForOrder(Long organizationId);
OrdersGroupPackageUseDto getGroupPackageForOrder(Long organizationId, Integer tcmFlag);
}

View File

@@ -1,8 +1,16 @@
package com.openhis.web.personalization.appservice.impl;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R;
import com.core.common.exception.ServiceException;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils;
import com.openhis.common.constant.PromptMsgConstant;
@@ -18,13 +26,8 @@ import com.openhis.web.doctorstation.dto.AdviceBaseDto;
import com.openhis.web.personalization.appservice.IOrdersGroupPackageAppService;
import com.openhis.web.personalization.dto.*;
import com.openhis.web.personalization.mapper.OrdersGroupPackageAppMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.core.framework.datasource.DynamicDataSourceContextHolder.log;
/**
* 组合套餐 实现类
@@ -61,10 +64,12 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
if (BindingType.PERSONAL.getValue().equals(packageTypeEnum)
&& ordersGroupPackageSaveDto.getPractitionerId() == null) {
throw new ServiceException("个人组套需选择人员");
// throw new ServiceException("个人组套需选择人员");
ordersGroupPackageSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
} else if (BindingType.ORGANIZATION.getValue().equals(packageTypeEnum)
&& ordersGroupPackageSaveDto.getOrganizationId() == null) {
throw new ServiceException("科室组套需选择科室");
// throw new ServiceException("科室组套需选择科室");
ordersGroupPackageSaveDto.setOrganizationId(SecurityUtils.getLoginUser().getOrgId());
}
// 保存主表
OrdersGroupPackage ordersGroupPackage = new OrdersGroupPackage();
@@ -73,6 +78,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
ordersGroupPackage.setPackageTypeEnum(packageTypeEnum);// 组套包类型
ordersGroupPackage.setOrganizationId(ordersGroupPackageSaveDto.getOrganizationId()); // 科室id
ordersGroupPackage.setPractitionerId(ordersGroupPackageSaveDto.getPractitionerId()); // 参与者id
ordersGroupPackage.setTcmFlag(ordersGroupPackageSaveDto.getTcmFlag()); // 中医标识
ordersGroupPackageService.saveOrUpdate(ordersGroupPackage);
// 编辑场景时,先删除原有的明细再新增
@@ -96,6 +102,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
ordersGroupPackageDetail.setMethodCode(ordersGroupPackageDetailSaveDto.getMethodCode()); // 给药途径
ordersGroupPackageDetail.setDoseQuantity(ordersGroupPackageDetailSaveDto.getDoseQuantity()); // 小单位单次剂量
ordersGroupPackageDetail.setGroupId(ordersGroupPackageDetailSaveDto.getGroupId()); // 组号
ordersGroupPackageDetail.setTherapyEnum(ordersGroupPackageDetailSaveDto.getTherapyEnum()); // 治疗类型
ordersGroupPackageDetailService.save(ordersGroupPackageDetail);
}
@@ -107,12 +114,14 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
*
* @param packageTypeEnum 类型枚举
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识
* @return 组合套餐
*/
@Override
public List<OrdersGroupPackageQueryDto> getGroupPackage(Integer packageTypeEnum, String searchKey) {
public List<OrdersGroupPackageQueryDto> getGroupPackage(Integer packageTypeEnum, String searchKey,
Integer tcmFlag) {
List<OrdersGroupPackageQueryDto> groupPackage =
ordersGroupPackageAppMapper.getGroupPackage(packageTypeEnum, null, null, searchKey);
ordersGroupPackageAppMapper.getGroupPackage(packageTypeEnum, null, null, searchKey, tcmFlag);
for (OrdersGroupPackageQueryDto ordersGroupPackageQueryDto : groupPackage) {
ordersGroupPackageQueryDto.setPackageTypeEnum_enumText(
EnumUtils.getInfoByValue(BindingType.class, ordersGroupPackageQueryDto.getPackageTypeEnum()));
@@ -151,19 +160,23 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
* 查询组合套餐,供开立医嘱使用
*
* @param organizationId 患者挂号对应的科室id
* @param tcmFlag 中医标识
* @return 组合套餐
*/
@Override
public OrdersGroupPackageUseDto getGroupPackageForOrder(Long organizationId) {
public OrdersGroupPackageUseDto getGroupPackageForOrder(Long organizationId, Integer tcmFlag) {
OrdersGroupPackageUseDto ordersGroupPackageUseDto = new OrdersGroupPackageUseDto();
// 当前参参与者id
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
// 当前登录账号的科室id
Long orgId = SecurityUtils.getLoginUser().getOrgId();
log.info("[getGroupPackageForOrder] 当前登录用户 practitionerId: {}, orgId: {}", practitionerId, orgId);
// 个人组套
List<OrdersGroupPackageQueryDto> personalGroupPackage =
ordersGroupPackageAppMapper.getGroupPackage(BindingType.PERSONAL.getValue(), null, practitionerId, null);
List<OrdersGroupPackageQueryDto> personalGroupPackage = ordersGroupPackageAppMapper
.getGroupPackage(BindingType.PERSONAL.getValue(), null, practitionerId, null, tcmFlag);
log.info("[getGroupPackageForOrder] 个人组套查询结果数: {}", personalGroupPackage.size());
if (!personalGroupPackage.isEmpty()) {
List<OrdersGroupPackageDto> personalList = personalGroupPackage.stream().map(queryDto -> {
OrdersGroupPackageDto dto = new OrdersGroupPackageDto();
@@ -208,10 +221,13 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
});
}
ordersGroupPackageUseDto.setPersonalList(personalList);
log.info("[getGroupPackageForOrder] 个人组套已设置,数量: {}", personalList.size());
} else {
log.info("[getGroupPackageForOrder] 个人组套查询结果为空");
}
// 科室组套
List<OrdersGroupPackageQueryDto> organizationGroupPackage =
ordersGroupPackageAppMapper.getGroupPackage(BindingType.ORGANIZATION.getValue(), orgId, null, null);
List<OrdersGroupPackageQueryDto> organizationGroupPackage = ordersGroupPackageAppMapper
.getGroupPackage(BindingType.ORGANIZATION.getValue(), orgId, null, null, tcmFlag);
if (!organizationGroupPackage.isEmpty()) {
List<OrdersGroupPackageDto> organizationList = organizationGroupPackage.stream().map(queryDto -> {
OrdersGroupPackageDto dto = new OrdersGroupPackageDto();
@@ -260,7 +276,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
// 全院组套
List<OrdersGroupPackageQueryDto> hospitalGroupPackage =
ordersGroupPackageAppMapper.getGroupPackage(BindingType.HOSPITAL.getValue(), null, null, null);
ordersGroupPackageAppMapper.getGroupPackage(BindingType.HOSPITAL.getValue(), null, null, null, tcmFlag);
if (!hospitalGroupPackage.isEmpty()) {
List<OrdersGroupPackageDto> hospitalList = hospitalGroupPackage.stream().map(queryDto -> {
OrdersGroupPackageDto dto = new OrdersGroupPackageDto();

View File

@@ -65,33 +65,39 @@ public class OrdersGroupPackageController {
* 查询个人组套
*
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 个人组套
*/
@GetMapping(value = "/get-personal")
public R<?> getPersonal(@RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.PERSONAL.getValue(), searchKey));
public R<?> getPersonal(@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "tcmFlag", defaultValue = "0") Integer tcmFlag) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.PERSONAL.getValue(), searchKey, tcmFlag));
}
/**
* 查询科室组套
*
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 科室组套
*/
@GetMapping(value = "/get-organization")
public R<?> getOrganization(@RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.ORGANIZATION.getValue(), searchKey));
public R<?> getOrganization(@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "tcmFlag", defaultValue = "0") Integer tcmFlag) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.ORGANIZATION.getValue(), searchKey, tcmFlag));
}
/**
* 查询全院组套
*
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 全院组套
*/
@GetMapping(value = "/get-hospital")
public R<?> getHospital(@RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.HOSPITAL.getValue(), searchKey));
public R<?> getHospital(@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "tcmFlag", defaultValue = "0") Integer tcmFlag) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.HOSPITAL.getValue(), searchKey, tcmFlag));
}
/**
@@ -120,12 +126,13 @@ public class OrdersGroupPackageController {
* 查询组合套餐,供开立医嘱使用
*
* @param organizationId 患者挂号对应的科室id
* @param tcmFlag 中医标识0-西医 1-中医,不传则不过滤
* @return 组合套餐
*/
@GetMapping(value = "/group-package-for-order")
public R<?>
getGroupPackageForOrder(@RequestParam(value = "organizationId", defaultValue = "0") Long organizationId) {
return R.ok(ordersGroupPackageAppService.getGroupPackageForOrder(organizationId));
public R<?> getGroupPackageForOrder(@RequestParam(value = "organizationId", required = false) Long organizationId,
@RequestParam(value = "tcmFlag", required = false) Integer tcmFlag) {
return R.ok(ordersGroupPackageAppService.getGroupPackageForOrder(organizationId, tcmFlag));
}
}

View File

@@ -63,4 +63,9 @@ public class OrdersGroupPackageDetailSaveDto {
*/
private Long groupId;
/**
* 治疗类型1-长期 2-临时
*/
private Integer therapyEnum;
}

View File

@@ -48,6 +48,11 @@ public class OrdersGroupPackageSaveDto {
@JsonSerialize(using = ToStringSerializer.class)
private Long practitionerId;
/**
* 中医标识0-西医 1-中医
*/
private Integer tcmFlag;
/**
* 明细集合
*/

View File

@@ -26,11 +26,12 @@ public interface OrdersGroupPackageAppMapper {
* @param organizationId 科室id
* @param practitionerId 参与者id
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 组合套餐
*/
List<OrdersGroupPackageQueryDto> getGroupPackage(@Param("packageTypeEnum") Integer packageTypeEnum,
@Param("organizationId") Long organizationId, @Param("practitionerId") Long practitionerId,
@Param("searchKey") String searchKey);
@Param("searchKey") String searchKey, @Param("tcmFlag") Integer tcmFlag);
/**
* 查询组合套餐明细

View File

@@ -230,7 +230,7 @@
WHERE T1.purpose_location_id = #{locationId}
AND T1.status_enum = #{statusEnum}
AND T1.type_enum = #{typeEnum}
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -292,7 +292,7 @@
WHERE T1.purpose_location_id = #{locationId}
AND T1.status_enum = #{statusEnum}
AND T1.type_enum = #{typeEnum}
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -442,7 +442,7 @@
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') = #{startTime}
</if>
<if test="endTime != null">
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
</if>
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
@@ -515,7 +515,7 @@
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') = #{startTime}
</if>
<if test="endTime != null">
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
</if>
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'

View File

@@ -419,7 +419,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -499,7 +499,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -229,7 +229,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -280,7 +280,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -384,7 +384,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -478,7 +478,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -268,7 +268,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -343,7 +343,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -266,7 +266,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -341,7 +341,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -49,7 +49,7 @@
<if test="adviceTypes != null and !adviceTypes.isEmpty() and (adviceTypes.contains(1) or adviceTypes.contains(2) or adviceTypes.contains(3))">
<!-- 如果有有效的adviceTypes则执行对应的查询 -->
<if test="adviceTypes.contains(1)">
(SELECT
(SELECT
DISTINCT ON (T1.ID)
T1.tenant_id,
1 AS advice_type,
@@ -81,7 +81,7 @@
CAST(T2.dose AS TEXT) AS dose,
T2.dose_unit_code AS dose_unit_code,
T3.NAME AS supplier,
T3.id AS supplier_id,
t3.id AS supplier_id,
T1.manufacturer_text AS manufacturer,
T5.id AS charge_item_definition_id,
T5.instance_table AS advice_table_name,
@@ -91,24 +91,21 @@
T1.dosage_instruction AS dosage_instruction,
T1.chrgitm_lv as chrgitm_lv
FROM med_medication_definition AS t1
INNER JOIN med_medication AS T2 ON T2.medication_def_id = T1.ID
INNER JOIN med_medication AS T2 ON T2.medication_def_id = t1.ID
AND T2.delete_flag = '0' AND T2.status_enum = #{statusEnum}
LEFT JOIN adm_supplier AS T3
ON T3.ID = T1.supply_id
AND T3.delete_flag = '0'
LEFT JOIN adm_charge_item_definition AS T5 ON T5.instance_id = T1.ID
AND T5.delete_flag = '0' AND T5.status_enum = #{statusEnum}
LEFT JOIN adm_organization_location AS T6
ON T6.distribution_category_code = T1.category_code
AND T6.delete_flag = '0' AND T6.item_code = '1' AND T6.organization_id = #{organizationId} AND
(CURRENT_TIME :: time (6) BETWEEN T6.start_time AND T6.end_time)
WHERE T1.delete_flag = '0'
LEFT JOIN adm_supplier AS T3 ON T3.ID = t1.supply_id AND T3.delete_flag = '0'
LEFT JOIN adm_charge_item_definition AS T5 ON T5.instance_id = t1.ID AND T5.delete_flag = '0' AND T5.status_enum = #{statusEnum}
LEFT JOIN adm_organization_location AS T6 ON T6.distribution_category_code = t1.category_code AND T6.delete_flag = '0' AND T6.item_code = '1' AND T6.organization_id = #{organizationId} AND (CURRENT_TIME :: time (6) BETWEEN T6.start_time AND T6.end_time)
WHERE t1.delete_flag = '0'
AND T2.status_enum = #{statusEnum}
<if test="pricingFlag ==1">
AND 1 = 2
</if>
<if test="searchKey != null and searchKey != ''">
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
</if>
<if test="adviceDefinitionIdParamList != null and !adviceDefinitionIdParamList.isEmpty()">
AND T1.id IN
AND t1.id IN
<foreach collection="adviceDefinitionIdParamList" item="itemId" open="(" separator="," close=")">
#{itemId}
</foreach>
@@ -151,7 +148,7 @@
'' AS dose,
'' AS dose_unit_code,
T2.NAME AS supplier,
T2.id AS supplier_id,
t2.id AS supplier_id,
T1.manufacturer_text AS manufacturer,
T4.id AS charge_item_definition_id,
T4.instance_table AS advice_table_name,
@@ -161,17 +158,16 @@
'' AS dosage_instruction,
T1.chrgitm_lv as chrgitm_lv
FROM adm_device_definition AS T1
LEFT JOIN adm_supplier AS T2
ON T2.ID = T1.supply_id
AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item_definition AS T4 ON T4.instance_id = T1.ID
AND T4.delete_flag = '0' AND T4.status_enum = #{statusEnum}
LEFT JOIN adm_organization_location AS T5 ON T5.distribution_category_code = T1.category_code
AND T5.delete_flag = '0' AND T5.item_code = '2' AND T5.organization_id = #{organizationId} AND
(CURRENT_TIME :: time (6) BETWEEN T5.start_time AND T5.end_time)
WHERE T1.delete_flag = '0'
LEFT JOIN adm_supplier AS T2 ON T2.ID = t1.supply_id AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item_definition AS T4 ON T4.instance_id = t1.ID AND T4.delete_flag = '0' AND T4.status_enum = #{statusEnum}
LEFT JOIN adm_organization_location AS T5 ON T5.distribution_category_code = t1.category_code AND T5.delete_flag = '0' AND T5.item_code = '2' AND T5.organization_id = #{organizationId} AND (CURRENT_TIME :: time (6) BETWEEN T5.start_time AND T5.end_time)
WHERE t1.delete_flag = '0'
AND t1.status_enum = #{statusEnum}
<if test="searchKey != null and searchKey != ''">
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
</if>
<if test="adviceDefinitionIdParamList != null and !adviceDefinitionIdParamList.isEmpty()">
AND T1.id IN
AND t1.id IN
<foreach collection="adviceDefinitionIdParamList" item="itemId" open="(" separator="," close=")">
#{itemId}
</foreach>
@@ -236,19 +232,18 @@
'' AS dosage_instruction,
T1.chrgitm_lv as chrgitm_lv
FROM wor_activity_definition AS T1
LEFT JOIN adm_charge_item_definition AS T2
ON T2.instance_id = T1.ID
AND T2.delete_flag = '0' AND T2.status_enum = #{statusEnum}
AND T2.instance_table = #{activityTableName}
LEFT JOIN adm_organization_location AS T3 ON T3.activity_definition_id = T1.ID
AND T3.delete_flag = '0' AND (CURRENT_TIME :: time (6) BETWEEN T3.start_time AND T3.end_time)
LEFT JOIN adm_charge_item_definition AS T2 ON T2.instance_id = t1.ID AND T2.delete_flag = '0' AND T2.status_enum = #{statusEnum} AND T2.instance_table = #{activityTableName}
LEFT JOIN adm_organization_location AS T3 ON T3.activity_definition_id = t1.ID AND T3.delete_flag = '0' AND (CURRENT_TIME :: time (6) BETWEEN T3.start_time AND T3.end_time)
<if test="organizationId != null">
AND T3.organization_id = #{organizationId}
</if>
WHERE T1.delete_flag = '0'
AND (T1.pricing_flag = 1 OR T1.pricing_flag IS NULL)
WHERE t1.delete_flag = '0'
AND (t1.pricing_flag = 1 OR t1.pricing_flag IS NULL)
<if test="searchKey != null and searchKey != ''">
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
</if>
<if test="adviceDefinitionIdParamList != null and !adviceDefinitionIdParamList.isEmpty()">
AND T1.id IN
AND t1.id IN
<foreach collection="adviceDefinitionIdParamList" item="itemId" open="(" separator="," close=")">
#{itemId}
</foreach>

View File

@@ -28,6 +28,10 @@
<if test="practitionerId != null">
AND togp.practitioner_id = #{practitionerId}
</if>
<if test="tcmFlag != null">
AND togp.tcm_flag = #{tcmFlag}
</if>
ORDER BY togp.create_time DESC
</select>
<select id="getGroupPackageDetail"

View File

@@ -54,12 +54,12 @@
LEFT JOIN med_medication_request T17
on T1.id = T17.refund_medicine_id
AND T17.delete_flag = '0'
WHERE T1.status_enum = #{completed} --'3'
AND T2.status_enum = #{eventStatus} --'4'
AND T1.delete_flag = '0'
AND TO_CHAR(T2.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
AND T1.refund_medicine_id IS NULL
AND T17.refund_medicine_id IS NULL
WHERE T1.status_enum = #{completed} --'3'
AND T2.status_enum = #{eventStatus} --'4'
AND T1.delete_flag = '0'
AND T2.dispense_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.refund_medicine_id IS NULL
AND T17.refund_medicine_id IS NULL
UNION ALL
@@ -86,12 +86,12 @@
LEFT JOIN wor_device_request T17
on T4.id = T17.refund_device_id
AND T17.delete_flag = '0'
WHERE T4.status_enum = #{completed} --'3'
AND T5.status_enum = #{eventStatus} --'4'
AND T4.delete_flag = '0'
AND TO_CHAR(T5.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
AND T4.refund_device_id IS NULL
AND T17.refund_device_id IS NULL
WHERE T4.status_enum = #{completed} --'3'
AND T5.status_enum = #{eventStatus} --'4'
AND T4.delete_flag = '0'
AND T5.dispense_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
AND T4.refund_device_id IS NULL
AND T17.refund_device_id IS NULL
) AS q1
UNION ALL
@@ -142,11 +142,11 @@
ON T10.id = T11.medication_def_id AND T11.delete_flag = '0'
LEFT JOIN wor_supply_delivery T12
ON T12.request_id = T9.id AND T12.delete_flag = '0'
WHERE (T9.type_enum = #{returnIssue} OR T9.type_enum = #{issueInventory})
AND T9.item_table = #{medDefTableName} --'med_medication_definition'
AND T12.status_enum = #{eventStatus}--'4'
AND T9.delete_flag = '0'
AND TO_CHAR(T12.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
WHERE (T9.type_enum = #{returnIssue} OR T9.type_enum = #{issueInventory})
AND T9.item_table = #{medDefTableName} --'med_medication_definition'
AND T12.status_enum = #{eventStatus}--'4'
AND T9.delete_flag = '0'
AND T12.occurrence_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
UNION ALL
@@ -169,11 +169,11 @@
ON T13.item_id = T14.id AND T14.delete_flag = '0'
LEFT JOIN wor_supply_delivery T15
ON T15.request_id = T13.id AND T15.delete_flag = '0'
WHERE (T13.type_enum = #{returnIssue} OR T13.type_enum = #{issueInventory})
AND T13.item_table = #{devDefTableName} --'adm_device_definition'
AND T15.status_enum = #{eventStatus}--'4'
AND T13.delete_flag = '0'
AND TO_CHAR(T15.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
WHERE (T13.type_enum = #{returnIssue} OR T13.type_enum = #{issueInventory})
AND T13.item_table = #{devDefTableName} --'adm_device_definition'
AND T15.status_enum = #{eventStatus}--'4'
AND T13.delete_flag = '0'
AND T15.occurrence_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
) AS q2
GROUP BY
name,
@@ -215,12 +215,11 @@
T16.name AS org_name,
SUM(T8.total_price) AS total_money
FROM med_medication_request T1
LEFT JOIN med_medication_dispense T2
ON T2.med_req_id = T1.id
AND T2.delete_flag = '0'
/* 时间区间 */
AND
TO_CHAR(T2.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
LEFT JOIN med_medication_dispense T2
ON T2.med_req_id = T1.id
AND T2.delete_flag = '0'
/* 时间区间 */
AND T2.dispense_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
LEFT JOIN adm_charge_item T8
ON T8.service_table = #{medReqTableName} --'med_medication_request'
AND T8.service_id = T1.id
@@ -248,11 +247,10 @@
SUM(T9.total_price) AS total_money
FROM wor_device_request T4
LEFT JOIN wor_device_dispense T5
ON T5.device_req_id = T4.id
AND T5.delete_flag = '0'
/* 时间区间 */
AND
TO_CHAR(T5.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
ON T5.device_req_id = T4.id
AND T5.delete_flag = '0'
/* 时间区间 */
AND T5.dispense_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
LEFT JOIN adm_charge_item T9
ON T9.service_table = #{devReqTableName} --'wor_device_request'
AND T9.service_id = T4.id
@@ -285,12 +283,11 @@
WHEN T9.type_enum = '9' THEN T9.total_price * -1
ELSE T9.total_price END) AS total_money
FROM wor_supply_request T9
LEFT JOIN wor_supply_delivery T12
ON T12.request_id = T9.id
AND T12.delete_flag = '0'
/* 时间区间 */
AND
TO_CHAR(T12.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
LEFT JOIN wor_supply_delivery T12
ON T12.request_id = T9.id
AND T12.delete_flag = '0'
/* 时间区间 */
AND T12.occurrence_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
LEFT JOIN adm_organization T16
ON T9.source_location_id = T16.id
AND T16.delete_flag = '0'

View File

@@ -24,7 +24,7 @@
AND T1.type_enum = #{typeEnum} -- 1:采购入库
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -40,7 +40,7 @@
AND T1.type_enum IN (#{productTransfer} ,#{productBatchTransfer} )
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -56,7 +56,7 @@
AND T1.type_enum IN (#{productTransfer} ,#{productBatchTransfer} )
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -72,7 +72,7 @@
AND T1.type_enum IN (#{productStocktaking} ,#{productBatchStocktaking} )
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -88,7 +88,7 @@
AND T1.type_enum = #{lossReportForm}
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -114,7 +114,7 @@
AND T1.status_enum = #{completed}--请求状态:已完成
AND T2.status_enum = #{agree}--请求状态:已发药
AND T1.delete_flag = '0'
AND TO_CHAR(T2.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.dispense_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
GROUP BY
T3.name,
T7.total_volume,
@@ -139,7 +139,7 @@
AND T4.status_enum = #{completed}--请求状态:已完成
AND T5.status_enum = #{agree}--请求状态:已发药
AND T4.delete_flag = '0'
AND TO_CHAR(T5.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T5.dispense_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
GROUP BY
T6.name,
T6.size,

View File

@@ -75,7 +75,7 @@
ON T1.encounter_id = T3.id
AND T3.delete_flag = '0'
WHERE T1.status_enum = #{billed}
AND TO_CHAR(T1.update_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.update_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
<select id="getTreatmentStat"
@@ -93,7 +93,7 @@
-- AS discharged_count--出院人次数
FROM adm_encounter T1
WHERE T1.class_enum = #{amb}
AND TO_CHAR(T1.reception_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.reception_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>

View File

@@ -38,4 +38,9 @@ public class OrdersGroupPackage extends HisBaseEntity {
*/
private Long practitionerId;
/**
* 中医标识0-西医 1-中医
*/
private Integer tcmFlag;
}

View File

@@ -66,4 +66,10 @@ public class OrdersGroupPackageDetail extends HisBaseEntity {
* 组号
*/
private Long groupId;
/**
* 治疗类型1-长期 2-临时
*/
private Integer therapyEnum;
}

View File

@@ -0,0 +1,885 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="1300px" @close="handleDialogClose">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<div class="dialog-top-row">
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" style="width: 220px" />
</el-form-item>
<el-form-item label="使用范围" v-if="showRangeSelector && !isEdit">
<el-select
v-model="rangeSelectValue"
placeholder="个人/科室/全院"
style="width: 220px"
@change="handleRangeChange"
>
<el-option label="个人" value="personal" />
<el-option label="科室" value="department" />
<el-option label="全院" value="hospital" />
</el-select>
</el-form-item>
<el-form-item
label="参与者"
prop="practitionerId"
v-if="currentTab === 'personal' && !isDoctorStation && !showRangeSelector"
>
<el-select
v-model="formData.practitionerId"
placeholder="请选择参与者"
clearable
style="width: 220px"
>
<el-option
v-for="item in participantListOptions"
:key="item.practitionerId"
:label="item.practitionerName"
:value="item.practitionerId"
/>
</el-select>
</el-form-item>
<el-form-item label="科室" prop="organizationId" v-if="currentTab === 'department'">
<el-tree-select
clearable
v-model="formData.organizationId"
:data="organization"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
check-strictly
default-expand-all
placeholder="请选择科室"
style="width: 220px"
:render-after-expand="false"
@change="handleOrgChange"
/>
</el-form-item>
</div>
</el-form>
<div style="margin-bottom: 10px">
<el-button type="primary" @click="handleAddRow">新增</el-button>
<el-button @click="handleCombineGroup">组合</el-button>
<el-button @click="handleSplitGroup">拆组</el-button>
</div>
<el-table
max-height="650"
ref="prescriptionRef"
:data="prescriptionList"
row-key="uniqueKey"
border
@cell-click="clickRow"
@selection-change="handleSelectionChange"
:expand-row-keys="expandOrder"
>
<el-table-column type="selection" width="55" :selectable="isRowSelectable" />
<el-table-column label="组" align="center" width="60">
<template #default="scope">
<span v-if="scope.row.groupId">{{ getGroupIcon(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="类型" align="center" width="120">
<template #default="scope">
<template v-if="!scope.row.groupPackageId">
<el-radio-group v-model="scope.row.therapyEnum" size="small">
<el-radio-button label="1">长期</el-radio-button>
<el-radio-button label="2">临时</el-radio-button>
</el-radio-group>
</template>
<span v-else>
{{
scope.row.therapyEnum == '1' ? '长期' : scope.row.therapyEnum == '2' ? '临时' : '-'
}}
</span>
</template>
</el-table-column>
<el-table-column label="医嘱" align="center" prop="productName" width="300">
<template #default="scope">
<template v-if="getRowDisabled(scope.row)">
<el-popover
:popper-style="{ padding: '0' }"
placement="bottom-start"
:visible="scope.row.showPopover"
:width="1200"
>
<adviceBaseList
ref="adviceTableRef"
:popoverVisible="scope.row.showPopover"
:adviceQueryParams="adviceQueryParams"
@selectAdviceBase="(row) => selectAdviceBase(scope.row.uniqueKey, row)"
/>
<template #reference>
<el-input
:ref="'adviceRef' + scope.$index"
style="width: 100%"
v-model="scope.row.adviceName"
placeholder="请选择项目"
@input="(value) => handleInput(value, scope.row, scope.$index)"
@click="handleFocus(scope.row, scope.$index)"
@keyup.enter.stop="handleFocus(scope.row, scope.$index)"
@keydown="
(e) => {
if (!scope.row.showPopover) return;
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
e.preventDefault();
adviceTableRef.handleKeyDown(e);
}
}
"
@blur="handleBlur(scope.row)"
/>
</template>
</el-popover>
</template>
<span v-else>{{ scope.row.adviceName }}</span>
</template>
</el-table-column>
<el-table-column label="单次剂量" align="center" width="250" prop="sortNumber">
<template #default="scope">
<template v-if="scope.row.adviceType == 1">
<!-- 新增/未保存行统一按数量 + 单位 = 剂量 + 单位展示 -->
<template v-if="!scope.row.groupPackageId">
<el-input
style="width: 70px; margin-right: 10px"
v-model="scope.row.doseQuantity"
@input="
(value) => {
scope.row.dose = value * scope.row.unitConversionRatio;
}
"
/>
<span>
{{
scope.row.minUnitCode_dictText ||
scope.row.unitCodeName ||
scope.row.unitCode_dictText ||
''
}}
</span>
<span>{{ ' = ' }}</span>
<el-input
style="width: 70px; margin-right: 10px"
v-model="scope.row.dose"
@input="
(value) => {
scope.row.doseQuantity = value / scope.row.unitConversionRatio;
}
"
/>
<span>
{{
scope.row.doseUnitCode_dictText ||
scope.row.unitCodeName ||
scope.row.unitCode_dictText ||
''
}}
</span>
</template>
<span v-else>{{ scope.row.dose }}</span>
</template>
<span v-else>{{ '-' }}</span>
</template>
</el-table-column>
<el-table-column label="给药途径" align="center" width="150" prop="sortNumber">
<template #default="scope">
<template v-if="scope.row.adviceType == 1">
<template v-if="!scope.row.groupPackageId">
<el-select v-model="scope.row.methodCode" placeholder="给药途径" clearable filterable>
<el-option
v-for="dict in method_code"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
<span v-else>{{ scope.row.methodCode }}</span>
</template>
<span v-else>{{ '-' }}</span>
</template>
</el-table-column>
<el-table-column label="用药频次" align="center" width="150" prop="sortNumber">
<template #default="scope">
<template v-if="scope.row.adviceType == 1">
<template v-if="!scope.row.groupPackageId">
<el-select
v-model="scope.row.rateCode"
placeholder="频次"
style="width: 120px"
filterable
>
<el-option
v-for="dict in getRateOptions(scope.row)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
<span v-else>{{ scope.row.rateCode_dictText }}</span>
</template>
<span v-else>{{ '-' }}</span>
</template>
</el-table-column>
<el-table-column label="用药天数" align="center" width="100" prop="sortNumber">
<template #default="scope">
<template v-if="scope.row.adviceType == 1">
<template v-if="!scope.row.groupPackageId">
<el-input
v-model="scope.row.dispensePerDuration"
@change="handleQuantityChange(scope.row)"
/>
</template>
<span v-else>{{ scope.row.dispensePerDuration }}</span>
</template>
<span v-else>{{ '-' }}</span>
</template>
</el-table-column>
<el-table-column label="总量/执行次数" align="center" width="150" prop="sortNumber">
<template #default="scope">
<template v-if="!scope.row.groupPackageId">
<el-input
v-model="scope.row.sortNumber"
type="number"
min="1"
@change="handleQuantityChange(scope.row)"
/>
</template>
<span v-else>{{ scope.row.sortNumber }}</span>
</template>
</el-table-column>
<el-table-column label="单位" align="center" width="120" prop="unitCode">
<template #default="scope">
<template v-if="!scope.row.groupPackageId">
<el-select
v-model="scope.row.selectUnitCode"
placeholder="请选择单位"
@change="handleUnitChange(scope.row)"
>
<el-option
v-if="scope.row.minUnitCode"
:key="scope.row.minUnitCode"
:label="scope.row.minUnitCode_dictText || scope.row.minUnitCode"
:value="scope.row.minUnitCode"
/>
<el-option
v-if="scope.row.unitCode"
:key="scope.row.unitCode"
:label="scope.row.unitCode_dictText || scope.row.unitCode"
:value="scope.row.unitCode"
/>
</el-select>
</template>
<span>{{ scope.row.unitCodeName }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="80">
<template #default="scope">
<el-button
type="danger"
icon="Delete"
circle
size="small"
@click="handleDeleteRow(scope.$index, scope.row)"
/>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitForm" :loading="submitLoading"> </el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue';
import adviceBaseList from './adviceBaseList';
import {
queryParticipantList,
queryGroupDetail,
getOrgTree,
} from './api.js';
import { saveOrderGroup } from '@/views/doctorstation/components/api.js';
import useUserStore from '@/store/modules/user';
import { ElMessage } from 'element-plus';
const props = defineProps({
// 是否在“医生站/住院医生站”场景复用个人不选人practitionerId 置空),科室可选科室
isDoctorStation: {
type: Boolean,
default: false,
},
method_code: {
type: Array,
default: () => [],
},
rate_code: {
type: Array,
default: () => [],
},
// 顶部是否显示“个人/科室/全院”切换(用于住院医生站顶部【组套】按钮场景)
showRangeSelector: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['saved']);
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const dialogVisible = ref(false);
const dialogTitle = ref('');
const currentTab = ref('personal'); // personal | department | hospital
const isEdit = ref(false);
const submitLoading = ref(false);
const rangeSelectValue = ref('personal');
const participantListOptions = ref([]);
const organization = ref([]);
const adviceQueryParams = reactive({});
const formData = reactive({
groupPackageId: undefined,
name: '',
practitionerId: undefined,
organizationId: undefined,
});
const prescriptionList = ref([]);
const expandOrder = ref([]);
const nextId = ref(1);
const rowIndex = ref(0);
const selectedRows = ref([]);
const groupIndex = ref(1);
const formRef = ref();
const prescriptionRef = ref();
const adviceTableRef = ref();
const formRules = computed(() => {
const rules = {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
};
// 独立页面:个人必须选参与者;科室必须选科室
if (!props.isDoctorStation && currentTab.value === 'personal') {
rules.practitionerId = [{ required: true, message: '请选择参与者', trigger: 'change' }];
}
if (currentTab.value === 'department') {
rules.organizationId = [{ required: true, message: '请选择科室', trigger: 'change' }];
}
return rules;
});
onMounted(() => {
queryParticipantList().then((res) => {
participantListOptions.value = res.data || [];
});
getOrgTree().then((res) => {
organization.value = res.data?.records || [];
});
});
function openAdd(tab) {
currentTab.value = tab;
isEdit.value = false;
dialogTitle.value = '新增医嘱';
dialogVisible.value = true;
if (props.showRangeSelector) rangeSelectValue.value = tab;
prescriptionList.value = [];
expandOrder.value = [];
nextId.value = 1;
rowIndex.value = 0;
selectedRows.value = [];
groupIndex.value = 1;
formData.groupPackageId = undefined;
formData.name = '';
formData.practitionerId = undefined;
formData.organizationId = undefined;
Object.keys(adviceQueryParams).forEach((key) => {
delete adviceQueryParams[key];
});
prescriptionRef.value?.clearSelection?.();
if (tab === 'personal') {
// 医生站场景:个人组套需要关联当前医生才能查询到
formData.practitionerId = userStore.practitionerId;
} else if (tab === 'department') {
formData.organizationId = userStore.orgId;
}
addEmptyRow();
}
function handleRangeChange(tab) {
openAdd(tab);
}
/**
* 从外部选中的医嘱直接生成组套明细
* @param tab 使用范围personal / department / hospital
* @param rows 选中的处方行列表
*/
function openFromSelection(tab, rows = []) {
currentTab.value = tab;
isEdit.value = false;
dialogTitle.value = '另存组套';
dialogVisible.value = true;
if (props.showRangeSelector) rangeSelectValue.value = tab;
// 重置表单
formData.groupPackageId = undefined;
formData.name = '';
formData.practitionerId = undefined;
formData.organizationId = undefined;
if (tab === 'personal') {
// 医生站场景:个人组套需要关联当前医生才能查询到
formData.practitionerId = userStore.practitionerId;
} else if (tab === 'department') {
formData.organizationId = userStore.orgId;
}
const validRows = (rows || []).filter((i) => i.adviceDefinitionId);
if (validRows.length === 0) {
proxy.$modal.msgWarning('所选医嘱中没有有效的医嘱项,请先选择医嘱后再另存组套');
dialogVisible.value = false;
return;
}
prescriptionList.value = validRows.map((row, index) => {
let therapyEnum = row.therapyEnum;
if (!therapyEnum && row.contentJson) {
try {
const content = JSON.parse(row.contentJson);
therapyEnum = content.therapyEnum;
} catch (e) {}
}
therapyEnum = therapyEnum != null ? String(therapyEnum) : '1';
return {
uniqueKey: index + 1,
showPopover: false,
check: false,
isEdit: false,
statusEnum: 1,
adviceDefinitionId: row.adviceDefinitionId,
adviceTableName: row.adviceTableName || 'advice_definition',
sortNumber: row.quantity ?? row.sortNumber ?? 1,
selectUnitCode: row.selectUnitCode || row.unitCode,
adviceName: row.adviceName || row.productName,
unitCodeName: row.unitCodeName || row.unitCode_dictText || '',
methodCode: row.methodCode,
rateCode: row.rateCode,
dispensePerDuration: row.dispensePerDuration,
dose: row.dose,
doseQuantity: row.doseQuantity,
minUnitCode: row.minUnitCode,
minUnitCode_dictText: row.minUnitCode_dictText,
unitCode: row.unitCode,
unitCode_dictText: row.unitCode_dictText,
groupId: row.groupId,
groupOrder: row.groupOrder,
therapyEnum: therapyEnum,
adviceType: row.adviceType,
};
});
expandOrder.value = [];
nextId.value = prescriptionList.value.length + 1;
}
function openEdit(tab, row) {
currentTab.value = tab;
isEdit.value = true;
dialogTitle.value = '编辑医嘱';
dialogVisible.value = true;
formData.groupPackageId = row.groupPackageId;
formData.name = row.name;
formData.practitionerId = row.practitionerId;
formData.organizationId = row.organizationId;
queryGroupDetail({ groupPackageId: row.groupPackageId }).then((res) => {
const detailList = res.data || [];
prescriptionList.value = detailList.map((item, index) => {
const therapyEnum = item.therapyEnum != null ? String(item.therapyEnum) : '1'; // 统一转成字符串
return {
uniqueKey: index + 1,
showPopover: false,
check: false,
isEdit: false,
statusEnum: 1,
groupPackageId: item.groupPackageId,
adviceDefinitionId: item.orderDefinitionId,
adviceTableName: item.orderDefinitionTable,
sortNumber: item.quantity,
selectUnitCode: item.unitCode,
adviceName: item.orderDefinitionName,
unitCodeName: item.unitCodeName,
groupId: item.groupId,
groupOrder: item.groupOrder,
therapyEnum, // 长期/临时类型1-长期2-临时
// 回显单次剂量/给药途径/用药频次/天数等字段
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
methodCode: item.methodCode,
rateCode: item.rateCode,
// 医嘱类型(药品=1没有则按表名推断药品表 -> 药品)
adviceType:
item.adviceType !== undefined
? item.adviceType
: item.orderDefinitionTable === 'med_medication_definition'
? 1
: undefined,
};
});
nextId.value = prescriptionList.value.length + 1;
addEmptyRow();
});
}
function addEmptyRow() {
prescriptionList.value.unshift({
uniqueKey: nextId.value++,
showPopover: false,
check: false,
isEdit: true,
statusEnum: 1,
therapyEnum: '1', // 默认长期
});
}
function handleAddRow() {
addEmptyRow();
}
function handleSelectionChange(selection) {
selectedRows.value = selection || [];
}
function handleCombineGroup() {
const rows = selectedRows.value || [];
if (rows.length <= 1) {
proxy.$modal.msgWarning('至少选择两项');
return;
}
// 必须都已选择医嘱
if (rows.some((r) => !r.adviceDefinitionId)) {
proxy.$modal.msgWarning('请先完成医嘱选择');
return;
}
// 相同分组用法需要相同
const methodSet = new Set();
rows.forEach((r) => {
if (r.methodCode != null) methodSet.add(r.methodCode);
});
if (methodSet.size > 1) {
proxy.$modal.msgWarning('同一分组药品用法必须相同');
return;
}
// 相同分组频次需要相同
const rateSet = new Set();
rows.forEach((r) => {
if (r.rateCode != null) rateSet.add(r.rateCode);
});
if (rateSet.size > 1) {
proxy.$modal.msgWarning('同一分组药品频次必须相同');
return;
}
const timestamp = Date.now().toString();
const gid = timestamp + groupIndex.value;
const newList = [...prescriptionList.value];
rows.forEach((item, selectIndex) => {
const idx = newList.findIndex((r) => r.uniqueKey === item.uniqueKey);
if (idx !== -1) {
newList[idx].groupId = gid;
newList[idx].groupOrder = selectIndex + 1;
}
});
groupIndex.value += 1;
prescriptionList.value = newList;
prescriptionRef.value?.clearSelection?.();
proxy.$modal.msgSuccess('组套成功');
}
function isRowSelectable(row) {
return !!row.adviceDefinitionId;
}
function getGroupIcon(row) {
if (!row.groupId) return '';
const sameGroup = prescriptionList.value
.filter((item) => item.groupId === row.groupId)
.sort((a, b) => {
const aOrder = a.groupOrder ?? 0;
const bOrder = b.groupOrder ?? 0;
return aOrder - bOrder;
});
if (sameGroup.length === 0) return '';
const index = sameGroup.findIndex((item) => item.uniqueKey === row.uniqueKey);
if (index === 0) {
return '┏';
}
if (index === sameGroup.length - 1) {
return '┗';
}
return '┃';
}
function getRateOptions(row) {
const all = props.rate_code || [];
if (row?.therapyEnum == '2') {
return all.filter(
(dict) => dict.value === 'ST' || (dict.label && dict.label.indexOf('立即') !== -1)
);
}
return all;
}
function handleSplitGroup() {
const rows = selectedRows.value || [];
if (rows.length < 1) {
proxy.$modal.msgWarning('至少选择一项');
return;
}
if (rows.some((r) => !r.groupId)) {
proxy.$modal.msgWarning('包含非组合数据无法拆组');
return;
}
const newList = [...prescriptionList.value];
rows.forEach((item) => {
const idx = newList.findIndex((r) => r.uniqueKey === item.uniqueKey);
if (idx !== -1) {
newList[idx].groupId = undefined;
newList[idx].groupOrder = undefined;
}
});
prescriptionList.value = newList;
prescriptionRef.value?.clearSelection?.();
proxy.$modal.msgSuccess('拆组成功');
}
function submitForm() {
formRef.value.validate((valid) => {
if (!valid) return;
// 验证至少选择了一条医嘱
const detailList = prescriptionList.value.filter((item) => item.adviceDefinitionId);
if (detailList.length === 0) {
proxy?.$modal?.msgWarning?.('请至少选择一条医嘱');
return;
}
submitLoading.value = true;
setTimeout(() => {
console.log('[submitForm] 当前tab:', currentTab.value);
const params = { ...formData };
console.log('[submitForm] formData:', formData);
// 添加 rangeCode 字段,用于后端判断组套类型
if (currentTab.value === 'personal') {
params.rangeCode = 1;
} else if (currentTab.value === 'department') {
params.rangeCode = 2;
} else {
params.rangeCode = 3;
}
console.log('[submitForm] 设置后的rangeCode:', params.rangeCode);
// 医生站场景:个人组套需要设置 practitionerId 为当前医生,否则查询时查不到
console.log('[submitForm] isDoctorStation:', props.isDoctorStation, 'currentTab:', currentTab.value);
console.log('[submitForm] userStore.practitionerId:', userStore.practitionerId);
if (props.isDoctorStation && currentTab.value === 'personal') {
params.practitionerId = userStore.practitionerId;
console.log('[submitForm] 已设置 practitionerId:', params.practitionerId);
} else {
console.log('[submitForm] 未设置 practitionerId, 当前值:', params.practitionerId);
}
params.detailList = detailList.map((item) => ({
orderDefinitionId: item.adviceDefinitionId,
orderDefinitionTable: item.adviceTableName,
quantity: item.sortNumber,
unitCode: item.selectUnitCode,
methodCode: item.methodCode,
rateCode: item.rateCode,
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
groupId: item.groupId,
groupOrder: item.groupOrder,
therapyEnum: item.therapyEnum || '1',
}));
console.log('[submitForm] 准备保存组套, params:', params);
// 使用 saveOrderGroup它会根据 rangeCode 自动选择正确的API
saveOrderGroup(params)
.then((res) => {
console.log('[submitForm] 保存API返回:', res);
if (res.code === 200) {
ElMessage.success(res.message || '保存成功');
console.log('[submitForm] 准备触发 saved 事件');
emit('saved');
console.log('[submitForm] saved 事件已触发');
dialogVisible.value = false;
prescriptionList.value = [];
} else {
proxy?.$modal?.msgError?.(res.message || '保存失败');
}
})
.catch((err) => {
console.error('保存失败:', err);
proxy?.$modal?.msgError?.(err?.message || '保存失败,请检查网络或联系管理员');
})
.finally(() => {
submitLoading.value = false;
});
}, 200);
});
}
function selectAdviceBase(key, row) {
const currentRow = prescriptionList.value[rowIndex.value];
const preservedTherapyEnum = currentRow?.therapyEnum || row.therapyEnum || '1';
prescriptionList.value[rowIndex.value] = {
...prescriptionList.value[rowIndex.value],
...JSON.parse(JSON.stringify(row)),
};
prescriptionList.value[rowIndex.value].dose = undefined;
prescriptionList.value[rowIndex.value].doseQuantity = undefined;
prescriptionList.value[rowIndex.value].doseUnitCode = row.doseUnitCode;
prescriptionList.value[rowIndex.value].minUnitCode = row.minUnitCode;
prescriptionList.value[rowIndex.value].unitCode =
row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode;
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode;
prescriptionList.value[rowIndex.value].isEdit = false;
prescriptionList.value[rowIndex.value].definitionId = JSON.parse(
JSON.stringify(row)
).chargeItemDefinitionId;
prescriptionList.value[rowIndex.value].therapyEnum = preservedTherapyEnum;
addEmptyRow();
expandOrder.value = [key];
}
function handleFocus(row, index) {
rowIndex.value = index;
row.showPopover = true;
adviceQueryParams.searchKey = row.adviceName || '';
// 设置 organizationId否则 adviceBaseList 会跳过 API 调用
adviceQueryParams.organizationId = userStore.orgId;
}
function handleInput(value, row, index) {
adviceQueryParams.searchKey = value || '';
handleFocus(row, index);
}
function handleBlur(row) {
row.showPopover = false;
}
function clickRow() {}
function getRowDisabled(row) {
return row.isEdit;
}
function handleQuantityChange(row) {
if (row.sortNumber && row.sortNumber > 0) row.sortNumber = parseInt(row.sortNumber);
else row.sortNumber = 1;
prescriptionList.value = [...prescriptionList.value];
}
function handleUnitChange(row) {
prescriptionList.value = [...prescriptionList.value];
}
function handleDeleteRow(index) {
if (prescriptionList.value.length <= 1) {
proxy.$modal.msgWarning('至少保留一行');
return;
}
proxy.$modal
.confirm('确定要删除该行吗?')
.then(() => {
prescriptionList.value.splice(index, 1);
proxy.$modal.msgSuccess('删除成功');
})
.catch(() => {});
}
function isLeafNode(node) {
return !node.children || node.children.length === 0;
}
function handleDialogClose() {
formRef.value?.resetFields?.();
prescriptionList.value = [];
}
function handleOrgChange(value) {
if (!value) return;
const findNode = (nodes) => {
for (let node of nodes) {
if (node.id === value) return node;
if (node.children && node.children.length > 0) {
const found = findNode(node.children);
if (found) return found;
}
}
return null;
};
const selectedNode = findNode(organization.value);
if (selectedNode && !isLeafNode(selectedNode)) {
proxy.$modal.msgWarning('只能选择末级科室');
formData.organizationId = null;
}
}
defineExpose({
openAdd,
openEdit,
openFromSelection,
});
</script>
<style scoped>
.dialog-top-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.dialog-top-row :deep(.el-form-item) {
margin-bottom: 0;
}
</style>

View File

@@ -1,12 +1,13 @@
<template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper">
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper" class="table-container">
<el-table
ref="adviceBaseRef"
height="400"
height="350"
:data="adviceBaseList"
highlight-current-row
@current-change="handleCurrentChange"
row-key="patientId"
v-loading="loading"
@cell-click="clickRow"
>
<el-table-column label="名称" align="center" prop="adviceName" />
@@ -21,13 +22,23 @@
<el-table-column label="注射药品" align="center" prop="injectFlag_enumText" />
<el-table-column label="皮试" align="center" prop="skinTestFlag_enumText" />
</el-table>
<!-- 分页组件 -->
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="queryParams.pageNum"
:page-size="20"
:total="total"
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</div>
</div>
</template>
<script setup>
import {nextTick} from 'vue';
import {getAdviceBaseInfo} from './api';
import {throttle} from 'lodash-es';
import {debounce} from 'lodash-es';
const props = defineProps({
adviceQueryParams: {
@@ -41,33 +52,85 @@ const props = defineProps({
});
const emit = defineEmits(['selectAdviceBase']);
const total = ref(0);
const loading = ref(false);
const adviceBaseRef = ref();
const tableWrapper = ref();
const currentIndex = ref(0); // 当前选中行索引
const currentSelectRow = ref({});
const queryParams = ref({
pageSize: 100,
pageSize: 20,
pageNum: 1,
adviceTypes: '1,2,3',
organizationId: null,
});
const adviceBaseList = ref([]);
// 节流函数
const throttledGetList = throttle(
// 前端缓存 - 使用 sessionStorage 持久化缓存
const CACHE_PREFIX = 'advice_cache_';
const CACHE_EXPIRE = 5 * 60 * 1000; // 缓存5分钟
function getCacheKey(orgId, pageNum) {
return CACHE_PREFIX + orgId + '_' + pageNum;
}
function getFromCache(orgId, pageNum) {
try {
const key = getCacheKey(orgId, pageNum);
const cachedData = sessionStorage.getItem(key);
if (!cachedData) return null;
const cacheData = JSON.parse(cachedData);
if (Date.now() - cacheData.timestamp > CACHE_EXPIRE) {
sessionStorage.removeItem(key);
return null;
}
console.log('从前端缓存获取:', key);
return cacheData;
} catch (e) {
console.error('读取缓存失败:', e);
return null;
}
}
function setToCache(orgId, pageNum, data, total) {
try {
const key = getCacheKey(orgId, pageNum);
const cacheData = {
data: data,
total: total,
timestamp: Date.now()
};
sessionStorage.setItem(key, JSON.stringify(cacheData));
console.log('写入前端缓存:', key);
} catch (e) {
console.error('写入缓存失败:', e);
}
}
// 防抖函数 - 避免重复请求
const debouncedGetList = debounce(
() => {
// 只有当 organizationId 有效时才请求
if (!queryParams.value.organizationId) {
console.log('organizationId 无效,跳过请求');
return;
}
getList();
},
300,
{ leading: true, trailing: true }
{ leading: false, trailing: true }
);
// 监听adviceQueryParams变化
watch(
() => props.adviceQueryParams,
(newValue) => {
console.log('adviceQueryParams 变化:', newValue);
queryParams.value.searchKey = newValue.searchKey;
queryParams.value.adviceType = newValue.adviceType;
console.log(queryParams.value);
throttledGetList();
queryParams.value.organizationId = newValue.organizationId;
console.log('更新后的queryParams:', queryParams.value);
debouncedGetList();
},
{ deep: true }
);
@@ -77,26 +140,77 @@ watch(
() => props.adviceQueryParams?.searchKey,
(newVal) => {
queryParams.value.searchKey = newVal;
throttledGetList();
debouncedGetList();
}
);
getList();
// 监听organizationId变化
watch(
() => props.adviceQueryParams?.organizationId,
(newVal) => {
console.log('watch organizationId 变化:', newVal);
queryParams.value.organizationId = newVal;
// 当organizationId有效时重新获取数据
if (newVal) {
debouncedGetList();
}
}
);
// 不再页面加载时立即请求,等待用户点击时再请求
function getList() {
// 确保有 organizationId 才调用API
if (!queryParams.value.organizationId) {
console.log('organizationId 为空跳过API调用');
return;
}
const orgId = queryParams.value.organizationId;
const pageNum = queryParams.value.pageNum;
// 尝试从本地缓存获取(只有第一页且无搜索关键字时使用缓存)
if (pageNum === 1 && !queryParams.value.searchKey) {
const cached = getFromCache(orgId, pageNum);
if (cached) {
adviceBaseList.value = cached.data;
total.value = cached.total;
return;
}
}
// 显示 loading
loading.value = true;
// queryParams.value.organizationId = '1922545444781481985';
getAdviceBaseInfo(queryParams.value).then((res) => {
adviceBaseList.value = res.data.records;
console.log(adviceBaseList.value)
total.value = res.data.total;
adviceBaseList.value = res.data.records || [];
console.log('获取到医嘱数据:', adviceBaseList.value)
total.value = res.data.total || 0;
// 缓存第一页数据
if (pageNum === 1 && !queryParams.value.searchKey) {
setToCache(orgId, pageNum, adviceBaseList.value, total.value);
}
nextTick(() => {
currentIndex.value = 0;
if (adviceBaseList.value.length > 0) {
if (adviceBaseList.value.length > 0 && adviceBaseRef.value) {
adviceBaseRef.value.setCurrentRow(adviceBaseList.value[0]);
}
});
})
.finally(() => {
loading.value = false;
});
}
// 当前页改变(分页)
function handlePageChange(page) {
queryParams.value.pageNum = page;
getList();
}
// 处理键盘事件
const handleKeyDown = (event) => {
const key = event.key;
@@ -155,7 +269,17 @@ defineExpose({
</script>
<style scoped>
.table-container {
display: flex;
flex-direction: column;
}
.pagination-wrapper {
display: flex;
justify-content: center;
padding: 10px 0;
background: #fff;
}
.popover-table-wrapper:focus {
outline: 2px solid #409eff; /* 聚焦时的高亮效果 */
outline: 2px solid #409eff;
}
</style>

View File

@@ -401,10 +401,10 @@ const { method_code, rate_code, distribution_category_code } = proxy.useDict(
'rate_code',
'distribution_category_code'
);
// 查询参数
const personalQuery = reactive({ searchKey: '' });
const departmentQuery = reactive({ searchKey: '' });
const hospitalQuery = reactive({ searchKey: '' });
// 查询参数 - 西医组套 tcmFlag = 0
const personalQuery = reactive({ searchKey: '', tcmFlag: 0 });
const departmentQuery = reactive({ searchKey: '', tcmFlag: 0 });
const hospitalQuery = reactive({ searchKey: '', tcmFlag: 0 });
// 加载状态
const loading = reactive({
@@ -462,18 +462,15 @@ function getInit() {
});
}
// 获取所有数据
function fetchAllData(params = {}) {
getPersonalListData(params);
getDepartmentListData(params);
getHospitalListData(params);
// 获取所有数据(不传参,保留查询框已有的参数)
function fetchAllData() {
getPersonalListData();
getDepartmentListData();
getHospitalListData();
}
// 获取个人医嘱列表
function getPersonalListData(params = {}) {
// 合并查询参数
Object.assign(personalQuery, params);
function getPersonalListData() {
loading.personal = true;
getPersonalList(personalQuery)
.then((response) => {
@@ -488,10 +485,7 @@ function getPersonalListData(params = {}) {
}
// 获取科室医嘱列表
function getDepartmentListData(params = {}) {
// 合并查询参数
Object.assign(departmentQuery, params);
function getDepartmentListData() {
loading.department = true;
getDeptList(departmentQuery)
.then((response) => {
@@ -506,10 +500,7 @@ function getDepartmentListData(params = {}) {
}
// 获取全院医嘱列表
function getHospitalListData(params = {}) {
// 合并查询参数
Object.assign(hospitalQuery, params);
function getHospitalListData() {
loading.hospital = true;
getAllList(hospitalQuery)
.then((response) => {
@@ -627,10 +618,11 @@ function submitForm() {
if (valid) {
submitLoading.value = true;
// 模拟提交操作这里应该调用相应的API
setTimeout(() => {
console.log('提交表单数据:', formData);
let params = { ...formData };
// 提交数据
console.log('提交表单数据:', formData);
let params = { ...formData };
// 添加 tcmFlag西医组套为 0
params.tcmFlag = 0;
// 过滤掉空列表项没有adviceDefinitionId的项
params.detailList = prescriptionList.value
.filter((item) => item.adviceDefinitionId) // 过滤掉空列表项
@@ -647,49 +639,70 @@ function submitForm() {
doseQuantity: item.doseQuantity,
};
});
console.log('保存参数:', params);
// 编辑模式
switch (currentTab.value) {
case 'personal':
savePersonal(params).then((res) => {
console.log('保存个人组套返回:', res);
if (res.code === 200) {
ElMessage.success(res.message);
ElMessage.success(res.msg || '保存成功');
// 重新获取数据以保持一致性
fetchAllData();
} else {
ElMessage.error(res.msg || '保存失败');
}
submitLoading.value = false;
dialogVisible.value = false;
// 清空处方列表
prescriptionList.value = [];
}).catch((err) => {
console.error('保存个人组套失败:', err);
ElMessage.error('保存失败: ' + (err.message || '未知错误'));
submitLoading.value = false;
});
break;
case 'department':
saveDepartment(params).then((res) => {
console.log('保存科室组套返回:', res);
if (res.code === 200) {
ElMessage.success(res.message);
ElMessage.success(res.msg || '保存成功');
// 重新获取数据以保持一致性
fetchAllData();
} else {
ElMessage.error(res.msg || '保存失败');
}
submitLoading.value = false;
dialogVisible.value = false;
// 清空处方列表
prescriptionList.value = [];
}).catch((err) => {
console.error('保存科室组套失败:', err);
ElMessage.error('保存失败: ' + (err.message || '未知错误'));
submitLoading.value = false;
});
break;
case 'hospital':
saveAll(params).then((res) => {
console.log('保存全院组套返回:', res);
if (res.code === 200) {
ElMessage.success(res.message);
ElMessage.success(res.msg || '保存成功');
// 重新获取数据以保持一致性
fetchAllData();
} else {
ElMessage.error(res.msg || '保存失败');
}
submitLoading.value = false;
dialogVisible.value = false;
// 清空处方列表
prescriptionList.value = [];
}).catch((err) => {
console.error('保存全院组套失败:', err);
ElMessage.error('保存失败: ' + (err.message || '未知错误'));
submitLoading.value = false;
});
break;
}
}, 500);
}
});
}
@@ -728,6 +741,10 @@ function handleFocus(row, index) {
row.showPopover = true;
// 将输入框的值传递给adviceBaseList组件作为查询条件
adviceQueryParams.searchKey = row.adviceName || '';
// 获取当前登录用户的科室ID
const userStore = useUserStore();
adviceQueryParams.organizationId = userStore.orgId;
console.log('handleFocus - organizationId:', userStore.orgId, 'userStore:', userStore);
}
// 处理输入事件

View File

@@ -0,0 +1,129 @@
import request from '@/utils/request'
/**
* 获取个人组套
* @param {*} queryParams
*/
export function getPersonalList(queryParams) {
return request({
url: '/personalization/orders-group-package/get-personal',
method: 'get',
params: queryParams
})
}
/**
* 获取科室组套
* @param {*} queryParams
*/
export function getDeptList(queryParams) {
return request({
url: '/personalization/orders-group-package/get-organization',
method: 'get',
params: queryParams
})
}
/**
* 获取全院组套
* @param {*} queryParams
*/
export function getAllList(queryParams) {
return request({
url: '/personalization/orders-group-package/get-hospital',
method: 'get',
params: queryParams
})
}
/**
* 保存个人组套
* @param {*} data
*/
export function savePersonal(data) {
return request({
url: '/personalization/orders-group-package/save-personal',
method: 'post',
data: data
})
}
/**
* 保存科室组套
* @param {*} data
*/
export function saveDepartment(data) {
return request({
url: '/personalization/orders-group-package/save-organization',
method: 'post',
data: data
})
}
/**
* 保存全院组套
* @param {*} data
*/
export function saveAll(data) {
return request({
url: '/personalization/orders-group-package/save-hospital',
method: 'post',
data: data
})
}
/**
* 查询组套明细
* @param {*} data
*/
export function queryGroupDetail(params) {
return request({
url: '/personalization/orders-group-package/get-group-package-detail',
method: 'get',
params: params
})
}
/**
* 删除组套
* @param {*} data
*/
export function deleteGroup(data) {
return request({
url: '/personalization/orders-group-package/group-package-detail?groupPackageId=' + data.groupPackageId,
method: 'delete'
})
}
/**
* 查询参与者下拉列表
* @param {*} data
*/
export function queryParticipantList(params) {
return request({
url: '/app-common/practitioner-list',
method: 'get',
params: params
})
}
/**
* 获取科室列表
*/
export function getOrgTree() {
return request({
url: '/base-data-manage/organization/organization',
method: 'get',
})
}
/**
* 获取中药列表
*/
export function getTcmMedicine(params) {
return request({
url: '/doctor-station/chinese-medical/tcm-advice-base-info',
method: 'get',
params: params,
});
}

View File

@@ -0,0 +1,269 @@
<template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper" class="table-container">
<el-table
ref="adviceBaseRef"
height="350"
:data="adviceBaseList"
highlight-current-row
@current-change="handleCurrentChange"
row-key="adviceCode"
v-loading="loading"
@cell-click="clickRow"
>
<el-table-column label="名称" align="center" prop="adviceName" />
<el-table-column label="类型" align="center" prop="categoryCodeText" />
<el-table-column label="医保等级" align="center" prop="chrgitmLv_dictText" />
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" />
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" />
<el-table-column label="库存数量" align="center">
<template #default="scope">{{ handleQuantity(scope.row) }}</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="queryParams.pageNum"
:page-size="20"
:total="total"
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue';
import { getTcmMedicine } from './api';
import { debounce } from 'lodash-es';
const props = defineProps({
searchKey: {
type: String,
default: '',
},
organizationId: {
type: String,
default: '',
},
});
const emit = defineEmits(['selectMedicine']);
const total = ref(0);
const loading = ref(false);
const adviceBaseRef = ref();
const tableWrapper = ref();
const currentIndex = ref(0);
const currentSelectRow = ref({});
const queryParams = ref({
pageSize: 20,
pageNum: 1,
searchKey: '',
organizationId: props.organizationId,
});
const adviceBaseList = ref([]);
// 前端缓存 - 使用 sessionStorage 持久化缓存
const TCM_CACHE_PREFIX = 'tcm_advice_cache_';
const TCM_CACHE_EXPIRE = 5 * 60 * 1000; // 缓存5分钟
function getTcmCacheKey(orgId, pageNum, searchKey) {
return TCM_CACHE_PREFIX + orgId + '_' + pageNum + '_' + (searchKey || 'none');
}
function getFromTcmCache(orgId, pageNum, searchKey) {
try {
const key = getTcmCacheKey(orgId, pageNum, searchKey);
const cachedData = sessionStorage.getItem(key);
if (!cachedData) return null;
const cacheData = JSON.parse(cachedData);
if (Date.now() - cacheData.timestamp > TCM_CACHE_EXPIRE) {
sessionStorage.removeItem(key);
return null;
}
console.log('从TCM前端缓存获取:', key);
return cacheData;
} catch (e) {
console.error('读取TCM缓存失败:', e);
return null;
}
}
function setToTcmCache(orgId, pageNum, searchKey, data, total) {
try {
const key = getTcmCacheKey(orgId, pageNum, searchKey);
const cacheData = {
data: data,
total: total,
timestamp: Date.now()
};
sessionStorage.setItem(key, JSON.stringify(cacheData));
console.log('写入TCM前端缓存:', key);
} catch (e) {
console.error('写入TCM缓存失败:', e);
}
}
// 防抖函数 - 避免重复请求
const debouncedGetList = debounce(
() => {
// 只有当 organizationId 有效时才请求
if (!queryParams.value.organizationId) {
console.log('organizationId 无效,跳过请求');
return;
}
getList();
},
300,
{ leading: false, trailing: true }
);
// 监听搜索关键字变化
watch(
() => props.searchKey,
(newValue) => {
queryParams.value.searchKey = newValue || '';
debouncedGetList();
},
{ deep: true }
);
// 监听 organizationId 变化
watch(
() => props.organizationId,
(newValue) => {
queryParams.value.organizationId = newValue;
// organizationId 变化时重新加载
if (newValue) {
debouncedGetList();
}
},
{ deep: true }
);
// 不再页面加载时立即请求,等待用户点击时再请求
function getList() {
const orgId = queryParams.value.organizationId;
const pageNum = queryParams.value.pageNum;
const searchKey = queryParams.value.searchKey;
// 尝试从本地缓存获取(只有第一页且无搜索关键字时使用缓存)
if (pageNum === 1 && !searchKey) {
const cached = getFromTcmCache(orgId, pageNum, searchKey);
if (cached) {
adviceBaseList.value = cached.data;
total.value = cached.total;
return;
}
}
// 显示 loading
loading.value = true;
getTcmMedicine(queryParams.value).then((res) => {
adviceBaseList.value = res.data.records || [];
total.value = res.data.total || 0;
// 缓存第一页数据
if (pageNum === 1 && !searchKey) {
setToTcmCache(orgId, pageNum, searchKey, adviceBaseList.value, total.value);
}
nextTick(() => {
currentIndex.value = 0;
if (adviceBaseList.value.length > 0 && adviceBaseRef.value) {
adviceBaseRef.value.setCurrentRow(adviceBaseList.value[0]);
}
});
})
.finally(() => {
loading.value = false;
});
}
// 当前页改变
function handlePageChange(page) {
queryParams.value.pageNum = page;
getList();
}
// 处理键盘事件
const handleKeyDown = (event) => {
const key = event.key;
const data = adviceBaseList.value;
switch (key) {
case 'ArrowUp':
event.preventDefault();
if (currentIndex.value > 0) {
currentIndex.value--;
setCurrentRow(data[currentIndex.value]);
}
break;
case 'ArrowDown':
event.preventDefault();
if (currentIndex.value < data.length - 1) {
currentIndex.value++;
setCurrentRow(data[currentIndex.value]);
}
break;
case 'Enter':
event.preventDefault();
if (currentSelectRow.value) {
emit('selectMedicine', currentSelectRow.value);
}
break;
}
};
function handleQuantity(row) {
if (row.inventoryList) {
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + (row.minUnitCode_dictText || '');
}
return 0;
}
// 设置选中行(带滚动)
const setCurrentRow = (row) => {
adviceBaseRef.value.setCurrentRow(row);
const tableBody = adviceBaseRef.value.$el.querySelector('.el-table__body-wrapper');
const currentRowEl = adviceBaseRef.value.$el.querySelector('.current-row');
if (tableBody && currentRowEl) {
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
};
// 当前行变化时更新索引
const handleCurrentChange = (currentRow) => {
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow);
currentSelectRow.value = currentRow;
};
function clickRow(row) {
emit('selectMedicine', row);
}
defineExpose({
handleKeyDown,
});
</script>
<style scoped>
.table-container {
display: flex;
flex-direction: column;
}
.pagination-wrapper {
display: flex;
justify-content: center;
padding: 10px 0;
background: #fff;
}
.popover-table-wrapper:focus {
outline: 2px solid #409eff;
}
</style>

View File

@@ -0,0 +1,820 @@
<template>
<div class="app-container tcm-medical-order-set">
<el-row :gutter="20" class="full-height">
<!-- 左侧树形结构 -->
<el-col :span="5" class="left-panel">
<el-card shadow="never" class="tree-card panel-shadow">
<template #header>
<span>中医组套</span>
</template>
<el-tree
:data="treeData"
node-key="id"
:props="treeProps"
default-expand-all
highlight-current
@node-click="(data, node) => handleTreeNodeClick(data, node)"
/>
</el-card>
</el-col>
<!-- 右侧操作区域 -->
<el-col :span="19" class="right-panel">
<!-- 上方表单:组套公共字段 -->
<el-card shadow="never" class="form-card panel-shadow">
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px">
<el-button type="primary" plain @click="clearForm">清空 </el-button>
<el-button type="primary" @click="createNew">新建 </el-button>
<el-button type="primary" @click="saveCommonForm">保存 </el-button>
</div>
<div class="common-bar">
<el-form :model="commonForm" :inline="true" class="demo-form-inline">
<el-form-item label="组套名称">
<el-input v-model="commonForm.name" placeholder="组套名称" clearable />
</el-form-item>
<el-form-item label="使用范围">
<el-select
v-model="commonForm.useRange"
placeholder="个人/科室/全院"
clearable
@change="handleRangeChange"
>
<el-option label="个人" value="personal" />
<el-option label="科室" value="department" />
<el-option label="全院" value="hospital" />
</el-select>
</el-form-item>
<el-form-item label="使用人" v-if="commonForm.useRange === 'personal'">
<el-select
v-model="commonForm.practitionerId"
placeholder="请选择使用人"
clearable
style="width: 200px"
>
<el-option
v-for="item in participantListOptions"
:key="item.practitionerId"
:label="item.practitionerName"
:value="item.practitionerId"
/>
</el-select>
</el-form-item>
<el-form-item label="科室" v-if="commonForm.useRange === 'department'">
<el-tree-select
clearable
v-model="commonForm.organizationId"
:data="organization"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
check-strictly
default-expand-all
placeholder="请选择科室"
style="width: 200px"
:render-after-expand="false"
/>
</el-form-item>
<el-form-item label="用法">
<el-select v-model="commonForm.method" placeholder="用法" clearable>
<el-option
v-for="item in method_code"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="频次">
<el-select v-model="commonForm.frequency" placeholder="频次" clearable>
<el-option
v-for="item in rate_code"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- 下方:中药明细卡片列表 -->
<el-card shadow="never" class="detail-card panel-shadow" style="height: 100%">
<div class="drug-card-list">
<!-- 已有药品卡片 -->
<el-card
v-for="(item, index) in drugList"
:key="item.id"
class="drug-card"
shadow="never"
>
<template v-if="!item.isEditing">
<div class="drug-card-body">
<div class="drug-top">
<div class="drug-view-name">{{ item.drugName || '-' }}</div>
</div>
<div class="drug-divider"></div>
<div class="drug-bottom" style="margin-top: 10px">
<div class="drug-bottom-left">
<span class="drug-view-qty">{{ item.quantity }} {{ item.unit }}</span>
</div>
<div class="drug-view-actions">
<el-button
type="primary"
text
:icon="Edit"
@click.stop="editDrug(item)"
title="编辑"
/>
<el-button
v-if="drugList.length > 1"
type="danger"
text
:icon="Delete"
@click.stop="removeDrug(index)"
title="删除"
/>
</div>
</div>
</div>
</template>
<template v-else>
<div class="drug-card-body">
<div class="drug-top">
<el-form :model="item" class="drug-card-form">
<el-form-item>
<el-popover
:popper-style="{ padding: '0' }"
placement="bottom-start"
:visible="item.showPopover"
:width="800"
>
<tcmMedicineList
:ref="(el) => setMedicineTableRef(el, item)"
:searchKey="item.searchKey"
:organizationId="commonForm.organizationId"
@selectMedicine="(row) => handleSelectMedicine(row, item)"
/>
<template #reference>
<el-input
v-model="item.drugName"
placeholder="请选择中药"
clearable
@input="(val) => handleSearchKeyChange(val, item)"
@click="() => handleClick(item)"
@keydown="(e) => handleKeyDown(e, item)"
@blur="() => handleBlur(item)"
/>
</template>
</el-popover>
</el-form-item>
</el-form>
</div>
<div class="drug-bottom">
<div class="drug-bottom-left">
<div class="quantity-wrap">
<el-input v-model="item.quantity" />
<el-input value="g" style="width: 80px" disabled />
</div>
</div>
</div>
<div class="drug-edit-actions">
<el-button type="primary" link @click.stop="saveDrug(item)"> 保存 </el-button>
<el-button type="danger" link @click.stop="removeDrug(index)"> 删除 </el-button>
</div>
</div>
</template>
</el-card>
<!-- 始终存在的新增卡片 -->
<el-card class="drug-card add-card" shadow="never" @click="addDrug">
<div class="add-card-content">
<el-icon class="add-icon">
<Plus />
</el-icon>
<span>新增</span>
</div>
</el-card>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="TcmMedicalOrderSet">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { ElMessage } from 'element-plus';
import {
getPersonalList,
getDeptList,
getAllList,
queryGroupDetail,
queryParticipantList,
getOrgTree,
savePersonal,
saveDepartment,
saveAll,
} from './components/api';
import TcmMedicineList from './components/tcmMedicineList.vue';
import { Edit, Delete, Plus } from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance();
// 字典数据
const { method_code, rate_code } = proxy.useDict('method_code', 'rate_code');
// 左侧树形结构数据
const treeData = ref([]);
const treeProps = {
children: 'children',
label: 'label',
value: 'value',
};
// 加载左侧树数据
function loadTreeData() {
Promise.all([
getPersonalList({ tcmFlag: 1 }),
getDeptList({ tcmFlag: 1 }),
getAllList({ tcmFlag: 1 }),
])
.then(([personalRes, deptRes, allRes]) => {
const personalList = personalRes?.data || [];
const deptList = deptRes?.data || [];
const allList = allRes?.data || [];
treeData.value = [
{
id: 'personal',
label: '个人',
children: personalList.map((item) => ({
id: item.groupPackageId,
value: item.groupPackageId,
label: item.name,
practitionerId: item.practitionerId || '',
organizationId: '',
})),
},
{
id: 'department',
label: '科室',
children: deptList.map((item) => ({
id: item.groupPackageId,
value: item.groupPackageId,
label: item.name,
practitionerId: '',
organizationId: item.organizationId || '',
})),
},
{
id: 'hospital',
label: '全院',
children: allList.map((item) => ({
id: item.groupPackageId,
value: item.groupPackageId,
label: item.name,
practitionerId: '',
organizationId: '',
})),
},
];
})
.catch(() => {
ElMessage.error('加载组套树失败');
});
}
onMounted(() => {
loadTreeData();
loadParticipantList();
loadOrganization();
});
// 使用人列表
const participantListOptions = ref([]);
// 科室树形数据
const organization = ref([]);
// 加载使用人列表
function loadParticipantList() {
queryParticipantList().then((res) => {
participantListOptions.value = res.data || [];
});
}
// 加载科室树
function loadOrganization() {
getOrgTree().then((res) => {
organization.value = res.data.records || [];
});
}
// 使用范围变更
function handleRangeChange() {
commonForm.practitionerId = '';
commonForm.organizationId = '';
}
// 上方公共字段表单
const commonForm = reactive({
name: '',
useRange: '',
practitionerId: '',
organizationId: '',
method: '',
frequency: '',
});
// 下方药品卡片列表
const drugList = ref([]);
const nextDrugId = ref(1);
const currentSetName = ref('');
// 动态设置表格组件引用
function setMedicineTableRef(el, item) {
if (el) {
item.tableRef = el;
}
}
function createEmptyDrug() {
return {
id: nextDrugId.value++,
drugCode: '',
drugName: '',
orderDefinitionId: '',
quantity: 1,
unitCode: '',
unit: 'g',
isEditing: true,
showPopover: false,
searchKey: '',
tableRef: null,
rateCode: '',
methodCode: '',
};
}
// 点击树节点
function handleTreeNodeClick(data, node) {
if (data.children != undefined) {
return;
}
// 从父节点获取使用范围personal/department/hospital
const useRange = node.parent?.data?.id || '';
// 组套名称从树节点获取
commonForm.name = data.label || '';
commonForm.useRange = useRange;
// 从树节点数据中获取使用人/科室
commonForm.practitionerId = data.practitionerId || '';
commonForm.organizationId = data.organizationId || '';
// 调用查询接口回显数据
queryGroupDetail({ groupPackageId: data.value })
.then((res) => {
const detailList = res.data || [];
if (detailList.length === 0) {
drugList.value = [createEmptyDrug()];
return;
}
// 取第一个药品的频次和用法赋值到表单
const firstDrug = detailList[0];
commonForm.method = firstDrug.methodCode || '';
commonForm.frequency = firstDrug.rateCode || '';
// 回显药品列表
drugList.value = detailList.map((detail, index) => ({
id: index,
drugCode: '',
drugName: detail.orderDefinitionName || '',
orderDefinitionId: detail.orderDefinitionId,
quantity: detail.quantity,
unitCode: detail.unitCode,
unit: detail.unitCode_dictText || 'g',
isEditing: false,
showPopover: false,
searchKey: '',
tableRef: null,
rateCode: detail.rateCode,
methodCode: detail.methodCode,
}));
currentSetName.value = data.label;
})
.catch(() => {
ElMessage.error('加载组套详情失败');
});
}
// 新增药品
function addDrug() {
// 如果有药品列表,检查上一个药品是否未保存
if (drugList.value.length > 0) {
const last = drugList.value[drugList.value.length - 1];
// 如果上一个药品未选择药品,不允许添加下一个
if (!last.orderDefinitionId) {
ElMessage.warning('请先选择上一个药品');
return;
}
// 如果上一个药品已有药品ID且处于编辑状态自动保存
if (last.isEditing) {
const msg = validateDrug(last);
if (!msg) {
last.isEditing = false;
}
}
}
drugList.value.push(createEmptyDrug());
}
// 删除药品
function removeDrug(index) {
if (drugList.value.length <= 1) {
ElMessage.warning('至少保留一条药品信息');
return;
}
drugList.value.splice(index, 1);
}
function validateDrug(item) {
if (!item.orderDefinitionId) return '请选择中药';
if (!item.quantity || Number(item.quantity) <= 0) return '请填写数量';
return '';
}
function saveDrug(item) {
const msg = validateDrug(item);
if (msg) {
ElMessage.warning(msg);
return false;
}
item.isEditing = false;
return true;
}
function editDrug(item) {
item.isEditing = true;
}
// 处理搜索关键字变化
function handleSearchKeyChange(value, item) {
item.searchKey = value;
}
// 处理点击输入框
function handleClick(item) {
item.showPopover = true;
}
// 处理失焦
function handleBlur(item) {
item.showPopover = false;
item.searchKey = '';
}
// 处理键盘事件(上下键和回车)
function handleKeyDown(event, item) {
if (!item.showPopover) return;
const key = event.key;
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(key)) {
event.preventDefault();
if (item.tableRef) {
item.tableRef.handleKeyDown(event);
}
}
}
// 选择中药时,回填信息
function handleSelectMedicine(row, item) {
item.drugCode = row.adviceCode;
item.drugName = row.adviceName;
item.orderDefinitionId = row.adviceDefinitionId;
item.unitCode = row.unitCode;
item.unit = row.unitCode_dictText || 'g';
item.showPopover = false;
item.searchKey = '';
}
// 清空表单
function clearForm() {
commonForm.name = '';
commonForm.useRange = '';
commonForm.practitionerId = '';
commonForm.organizationId = '';
commonForm.method = '';
commonForm.frequency = '';
drugList.value = [];
}
// 新建组套
function createNew() {
clearForm();
commonForm.useRange = 'personal';
drugList.value = [createEmptyDrug()];
}
// 保存组套
function saveCommonForm() {
// 校验组套名称
if (!commonForm.name) {
ElMessage.warning('请填写组套名称');
return;
}
// 校验使用范围
if (!commonForm.useRange) {
ElMessage.warning('请选择使用范围');
return;
}
// 校验个人时选择使用人
if (commonForm.useRange === 'personal' && !commonForm.practitionerId) {
ElMessage.warning('请选择使用人');
return;
}
// 校验科室时选择科室
if (commonForm.useRange === 'department' && !commonForm.organizationId) {
ElMessage.warning('请选择科室');
return;
}
// 校验药品列表
if (drugList.value.length === 0) {
ElMessage.warning('请添加药品');
return;
}
// 校验每个药品
for (let i = 0; i < drugList.value.length; i++) {
const item = drugList.value[i];
if (!item.orderDefinitionId) {
ElMessage.warning(`请选择第${i + 1}个药品`);
return;
}
if (!item.quantity || Number(item.quantity) <= 0) {
ElMessage.warning(`请填写第${i + 1}个药品的数量`);
return;
}
}
// 生成组套ID时间戳
const groupId = Date.now();
// 构建详情列表
const detailList = drugList.value.map((item) => ({
orderDefinitionId: item.orderDefinitionId,
orderDefinitionTable: 'order_definition',
quantity: item.quantity,
unitCode: item.unitCode,
dose: item.quantity,
rateCode: commonForm.frequency,
dispensePerDuration: 7,
methodCode: commonForm.method,
doseQuantity: item.quantity,
groupId: groupId,
}));
// 构建请求数据
const data = {
name: commonForm.name,
tcmFlag: 1,
detailList: detailList,
};
// 根据使用范围添加不同字段
if (commonForm.useRange === 'personal') {
data.practitionerId = commonForm.practitionerId;
} else if (commonForm.useRange === 'department') {
data.organizationId = commonForm.organizationId;
}
// 全院不传 practitionerId 和 organizationId
// 调用对应接口
let saveApi;
if (commonForm.useRange === 'personal') {
saveApi = savePersonal;
} else if (commonForm.useRange === 'department') {
saveApi = saveDepartment;
} else {
saveApi = saveAll;
}
saveApi(data)
.then(() => {
ElMessage.success('保存成功');
loadTreeData();
})
.catch((err) => {
ElMessage.error('保存失败:' + (err.message || '未知错误'));
});
}
</script>
<style scoped>
.tcm-medical-order-set {
height: 90vh;
}
.full-height {
height: 100%;
}
.left-panel,
.right-panel {
height: 100%;
}
.tree-card {
height: 100%;
}
.right-panel {
display: flex;
flex-direction: column;
gap: 10px;
}
.form-card,
.detail-card {
width: 100%;
}
.panel-shadow {
border: 1px solid var(--el-border-color);
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.1);
}
.common-form {
display: flex;
flex-wrap: wrap;
gap: 10px 14px;
}
.common-bar {
display: flex;
align-items: center;
justify-content: space-between;
}
.common-item {
width: 300px;
margin-bottom: 0;
}
.common-item :deep(.el-input__wrapper),
.common-item :deep(.el-select__wrapper) {
height: 40px;
}
.common-item :deep(.el-input__inner) {
height: 40px;
}
.drug-card-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.drug-card {
width: 220px;
border: 1px solid var(--el-border-color);
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.12);
height: 120px;
}
.drug-card :deep(.el-card__body) {
padding: 10px 10px 8px;
}
.drug-card-body {
display: flex;
flex-direction: column;
height: 120px;
}
.drug-top {
flex: 0 0 auto;
}
.drug-divider {
margin: 6px 0;
border-top: 1px dashed var(--el-border-color);
}
.drug-bottom {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.drug-bottom-left {
flex: 1 1 auto;
min-width: 0;
}
.drug-card-form {
margin-top: 0;
}
.compact-item {
margin-bottom: 8px;
}
.compact-item :deep(.el-form-item__label) {
padding: 0 0 4px;
line-height: 1.2;
}
.compact-item :deep(.el-form-item__content) {
line-height: 1.2;
}
.quantity-wrap {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: nowrap;
}
.unit-select {
width: 80px;
}
.drug-select {
flex: 1;
min-width: 0;
}
.drug-edit-actions {
display: flex;
justify-content: flex-end;
gap: 0px;
padding-top: 2px;
flex-shrink: 0;
}
.drug-view-name {
font-weight: 600;
color: var(--el-text-color-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.drug-view-qty {
font-size: 13px;
color: var(--el-text-color-regular);
flex: none;
}
.drug-view-actions {
display: flex;
justify-content: flex-end;
gap: 6px;
flex-shrink: 0;
}
.drug-view-actions :deep(.el-button),
.drug-edit-actions :deep(.el-button) {
padding: 4px;
min-width: 0;
}
.add-card {
border-style: dashed;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-color-primary);
box-shadow: 0 3px 14px rgba(64, 158, 255, 0.18);
}
.add-card-content {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.add-icon {
font-size: 18px;
}
.empty-tip {
margin-top: 80px;
}
.demo-form-inline .el-input {
--el-input-width: 200px;
}
.demo-form-inline .el-select {
--el-select-width: 200px;
}
.el-form-item--default {
margin-bottom: 10px;
}
</style>

View File

@@ -1,33 +1,14 @@
<template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper">
<!-- 医保等级测试区域已隐藏 -->
<!--
<div style="margin-bottom: 20px; padding: 10px; border: 1px solid #ccc; background: #f5f5f5;">
<h3>医保等级测试</h3>
<div>
<div>1 -> {{ getMedicalInsuranceLevel({chrgitmLv: '1'}) }}</div>
<div>2 -> {{ getMedicalInsuranceLevel({chrgitmLv: '2'}) }}</div>
<div>3 -> {{ getMedicalInsuranceLevel({chrgitmLv: '3'}) }}</div>
<div> -> {{ getMedicalInsuranceLevel({chrgitmLv: '甲'}) }}</div>
<div> -> {{ getMedicalInsuranceLevel({chrgitmLv: '乙'}) }}</div>
<div> -> {{ getMedicalInsuranceLevel({chrgitmLv: '自'}) }}</div>
<div>甲类 -> {{ getMedicalInsuranceLevel({chrgitmLv: '甲类'}) }}</div>
<div>乙类 -> {{ getMedicalInsuranceLevel({chrgitmLv: '乙类'}) }}</div>
<div>自费 -> {{ getMedicalInsuranceLevel({chrgitmLv: '自费'}) }}</div>
</div>
</div>
-->
<!-- 使用Element Plus的虚拟滚动表格 -->
<el-table
ref="adviceBaseRef"
v-loading="loading"
height="400"
:data="filteredAdviceBaseList"
:data="adviceBaseList"
highlight-current-row
@current-change="handleCurrentChange"
row-key="adviceDefinitionId"
@cell-click="clickRow"
v-loading="loading"
>
<el-table-column label="名称" align="center" prop="adviceName" width="200" show-overflow-tooltip />
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" width="100" />
@@ -85,50 +66,9 @@
</template>
<script setup>
import { getCurrentInstance, nextTick, onMounted, ref, computed, watch } from 'vue';
import { nextTick, ref, watch, computed } from 'vue';
import { getAdviceBaseInfo, getDeviceList, getConsultationActivities } from './api';
import { throttle, debounce } from 'lodash-es';
const { proxy } = getCurrentInstance();
// 使用系统统一的数据字典
const { med_category_code, chrgitm_lv } = proxy.useDict('med_category_code', 'chrgitm_lv');
// 医保等级映射(作为后备使用)
const medicalInsuranceLevelMap = {
'1': '甲类',
'2': '乙类',
'3': '自费'
};
// 获取药品分类名称 - 使用系统统一的数据字典
function getCategoryName(row) {
if (!row) return '-';
if (row.adviceType === 1) { // 药品类型
// 优先使用系统统一的药品分类数据字典
if (med_category_code.value && med_category_code.value.length > 0) {
const found = med_category_code.value.find(item => String(item.value) === String(row.categoryCode));
if (found) {
return found.label;
}
}
// 兼容处理:对于中草药可能的多种编码形式
const herbCodes = ['3', '03', '4', 3, 4];
if (herbCodes.includes(row.categoryCode)) {
return '中草药';
}
return '-';
} else if (row.adviceType === 2 || row.adviceType === 4) { // 🔧 Bug Fix: 耗材类型2 或 4
return '耗材';
} else if (row.adviceType === 3) { // 诊疗类型
// 对于诊疗类型activityType 就是 category_code 的整数值,使用 activityType_dictText 显示
return row.activityType_dictText || row.categoryCode_dictText || row.categoryCode || '诊疗';
}
// 其他情况使用 categoryCode_dictText
return row.categoryCode_dictText || '-';
}
import { throttle } from 'lodash-es';
// 获取医保等级 - 简单直接的实现
function getMedicalInsuranceLevel(record) {
@@ -162,148 +102,121 @@ const props = defineProps({
type: Object,
required: true,
},
// 预加载的数据
preloadedData: {
type: Array,
default: () => []
},
// 预加载是否完成
preloadedLoaded: {
type: Boolean,
default: false
},
});
const emit = defineEmits(['selectAdviceBase']);
// 使用缓存机制
const searchCache = new Map();
const allAdviceData = ref([]); // 预加载数据
const isDataLoaded = ref(false); // 数据是否已加载
const adviceBaseList = ref([]);
const loading = ref(false);
const currentSelectRow = ref({});
const currentIndex = ref(0);
const tableWrapper = ref();
const loading = ref(false);
const isRequestInProgress = ref(false); // 请求进行中的标志
const adviceBaseRef = ref();
// 计算属性:根据搜索条件过滤数据
const filteredAdviceBaseList = computed(() => {
let result = [];
if (!props.adviceQueryParams.searchKey) {
result = adviceBaseList.value.slice(0, 50); // 返回前50个常用项目
} else {
const searchKey = props.adviceQueryParams.searchKey.toLowerCase();
result = adviceBaseList.value.filter(item =>
item.adviceName.toLowerCase().includes(searchKey) ||
item.py_str?.toLowerCase().includes(searchKey) ||
item.wb_str?.toLowerCase().includes(searchKey)
).slice(0, 100); // 限制返回数量
}
// 过滤无库存的药品(只针对药品类型 adviceType === 1
// Bug #129 修复确保库存为0的药品不被检索出来
result = result.filter(item => {
if (item.adviceType === 1) {
// 检查是否有库存
if (item.inventoryList && item.inventoryList.length > 0) {
// 计算总库存数量,确保转换为数字进行正确计算
const totalQuantity = item.inventoryList.reduce((sum, inv) => {
const qty = inv.quantity !== undefined && inv.quantity !== null
? (typeof inv.quantity === 'number' ? inv.quantity : Number(inv.quantity) || 0)
: 0;
return sum + qty;
}, 0);
return totalQuantity > 0;
}
return false; // 无库存列表或库存为空,视为无库存
}
return true; // 非药品类型不过滤
});
return result;
// 查询参数 - 与后端API参数名一致
const queryParams = ref({
pageSize: 100,
pageNo: 1,
adviceTypes: '1,2,3',
});
// 预加载数据
async function preloadData() {
if (isDataLoaded.value) return;
try {
const queryParams = {
pageSize: 10000, // 加载更多数据用于本地搜索
pageNum: 1,
adviceTypes: '1,2,3',
organizationId: props.patientInfo.orgId
};
const res = await getAdviceBaseInfo(queryParams);
allAdviceData.value = res.data?.records || [];
isDataLoaded.value = true;
} catch (error) {
console.error('预加载数据失败:', error);
}
}
// 节流函数 - 增强防抖机制
// 节流函数 - 与V1.3一致300ms首次立即响应
const throttledGetList = throttle(
async () => {
// 只有在没有进行中的请求时才执行
if (!isRequestInProgress.value) {
await getList();
}
() => {
getList();
},
500, // 增加到500ms减少频繁请求
{ leading: false, trailing: true }
300,
{ leading: true, trailing: true }
);
// 监听参数变化 - 使用防抖优化
// 监听参数变化 - 优化:优先使用预加载数据
watch(
() => props.adviceQueryParams,
(newValue) => {
// 只有在搜索关键词长度达到一定要求时才触发搜索
if (newValue.searchKey && newValue.searchKey.length < 2) {
adviceBaseList.value = [];
return;
queryParams.value.searchKey = newValue.searchKey;
if (newValue.adviceType) {
queryParams.value.adviceTypes = [newValue.adviceType].join(',');
} else {
queryParams.value.adviceTypes = '1,2,3';
}
throttledGetList();
},
{ deep: true }
);
// 获取数据
async function getList() {
// 防止重复请求
if (isRequestInProgress.value) {
return; // 如果请求正在进行中,直接返回
}
// 设置请求进行中的标志
isRequestInProgress.value = true;
loading.value = true; // 显示加载状态
try {
// 生成缓存键
const cacheKey = `${props.adviceQueryParams.searchKey}_${props.adviceQueryParams.adviceTypes}_${props.adviceQueryParams.categoryCode}_${props.patientInfo.orgId}`;
// 检查缓存
if (searchCache.has(cacheKey)) {
const cachedData = searchCache.get(cacheKey);
if (Date.now() - cachedData.timestamp < 300000) { // 5分钟有效期
adviceBaseList.value = cachedData.data;
return;
}
// 获取数据 - 优化:优先使用预加载数据
function getList() {
const searchKey = queryParams.value.searchKey || '';
const adviceTypes = queryParams.value.adviceTypes || '1,2,3';
// 判断是否可以使用预加载数据
// 条件:预加载数据已就绪,且是普通药品/诊疗类型(adviceTypes包含1,2,3),且没有搜索关键词或搜索关键词很短
const canUsePreloaded = props.preloadedLoaded &&
props.preloadedData &&
props.preloadedData.length > 0 &&
(adviceTypes === '1,2,3' || adviceTypes === '1' || adviceTypes === '2' || adviceTypes === '3');
if (canUsePreloaded) {
// 使用预加载数据
console.log('[adviceBaseList] 使用预加载数据,共', props.preloadedData.length, '条');
let filteredData = [...props.preloadedData]; // 创建副本避免修改原数据
// 根据 adviceTypes 过滤
if (adviceTypes !== '1,2,3') {
const types = adviceTypes.split(',').map(t => parseInt(t));
filteredData = filteredData.filter(item => types.includes(item.adviceType));
}
// 根据搜索关键词过滤
if (searchKey && searchKey.length >= 1) {
const lowerSearchKey = searchKey.toLowerCase();
filteredData = filteredData.filter(item =>
(item.adviceName && item.adviceName.toLowerCase().includes(lowerSearchKey)) ||
(item.adviceDefinitionId && item.adviceDefinitionId.toString().includes(searchKey))
);
}
adviceBaseList.value = filteredData;
nextTick(() => {
currentIndex.value = 0;
});
// 如果有搜索关键词,还需要后台请求更精确的结果
if (searchKey && searchKey.length >= 2) {
fetchFromApi(searchKey);
}
return;
}
// 不能使用预加载数据直接请求API
fetchFromApi(searchKey);
}
const queryParams = {
pageSize: 1000, // 增加页面大小以适应虚拟滚动
pageNum: 1,
adviceTypes: props.adviceQueryParams.adviceTypes || '1,2,3',
searchKey: props.adviceQueryParams.searchKey || '',
categoryCode: props.adviceQueryParams.categoryCode || '',
organizationId: props.patientInfo.orgId
};
// 从API获取数据
function fetchFromApi(searchKey) {
loading.value = true;
queryParams.value.organizationId = props.patientInfo.orgId;
const isConsumables = queryParams.value.adviceTypes === '2';
const isConsultation = queryParams.value.adviceTypes === '5';
const isConsumables = queryParams.adviceTypes === '2' || queryParams.adviceTypes === 2;
const isConsultation = queryParams.adviceTypes === '5' || queryParams.adviceTypes === 5;
if (isConsultation) {
// 会诊类型:调用会诊项目接口
const res = await getConsultationActivities();
if (isConsultation) {
// 会诊类型
getConsultationActivities().then((res) => {
if (res.data && Array.isArray(res.data)) {
const result = res.data.map((item) => ({
adviceBaseList.value = res.data.map((item) => ({
adviceName: item.name || item.activityName,
adviceType: 5, // 会诊类型
unitCode: '111', // 次
adviceType: 5,
unitCode: '111',
unitCode_dictText: '次',
minUnitCode: '111',
minUnitCode_dictText: '次',
@@ -322,34 +235,32 @@ async function getList() {
injectFlag_enumText: '否',
skinTestFlag: 0,
skinTestFlag_enumText: '否',
categoryCode: 31, // 会诊的category_enum
categoryCode: 31,
unitPrice: item.price || 0,
...item,
}));
// 缓存结果
searchCache.set(cacheKey, {
data: result,
timestamp: Date.now()
nextTick(() => {
currentIndex.value = 0;
});
adviceBaseList.value = result;
} else {
adviceBaseList.value = [];
}
} else if (isConsumables) {
const deviceQueryParams = {
pageNo: queryParams.pageNum || 1,
pageSize: queryParams.pageSize || 1000,
searchKey: queryParams.searchKey || '',
statusEnum: 2,
};
}).finally(() => {
loading.value = false;
});
return;
}
const res = await getDeviceList(deviceQueryParams);
if (isConsumables) {
// 耗材类型
getDeviceList({
pageNo: 1,
pageSize: 100,
searchKey: searchKey || '',
statusEnum: 2,
}).then((res) => {
if (res.data && res.data.records) {
const result = res.data.records.map((item) => ({
adviceBaseList.value = res.data.records.map((item) => ({
adviceName: item.name || item.busNo,
adviceType: 4, // 🔧 Bug #147 修复前端耗材类型值为4保存时会转换为后端类型值2
adviceType: 4,
unitCode: item.unitCode || '',
unitCode_dictText: item.unitCode_dictText || '',
minUnitCode: item.minUnitCode || item.unitCode || '',
@@ -359,7 +270,7 @@ async function getList() {
priceList: item.price ? [{ price: item.price }] : (item.retailPrice ? [{ price: item.retailPrice }] : []),
inventoryList: [],
adviceDefinitionId: item.id,
adviceTableName: 'adm_device_definition', // 🔧 Bug #177 修复:添加耗材表名,用于后端库存匹配
adviceTableName: 'adm_device_definition',
chargeItemDefinitionId: item.id,
positionId: item.locationId,
positionName: item.locationId_dictText || '',
@@ -375,66 +286,41 @@ async function getList() {
deviceName: item.name,
...item,
}));
// 缓存结果
searchCache.set(cacheKey, {
data: result,
timestamp: Date.now()
nextTick(() => {
currentIndex.value = 0;
});
adviceBaseList.value = result;
} else {
adviceBaseList.value = [];
}
} else {
const res = await getAdviceBaseInfo(queryParams);
let result = res.data?.records || [];
}).finally(() => {
loading.value = false;
});
return;
}
// 缓存结果
searchCache.set(cacheKey, {
data: result,
timestamp: Date.now()
// 普通药品/诊疗 - 与V1.3一致的处理方式
getAdviceBaseInfo(queryParams.value).then((res) => {
if (res.data && res.data.records && res.data.records.length > 0) {
// 与V1.3一致:在获取数据后直接过滤无库存的药品
adviceBaseList.value = res.data.records.filter((item) => {
if (item.adviceType == 1 || item.adviceType == 2) {
return handleQuantity(item) != 0;
} else {
return true;
}
});
nextTick(() => {
currentIndex.value = 0;
});
adviceBaseList.value = result;
}
} catch (error) {
console.error('获取数据失败:', error);
adviceBaseList.value = [];
} finally {
// 无论成功或失败,都要清除请求标志和加载状态
isRequestInProgress.value = false;
}).finally(() => {
loading.value = false;
}
});
}
// 格式化剂量显示
function formatDose(item) {
if (!item.dose || isNaN(parseFloat(item.dose))) {
return '-';
}
const num = parseFloat(item.dose);
if (num.toFixed(2) === '0.00') {
return '-';
}
return num.toFixed(2) + (item.doseUnitCode_dictText || '');
}
// 从priceList列表中获取价格
// 从priceList列表中获取价格 - 与V1.3一致
function getPriceFromInventory(row) {
if (row.priceList && row.priceList.length > 0) {
const price = row.priceList[0].price;
// 检查价格是否为有效数字
if (price !== undefined && price !== null && !isNaN(price) && isFinite(price)) {
return Number(price).toFixed(2) + ' 元';
}
// 如果价格无效,尝试从其他可能的字段获取价格
if (row.totalPrice !== undefined && row.totalPrice !== null && !isNaN(row.totalPrice) && isFinite(row.totalPrice)) {
return Number(row.totalPrice).toFixed(2) + ' 元';
}
if (row.price !== undefined && row.price !== null && !isNaN(row.price) && isFinite(row.price)) {
return Number(row.price).toFixed(2) + ' 元';
}
const price = row.priceList[0].price || 0;
return Number(price).toFixed(2) + ' 元';
}
return '-';
}
@@ -442,31 +328,29 @@ function getPriceFromInventory(row) {
function handleQuantity(row) {
if (row.inventoryList && row.inventoryList.length > 0) {
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + (row.minUnitCode_dictText || '');
return totalQuantity.toString() + row.minUnitCode_dictText;
}
return '0';
return 0;
}
// 处理键盘事件
// 处理键盘事件 - 与V1.3一致
const handleKeyDown = (event) => {
const key = event.key;
const data = filteredAdviceBaseList.value;
if (data.length === 0) return;
const data = adviceBaseList.value;
switch (key) {
case 'ArrowUp': // 上箭头
event.preventDefault();
if (currentIndex.value > 0) {
currentIndex.value--;
currentSelectRow.value = data[currentIndex.value];
setCurrentRow(data[currentIndex.value]);
}
break;
case 'ArrowDown': // 下箭头
event.preventDefault();
if (currentIndex.value < data.length - 1) {
currentIndex.value++;
currentSelectRow.value = data[currentIndex.value];
setCurrentRow(data[currentIndex.value]);
}
break;
case 'Enter': // 回车键
@@ -478,9 +362,19 @@ const handleKeyDown = (event) => {
}
};
// 当前行变化时更新索引
// 设置选中行(带滚动)- 与V1.3一致
const setCurrentRow = (row) => {
adviceBaseRef.value.setCurrentRow(row);
const tableBody = adviceBaseRef.value.$el.querySelector('.el-table__body-wrapper');
const currentRowEl = adviceBaseRef.value.$el.querySelector('.current-row');
if (tableBody && currentRowEl) {
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
};
// 当前行变化时更新索引 - 与V1.3一致
const handleCurrentChange = (currentRow) => {
currentIndex.value = filteredAdviceBaseList.value.findIndex((item) => item.adviceDefinitionId === currentRow.adviceDefinitionId);
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow);
currentSelectRow.value = currentRow;
};
@@ -489,12 +383,6 @@ function clickRow(row) {
emit('selectAdviceBase', row);
}
// 初始化
onMounted(() => {
// preloadData(); // 预加载数据
getList(); // 获取初始数据
});
defineExpose({
handleKeyDown,
});
@@ -502,4 +390,4 @@ defineExpose({
<style scoped>
/* 保留原有的表格样式 */
</style>
</style>

View File

@@ -780,6 +780,48 @@ export function getOrderGroup(data) {
});
}
/**
* 保存组套
* @param {Object} data - 组套数据,包含 rangeCode 字段
*/
export function saveOrderGroup(data) {
// 根据使用范围选择不同的接口
let url = '/personalization/orders-group-package/save-personal';
console.log('[saveOrderGroup] data.rangeCode:', data?.rangeCode);
if (data.rangeCode === 2) {
url = '/personalization/orders-group-package/save-organization';
} else if (data.rangeCode === 3) {
url = '/personalization/orders-group-package/save-hospital';
}
console.log('[saveOrderGroup] 使用的URL:', url);
return request({
url: url,
method: 'post',
data: data,
});
}
/**
* 删除组套
*/
export function deleteGroup(data) {
return request({
url: '/personalization/orders-group-package/group-package-detail?groupPackageId=' + data.groupPackageId,
method: 'delete',
});
}
/**
* 查询组套详情
*/
export function queryGroupDetail(data) {
return request({
url: '/personalization/orders-group-package/get-detail',
method: 'get',
params: data,
});
}
/**
* 查询项目绑定信息
*/

View File

@@ -1,38 +1,310 @@
<template>
<el-drawer v-model="drawer" title="组套信息" direction="ltr">
<div style="margin: 10px 0px">
<el-input
v-model="queryParams.searchKey"
placeholder="请输入组套信息"
clearable
style="width: 45%; margin-bottom: -6px; margin-right: 50px"
@keyup.enter="getList"
<el-drawer v-model="drawer" title="医嘱组套" direction="ltr" size="700px">
<!-- 列表视图 -->
<div v-if="!editMode">
<div style="margin: 10px 0px">
<el-input
v-model="queryParams.searchKey"
placeholder="请输入组套名称搜索"
clearable
style="width: 45%; margin-bottom: -6px; margin-right: 20px"
@keyup.enter="handleSearch"
>
<template #append>
<el-button icon="Search" @click="handleSearch" />
</template>
</el-input>
<el-radio-group v-model="queryParams.rangeCode" @change="handelRadioChange">
<el-radio-button :label="1">个人</el-radio-button>
<el-radio-button :label="2">科室</el-radio-button>
<el-radio-button :label="3">全院</el-radio-button>
</el-radio-group>
<el-button
type="primary"
style="float: right"
@click="handleManageGroup"
v-if="checkPermi(['personalization:ordersGroupPackage:manage'])"
>
管理组套
</el-button>
</div>
<el-table
:data="filteredOrderList"
highlight-current-row
v-loading="loading"
border
stripe
style="margin-top: 10px"
>
<template #append>
<el-button icon="Search" @click="getList" />
</template>
</el-input>
<el-radio-group v-model="queryParams.rangeCode" @change="handelRadioChange">
<el-radio-button :label="1">个人</el-radio-button>
<el-radio-button :label="2">科室</el-radio-button>
<el-radio-button :label="3">全院</el-radio-button>
</el-radio-group>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="组套名称" align="left" prop="name" min-width="180" show-overflow-tooltip>
<template #default="scope">
<el-tooltip content="点击进入编辑" placement="top">
<span
style="cursor: pointer; color: #409eff; font-weight: 500; text-decoration: underline;"
@click="handleEditGroup(scope.row)"
>
{{ scope.row.name }}
</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="使用范围" align="center" prop="rangeCode_dictText" width="90" />
<el-table-column label="明细数量" align="center" width="80">
<template #default="scope">
<el-tag size="small" type="info">{{ scope.row.detailList?.length || 0 }} </el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handlePreviewGroup(scope.row)">
预览
</el-button>
<el-button type="success" link @click="handleUseOrderGroup(scope.row)">
<el-icon><Select /></el-icon>
应用
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="filteredOrderList.length === 0 && !loading" style="text-align: center; padding: 40px; color: #909399;">
<el-icon :size="48" style="margin-bottom: 10px"><Box /></el-icon>
<p>暂无组套数据</p>
<p style="font-size: 12px; margin-top: 5px;">您可以在"个性化设置 > 医嘱组套"中创建组套</p>
</div>
</div>
<el-table :data="orderList">
<el-table-column label="组套名称" align="center" prop="name" />
<!-- <el-table-column label="组套类型" align="center" prop="typeEnum_enumText" /> -->
<el-table-column label="使用范围" align="center" prop="rangeCode_dictText" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" link @click="handleUseOrderGroup(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<!-- 编辑视图 -->
<div v-else>
<!-- 返回按钮 -->
<div style="margin-bottom: 15px; border-bottom: 1px solid #ebeef5; padding-bottom: 10px;">
<el-button @click="handleCancelEdit">
<el-icon><ArrowLeft /></el-icon>
返回列表
</el-button>
<span style="margin-left: 15px; font-weight: 600; font-size: 16px;">编辑组套</span>
<el-button type="primary" style="float: right" @click="handleSaveGroup" :loading="saving">
<el-icon><Check /></el-icon>
保存组套
</el-button>
</div>
<!-- 基本信息 -->
<div style="margin-bottom: 20px;">
<h4 style="margin: 0 0 15px 0; color: #606266;">基本信息</h4>
<el-form :model="editingGroup" label-width="100px" :inline="true">
<el-form-item label="组套名称" required>
<el-input
v-model="editingGroup.name"
placeholder="请输入组套名称"
style="width: 220px"
clearable
/>
</el-form-item>
<el-form-item label="组套类型">
<el-select v-model="editingGroup.typeEnum" placeholder="请选择" style="width: 150px">
<el-option label="医嘱组套" :value="1" />
<el-option label="诊疗组套" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="使用范围">
<el-select v-model="editingGroup.rangeCode" placeholder="请选择" style="width: 150px">
<el-option label="个人" :value="1" />
<el-option label="科室" :value="2" />
<el-option label="全院" :value="3" />
</el-select>
</el-form-item>
</el-form>
</div>
<!-- 项目列表 -->
<div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h4 style="margin: 0; color: #606266;">
项目列表
<el-tag size="small" type="info">{{ editingGroup.detailList?.length || 0 }} </el-tag>
</h4>
<el-button type="primary" link @click="handleAddItem">
<el-icon><Plus /></el-icon>
添加项目
</el-button>
</div>
<el-table :data="editingGroup.detailList" border size="small" v-loading="detailLoading">
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column label="医嘱名称" prop="orderDefinitionName" min-width="150" show-overflow-tooltip />
<el-table-column label="数量" width="80" align="center">
<template #default="scope">
<el-input-number
v-model="scope.row.quantity"
:min="1"
:controls="false"
size="small"
style="width: 60px"
/>
</template>
</el-table-column>
<el-table-column label="单位" prop="unitCodeName" width="60" align="center" />
<el-table-column label="用法" width="100" align="center">
<template #default="scope">
<el-select
v-model="scope.row.methodCode"
placeholder="用法"
size="small"
filterable
clearable
>
<el-option
v-for="dict in method_code"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="频次" width="100" align="center">
<template #default="scope">
<el-select
v-model="scope.row.rateCode"
placeholder="频次"
size="small"
filterable
clearable
>
<el-option
v-for="dict in rate_code"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center" fixed="right">
<template #default="scope">
<el-button type="danger" link size="small" @click="handleRemoveItem(scope.$index)">
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="!editingGroup.detailList || editingGroup.detailList.length === 0"
style="text-align: center; padding: 30px; color: #909399; border: 1px dashed #dcdfe6; margin-top: 10px;">
<p>暂无项目点击"添加项目"按钮添加</p>
</div>
</div>
</div>
<!-- 组套预览对话框 -->
<el-dialog v-model="previewVisible" title="组套预览" width="600px" append-to-body>
<div v-if="currentGroup">
<h4>{{ currentGroup.name }}</h4>
<p style="color: #909399; font-size: 14px;">
使用范围{{ currentGroup.rangeCode_dictText }} |
包含 {{ currentGroup.detailList?.length || 0 }} 个医嘱项
</p>
<el-table :data="currentGroup.detailList" border size="small" style="margin-top: 15px;">
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column label="医嘱名称" prop="orderDefinitionName" min-width="180" show-overflow-tooltip />
<el-table-column label="数量" prop="quantity" width="80" align="center" />
<el-table-column label="单位" prop="unitCodeName" width="80" align="center" />
<el-table-column label="用法" prop="methodCode_dictText" width="100" align="center" />
<el-table-column label="频次" prop="rateCode_dictText" width="100" align="center" />
</el-table>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="previewVisible = false">取消</el-button>
<el-button type="primary" @click="confirmUseGroup">应用到当前患者</el-button>
</span>
</template>
</el-dialog>
<!-- 添加项目对话框 -->
<el-dialog v-model="addItemVisible" title="添加医嘱项目" width="800px" append-to-body>
<div style="margin-bottom: 15px;">
<el-input
v-model="itemSearchKey"
placeholder="输入医嘱名称或拼音码搜索"
clearable
@keyup.enter="searchAdviceItems"
style="width: 300px"
>
<template #append>
<el-button icon="Search" @click="searchAdviceItems" />
</template>
</el-input>
</div>
<el-table
:data="adviceItemList"
border
highlight-current-row
@current-change="handleItemSelect"
v-loading="itemLoading"
max-height="400"
>
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column label="医嘱名称" prop="adviceName" min-width="150" show-overflow-tooltip />
<el-table-column label="类型" prop="adviceType_dictText" width="80" align="center" />
<el-table-column label="规格" prop="volume" width="100" show-overflow-tooltip />
<el-table-column label="单价" width="80" align="right">
<template #default="scope">
{{ scope.row.unitPrice?.toFixed(2) || '-' }}
</template>
</el-table-column>
<el-table-column label="单位" prop="unitCode_dictText" width="60" align="center" />
</el-table>
<div v-if="adviceItemList.length === 0 && !itemLoading"
style="text-align: center; padding: 30px; color: #909399;">
<p>请输入关键词搜索医嘱项目</p>
</div>
<!-- 选中项目的配置 -->
<div v-if="selectedItem" style="margin-top: 15px; padding: 15px; background: #f5f7fa; border-radius: 4px;">
<h4 style="margin: 0 0 10px 0;">配置项目{{ selectedItem.adviceName }}</h4>
<el-form :inline="true" label-width="80px">
<el-form-item label="数量">
<el-input-number v-model="newItemConfig.quantity" :min="1" :controls="false" />
</el-form-item>
<el-form-item label="用法">
<el-select v-model="newItemConfig.methodCode" placeholder="用法" filterable clearable style="width: 120px">
<el-option v-for="dict in method_code" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="频次">
<el-select v-model="newItemConfig.rateCode" placeholder="频次" filterable clearable style="width: 120px">
<el-option v-for="dict in rate_code" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="addItemVisible = false">取消</el-button>
<el-button type="primary" @click="confirmAddItem" :disabled="!selectedItem">
添加到组套
</el-button>
</span>
</template>
</el-dialog>
</el-drawer>
</template>
<script setup>
import {getOrderGroup} from '../api';
import { getOrderGroup, getAdviceBaseInfo, saveOrderGroup } from '../api';
import { Select, Box, ArrowLeft, Check, Plus, Delete } from '@element-plus/icons-vue';
import { checkPermi } from '@/utils/permission';
import { ElMessage } from 'element-plus';
const { proxy } = getCurrentInstance();
const { method_code, rate_code } = proxy.useDict('method_code', 'rate_code');
const props = defineProps({
diagnosis: {
@@ -40,52 +312,364 @@ const props = defineProps({
required: true,
},
organizationId: {
type: String,
type: [String, Number],
required: true,
},
patientInfo: {
type: Object,
default: () => ({})
}
});
const drawer = ref(false);
const orderList = ref([]);
const result = ref([]);
const loading = ref(false);
const previewVisible = ref(false);
const currentGroup = ref(null);
// 编辑模式相关
const editMode = ref(false);
const editingGroup = ref({});
const saving = ref(false);
const detailLoading = ref(false);
// 添加项目相关
const addItemVisible = ref(false);
const itemSearchKey = ref('');
const adviceItemList = ref([]);
const itemLoading = ref(false);
const selectedItem = ref(null);
const newItemConfig = ref({
quantity: 1,
methodCode: '',
rateCode: ''
});
const emit = defineEmits(['useOrderGroup']);
const queryParams = ref({
typeEnum: 1,
searchKey: '',
rangeCode: 2,
});
function handleOpen() {
// 根据搜索关键字过滤组套列表
const filteredOrderList = computed(() => {
if (!queryParams.value.searchKey) {
return orderList.value;
}
const keyword = queryParams.value.searchKey.toLowerCase();
return orderList.value.filter(item =>
item.name && item.name.toLowerCase().includes(keyword)
);
});
function handleOpen(group = null, scope = 'personal') {
drawer.value = true;
// 设置范围
if (scope === 'personal') {
queryParams.value.rangeCode = 1;
} else if (scope === 'department') {
queryParams.value.rangeCode = 2;
} else if (scope === 'hospital') {
queryParams.value.rangeCode = 3;
}
// 如果有传入组套数据,进入编辑模式
if (group && group.groupPackageId) {
editMode.value = true;
editingGroup.value = JSON.parse(JSON.stringify(group));
if (!editingGroup.value.detailList) {
editingGroup.value.detailList = [];
}
} else {
editMode.value = false;
}
getList();
}
function handleSearch() {
console.log('搜索组套:', queryParams.value.searchKey);
}
function handelRadioChange(value) {
queryParams.value.rangeCode = value;
switch (value) {
case 1:
orderList.value = result.value.personalList;
orderList.value = result.value.personalList || [];
break;
case 2:
orderList.value = result.value.organizationList;
orderList.value = result.value.organizationList || [];
break;
case 3:
orderList.value = result.value.hospitalList;
orderList.value = result.value.hospitalList || [];
break;
}
}
// 进入编辑模式
function handleEditGroup(row) {
editMode.value = true;
// 深拷贝组套数据,避免直接修改原数据
editingGroup.value = JSON.parse(JSON.stringify(row));
// 确保 detailList 存在
if (!editingGroup.value.detailList) {
editingGroup.value.detailList = [];
}
}
// 取消编辑
function handleCancelEdit() {
editMode.value = false;
editingGroup.value = {};
}
// 保存组套
async function handleSaveGroup() {
if (!editingGroup.value.name?.trim()) {
ElMessage.warning('请输入组套名称');
return;
}
saving.value = true;
try {
// 构建保存数据
const saveData = {
groupPackageId: editingGroup.value.groupPackageId || editingGroup.value.id,
name: editingGroup.value.name,
packageTypeEnum: editingGroup.value.typeEnum || 1,
rangeCode: editingGroup.value.rangeCode,
detailList: editingGroup.value.detailList?.map((item, index) => ({
id: item.id,
orderDefinitionId: item.orderDefinitionId,
orderDefinitionName: item.orderDefinitionName,
quantity: item.quantity,
unitCode: item.unitCode,
unitCodeName: item.unitCodeName,
methodCode: item.methodCode,
rateCode: item.rateCode,
dose: item.dose,
doseQuantity: item.doseQuantity,
dispensePerDuration: item.dispensePerDuration,
sortOrder: index
})) || []
};
const res = await saveOrderGroup(saveData);
if (res.code === 200) {
ElMessage.success('保存成功');
editMode.value = false;
editingGroup.value = {};
// 刷新列表
getList();
} else {
ElMessage.error(res.message || '保存失败');
}
} catch (error) {
console.error('保存组套失败:', error);
ElMessage.error('保存失败:' + (error.message || '未知错误'));
} finally {
saving.value = false;
}
}
// 打开添加项目对话框
function handleAddItem() {
addItemVisible.value = true;
itemSearchKey.value = '';
adviceItemList.value = [];
selectedItem.value = null;
newItemConfig.value = {
quantity: 1,
methodCode: '',
rateCode: ''
};
}
// 搜索医嘱项目
async function searchAdviceItems() {
console.log('点击搜索按钮, itemSearchKey:', itemSearchKey.value, 'organizationId:', props.organizationId);
if (!itemSearchKey.value.trim()) {
ElMessage.warning('请输入搜索关键词');
return;
}
// 确保 organizationId 是数字类型
const orgId = Number(props.organizationId) || null;
itemLoading.value = true;
try {
const params = {
searchKey: itemSearchKey.value,
organizationId: orgId,
pageNo: 1,
pageSize: 50,
adviceTypes: '1,2,3'
};
console.log('搜索参数:', params);
const res = await getAdviceBaseInfo(params);
console.log('搜索返回结果:', res);
adviceItemList.value = res.data?.records || res.rows || [];
} catch (error) {
console.error('搜索医嘱项目失败:', error);
ElMessage.error('搜索失败: ' + (error.message || '未知错误'));
} finally {
itemLoading.value = false;
}
}
// 选择医嘱项目
function handleItemSelect(row) {
selectedItem.value = row;
// 设置默认值
newItemConfig.value = {
quantity: 1,
methodCode: row?.methodCode || '',
rateCode: row?.rateCode || ''
};
}
// 确认添加项目到组套
function confirmAddItem() {
if (!selectedItem.value) return;
const newItem = {
orderDefinitionId: selectedItem.value.adviceDefinitionId,
orderDefinitionName: selectedItem.value.adviceName,
quantity: newItemConfig.value.quantity,
unitCode: selectedItem.value.unitCode,
unitCodeName: selectedItem.value.unitCode_dictText,
methodCode: newItemConfig.value.methodCode,
rateCode: newItemConfig.value.rateCode,
dose: selectedItem.value.dose,
doseQuantity: selectedItem.value.doseQuantity,
dispensePerDuration: selectedItem.value.dispensePerDuration,
// 保留医嘱库完整信息用于后续应用
orderDetailInfos: selectedItem.value
};
if (!editingGroup.value.detailList) {
editingGroup.value.detailList = [];
}
editingGroup.value.detailList.push(newItem);
addItemVisible.value = false;
ElMessage.success('添加成功');
}
// 移除项目
function handleRemoveItem(index) {
editingGroup.value.detailList.splice(index, 1);
}
// 单击应用按钮
function handleUseOrderGroup(row) {
emit('useOrderGroup', row.detailList);
if (!row.detailList || row.detailList.length === 0) {
ElMessage.warning('该组套没有明细项');
return;
}
// 🔧 数据预处理:确保每个明细项都有完整的医嘱信息
const processedDetailList = row.detailList.map(item => {
const orderDetail = item.orderDetailInfos || {};
return {
// 组套明细字段
...item,
// 医嘱库字段(可能为空,需要兜底)
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
adviceType: orderDetail.adviceType,
adviceDefinitionId: item.orderDefinitionId || orderDetail.adviceDefinitionId,
// 价格和库存
unitPrice: orderDetail.unitPrice,
minUnitPrice: orderDetail.minUnitPrice,
inventoryList: orderDetail.inventoryList || [],
priceList: orderDetail.priceList || [],
partPercent: orderDetail.partPercent || 1,
positionId: orderDetail.positionId,
defaultLotNumber: orderDetail.defaultLotNumber,
// 单位信息
unitCode: item.unitCode || orderDetail.unitCode,
unitCodeName: item.unitCodeName || orderDetail.unitCode_dictText,
minUnitCode: orderDetail.minUnitCode,
doseUnitCode: orderDetail.doseUnitCode,
// 合并后的完整对象(用于 setValue
mergedDetail: {
...orderDetail,
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
adviceType: orderDetail.adviceType,
quantity: item.quantity,
unitCode: item.unitCode || orderDetail.unitCode,
unitCodeName: item.unitCodeName,
dose: item.dose || orderDetail.dose,
rateCode: item.rateCode || orderDetail.rateCode,
methodCode: item.methodCode || orderDetail.methodCode,
dispensePerDuration: item.dispensePerDuration || orderDetail.dispensePerDuration,
doseQuantity: item.doseQuantity,
inventoryList: orderDetail.inventoryList || [],
priceList: orderDetail.priceList || [],
partPercent: orderDetail.partPercent || 1,
positionId: orderDetail.positionId,
defaultLotNumber: orderDetail.defaultLotNumber,
}
};
});
emit('useOrderGroup', processedDetailList);
drawer.value = false;
editMode.value = false;
}
// 预览组套
function handlePreviewGroup(row) {
currentGroup.value = row;
previewVisible.value = true;
}
// 确认应用组套(从预览对话框)
function confirmUseGroup() {
if (currentGroup.value) {
handleUseOrderGroup(currentGroup.value);
previewVisible.value = false;
}
}
// 跳转到组套管理页面
function handleManageGroup() {
window.open('/basicmanage/ordersCombination', '_blank');
}
function getList() {
getOrderGroup({ organizationId: props.organizationId }).then((res) => {
result.value = res.data;
handelRadioChange(queryParams.value.rangeCode);
});
loading.value = true;
getOrderGroup({ organizationId: props.organizationId })
.then((res) => {
result.value = res.data || {};
handelRadioChange(queryParams.value.rangeCode);
})
.catch((err) => {
console.error('获取组套列表失败:', err);
ElMessage.error('获取组套列表失败');
})
.finally(() => {
loading.value = false;
});
}
defineExpose({
handleOpen,
});
</script>
</script>
<style scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div style="width: 100%">
<div style="width: 100%; display: flex; flex-direction: column;">
<div style="margin-bottom: 5px">
<el-button type="primary" @click="handleAddPrescription()" :disabled="false">
新增
@@ -7,8 +7,8 @@
<el-button type="primary" @click="handleSaveBatch()" :disabled="isSaving"> 保存 </el-button>
<el-button type="primary" @click="handleSave()" :disabled="isSaving"> 签发 </el-button>
<el-button type="warning" plain @click="handleSingOut()" :disabled="false"> 撤回 </el-button>
<el-button type="primary" plain @click="proxy.$refs.orderGroupRef.handleOpen()" :disabled="false">
组套
<el-button type="primary" plain @click="openOrderSetDialog('personal')" :disabled="false">
另存组套
</el-button>
<el-button type="primary" plain :disabled="false" @click="proxy.$refs.prescriptionHistoryRef.handleOpen()">
历史
@@ -49,9 +49,141 @@
刷新
</el-button>
</div>
<el-table max-height="650" ref="prescriptionRef" :data="prescriptionList" row-key="uniqueKey" border
@cell-click="clickRow" @row-dblclick="clickRowDb" v-loading="loading" :expand-row-keys="expandOrder"
@select="handleSelectionChange">
<!-- V1.3 风格侧边栏组套列表 -->
<div class="order-group-container">
<div class="order-group-sidebar">
<div class="order-group-section">
<div class="order-group-section-header" @click="toggleOrderGroupSection('personal')">
<span>个人</span>
<el-icon @click.stop="toggleOrderGroupSection('personal')">
<Minus v-if="personalOrderGroupsExpanded" />
<Plus v-else />
</el-icon>
</div>
<div v-show="personalOrderGroupsExpanded" class="order-group-section-body">
<div
v-for="item in personalOrderGroups"
:key="item.id || item.groupPackageId || item.name"
class="order-group-item"
@dblclick="handleClickOrderGroup(item)"
@contextmenu.prevent="handleOrderGroupContextMenu($event, item, 'personal')"
>
<span class="order-group-item-name">{{ item.name }}</span>
<div class="order-group-item-actions" @click.stop>
<el-icon
v-if="hasGroupPermission(item, 'personal')"
class="order-group-action-icon"
@click="handleEditOrderGroup(item, 'personal')"
title="编辑"
>
<Edit />
</el-icon>
<el-icon
v-if="hasGroupPermission(item, 'personal')"
class="order-group-action-icon"
@click="handleDeleteOrderGroup(item, 'personal')"
title="删除"
>
<Delete />
</el-icon>
</div>
</div>
<div class="order-group-add" @click="openOrderSetDialog('personal')">
<el-icon><Plus /></el-icon>
添加
</div>
</div>
</div>
<div class="order-group-section">
<div class="order-group-section-header" @click="toggleOrderGroupSection('department')">
<span>科室</span>
<el-icon @click.stop="toggleOrderGroupSection('department')">
<Minus v-if="deptOrderGroupsExpanded" />
<Plus v-else />
</el-icon>
</div>
<div v-show="deptOrderGroupsExpanded" class="order-group-section-body">
<div
v-for="item in deptOrderGroups"
:key="item.id || item.groupPackageId || item.name"
class="order-group-item"
@dblclick="handleClickOrderGroup(item)"
@contextmenu.prevent="handleOrderGroupContextMenu($event, item, 'department')"
>
<span class="order-group-item-name">{{ item.name }}</span>
<div class="order-group-item-actions" @click.stop>
<el-icon
v-if="hasGroupPermission(item, 'department')"
class="order-group-action-icon"
@click="handleEditOrderGroup(item, 'department')"
title="编辑"
>
<Edit />
</el-icon>
<el-icon
v-if="hasGroupPermission(item, 'department')"
class="order-group-action-icon"
@click="handleDeleteOrderGroup(item, 'department')"
title="删除"
>
<Delete />
</el-icon>
</div>
</div>
<div class="order-group-add" @click="openOrderSetDialog('department')">
<el-icon><Plus /></el-icon>
添加
</div>
</div>
</div>
<div class="order-group-section">
<div class="order-group-section-header" @click="toggleOrderGroupSection('hospital')">
<span>全院</span>
<el-icon @click.stop="toggleOrderGroupSection('hospital')">
<Minus v-if="hospitalOrderGroupsExpanded" />
<Plus v-else />
</el-icon>
</div>
<div v-show="hospitalOrderGroupsExpanded" class="order-group-section-body">
<div
v-for="item in hospitalOrderGroups"
:key="item.id || item.groupPackageId || item.name"
class="order-group-item"
@dblclick="handleClickOrderGroup(item)"
@contextmenu.prevent="handleOrderGroupContextMenu($event, item, 'hospital')"
>
<span class="order-group-item-name">{{ item.name }}</span>
<div class="order-group-item-actions" @click.stop>
<el-icon
v-if="hasGroupPermission(item, 'hospital')"
class="order-group-action-icon"
@click="handleEditOrderGroup(item, 'hospital')"
title="编辑"
>
<Edit />
</el-icon>
<el-icon
v-if="hasGroupPermission(item, 'hospital')"
class="order-group-action-icon"
@click="handleDeleteOrderGroup(item, 'hospital')"
title="删除"
>
<Delete />
</el-icon>
</div>
</div>
<div class="order-group-add" @click="openOrderSetDialog('hospital')">
<el-icon><Plus /></el-icon>
添加
</div>
</div>
</div>
</div>
<el-table max-height="650" ref="prescriptionRef" :data="prescriptionList" row-key="uniqueKey" border
@cell-click="clickRow" @row-dblclick="clickRowDb" v-loading="loading" :expand-row-keys="expandOrder"
@select="handleSelectionChange">
<el-table-column type="expand" width="1" style="width: 0">
<template #default="scope">
<el-form :model="scope.row" :rules="rowRules" :ref="'formRef' + scope.$index" v-arrow-navigate>
@@ -454,7 +586,10 @@
<el-popover :popper-style="{ padding: '0' }" placement="bottom-start" :visible="scope.row.showPopover"
:width="1200" trigger="manual">
<adviceBaseList ref="adviceTableRef" :popoverVisible="scope.row.showPopover"
:adviceQueryParams="adviceQueryParams" :patientInfo="props.patientInfo" @selectAdviceBase="
:adviceQueryParams="adviceQueryParams" :patientInfo="props.patientInfo"
:preloadedData="preloadedAdviceData"
:preloadedLoaded="preloadedAdviceLoaded"
@selectAdviceBase="
(row) => {
selectAdviceBase(scope.row.uniqueKey, row);
}
@@ -578,8 +713,15 @@
</template>
</el-table-column>
</el-table>
<OrderGroupDrawer ref="orderGroupRef" :diagnosis="diagnosisInfo" :organizationId="props.patientInfo.orgId"
@useOrderGroup="handleSaveGroup" />
</div>
<MedicalOrderSetDialog
ref="orderSetDialogRef"
:is-doctor-station="true"
:method_code="method_code"
:rate_code="rate_code"
:show-range-selector="true"
@saved="handleOrderSetSaved"
/>
<PrescriptionHistory ref="prescriptionHistoryRef" :diagnosis="diagnosisInfo" :patientInfo="props.patientInfo"
@userPrescriptionHistory="handleSaveHistory" />
<OrderBindInfo ref="orderBindInfoRef" @submit="handleOrderBindInfo" />
@@ -631,19 +773,22 @@ import {
savePrescriptionSign,
singOut,
updateGroupId,
getOrderGroup,
deleteGroup,
queryGroupDetail,
} from '../api';
import { advicePrint, getAdjustPriceSwitchState } from '@/api/public';
import adviceBaseList from '../adviceBaseList.vue';
import { computed, getCurrentInstance, nextTick, ref, watch, onMounted, onBeforeUnmount } from 'vue';
import { calculateQuantityByDays } from '@/utils/his';
import OrderGroupDrawer from './orderGroupDrawer';
import MedicalOrderSetDialog from '@/views/basicmanage/medicalOrderSet/components/MedicalOrderSetDialog.vue';
import PrescriptionHistory from './prescriptionHistory';
import OrderBindInfo from './orderBindInfo';
import SkinTestInfo from './skinTestInfo';
import Decimal from 'decimal.js';
import useUserStore from '@/store/modules/user';
import { ElMessage, ElMessageBox } from 'element-plus';
import { ArrowDown, Search, Memo } from '@element-plus/icons-vue';
import { ArrowDown, Search, Memo, Minus, Plus, Edit, Delete } from '@element-plus/icons-vue';
import printUtils, { getPrinterList, PRINT_TEMPLATE, savePrinterToCache, } from '@/utils/printUtils';
import Template from "@/views/inpatientDoctor/home/emr/components/template.vue";
@@ -706,6 +851,27 @@ const isAdding = ref(false);
const isSaving = ref(false);
const prescriptionRef = ref();
const expandOrder = ref([]); //目前的展开行
// 预加载的医嘱基础数据 - 用于优化搜索响应速度
const preloadedAdviceData = ref([]);
const preloadedAdviceLoading = ref(false);
const preloadedAdviceLoaded = ref(false);
// V1.3 风格侧边栏 - 组套列表相关变量
const personalOrderGroups = ref([]);
const deptOrderGroups = ref([]);
const hospitalOrderGroups = ref([]);
const personalOrderGroupsExpanded = ref(true);
const deptOrderGroupsExpanded = ref(false);
const hospitalOrderGroupsExpanded = ref(false);
const orderGroupLoaded = ref({
personal: false,
department: false,
hospital: false,
});
// 更新展开行的函数
const updateExpandOrder = (keys) => {
expandOrder.value = keys;
@@ -810,15 +976,58 @@ const currentPrescriptionId = ref(null); // 当前活跃的处方ID
const prescriptionCounter = ref(0); // 处方计数器
const allPrescriptionsData = ref({}); // 存储所有处方的数据,格式: { prescriptionId: [...prescriptionList] }
const allPrescriptionCheckStates = ref({}); // 存储每个处方的选中状态,格式: { prescriptionId: { checkedIndexes: [], checkAll: false } }
/**
* 预加载医嘱基础数据 - 在后台加载药品/诊疗数据,提升搜索响应速度
*/
async function preloadAdviceData() {
if (preloadedAdviceLoading.value || preloadedAdviceLoaded.value) return;
if (!props.patientInfo?.orgId) return;
preloadedAdviceLoading.value = true;
console.log('[Preload] 开始预加载医嘱基础数据...');
try {
const res = await getAdviceBaseInfo({
pageSize: 100,
pageNo: 1,
adviceTypes: '1,2,3', // 药品、耗材、诊疗
organizationId: props.patientInfo.orgId,
});
if (res.data?.records?.length > 0) {
// 过滤无库存的药品
preloadedAdviceData.value = res.data.records.filter((item) => {
if (item.adviceType == 1 || item.adviceType == 2) {
const inventoryList = item.inventoryList || [];
const totalQuantity = inventoryList.reduce((sum, inv) => sum + (inv.quantity || 0), 0);
return totalQuantity > 0;
}
return true;
});
preloadedAdviceLoaded.value = true;
console.log(`[Preload] 预加载完成,共 ${preloadedAdviceData.value.length} 条数据`);
}
} catch (error) {
console.error('[Preload] 预加载失败:', error);
} finally {
preloadedAdviceLoading.value = false;
}
}
onMounted(() => {
document.addEventListener('keydown', escKeyListener);
// 初始化时预加载组织数据,避免选择诊疗/会诊项目时显示"无数据"
// 初始化时预加载组织数据避免选择诊疗/会诊项目时显示"无数据"
getOrgList();
// 初始化时自动创建第一个西药处方
if (westernPrescriptions.value.length === 0) {
createNewPrescription();
handleAddPrescription(null, false);
}
// 默认展开个人:只请求个人组套
if (props.patientInfo?.orgId) {
fetchOrderGroups('personal');
}
});
onBeforeUnmount(() => {
@@ -863,6 +1072,20 @@ watch(
}
);
// 初次进入页面时 patientInfo 可能还未就绪,导致个人组套未拉取;这里等 orgId 有值后再补拉一次
watch(
() => props.patientInfo?.orgId,
(orgId) => {
if (!orgId) return;
if (!orderGroupLoaded.value.personal) {
fetchOrderGroups('personal');
}
// 预加载医嘱基础数据,提升搜索响应速度
preloadAdviceData();
},
{ immediate: true }
);
// // 打印机相关响应式变量
// const printerList = ref([]);
// const selectedPrinter = ref('');
@@ -1282,7 +1505,14 @@ function getListInfo(addNewRow) {
const res = await getPrescriptionList(props.patientInfo.encounterId);
prescriptionList.value = res.data.map((item) => {
const contentJson = JSON.parse(item.contentJson);
// 防止 contentJson 为空或 undefined 导致 JSON.parse 报错
let contentJson = {};
try {
contentJson = item.contentJson ? JSON.parse(item.contentJson) : {};
} catch (e) {
console.warn('JSON.parse contentJson 失败:', e, 'item:', item);
contentJson = {};
}
// 🎯 判断是否为会诊医嘱:
// 方法1检查 category_enum 字段(需要后端重新编译)
@@ -1388,6 +1618,13 @@ function handleSelectionChange(selection, row) {
// 新增医嘱
function handleAddPrescription(prescriptionId, showWarning = true) {
// 关闭所有已打开的popover避免遮挡新增按钮
prescriptionList.value.forEach(row => {
if (row.showPopover) {
row.showPopover = false;
}
});
// 如果传入了处方ID先切换到该处方
if (prescriptionId && prescriptionId !== currentPrescriptionId.value) {
switchToActivePrescription(prescriptionId);
@@ -2613,7 +2850,8 @@ function setValue(row) {
// 🔧 Bug #147 修复:耗材(adviceType=4)和诊疗(adviceType=3, adviceType=5)不检查库存
// 耗材从getDeviceList接口获取inventoryList为空但需要设置价格
if (row.adviceType != 3 && row.adviceType != 4 && row.adviceType != 5) {
if (row.inventoryList && row.inventoryList.length == 0) {
// 🔧 Bug #144 补充修复:检查 inventoryList 是否存在,避免 undefined 错误
if (!row.inventoryList || row.inventoryList.length == 0) {
expandOrder.value = [];
proxy.$modal.msgWarning(row.adviceName + '无库存');
return;
@@ -2693,19 +2931,29 @@ function setValue(row) {
// 诊疗类型adviceType == 3
prescriptionList.value[rowIndex.value].orgId = JSON.parse(JSON.stringify(row)).positionId;
prescriptionList.value[rowIndex.value].quantity = 1;
prescriptionList.value[rowIndex.value].unitPrice = row.priceList[0].price;
prescriptionList.value[rowIndex.value].totalPrice = row.priceList[0].price;
// 🔧 Bug #144 修复:安全访问 priceList防止 orderDetailInfos 为空时出错
if (row.priceList && row.priceList.length > 0) {
prescriptionList.value[rowIndex.value].unitPrice = row.priceList[0].price;
prescriptionList.value[rowIndex.value].totalPrice = row.priceList[0].price;
} else {
prescriptionList.value[rowIndex.value].unitPrice = 0;
prescriptionList.value[rowIndex.value].totalPrice = 0;
console.warn('医嘱项价格列表为空:', row.adviceName || '未知医嘱');
}
}
}
}
// 选择组套
// 选择组套 - 适配新版 OrderGroupDrawer 组件
function handleSaveGroup(orderGroupList) {
if (!orderGroupList || !Array.isArray(orderGroupList) || orderGroupList.length === 0) {
proxy.$modal.msgWarning('组套数据为空');
return;
}
// 记录成功添加的数量
let successCount = 0;
orderGroupList.forEach((item) => {
rowIndex.value = prescriptionList.value.length;
@@ -2715,40 +2963,62 @@ function handleSaveGroup(orderGroupList) {
return;
}
// 获取实际的医嘱项目数据
// 如果 item 包含 orderDetailInfos则使用它否则直接使用 item
const orderDetail = item.orderDetailInfos || item;
// 🔥 新版组件已经预处理了数据,优先使用 mergedDetail
// 如果没有 mergedDetail则回退到旧版处理逻辑
const mergedDetail = item.mergedDetail || {
// 医嘱库基础信息
...(item.orderDetailInfos || {}),
// 组套级别字段
adviceName: item.orderDetailInfos?.adviceName || item.orderDefinitionName || '未知项目',
adviceType: item.orderDetailInfos?.adviceType,
adviceDefinitionId: item.orderDefinitionId || item.orderDetailInfos?.adviceDefinitionId,
quantity: item.quantity,
unitCode: item.unitCode || item.orderDetailInfos?.unitCode,
unitCodeName: item.unitCodeName,
dose: item.dose || item.orderDetailInfos?.dose,
rateCode: item.rateCode || item.orderDetailInfos?.rateCode,
methodCode: item.methodCode || item.orderDetailInfos?.methodCode,
dispensePerDuration: item.dispensePerDuration || item.orderDetailInfos?.dispensePerDuration,
doseQuantity: item.doseQuantity,
inventoryList: item.orderDetailInfos?.inventoryList || [],
priceList: item.orderDetailInfos?.priceList || [],
partPercent: item.orderDetailInfos?.partPercent || 1,
positionId: item.orderDetailInfos?.positionId,
defaultLotNumber: item.orderDetailInfos?.defaultLotNumber,
};
if (!orderDetail) {
console.warn('组套中的项目详情为空');
return;
}
// 在 setValue 之前预初始化空行
prescriptionList.value[rowIndex.value] = {
uniqueKey: nextId.value++,
isEdit: true,
statusEnum: 1,
};
// 使用医嘱项目详情设置值
setValue(orderDetail);
setValue(mergedDetail);
// 创建新的处方项目
const newRow = {
...prescriptionList.value[rowIndex.value],
uniqueKey: nextId.value++,
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
accountId: accountId.value, // 费用性质
quantity: item.quantity, // 数量
methodCode: item.methodCode, // 用法
rateCode: item.rateCode, // 频次
dispensePerDuration: item.dispensePerDuration, // 用药天数
dose: item.dose, // 单次用量
doseQuantity: item.doseQuantity, // 单次用量(小单位)
executeNum: 1, // 执行次数默认1
unitCode: item.unitCode, // 大单位
accountId: accountId.value,
quantity: item.quantity,
methodCode: item.methodCode,
rateCode: item.rateCode,
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
executeNum: 1,
unitCode: item.unitCode,
unitCode_dictText: item.unitCodeName || '',
statusEnum: 1, // 签发状态 默认是待保存
orgId: item.positionId, // 默认执行科室
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1', // 1新增2编辑
conditionId: conditionId.value, // 诊断id
conditionDefinitionId: conditionDefinitionId.value, // 诊断定义id
encounterDiagnosisId: encounterDiagnosisId.value, // 就诊诊断id
statusEnum: 1,
orgId: item.orderDetailInfos?.positionId || mergedDetail.positionId,
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
conditionId: conditionId.value,
conditionDefinitionId: conditionDefinitionId.value,
encounterDiagnosisId: encounterDiagnosisId.value,
};
// 计算价格和总量
@@ -2760,12 +3030,18 @@ function handleSaveGroup(orderGroupList) {
} else {
newRow.price = newRow.unitPrice;
newRow.totalPrice = (item.quantity * newRow.unitPrice).toFixed(6);
newRow.minUnitQuantity = item.quantity * (orderDetail.partPercent || 1);
newRow.minUnitQuantity = item.quantity * (item.orderDetailInfos?.partPercent || mergedDetail.partPercent || 1);
}
newRow.contentJson = JSON.stringify(newRow);
prescriptionList.value[rowIndex.value] = newRow;
successCount++;
});
// 显示成功提示
if (successCount > 0) {
proxy.$modal.msgSuccess(`成功添加 ${successCount} 个医嘱项`);
}
}
// 历史医嘱复用
@@ -3229,6 +3505,326 @@ function calculateQuantity(type, dose, count, partPercent) {
return Math.ceil(dose) * count;
}
}
// V1.3 风格侧边栏 - 组套列表方法
async function fetchOrderGroups(scope, { force = false } = {}) {
console.log('[fetchOrderGroups] 开始加载, scope:', scope, 'force:', force, '当前loaded状态:', orderGroupLoaded.value[scope]);
if (!force && orderGroupLoaded.value[scope]) {
console.log('[fetchOrderGroups] 已加载过且非强制刷新,跳过');
return;
}
const orgId = props.patientInfo?.orgId;
console.log('[fetchOrderGroups] orgId:', orgId);
if (!orgId) {
console.log('[fetchOrderGroups] orgId 为空,返回');
return;
}
try {
if (scope === 'personal') {
// 查询个人组套:不传 organizationId后端使用登录用户的 practitionerId 查询
console.log('[fetchOrderGroups] 查询个人组套, params:', { organizationId: null, practitionerId: null });
const res = await getOrderGroup({ organizationId: null });
console.log('[fetchOrderGroups] 个人组套API返回完整数据:', JSON.stringify(res, null, 2));
const data = res?.data || {};
console.log('[fetchOrderGroups] data Keys:', Object.keys(data));
personalOrderGroups.value = data.personalList || [];
console.log('[fetchOrderGroups] 个人组套数据:', personalOrderGroups.value);
} else if (scope === 'department') {
const res = await getOrderGroup({ organizationId: null });
const data = res?.data || {};
deptOrderGroups.value = data.organizationList || [];
} else if (scope === 'hospital') {
const res = await getOrderGroup({ organizationId: orgId });
const data = res?.data || {};
hospitalOrderGroups.value = data.hospitalList || [];
}
orderGroupLoaded.value[scope] = true;
console.log('[fetchOrderGroups] 加载完成, scope:', scope);
} catch (e) {
console.error('[fetchOrderGroups] 加载组套列表失败:', e);
}
}
function toggleOrderGroupSection(scope) {
if (scope === 'personal') {
personalOrderGroupsExpanded.value = !personalOrderGroupsExpanded.value;
if (personalOrderGroupsExpanded.value) fetchOrderGroups('personal');
return;
}
if (scope === 'department') {
deptOrderGroupsExpanded.value = !deptOrderGroupsExpanded.value;
if (deptOrderGroupsExpanded.value) fetchOrderGroups('department');
return;
}
if (scope === 'hospital') {
hospitalOrderGroupsExpanded.value = !hospitalOrderGroupsExpanded.value;
if (hospitalOrderGroupsExpanded.value) fetchOrderGroups('hospital');
}
}
// 解析 childrenJson
const parseChildrenJson = (childrenJson) => {
if (!childrenJson) return [];
try {
const childrenData = typeof childrenJson === 'string' ? JSON.parse(childrenJson) : childrenJson;
return Array.isArray(childrenData) ? childrenData : [];
} catch (e) {
return [];
}
};
// 点击组套 - 双击添加到处方
async function handleClickOrderGroup(group) {
if (!group) return;
// 获取组套详情
let detailList = group.detailList || parseChildrenJson(group.childrenJson);
if (!detailList || detailList.length === 0) {
if (group.groupPackageId) {
try {
const res = await queryGroupDetail({ groupPackageId: group.groupPackageId });
detailList = res.data || [];
} catch (error) {
console.error('获取组套详情失败:', error);
proxy.$modal.msgError('获取组套详情失败');
return;
}
} else {
return;
}
}
// 检查库存和取药科室配置
const checkResults = await checkOrderGroupAvailability(detailList);
if (!checkResults.canUse) {
proxy.$modal.msgWarning(checkResults.message);
return;
}
handleSaveGroup(detailList);
}
// 检查组套的库存和取药科室配置
async function checkOrderGroupAvailability(detailList) {
if (!detailList || detailList.length === 0) {
return { canUse: false, message: '组套为空,无法使用' };
}
const orgId = props.patientInfo?.orgId;
if (!orgId) {
return { canUse: false, message: '无法获取科室信息' };
}
const unavailableItems = [];
for (const item of detailList) {
const orderDefinitionId = item.orderDefinitionId || item.adviceDefinitionId;
if (!orderDefinitionId) continue;
try {
const res = await getAdviceBaseInfo({
adviceDefinitionIdParamList: orderDefinitionId,
organizationId: orgId,
});
if (!res.data || !res.data.records || res.data.records.length === 0) {
unavailableItems.push({
name: item.orderDefinitionName || item.adviceName || '未知项目',
reason: '无法获取项目信息',
});
continue;
}
const adviceInfo = res.data.records[0];
if (adviceInfo.adviceType != 3) {
if (!adviceInfo.positionId) {
unavailableItems.push({
name: adviceInfo.adviceName || adviceInfo.productName,
reason: '未配置取药科室',
});
continue;
}
if (!adviceInfo.inventoryList || adviceInfo.inventoryList.length === 0) {
unavailableItems.push({
name: adviceInfo.adviceName || adviceInfo.productName,
reason: '无库存',
});
continue;
}
const hasAvailableStock = adviceInfo.inventoryList.some(
(stock) => stock.quantity > 0 && stock.locationId === adviceInfo.positionId
);
if (!hasAvailableStock) {
unavailableItems.push({
name: adviceInfo.adviceName || adviceInfo.productName,
reason: '库存不足',
});
}
}
} catch (e) {
console.error('检查组套可用性失败:', e);
}
}
if (unavailableItems.length > 0) {
const itemNames = unavailableItems.slice(0, 5).map((item) => item.name).join('、');
return {
canUse: false,
message: `以下项目无法使用:${itemNames},请检查后再试`,
};
}
return { canUse: true, message: '' };
}
// 编辑组套
function handleEditOrderGroup(group, scope) {
if (!group.groupPackageId) {
proxy.$modal.msgWarning('该组套无法编辑');
return;
}
// 权限检查
const currentPractitionerId = userStore.practitionerId;
const currentOrgId = userStore.orgId;
if (scope === 'personal') {
// 个人组套:只能编辑自己创建的
if (group.practitionerId && group.practitionerId !== currentPractitionerId) {
proxy.$modal.msgWarning('只能编辑自己创建的个人组套');
return;
}
} else if (scope === 'department') {
// 科室组套:只能编辑自己科室的
if (group.organizationId && group.organizationId !== currentOrgId) {
proxy.$modal.msgWarning('只能编辑自己科室的组套');
return;
}
} else if (scope === 'hospital') {
// 全院组套:需要管理权限
// 暂时允许所有医生编辑,后续可根据需要添加权限控制
}
orderSetDialogScope.value = scope;
orderSetDialogRef.value?.openEdit?.(scope, group);
}
// 删除组套
function handleDeleteOrderGroup(group, scope) {
if (!group.groupPackageId) {
proxy.$modal.msgWarning('该组套无法删除');
return;
}
// 权限检查
const currentPractitionerId = userStore.practitionerId;
const currentOrgId = userStore.orgId;
if (scope === 'personal') {
// 个人组套:只能删除自己创建的
if (group.practitionerId && group.practitionerId !== currentPractitionerId) {
proxy.$modal.msgWarning('只能删除自己创建的个人组套');
return;
}
} else if (scope === 'department') {
// 科室组套:只能删除自己科室的
if (group.organizationId && group.organizationId !== currentOrgId) {
proxy.$modal.msgWarning('只能删除自己科室的组套');
return;
}
} else if (scope === 'hospital') {
// 全院组套:需要管理权限
// 暂时允许所有医生删除,后续可根据需要添加权限控制
}
proxy.$modal
.confirm('确定要删除该组套吗?')
.then(() => {
deleteGroup({ groupPackageId: group.groupPackageId })
.then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('删除成功');
// 刷新对应范围的组套列表
orderGroupLoaded.value[scope] = false;
fetchOrderGroups(scope, { force: true });
} else {
proxy.$modal.msgError(res.message || '删除失败');
}
})
.catch((error) => {
console.error('删除组套失败:', error);
proxy.$modal.msgError('删除失败');
});
})
.catch(() => {});
}
// 处理组套右键菜单(预留)
function handleOrderGroupContextMenu(event, group, scope) {
// 可以在这里实现右键菜单功能
}
// 检查组套编辑/删除权限
function hasGroupPermission(group, scope) {
const currentPractitionerId = userStore.practitionerId;
const currentOrgId = userStore.orgId;
if (scope === 'personal') {
// 个人组套:只能编辑/删除自己创建的
return !group.practitionerId || group.practitionerId === currentPractitionerId;
} else if (scope === 'department') {
// 科室组套:只能编辑/删除自己科室的
return !group.organizationId || group.organizationId === currentOrgId;
} else if (scope === 'hospital') {
// 全院组套:暂时允许所有医生操作
return true;
}
return false;
}
const orderSetDialogRef = ref(null);
const orderSetDialogScope = ref('personal');
// 打开组套弹窗
function openOrderSetDialog(scope) {
orderSetDialogScope.value = scope || 'personal';
const selectedRaw =
prescriptionRef.value && prescriptionRef.value.getSelectionRows
? prescriptionRef.value.getSelectionRows()
: [];
const selected = (selectedRaw || []).map((row) => {
const clone = { ...row };
if (clone.therapyEnum == null) {
clone.therapyEnum = '2';
}
return clone;
});
if (selected && selected.length > 0) {
// 列表有选中行:弹窗中直接展示这些选中项用于"另存组套"
orderSetDialogRef.value?.openFromSelection?.(orderSetDialogScope.value, selected);
} else {
// 无选中行:按原逻辑,打开空白弹窗
orderSetDialogRef.value?.openAdd?.(orderSetDialogScope.value);
}
}
function handleOrderSetSaved() {
console.log('[handleOrderSetSaved] ******************** 事件被触发 ********************');
console.log('[handleOrderSetSaved] 当前scope:', orderSetDialogScope.value);
// 保存后刷新当前 scope 的组套列表(懒加载 + 强制刷新)
orderGroupLoaded.value[orderSetDialogScope.value] = false;
console.log('[handleOrderSetSaved] orderGroupLoaded 已重置:', orderGroupLoaded.value);
fetchOrderGroups(orderSetDialogScope.value, { force: true });
}
defineExpose({ getListInfo, getDiagnosisInfo });
</script>
@@ -3282,4 +3878,137 @@ defineExpose({ getListInfo, getDiagnosisInfo });
.el-table__cell .el-form-item--default {
margin-bottom: 0px;
}
/* V1.3 风格侧边栏 - 组套列表样式 */
.order-group-container {
display: flex;
flex-direction: row;
width: 100%;
align-items: flex-start;
}
.order-group-sidebar {
width: 260px;
border-right: 1px solid #ebeef5;
display: flex;
flex-direction: column;
gap: 8px;
flex-shrink: 0;
}
/* 确保表格与侧边栏顶部对齐 */
.order-group-container .el-table {
flex: 1;
min-width: 0;
}
/* 组套区域样式 */
.order-group-section {
background: #fff;
border-radius: 4px;
border: 1px solid #ebeef5;
overflow: hidden;
display: flex;
flex-direction: column;
max-height: 100%;
}
.order-group-section:first-child {
margin-top: 0;
}
.order-group-section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
font-weight: 600;
font-size: 14px;
background: #f8fafc;
border-bottom: 1px solid #ebeef5;
cursor: pointer;
user-select: none;
}
.order-group-section-header:hover {
background: #f0f2f5;
}
.order-group-section-body {
max-height: 300px;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.order-group-item {
padding: 8px 12px;
font-size: 13px;
cursor: pointer;
border-top: 1px solid #f2f3f5;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.order-group-item:hover {
background: #f5f7fa;
}
.order-group-item:hover .order-group-item-actions {
display: flex;
}
.order-group-item-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.order-group-item-actions {
display: none;
align-items: center;
gap: 4px;
margin-left: 8px;
flex-shrink: 0;
}
.order-group-action-icon {
cursor: pointer;
color: #909399;
font-size: 14px;
transition: color 0.2s;
padding: 2px;
}
.order-group-action-icon:hover {
color: #409eff;
}
.order-group-add {
padding: 8px 12px;
cursor: pointer;
text-align: center;
color: #409eff;
font-size: 13px;
border-top: 1px solid #f2f3f5;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
margin-top: auto;
flex-shrink: 0;
}
.order-group-add:hover {
background: #f5f7fa;
}
/* 确保表格填充剩余空间 */
.el-table {
flex: 1;
min-width: 0;
}
</style>

View File

@@ -1165,7 +1165,8 @@ function setValue(row) {
let selectedStock = undefined;
if (row.adviceType != 3) {
if (row.inventoryList && row.inventoryList.length == 0) {
// 🔧 Bug #144 修复:检查 inventoryList 是否存在,避免 undefined 错误
if (!row.inventoryList || row.inventoryList.length == 0) {
expandOrder.value = [];
proxy.$modal.msgWarning(row.adviceName + '无库存');
return;
@@ -1229,35 +1230,75 @@ function setValue(row) {
prescriptionList.value[rowIndex.value] = updatedRow;
}
// 选择组套
// 选择组套 - 适配新版 OrderGroupDrawer 组件
function handleSaveGroup(orderGroupList) {
if (!orderGroupList || !Array.isArray(orderGroupList) || orderGroupList.length === 0) {
proxy.$modal.msgWarning('组套数据为空');
return;
}
let successCount = 0;
orderGroupList.forEach((item) => {
rowIndex.value = prescriptionList.value.length;
setValue(item.orderDetailInfos);
if (!item) {
console.warn('组套中的项目为空');
return;
}
// 🔥 新版组件已经预处理了数据,优先使用 mergedDetail
const mergedDetail = item.mergedDetail || {
...(item.orderDetailInfos || {}),
adviceName: item.orderDetailInfos?.adviceName || item.orderDefinitionName || '未知项目',
adviceType: item.orderDetailInfos?.adviceType,
adviceDefinitionId: item.orderDefinitionId || item.orderDetailInfos?.adviceDefinitionId,
quantity: item.quantity,
unitCode: item.unitCode || item.orderDetailInfos?.unitCode,
unitCodeName: item.unitCodeName,
dose: item.dose || item.orderDetailInfos?.dose,
rateCode: item.rateCode || item.orderDetailInfos?.rateCode,
methodCode: item.methodCode || item.orderDetailInfos?.methodCode,
dispensePerDuration: item.dispensePerDuration || item.orderDetailInfos?.dispensePerDuration,
doseQuantity: item.doseQuantity,
inventoryList: item.orderDetailInfos?.inventoryList || [],
priceList: item.orderDetailInfos?.priceList || [],
partPercent: item.orderDetailInfos?.partPercent || 1,
positionId: item.orderDetailInfos?.positionId,
defaultLotNumber: item.orderDetailInfos?.defaultLotNumber,
therapyEnum: item.orderDetailInfos?.therapyEnum || '1',
};
// 预初始化空行
prescriptionList.value[rowIndex.value] = {
uniqueKey: nextId.value++,
isEdit: true,
statusEnum: 1,
};
setValue(mergedDetail);
// 创建新的处方项目
const newRow = {
...prescriptionList.value[rowIndex.value],
uniqueKey: nextId.value++,
patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId,
accountId: accountId.value, // 费用性质
quantity: item.quantity, // 数量
methodCode: item.methodCode, // 用法
rateCode: item.rateCode, // 频次
dispensePerDuration: item.dispensePerDuration, // 用药天数
dose: item.dose, // 单次用量
doseQuantity: item.doseQuantity, // 单次用量(小单位)
executeNum: 1, // 执行次数默认1
unitCode: item.unitCode, // 大单位
accountId: accountId.value,
quantity: item.quantity,
methodCode: item.methodCode,
rateCode: item.rateCode,
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
executeNum: 1,
unitCode: item.unitCode,
unitCode_dictText: item.unitCodeName || '',
statusEnum: 1, // 签发状态 默认是待保存
orgId: item.positionId, // 默认执行科室
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1', // 1新增2编辑
conditionId: conditionId.value, // 诊断id
conditionDefinitionId: conditionDefinitionId.value, // 诊断定义id
encounterDiagnosisId: encounterDiagnosisId.value, // 就诊诊断id
// 确保 therapyEnum 被正确设置,默认为长期医嘱('1')
statusEnum: 1,
orgId: item.orderDetailInfos?.positionId || mergedDetail.positionId,
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
conditionId: conditionId.value,
conditionDefinitionId: conditionDefinitionId.value,
encounterDiagnosisId: encounterDiagnosisId.value,
therapyEnum: prescriptionList.value[rowIndex.value]?.therapyEnum || '1',
};
@@ -1270,12 +1311,17 @@ function handleSaveGroup(orderGroupList) {
} else {
newRow.price = newRow.unitPrice;
newRow.totalPrice = (item.quantity * newRow.unitPrice).toFixed(6);
newRow.minUnitQuantity = item.quantity * item.orderDetailInfos.partPercent;
newRow.minUnitQuantity = item.quantity * (item.orderDetailInfos?.partPercent || mergedDetail.partPercent || 1);
}
newRow.contentJson = JSON.stringify(newRow);
prescriptionList.value[rowIndex.value] = newRow;
successCount++;
});
if (successCount > 0) {
proxy.$modal.msgSuccess(`成功添加 ${successCount} 个医嘱项`);
}
}
// 历史医嘱复用