feat(surgery): 完善手术管理功能模块
- 添加手术申请相关API接口,包括根据患者ID查询就诊列表功能 - 在医生工作站界面集成手术申请功能选项卡 - 实现手术管理页面的完整功能,包括手术申请的增删改查 - 添加手术排期、开始、完成等状态流转功能 - 优化手术管理页面表格展示,增加手术类型、等级、计划时间等字段 - 实现手术申请表单的完整编辑和查看模式 - 集成患者信息和就诊记录关联功能 - 添加手术室、医生、护士等资源选择功能 - 更新系统依赖配置,添加core-common模块 - 优化图标资源和manifest配置文件 - 调整患者档案和门诊记录相关状态枚举
This commit is contained in:
0
.trae/rules/project_rules.md
Normal file
0
.trae/rules/project_rules.md
Normal file
10
check_surgery_fields.sql
Normal file
10
check_surgery_fields.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
-- 检查手术表中所有字段是否存在
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
character_maximum_length,
|
||||
is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name LIKE '%name%'
|
||||
ORDER BY column_name;
|
||||
82
fill_missing_surgery_name_fields.sql
Normal file
82
fill_missing_surgery_name_fields.sql
Normal file
@@ -0,0 +1,82 @@
|
||||
-- 修复已存在的手术记录中缺失的名称字段
|
||||
-- 注意:这只是一个示例,实际执行前请根据您的数据库表结构调整
|
||||
|
||||
-- 填充患者姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET patient_name = p.name
|
||||
FROM public.adm_patient p
|
||||
WHERE s.patient_id = p.id
|
||||
AND s.patient_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充主刀医生姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET main_surgeon_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.main_surgeon_id = u.user_id
|
||||
AND s.main_surgeon_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充麻醉医生姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET anesthetist_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.anesthetist_id = u.user_id
|
||||
AND s.anesthetist_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充助手1姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET assistant_1_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.assistant_1_id = u.user_id
|
||||
AND s.assistant_1_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充助手2姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET assistant_2_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.assistant_2_id = u.user_id
|
||||
AND s.assistant_2_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充巡回护士姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET scrub_nurse_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.scrub_nurse_id = u.user_id
|
||||
AND s.scrub_nurse_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充手术室名称
|
||||
UPDATE public.cli_surgery s
|
||||
SET operating_room_name = r.name
|
||||
FROM public.cli_operating_room r
|
||||
WHERE s.operating_room_id = r.id
|
||||
AND s.operating_room_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充执行科室名称
|
||||
UPDATE public.cli_surgery s
|
||||
SET org_name = o.name
|
||||
FROM public.adm_organization o
|
||||
WHERE s.org_id = o.id
|
||||
AND s.org_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充申请科室名称
|
||||
UPDATE public.cli_surgery s
|
||||
SET apply_dept_name = o.name
|
||||
FROM public.adm_organization o
|
||||
WHERE s.apply_dept_id = o.id
|
||||
AND s.apply_dept_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充申请医生姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET apply_doctor_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.apply_doctor_id = u.user_id
|
||||
AND s.apply_doctor_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
@@ -60,6 +60,11 @@
|
||||
<artifactId>core-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.core</groupId>
|
||||
<artifactId>core-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JSQLParser - 用于MyBatis Plus -->
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.openhis.web.basedatamanage.appservice;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.openhis.web.basedatamanage.dto.OperatingRoomDto;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 手术室应用Service接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-01-04
|
||||
*/
|
||||
public interface IOperatingRoomAppService {
|
||||
|
||||
/**
|
||||
* 分页查询手术室列表
|
||||
*
|
||||
* @param operatingRoomDto 查询条件
|
||||
* @param pageNo 当前页
|
||||
* @param pageSize 每页条数
|
||||
* @param request 请求
|
||||
* @return 手术室列表
|
||||
*/
|
||||
R<?> getOperatingRoomPage(OperatingRoomDto operatingRoomDto, Integer pageNo, Integer pageSize,
|
||||
HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 根据ID查询手术室详情
|
||||
*
|
||||
* @param id 手术室ID
|
||||
* @return 手术室详情
|
||||
*/
|
||||
R<?> getOperatingRoomById(Long id);
|
||||
|
||||
/**
|
||||
* 新增手术室
|
||||
*
|
||||
* @param operatingRoomDto 手术室信息
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> addOperatingRoom(@Validated OperatingRoomDto operatingRoomDto);
|
||||
|
||||
/**
|
||||
* 修改手术室
|
||||
*
|
||||
* @param operatingRoomDto 手术室信息
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> updateOperatingRoom(@Validated OperatingRoomDto operatingRoomDto);
|
||||
|
||||
/**
|
||||
* 删除手术室
|
||||
*
|
||||
* @param ids 手术室ID(支持批量)
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> deleteOperatingRoom(String ids);
|
||||
|
||||
/**
|
||||
* 启用手术室
|
||||
*
|
||||
* @param ids 手术室ID数组
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> enableOperatingRoom(java.util.List<Long> ids);
|
||||
|
||||
/**
|
||||
* 停用手术室
|
||||
*
|
||||
* @param ids 手术室ID数组
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> disableOperatingRoom(java.util.List<Long> ids);
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package com.openhis.web.basedatamanage.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.core.common.utils.AssignSeqUtil;
|
||||
import com.core.common.utils.ChineseConvertUtils;
|
||||
import com.core.common.utils.StringUtils;
|
||||
import com.openhis.administration.domain.OperatingRoom;
|
||||
import com.openhis.administration.mapper.OperatingRoomMapper;
|
||||
import com.openhis.administration.service.IOperatingRoomService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import com.openhis.common.enums.AssignSeqEnum;
|
||||
import com.openhis.common.enums.LocationStatus;
|
||||
import com.openhis.common.utils.HisPageUtils;
|
||||
import com.openhis.common.utils.HisQueryUtils;
|
||||
import com.openhis.web.basedatamanage.appservice.IOperatingRoomAppService;
|
||||
import com.openhis.web.basedatamanage.dto.OperatingRoomDto;
|
||||
import com.openhis.web.common.appservice.ICommonService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 手术室应用Service实现类
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-01-04
|
||||
*/
|
||||
@Service
|
||||
public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
|
||||
|
||||
@Resource
|
||||
private IOperatingRoomService operatingRoomService;
|
||||
|
||||
@Resource
|
||||
private OperatingRoomMapper operatingRoomMapper;
|
||||
|
||||
@Resource
|
||||
private AssignSeqUtil assignSeqUtil;
|
||||
|
||||
@Resource
|
||||
private ICommonService commonService;
|
||||
|
||||
/**
|
||||
* 分页查询手术室列表
|
||||
*
|
||||
* @param operatingRoomDto 查询条件
|
||||
* @param pageNo 当前页
|
||||
* @param pageSize 每页条数
|
||||
* @param request 请求
|
||||
* @return 手术室列表
|
||||
*/
|
||||
@Override
|
||||
public R<?> getOperatingRoomPage(OperatingRoomDto operatingRoomDto, Integer pageNo, Integer pageSize,
|
||||
HttpServletRequest request) {
|
||||
// 构建查询条件
|
||||
QueryWrapper<OperatingRoom> queryWrapper = HisQueryUtils.buildQueryWrapper(operatingRoomDto,
|
||||
operatingRoomDto.getName(),
|
||||
new HashSet<>(Arrays.asList("name", "py_str", "wb_str")), request);
|
||||
|
||||
// 设置排序
|
||||
queryWrapper.orderByDesc("display_order").orderByDesc("create_time");
|
||||
|
||||
// 查询手术室分页列表
|
||||
Page<OperatingRoomDto> operatingRoomPage =
|
||||
HisPageUtils.selectPage(operatingRoomMapper, queryWrapper, pageNo, pageSize, OperatingRoomDto.class);
|
||||
|
||||
// 处理枚举字段显示文本
|
||||
operatingRoomPage.getRecords().forEach(e -> {
|
||||
// 状态
|
||||
e.setStatusEnum_dictText(e.getStatusEnum() != null && e.getStatusEnum() == 1 ? "启用" : "停用");
|
||||
// 拼音码
|
||||
e.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(e.getName()));
|
||||
// 五笔码
|
||||
e.setWbStr(ChineseConvertUtils.toWBFirstLetter(e.getName()));
|
||||
});
|
||||
|
||||
return R.ok(operatingRoomPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询手术室详情
|
||||
*
|
||||
* @param id 手术室ID
|
||||
* @return 手术室详情
|
||||
*/
|
||||
@Override
|
||||
public R<?> getOperatingRoomById(Long id) {
|
||||
OperatingRoom operatingRoom = operatingRoomService.getById(id);
|
||||
if (operatingRoom == null) {
|
||||
return R.fail("手术室信息不存在");
|
||||
}
|
||||
|
||||
OperatingRoomDto operatingRoomDto = new OperatingRoomDto();
|
||||
BeanUtils.copyProperties(operatingRoom, operatingRoomDto);
|
||||
|
||||
// 状态描述
|
||||
operatingRoomDto.setStatusEnum_dictText(
|
||||
operatingRoom.getStatusEnum() != null && operatingRoom.getStatusEnum() == 1 ? "启用" : "停用");
|
||||
|
||||
// 如果有机构ID,查询机构名称
|
||||
if (operatingRoom.getOrganizationId() != null) {
|
||||
String orgName = commonService.getOrgNameById(operatingRoom.getOrganizationId());
|
||||
operatingRoomDto.setOrganizationName(orgName);
|
||||
}
|
||||
|
||||
return R.ok(operatingRoomDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增手术室
|
||||
*
|
||||
* @param operatingRoomDto 手术室信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public R<?> addOperatingRoom(OperatingRoomDto operatingRoomDto) {
|
||||
// 校验名称不能为空
|
||||
if (StringUtils.isEmpty(operatingRoomDto.getName())) {
|
||||
return R.fail("手术室名称不能为空");
|
||||
}
|
||||
|
||||
// 去除空格
|
||||
String name = operatingRoomDto.getName().replaceAll("[ ]", "");
|
||||
operatingRoomDto.setName(name);
|
||||
|
||||
// 判断是否存在同名
|
||||
if (isExistName(name, null)) {
|
||||
return R.fail("【" + name + "】已存在");
|
||||
}
|
||||
|
||||
OperatingRoom operatingRoom = new OperatingRoom();
|
||||
BeanUtils.copyProperties(operatingRoomDto, operatingRoom);
|
||||
|
||||
// 生成编码
|
||||
String code = assignSeqUtil.getSeq(AssignSeqEnum.OPERATING_ROOM_BUS_NO.getPrefix(), 3);
|
||||
operatingRoom.setBusNo(code);
|
||||
|
||||
// 拼音码
|
||||
operatingRoom.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(operatingRoomDto.getName()));
|
||||
// 五笔码
|
||||
operatingRoom.setWbStr(ChineseConvertUtils.toWBFirstLetter(operatingRoomDto.getName()));
|
||||
|
||||
boolean result = operatingRoomService.save(operatingRoom);
|
||||
if (result) {
|
||||
return R.ok(null, "新增成功");
|
||||
}
|
||||
return R.fail("新增失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改手术室
|
||||
*
|
||||
* @param operatingRoomDto 手术室信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public R<?> updateOperatingRoom(OperatingRoomDto operatingRoomDto) {
|
||||
// 校验手术室是否存在
|
||||
OperatingRoom existOperatingRoom = operatingRoomService.getById(operatingRoomDto.getId());
|
||||
if (existOperatingRoom == null) {
|
||||
return R.fail("手术室信息不存在");
|
||||
}
|
||||
|
||||
// 校验名称不能为空
|
||||
if (StringUtils.isEmpty(operatingRoomDto.getName())) {
|
||||
return R.fail("手术室名称不能为空");
|
||||
}
|
||||
|
||||
// 去除空格
|
||||
String name = operatingRoomDto.getName().replaceAll("[ ]", "");
|
||||
operatingRoomDto.setName(name);
|
||||
|
||||
// 判断是否存在同名(排除自己)
|
||||
if (isExistName(name, operatingRoomDto.getId())) {
|
||||
return R.fail("【" + name + "】已存在");
|
||||
}
|
||||
|
||||
OperatingRoom operatingRoom = new OperatingRoom();
|
||||
BeanUtils.copyProperties(operatingRoomDto, operatingRoom);
|
||||
|
||||
// 拼音码
|
||||
operatingRoom.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(operatingRoomDto.getName()));
|
||||
// 五笔码
|
||||
operatingRoom.setWbStr(ChineseConvertUtils.toWBFirstLetter(operatingRoomDto.getName()));
|
||||
|
||||
boolean result = operatingRoomService.updateById(operatingRoom);
|
||||
if (result) {
|
||||
return R.ok(null, "修改成功");
|
||||
}
|
||||
return R.fail("修改失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除手术室
|
||||
*
|
||||
* @param ids 手术室ID(支持批量)
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public R<?> deleteOperatingRoom(String ids) {
|
||||
// 解析ID字符串
|
||||
String[] idArray = ids.split(",");
|
||||
List<Long> idList = new ArrayList<>();
|
||||
for (String idStr : idArray) {
|
||||
try {
|
||||
idList.add(Long.parseLong(idStr.trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
return R.fail("ID格式错误");
|
||||
}
|
||||
}
|
||||
|
||||
// 删除手术室
|
||||
boolean result = operatingRoomService.removeByIds(idList);
|
||||
if (result) {
|
||||
return R.ok(null, "删除成功");
|
||||
}
|
||||
return R.fail("删除失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用手术室
|
||||
*
|
||||
* @param ids 手术室ID数组
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public R<?> enableOperatingRoom(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return R.fail("请选择要启用的手术室");
|
||||
}
|
||||
|
||||
// 批量更新状态为启用
|
||||
List<OperatingRoom> operatingRooms = operatingRoomService.listByIds(ids);
|
||||
for (OperatingRoom operatingRoom : operatingRooms) {
|
||||
operatingRoom.setStatusEnum(LocationStatus.ACTIVE.getValue());
|
||||
}
|
||||
|
||||
boolean result = operatingRoomService.updateBatchById(operatingRooms);
|
||||
if (result) {
|
||||
return R.ok("启用成功");
|
||||
}
|
||||
return R.fail("启用失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用手术室
|
||||
*
|
||||
* @param ids 手术室ID数组
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public R<?> disableOperatingRoom(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return R.fail("请选择要停用的手术室");
|
||||
}
|
||||
|
||||
// 批量更新状态为停用
|
||||
List<OperatingRoom> operatingRooms = operatingRoomService.listByIds(ids);
|
||||
for (OperatingRoom operatingRoom : operatingRooms) {
|
||||
operatingRoom.setStatusEnum(LocationStatus.INACTIVE.getValue());
|
||||
}
|
||||
|
||||
boolean result = operatingRoomService.updateBatchById(operatingRooms);
|
||||
if (result) {
|
||||
return R.ok("停用成功");
|
||||
}
|
||||
return R.fail("停用失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断名称是否已存在
|
||||
*
|
||||
* @param name 名称
|
||||
* @param excludeId 排除的ID
|
||||
* @return 是否存在
|
||||
*/
|
||||
private boolean isExistName(String name, Long excludeId) {
|
||||
LambdaQueryWrapper<OperatingRoom> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(OperatingRoom::getName, name);
|
||||
if (excludeId != null) {
|
||||
queryWrapper.ne(OperatingRoom::getId, excludeId);
|
||||
}
|
||||
return operatingRoomService.count(queryWrapper) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.openhis.web.basedatamanage.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.openhis.web.basedatamanage.appservice.IOperatingRoomAppService;
|
||||
import com.openhis.web.basedatamanage.dto.OperatingRoomDto;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 手术室管理Controller
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-01-04
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/base-data-manage/operating-room")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class OperatingRoomController {
|
||||
|
||||
@Resource
|
||||
private IOperatingRoomAppService operatingRoomAppService;
|
||||
|
||||
/**
|
||||
* 分页查询手术室列表
|
||||
*
|
||||
* @param operatingRoomDto 查询条件
|
||||
* @param pageNo 当前页码
|
||||
* @param pageSize 查询条数
|
||||
* @param request 请求
|
||||
* @return 手术室列表
|
||||
*/
|
||||
@GetMapping(value = "/list")
|
||||
public R<?> getOperatingRoomPage(OperatingRoomDto operatingRoomDto,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest request) {
|
||||
return operatingRoomAppService.getOperatingRoomPage(operatingRoomDto, pageNo, pageSize, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手术室详情
|
||||
*
|
||||
* @param id 手术室ID
|
||||
* @return 手术室详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public R<?> getOperatingRoomById(@PathVariable Long id) {
|
||||
return operatingRoomAppService.getOperatingRoomById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增手术室
|
||||
*
|
||||
* @param operatingRoomDto 手术室信息
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping
|
||||
public R<?> addOperatingRoom(@Validated @RequestBody OperatingRoomDto operatingRoomDto) {
|
||||
return operatingRoomAppService.addOperatingRoom(operatingRoomDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改手术室
|
||||
*
|
||||
* @param operatingRoomDto 手术室信息
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PutMapping
|
||||
public R<?> updateOperatingRoom(@Validated @RequestBody OperatingRoomDto operatingRoomDto) {
|
||||
return operatingRoomAppService.updateOperatingRoom(operatingRoomDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除手术室
|
||||
*
|
||||
* @param ids 手术室ID(支持批量)
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<?> deleteOperatingRoom(@PathVariable String ids) {
|
||||
return operatingRoomAppService.deleteOperatingRoom(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用手术室
|
||||
*
|
||||
* @param ids 手术室ID数组
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PutMapping("/enable")
|
||||
public R<?> enableOperatingRoom(@RequestBody List<Long> ids) {
|
||||
return operatingRoomAppService.enableOperatingRoom(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用手术室
|
||||
*
|
||||
* @param ids 手术室ID数组
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PutMapping("/disable")
|
||||
public R<?> disableOperatingRoom(@RequestBody List<Long> ids) {
|
||||
return operatingRoomAppService.disableOperatingRoom(ids);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.openhis.web.basedatamanage.dto;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 手术室DTO
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-01-04
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OperatingRoomDto implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
private String busNo;
|
||||
|
||||
/**
|
||||
* 手术室名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 所属机构ID
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long organizationId;
|
||||
|
||||
/**
|
||||
* 机构名称
|
||||
*/
|
||||
private String organizationName;
|
||||
|
||||
/**
|
||||
* 位置描述
|
||||
*/
|
||||
private String locationDescription;
|
||||
|
||||
/**
|
||||
* 设备配置
|
||||
*/
|
||||
private String equipmentConfig;
|
||||
|
||||
/**
|
||||
* 容纳人数
|
||||
*/
|
||||
private Integer capacity;
|
||||
|
||||
/**
|
||||
* 状态编码
|
||||
*/
|
||||
private Integer statusEnum;
|
||||
|
||||
/**
|
||||
* 状态描述
|
||||
*/
|
||||
private String statusEnum_dictText;
|
||||
|
||||
/**
|
||||
* 显示顺序
|
||||
*/
|
||||
private Integer displayOrder;
|
||||
|
||||
/**
|
||||
* 拼音码
|
||||
*/
|
||||
private String pyStr;
|
||||
|
||||
/**
|
||||
* 五笔码
|
||||
*/
|
||||
private String wbStr;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
}
|
||||
@@ -37,10 +37,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -119,6 +116,19 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
||||
List<Long> patientIdList =
|
||||
iEncounterService.list().stream().map(e -> e.getPatientId()).collect(Collectors.toList());
|
||||
|
||||
// 一次性获取所有患者标识
|
||||
List<Long> patientIds = patientMetadataPage.getRecords().stream()
|
||||
.map(PatientMetadata::getId)
|
||||
.collect(Collectors.toList());
|
||||
final Map<Long, List<PatientIdentifier>> patientIdentifierMap;
|
||||
if (!patientIds.isEmpty()) {
|
||||
patientIdentifierMap = patientIdentifierService.list(
|
||||
new LambdaQueryWrapper<PatientIdentifier>().in(PatientIdentifier::getPatientId, patientIds)
|
||||
).stream().collect(Collectors.groupingBy(PatientIdentifier::getPatientId));
|
||||
} else {
|
||||
patientIdentifierMap = new HashMap<>();
|
||||
}
|
||||
|
||||
patientMetadataPage.getRecords().forEach(e -> {
|
||||
// 性别枚举
|
||||
e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));
|
||||
@@ -127,9 +137,8 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
||||
// 初复诊
|
||||
e.setFirstEnum_enumText(patientIdList.contains(e.getId()) ? EncounterType.FOLLOW_UP.getInfo()
|
||||
: EncounterType.INITIAL.getInfo());
|
||||
// 患者标识
|
||||
List<PatientIdentifier> patientIdentifiers = patientIdentifierService
|
||||
.list(new LambdaQueryWrapper<PatientIdentifier>().eq(PatientIdentifier::getPatientId, e.getId()));
|
||||
// 患者标识 - 从Map中获取,避免N+1查询
|
||||
List<PatientIdentifier> patientIdentifiers = patientIdentifierMap.get(e.getId());
|
||||
if (patientIdentifiers != null && !patientIdentifiers.isEmpty()) {
|
||||
// 取第一个标识号,如果需要可以根据业务需求选择其他逻辑
|
||||
e.setIdentifierNo(patientIdentifiers.get(0).getIdentifierNo());
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.openhis.web.clinicalmanage.appservice;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.openhis.administration.domain.Encounter;
|
||||
import com.openhis.web.clinicalmanage.dto.SurgeryDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 手术管理应用Service接口
|
||||
*
|
||||
@@ -62,4 +65,12 @@ public interface ISurgeryAppService {
|
||||
* @return 结果
|
||||
*/
|
||||
R<?> updateSurgeryStatus(Long id, Integer statusEnum);
|
||||
|
||||
/**
|
||||
* 根据患者ID查询就诊列表
|
||||
*
|
||||
* @param patientId 患者ID
|
||||
* @return 就诊列表
|
||||
*/
|
||||
R<List<Encounter>> getEncounterListByPatientId(Long patientId);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,53 @@
|
||||
package com.openhis.web.clinicalmanage.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.domain.entity.SysUser;
|
||||
import com.core.common.utils.MessageUtils;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.core.system.service.ISysUserService;
|
||||
import com.openhis.administration.domain.ChargeItem;
|
||||
import com.openhis.administration.domain.Encounter;
|
||||
import com.openhis.administration.domain.OperatingRoom;
|
||||
import com.openhis.administration.domain.Organization;
|
||||
import com.openhis.administration.domain.Patient;
|
||||
import com.openhis.administration.service.IAccountService;
|
||||
import com.openhis.administration.service.IChargeItemService;
|
||||
import com.openhis.administration.service.IEncounterService;
|
||||
import com.openhis.administration.service.IOrganizationService;
|
||||
import com.openhis.administration.service.IOperatingRoomService;
|
||||
import com.openhis.administration.service.IPatientService;
|
||||
import com.openhis.common.constant.PromptMsgConstant;
|
||||
import com.openhis.clinical.domain.Surgery;
|
||||
import com.openhis.clinical.service.ISurgeryService;
|
||||
import com.openhis.common.constant.CommonConstants;
|
||||
import com.openhis.common.constant.PromptMsgConstant;
|
||||
import com.openhis.common.enums.ChargeItemStatus;
|
||||
import com.openhis.common.enums.GenerateSource;
|
||||
import com.openhis.common.enums.RequestStatus;
|
||||
import com.openhis.common.enums.TherapyTimeType;
|
||||
import com.openhis.common.utils.HisQueryUtils;
|
||||
import com.openhis.document.domain.RequestForm;
|
||||
import com.openhis.document.service.IRequestFormService;
|
||||
import com.openhis.web.clinicalmanage.appservice.ISurgeryAppService;
|
||||
import com.openhis.web.clinicalmanage.dto.SurgeryDto;
|
||||
import com.openhis.web.clinicalmanage.mapper.SurgeryAppMapper;
|
||||
import com.openhis.workflow.domain.ServiceRequest;
|
||||
import com.openhis.workflow.service.IActivityDefinitionService;
|
||||
import com.openhis.workflow.service.IServiceRequestService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static com.core.framework.datasource.DynamicDataSourceContextHolder.log;
|
||||
|
||||
/**
|
||||
* 手术管理应用Service业务层处理
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-12-30
|
||||
*/
|
||||
@Service
|
||||
public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
||||
|
||||
@@ -40,6 +60,33 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
||||
@Resource
|
||||
private IPatientService patientService;
|
||||
|
||||
@Resource
|
||||
private IEncounterService encounterService;
|
||||
|
||||
@Resource
|
||||
private IRequestFormService requestFormService;
|
||||
|
||||
@Resource
|
||||
private IServiceRequestService serviceRequestService;
|
||||
|
||||
@Resource
|
||||
private IChargeItemService chargeItemService;
|
||||
|
||||
@Resource
|
||||
private IActivityDefinitionService activityDefinitionService;
|
||||
|
||||
@Resource
|
||||
private IAccountService accountService;
|
||||
|
||||
@Resource
|
||||
private IOrganizationService organizationService;
|
||||
|
||||
@Resource
|
||||
private ISysUserService sysUserService;
|
||||
|
||||
@Resource
|
||||
private IOperatingRoomService operatingRoomService;
|
||||
|
||||
/**
|
||||
* 分页查询手术列表
|
||||
*
|
||||
@@ -85,6 +132,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> addSurgery(SurgeryDto surgeryDto) {
|
||||
// 校验患者是否存在
|
||||
Patient patient = patientService.getById(surgeryDto.getPatientId());
|
||||
@@ -92,14 +140,169 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
||||
return R.fail("患者信息不存在");
|
||||
}
|
||||
|
||||
// 校验就诊ID是否存在
|
||||
if (surgeryDto.getEncounterId() == null) {
|
||||
return R.fail("请选择就诊流水号");
|
||||
}
|
||||
|
||||
// 校验就诊记录是否存在
|
||||
Encounter encounter = encounterService.getById(surgeryDto.getEncounterId());
|
||||
if (encounter == null) {
|
||||
return R.fail("就诊记录不存在");
|
||||
}
|
||||
|
||||
// 获取患者的自费账户ID
|
||||
Long accountId = accountService.getSelfPayAccount(surgeryDto.getEncounterId());
|
||||
if (accountId == null) {
|
||||
return R.fail("未找到患者的账户信息,请先完成挂号或住院登记");
|
||||
}
|
||||
|
||||
// 当前登录账号的科室id
|
||||
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||
// 当前参与者ID
|
||||
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||
// 当前用户ID
|
||||
Long userId = SecurityUtils.getLoginUser().getUserId();
|
||||
// 当前时间
|
||||
Date curDate = new Date();
|
||||
|
||||
// 获取申请医生姓名(从当前登录用户信息中获取)
|
||||
String applyDoctorName = SecurityUtils.getLoginUser().getUser().getNickName();
|
||||
|
||||
// 获取申请科室名称
|
||||
String applyDeptName = null;
|
||||
// 优先从用户信息的部门中获取
|
||||
if (SecurityUtils.getLoginUser().getUser().getDept() != null) {
|
||||
applyDeptName = SecurityUtils.getLoginUser().getUser().getDept().getDeptName();
|
||||
}
|
||||
// 如果用户信息中没有部门名称,则从机构表中查询
|
||||
if (applyDeptName == null && orgId != null) {
|
||||
Organization org = organizationService.getById(orgId);
|
||||
if (org != null) {
|
||||
applyDeptName = org.getName();
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为实体对象
|
||||
Surgery surgery = new Surgery();
|
||||
BeanUtils.copyProperties(surgeryDto, surgery);
|
||||
|
||||
// 清空名称字段,确保从ID重新查询并填充
|
||||
surgery.setPatientName(null);
|
||||
surgery.setMainSurgeonName(null);
|
||||
surgery.setAnesthetistName(null);
|
||||
surgery.setAssistant1Name(null);
|
||||
surgery.setAssistant2Name(null);
|
||||
surgery.setScrubNurseName(null);
|
||||
surgery.setOperatingRoomName(null);
|
||||
surgery.setOrgName(null);
|
||||
surgery.setApplyDoctorName(null);
|
||||
surgery.setApplyDeptName(null);
|
||||
|
||||
// 设置申请医生信息(默认使用当前登录医生)
|
||||
// 注意:必须放在 copyProperties 之后,确保覆盖前端可能传递的空值
|
||||
log.info("设置申请医生信息 - doctorId: {}, doctorName: {}, deptId: {}, deptName: {}",
|
||||
practitionerId, applyDoctorName, orgId, applyDeptName);
|
||||
log.info("前端提交的数据 - applyDoctorId: {}, applyDoctorName: {}, applyDeptId: {}, applyDeptName: {}",
|
||||
surgeryDto.getApplyDoctorId(), surgeryDto.getApplyDoctorName(), surgeryDto.getApplyDeptId(), surgeryDto.getApplyDeptName());
|
||||
|
||||
surgery.setApplyDoctorId(practitionerId);
|
||||
surgery.setApplyDoctorName(applyDoctorName);
|
||||
surgery.setApplyDeptId(orgId);
|
||||
surgery.setApplyDeptName(applyDeptName);
|
||||
|
||||
// 填充其他人员字段的名称
|
||||
fillSurgeryNameFields(surgery);
|
||||
|
||||
// 设置创建者ID(因为数据库中 create_by 是 bigint 类型)
|
||||
// 这个值会被 MybastisColumnsHandler 自动填充,所以这里不需要设置
|
||||
|
||||
log.info("准备插入手术记录 - applyDoctorId: {}, applyDoctorName: {}, applyDeptId: {}, applyDeptName: {}",
|
||||
surgery.getApplyDoctorId(), surgery.getApplyDoctorName(), surgery.getApplyDeptId(), surgery.getApplyDeptName());
|
||||
log.info("准备插入手术记录 - mainSurgeonId: {}, mainSurgeonName: {}, anesthetistId: {}, anesthetistName: {}",
|
||||
surgery.getMainSurgeonId(), surgery.getMainSurgeonName(), surgery.getAnesthetistId(), surgery.getAnesthetistName());
|
||||
log.info("准备插入手术记录 - assistant1Id: {}, assistant1Name: {}, assistant2Id: {}, assistant2Name: {}",
|
||||
surgery.getAssistant1Id(), surgery.getAssistant1Name(), surgery.getAssistant2Id(), surgery.getAssistant2Name());
|
||||
log.info("准备插入手术记录 - operatingRoomId: {}, operatingRoomName: {}, orgId: {}, orgName: {}",
|
||||
surgery.getOperatingRoomId(), surgery.getOperatingRoomName(), surgery.getOrgId(), surgery.getOrgName());
|
||||
|
||||
Long surgeryId = surgeryService.insertSurgery(surgery);
|
||||
|
||||
log.info("手术记录插入成功 - surgeryId: {}, surgeryNo: {}", surgeryId, surgery.getSurgeryNo());
|
||||
|
||||
// 生成处方号(医嘱号)
|
||||
String prescriptionNo = surgery.getSurgeryNo();
|
||||
|
||||
// 保存申请单
|
||||
RequestForm requestForm = new RequestForm();
|
||||
requestForm.setTypeCode("SURGERY"); // 申请单类型
|
||||
requestForm.setPrescriptionNo(prescriptionNo); // 处方号(使用手术单号)
|
||||
requestForm.setName("手术申请单"); // 名称
|
||||
requestForm.setEncounterId(surgeryDto.getEncounterId()); // 就诊ID
|
||||
requestForm.setRequesterId(practitionerId); // 申请人
|
||||
requestForm.setDescJson(buildDescJson(surgeryDto)); // 描述内容
|
||||
requestFormService.save(requestForm);
|
||||
|
||||
// 生成手术医嘱
|
||||
ServiceRequest serviceRequest = new ServiceRequest();
|
||||
serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());
|
||||
serviceRequest.setBusNo(String.format("%04d", (int) (Math.random() * 10000)));
|
||||
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||
serviceRequest.setPrescriptionNo(prescriptionNo);
|
||||
serviceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());// 治疗类型
|
||||
serviceRequest.setQuantity(BigDecimal.valueOf(1)); // 请求数量
|
||||
serviceRequest.setUnitCode("次"); // 请求单位编码
|
||||
serviceRequest.setCategoryEnum(4); // 请求类型:4-手术
|
||||
serviceRequest.setActivityId(surgeryId); // 手术ID作为诊疗定义id
|
||||
serviceRequest.setPatientId(surgeryDto.getPatientId()); // 患者
|
||||
serviceRequest.setRequesterId(practitionerId); // 开方医生
|
||||
serviceRequest.setEncounterId(surgeryDto.getEncounterId()); // 就诊id
|
||||
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
|
||||
serviceRequest.setOrgId(orgId); // 执行科室
|
||||
serviceRequestService.save(serviceRequest);
|
||||
|
||||
// 生成收费项目
|
||||
ChargeItem chargeItem = new ChargeItem();
|
||||
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
||||
chargeItem.setBusNo("CI" + serviceRequest.getBusNo());
|
||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||
chargeItem.setPatientId(surgeryDto.getPatientId()); // 患者
|
||||
chargeItem.setContextEnum(3); // 类型:3-诊疗
|
||||
chargeItem.setEncounterId(surgeryDto.getEncounterId()); // 就诊id
|
||||
chargeItem.setAccountId(accountId); // 账户ID
|
||||
chargeItem.setDefinitionId(surgeryId); // 手术ID作为费用定价ID
|
||||
chargeItem.setEntererId(practitionerId);// 开立人ID
|
||||
chargeItem.setEnteredDate(curDate); // 开立时间
|
||||
chargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);// 医疗服务类型
|
||||
chargeItem.setServiceId(serviceRequest.getId()); // 医疗服务ID
|
||||
chargeItem.setProductTable("cli_surgery");// 手术表
|
||||
chargeItem.setProductId(surgeryId);// 手术ID作为收费项id
|
||||
chargeItem.setRequestingOrgId(orgId); // 开立科室
|
||||
chargeItem.setQuantityValue(BigDecimal.valueOf(1)); // 数量
|
||||
chargeItem.setQuantityUnit("次"); // 单位
|
||||
chargeItem.setUnitPrice(surgeryDto.getSurgeryFee() != null ? surgeryDto.getSurgeryFee() : new BigDecimal("0.0")); // 单价
|
||||
chargeItem.setTotalPrice(surgeryDto.getTotalFee() != null ? surgeryDto.getTotalFee() : new BigDecimal("0.0")); // 总价
|
||||
chargeItemService.save(chargeItem);
|
||||
|
||||
return R.ok(surgeryId, MessageUtils.createMessage(PromptMsgConstant.Common.M00001, new Object[]{"手术信息"}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建描述JSON
|
||||
*
|
||||
* @param surgeryDto 手术信息
|
||||
* @return JSON字符串
|
||||
*/
|
||||
private String buildDescJson(SurgeryDto surgeryDto) {
|
||||
return String.format(
|
||||
"{\"surgeryName\":\"%s\",\"surgeryLevel\":\"%s\",\"surgeryIndication\":\"%s\",\"preoperativeDiagnosis\":\"%s\"}",
|
||||
surgeryDto.getSurgeryName() != null ? surgeryDto.getSurgeryName() : "",
|
||||
surgeryDto.getSurgeryLevel() != null ? surgeryDto.getSurgeryLevel() : "",
|
||||
surgeryDto.getSurgeryIndication() != null ? surgeryDto.getSurgeryIndication() : "",
|
||||
surgeryDto.getPreoperativeDiagnosis() != null ? surgeryDto.getPreoperativeDiagnosis() : ""
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改手术信息
|
||||
*
|
||||
@@ -118,6 +321,21 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
||||
Surgery surgery = new Surgery();
|
||||
BeanUtils.copyProperties(surgeryDto, surgery);
|
||||
|
||||
// 先清空名称字段,确保重新填充
|
||||
surgery.setPatientName(null);
|
||||
surgery.setMainSurgeonName(null);
|
||||
surgery.setAnesthetistName(null);
|
||||
surgery.setAssistant1Name(null);
|
||||
surgery.setAssistant2Name(null);
|
||||
surgery.setScrubNurseName(null);
|
||||
surgery.setOperatingRoomName(null);
|
||||
surgery.setOrgName(null);
|
||||
surgery.setApplyDoctorName(null);
|
||||
surgery.setApplyDeptName(null);
|
||||
|
||||
// 填充其他人员字段的名称
|
||||
fillSurgeryNameFields(surgery);
|
||||
|
||||
surgeryService.updateSurgery(surgery);
|
||||
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"手术信息"}));
|
||||
}
|
||||
@@ -163,4 +381,124 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
||||
surgeryService.updateSurgeryStatus(id, statusEnum);
|
||||
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"手术状态"}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据患者ID查询就诊列表
|
||||
*
|
||||
* @param patientId 患者ID
|
||||
* @return 就诊列表
|
||||
*/
|
||||
@Override
|
||||
public R<List<Encounter>> getEncounterListByPatientId(Long patientId) {
|
||||
if (patientId == null) {
|
||||
return R.fail("患者ID不能为空");
|
||||
}
|
||||
|
||||
// 查询该患者的所有就诊记录(进行中的优先)
|
||||
QueryWrapper<Encounter> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("patient_id", patientId)
|
||||
.in("status_enum", 2, 3) // 2-进行中, 3-已完成
|
||||
.orderByAsc("CASE WHEN status_enum = 2 THEN 0 ELSE 1 END") // 进行中的排在前面
|
||||
.orderByDesc("start_time"); // 按开始时间倒序
|
||||
|
||||
List<Encounter> encounterList = encounterService.list(wrapper);
|
||||
|
||||
if (encounterList == null || encounterList.isEmpty()) {
|
||||
return R.fail("该患者暂无就诊记录,请先挂号或办理住院");
|
||||
}
|
||||
|
||||
return R.ok(encounterList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充手术记录中的名称字段
|
||||
* 根据ID反向查询用户表、机构表、手术室表、患者表、就诊表,填充对应的名称字段
|
||||
*
|
||||
* @param surgery 手术实体对象
|
||||
*/
|
||||
private void fillSurgeryNameFields(Surgery surgery) {
|
||||
// 填充患者姓名
|
||||
if (surgery.getPatientId() != null) {
|
||||
Patient patient = patientService.getById(surgery.getPatientId());
|
||||
if (patient != null) {
|
||||
surgery.setPatientName(patient.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充主刀医生姓名
|
||||
if (surgery.getMainSurgeonId() != null) {
|
||||
SysUser mainSurgeon = sysUserService.selectUserById(surgery.getMainSurgeonId());
|
||||
if (mainSurgeon != null) {
|
||||
surgery.setMainSurgeonName(mainSurgeon.getNickName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充麻醉医生姓名
|
||||
if (surgery.getAnesthetistId() != null) {
|
||||
SysUser anesthetist = sysUserService.selectUserById(surgery.getAnesthetistId());
|
||||
if (anesthetist != null) {
|
||||
surgery.setAnesthetistName(anesthetist.getNickName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充助手1姓名
|
||||
if (surgery.getAssistant1Id() != null) {
|
||||
SysUser assistant1 = sysUserService.selectUserById(surgery.getAssistant1Id());
|
||||
if (assistant1 != null) {
|
||||
surgery.setAssistant1Name(assistant1.getNickName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充助手2姓名
|
||||
if (surgery.getAssistant2Id() != null) {
|
||||
SysUser assistant2 = sysUserService.selectUserById(surgery.getAssistant2Id());
|
||||
if (assistant2 != null) {
|
||||
surgery.setAssistant2Name(assistant2.getNickName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充巡回护士姓名
|
||||
if (surgery.getScrubNurseId() != null) {
|
||||
SysUser scrubNurse = sysUserService.selectUserById(surgery.getScrubNurseId());
|
||||
if (scrubNurse != null) {
|
||||
surgery.setScrubNurseName(scrubNurse.getNickName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充手术室名称
|
||||
if (surgery.getOperatingRoomId() != null) {
|
||||
OperatingRoom operatingRoom = operatingRoomService.getById(surgery.getOperatingRoomId());
|
||||
if (operatingRoom != null) {
|
||||
surgery.setOperatingRoomName(operatingRoom.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充执行科室名称
|
||||
if (surgery.getOrgId() != null) {
|
||||
Organization org = organizationService.getById(surgery.getOrgId());
|
||||
if (org != null) {
|
||||
surgery.setOrgName(org.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充申请科室名称(如果还没有设置)
|
||||
if (surgery.getApplyDeptId() != null && (surgery.getApplyDeptName() == null || surgery.getApplyDeptName().isEmpty())) {
|
||||
Organization applyDept = organizationService.getById(surgery.getApplyDeptId());
|
||||
if (applyDept != null) {
|
||||
surgery.setApplyDeptName(applyDept.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充申请医生姓名(如果还没有设置)
|
||||
if (surgery.getApplyDoctorId() != null && (surgery.getApplyDoctorName() == null || surgery.getApplyDoctorName().isEmpty())) {
|
||||
SysUser applyDoctor = sysUserService.selectUserById(surgery.getApplyDoctorId());
|
||||
if (applyDoctor != null) {
|
||||
surgery.setApplyDoctorName(applyDoctor.getNickName());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("填充手术名称字段完成 - patientName: {}, mainSurgeonName: {}, anesthetistName: {}, assistant1Name: {}, assistant2Name: {}, scrubNurseName: {}, operatingRoomName: {}, orgName: {}",
|
||||
surgery.getPatientName(), surgery.getMainSurgeonName(), surgery.getAnesthetistName(), surgery.getAssistant1Name(),
|
||||
surgery.getAssistant2Name(), surgery.getScrubNurseName(), surgery.getOperatingRoomName(), surgery.getOrgName());
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@ package com.openhis.web.clinicalmanage.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.openhis.administration.domain.Encounter;
|
||||
import com.openhis.web.clinicalmanage.appservice.ISurgeryAppService;
|
||||
import com.openhis.web.clinicalmanage.dto.SurgeryDto;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 手术管理Controller业务层处理
|
||||
*
|
||||
@@ -93,4 +96,15 @@ public class SurgeryController {
|
||||
public R<?> updateSurgeryStatus(@RequestParam Long id, @RequestParam Integer statusEnum) {
|
||||
return surgeryAppService.updateSurgeryStatus(id, statusEnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据患者ID查询就诊列表
|
||||
*
|
||||
* @param patientId 患者ID
|
||||
* @return 就诊列表
|
||||
*/
|
||||
@GetMapping(value = "/encounter-list")
|
||||
public R<List<Encounter>> getEncounterListByPatientId(@RequestParam Long patientId) {
|
||||
return surgeryAppService.getEncounterListByPatientId(patientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,23 @@ public class SurgeryDto {
|
||||
/** 就诊流水号 */
|
||||
private String encounterNo;
|
||||
|
||||
/** 申请医生ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long applyDoctorId;
|
||||
|
||||
/** 申请医生姓名 */
|
||||
private String applyDoctorName;
|
||||
|
||||
/** 申请科室ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long applyDeptId;
|
||||
|
||||
/** 申请科室名称 */
|
||||
private String applyDeptName;
|
||||
|
||||
/** 手术指征 */
|
||||
private String surgeryIndication;
|
||||
|
||||
/** 手术名称 */
|
||||
private String surgeryName;
|
||||
|
||||
@@ -133,6 +150,13 @@ public class SurgeryDto {
|
||||
/** 手术室名称 */
|
||||
private String operatingRoomName;
|
||||
|
||||
/** 手术室所属机构ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long operatingRoomOrgId;
|
||||
|
||||
/** 手术室所属机构名称 */
|
||||
private String operatingRoomOrgName;
|
||||
|
||||
/** 执行科室ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long orgId;
|
||||
|
||||
@@ -203,4 +203,12 @@ public interface ICommonService {
|
||||
* @return 处理结果
|
||||
*/
|
||||
R<?> lotNumberMatch(List<Long> encounterIdList);
|
||||
|
||||
/**
|
||||
* 根据机构ID获取机构名称
|
||||
*
|
||||
* @param orgId 机构ID
|
||||
* @return 机构名称
|
||||
*/
|
||||
String getOrgNameById(Long orgId);
|
||||
}
|
||||
|
||||
@@ -816,4 +816,24 @@ public class CommonServiceImpl implements ICommonService {
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据机构ID获取机构名称
|
||||
*
|
||||
* @param orgId 机构ID
|
||||
* @return 机构名称
|
||||
*/
|
||||
@Override
|
||||
public String getOrgNameById(Long orgId) {
|
||||
if (orgId == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Organization organization = organizationService.getById(orgId);
|
||||
if (organization == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return organization.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.openhis.web.doctorstation.appservice;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientPatientDto;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientQueryParam;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientStatsDto;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 今日门诊服务接口
|
||||
*/
|
||||
public interface ITodayOutpatientService {
|
||||
|
||||
/**
|
||||
* 获取今日门诊统计信息
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 今日门诊统计
|
||||
*/
|
||||
TodayOutpatientStatsDto getTodayOutpatientStats(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 分页查询今日门诊患者列表
|
||||
*
|
||||
* @param queryParam 查询参数
|
||||
* @param request HTTP请求
|
||||
* @return 分页患者列表
|
||||
*/
|
||||
IPage<TodayOutpatientPatientDto> getTodayOutpatientPatients(TodayOutpatientQueryParam queryParam,
|
||||
HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 获取今日待就诊患者队列(按挂号时间排序)
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 待就诊患者列表
|
||||
*/
|
||||
List<TodayOutpatientPatientDto> getWaitingPatients(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 获取今日就诊中患者列表
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 就诊中患者列表
|
||||
*/
|
||||
List<TodayOutpatientPatientDto> getInProgressPatients(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 获取今日已完成就诊患者列表
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 已完成就诊患者列表
|
||||
*/
|
||||
List<TodayOutpatientPatientDto> getCompletedPatients(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 获取患者就诊详情
|
||||
*
|
||||
* @param encounterId 就诊记录ID
|
||||
* @param request HTTP请求
|
||||
* @return 患者就诊详情
|
||||
*/
|
||||
TodayOutpatientPatientDto getPatientDetail(Long encounterId, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 批量更新患者状态
|
||||
*
|
||||
* @param encounterIds 就诊记录ID列表
|
||||
* @param targetStatus 目标状态
|
||||
* @param request HTTP请求
|
||||
* @return 更新结果
|
||||
*/
|
||||
R<?> batchUpdatePatientStatus(List<Long> encounterIds, Integer targetStatus, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 接诊患者
|
||||
*
|
||||
* @param encounterId 就诊记录ID
|
||||
* @param request HTTP请求
|
||||
* @return 接诊结果
|
||||
*/
|
||||
R<?> receivePatient(Long encounterId, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 完成就诊
|
||||
*
|
||||
* @param encounterId 就诊记录ID
|
||||
* @param request HTTP请求
|
||||
* @return 完成结果
|
||||
*/
|
||||
R<?> completeVisit(Long encounterId, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 取消就诊
|
||||
*
|
||||
* @param encounterId 就诊记录ID
|
||||
* @param reason 取消原因
|
||||
* @param request HTTP请求
|
||||
* @return 取消结果
|
||||
*/
|
||||
R<?> cancelVisit(Long encounterId, String reason, HttpServletRequest request);
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package com.openhis.web.doctorstation.appservice.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.utils.SecurityUtils;
|
||||
import com.openhis.common.constant.CommonConstants;
|
||||
import com.openhis.common.enums.*;
|
||||
import com.openhis.common.utils.EnumUtils;
|
||||
import com.openhis.common.utils.HisQueryUtils;
|
||||
import com.openhis.web.doctorstation.appservice.ITodayOutpatientService;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientPatientDto;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientQueryParam;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientStatsDto;
|
||||
import com.openhis.web.doctorstation.mapper.TodayOutpatientMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 今日门诊服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class TodayOutpatientServiceImpl implements ITodayOutpatientService {
|
||||
|
||||
@Resource
|
||||
private TodayOutpatientMapper todayOutpatientMapper;
|
||||
|
||||
@Override
|
||||
public TodayOutpatientStatsDto getTodayOutpatientStats(HttpServletRequest request) {
|
||||
Long doctorId = SecurityUtils.getLoginUser().getUserId();
|
||||
Long departmentId = SecurityUtils.getLoginUser().getOrgId();
|
||||
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||
String today = DateUtil.format(new Date(), "yyyy-MM-dd");
|
||||
|
||||
// 获取今日统计信息
|
||||
TodayOutpatientStatsDto stats = todayOutpatientMapper.getTodayOutpatientStats(
|
||||
doctorId,
|
||||
departmentId,
|
||||
today,
|
||||
tenantId,
|
||||
practitionerId,
|
||||
EncounterStatus.PLANNED.getValue(), // plannedStatus - Integer
|
||||
EncounterStatus.IN_PROGRESS.getValue(), // inProgressStatus - Integer
|
||||
EncounterStatus.DISCHARGED.getValue(), // dischargedStatus - Integer
|
||||
EncounterStatus.CANCELLED.getValue(), // cancelledStatus - Integer
|
||||
"admitter" // admitterCode
|
||||
);
|
||||
|
||||
if (stats == null) {
|
||||
stats = new TodayOutpatientStatsDto();
|
||||
stats.setTotalRegistered(0)
|
||||
.setWaitingCount(0)
|
||||
.setInProgressCount(0)
|
||||
.setCompletedCount(0)
|
||||
.setCancelledCount(0)
|
||||
.setAverageWaitingTime(0)
|
||||
.setAverageVisitTime(0)
|
||||
.setDoctorCount(0);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<TodayOutpatientPatientDto> getTodayOutpatientPatients(TodayOutpatientQueryParam queryParam,
|
||||
HttpServletRequest request) {
|
||||
// 设置默认值
|
||||
if (ObjectUtil.isEmpty(queryParam.getDoctorId())) {
|
||||
queryParam.setDoctorId(SecurityUtils.getLoginUser().getUserId());
|
||||
}
|
||||
if (ObjectUtil.isEmpty(queryParam.getDepartmentId())) {
|
||||
queryParam.setDepartmentId(SecurityUtils.getLoginUser().getOrgId());
|
||||
}
|
||||
if (ObjectUtil.isEmpty(queryParam.getQueryDate())) {
|
||||
queryParam.setQueryDate(DateUtil.format(new Date(), "yyyy-MM-dd"));
|
||||
}
|
||||
|
||||
// 保存原始值用于后续查询
|
||||
Long doctorId = queryParam.getDoctorId();
|
||||
Long departmentId = queryParam.getDepartmentId();
|
||||
String queryDate = queryParam.getQueryDate();
|
||||
Integer sortField = queryParam.getSortField();
|
||||
Integer sortOrder = queryParam.getSortOrder();
|
||||
Integer statusEnum = queryParam.getStatusEnum();
|
||||
Integer pageNo = queryParam.getPageNo();
|
||||
Integer pageSize = queryParam.getPageSize();
|
||||
|
||||
// 清空不需要通过QueryWrapper处理的字段(避免生成错误的WHERE条件)
|
||||
queryParam.setDoctorId(null);
|
||||
queryParam.setDepartmentId(null);
|
||||
queryParam.setQueryDate(null);
|
||||
queryParam.setSortField(null);
|
||||
queryParam.setSortOrder(null);
|
||||
queryParam.setPageNo(null);
|
||||
queryParam.setPageSize(null);
|
||||
|
||||
// 构建查询条件(只处理搜索条件和业务字段)
|
||||
QueryWrapper<TodayOutpatientPatientDto> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(CommonConstants.Common.TENANT_ID, SecurityUtils.getLoginUser().getTenantId());
|
||||
|
||||
// 处理模糊查询关键字
|
||||
if (ObjectUtil.isNotEmpty(queryParam.getSearchKey())) {
|
||||
String searchKey = queryParam.getSearchKey();
|
||||
queryWrapper.and(wrapper -> {
|
||||
wrapper.or().like("pt.name", searchKey)
|
||||
.or().like("pt.id_card", searchKey)
|
||||
.or().like("pt.phone", searchKey)
|
||||
.or().like("enc.bus_no", searchKey);
|
||||
});
|
||||
}
|
||||
|
||||
// 处理其他业务字段条件
|
||||
if (ObjectUtil.isNotEmpty(queryParam.getStatusEnum())) {
|
||||
queryWrapper.eq("enc.status_enum", queryParam.getStatusEnum());
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(queryParam.getTypeCode())) {
|
||||
queryWrapper.eq("pt.type_code", queryParam.getTypeCode());
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(queryParam.getImportantFlag())) {
|
||||
queryWrapper.eq("enc.important_flag", queryParam.getImportantFlag());
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(queryParam.getHasPrescription())) {
|
||||
queryWrapper.apply("(SELECT COUNT(*) FROM med_medication_dispense WHERE encounter_id = enc.id AND delete_flag = '0') > 0");
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(queryParam.getHasExamination())) {
|
||||
queryWrapper.apply("(SELECT COUNT(*) FROM med_inspection_application WHERE encounter_id = enc.id AND delete_flag = '0') > 0");
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(queryParam.getHasLaboratory())) {
|
||||
queryWrapper.apply("(SELECT COUNT(*) FROM med_test_application WHERE encounter_id = enc.id AND delete_flag = '0') > 0");
|
||||
}
|
||||
|
||||
// 添加时间条件
|
||||
queryWrapper.apply("enc.start_time::DATE >= CAST({0} AS DATE)", queryDate);
|
||||
queryWrapper.apply("enc.start_time::DATE <= CAST({0} AS DATE)", queryDate);
|
||||
|
||||
// 添加医生条件 - 查询当前医生的门诊患者
|
||||
queryWrapper.eq("ep.practitioner_id", SecurityUtils.getLoginUser().getPractitionerId());
|
||||
|
||||
// 添加状态条件
|
||||
if (ObjectUtil.isNotEmpty(statusEnum)) {
|
||||
queryWrapper.eq("enc.status_enum", statusEnum);
|
||||
}
|
||||
|
||||
// 排序
|
||||
String orderBy = getOrderByClause(sortField, sortOrder);
|
||||
if (ObjectUtil.isNotEmpty(orderBy)) {
|
||||
queryWrapper.orderBy(true, sortOrder == 1, orderBy);
|
||||
} else {
|
||||
queryWrapper.orderByDesc("enc.start_time");
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
IPage<TodayOutpatientPatientDto> result = todayOutpatientMapper.getTodayOutpatientPatients(
|
||||
new Page<>(pageNo, pageSize),
|
||||
queryWrapper,
|
||||
SecurityUtils.getLoginUser().getTenantId(),
|
||||
"admitter");
|
||||
|
||||
// 处理枚举字段显示文本
|
||||
result.getRecords().forEach(patient -> {
|
||||
// 性别
|
||||
patient.setGenderEnumEnumText(
|
||||
EnumUtils.getInfoByValue(AdministrativeGender.class, patient.getGenderEnum()));
|
||||
|
||||
// 就诊状态
|
||||
patient.setStatusEnumEnumText(
|
||||
EnumUtils.getInfoByValue(EncounterStatus.class, patient.getStatusEnum()));
|
||||
|
||||
// 就诊对象状态
|
||||
patient.setSubjectStatusEnumEnumText(
|
||||
EnumUtils.getInfoByValue(EncounterSubjectStatus.class, patient.getSubjectStatusEnum()));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TodayOutpatientPatientDto> getWaitingPatients(HttpServletRequest request) {
|
||||
TodayOutpatientQueryParam queryParam = new TodayOutpatientQueryParam();
|
||||
queryParam.setStatusEnum(EncounterStatus.PLANNED.getValue());
|
||||
queryParam.setSortField(1); // 按挂号时间排序
|
||||
queryParam.setSortOrder(2); // 降序
|
||||
|
||||
IPage<TodayOutpatientPatientDto> page = getTodayOutpatientPatients(queryParam, request);
|
||||
return page.getRecords();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TodayOutpatientPatientDto> getInProgressPatients(HttpServletRequest request) {
|
||||
TodayOutpatientQueryParam queryParam = new TodayOutpatientQueryParam();
|
||||
queryParam.setStatusEnum(EncounterStatus.IN_PROGRESS.getValue());
|
||||
queryParam.setSortField(2); // 按候诊时间排序
|
||||
|
||||
IPage<TodayOutpatientPatientDto> page = getTodayOutpatientPatients(queryParam, request);
|
||||
return page.getRecords();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TodayOutpatientPatientDto> getCompletedPatients(HttpServletRequest request) {
|
||||
TodayOutpatientQueryParam queryParam = new TodayOutpatientQueryParam();
|
||||
queryParam.setStatusEnum(EncounterStatus.DISCHARGED.getValue());
|
||||
queryParam.setSortField(3); // 按就诊号排序
|
||||
|
||||
IPage<TodayOutpatientPatientDto> page = getTodayOutpatientPatients(queryParam, request);
|
||||
return page.getRecords();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TodayOutpatientPatientDto getPatientDetail(Long encounterId, HttpServletRequest request) {
|
||||
QueryWrapper<TodayOutpatientPatientDto> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("enc.id", encounterId);
|
||||
queryWrapper.eq("enc.tenant_id", SecurityUtils.getLoginUser().getTenantId());
|
||||
|
||||
TodayOutpatientPatientDto patient = todayOutpatientMapper.getPatientDetail(
|
||||
queryWrapper,
|
||||
SecurityUtils.getLoginUser().getTenantId(),
|
||||
"admitter");
|
||||
|
||||
if (patient != null) {
|
||||
// 处理枚举字段显示文本
|
||||
patient.setGenderEnumEnumText(
|
||||
EnumUtils.getInfoByValue(AdministrativeGender.class, patient.getGenderEnum()));
|
||||
patient.setStatusEnumEnumText(
|
||||
EnumUtils.getInfoByValue(EncounterStatus.class, patient.getStatusEnum()));
|
||||
patient.setSubjectStatusEnumEnumText(
|
||||
EnumUtils.getInfoByValue(EncounterSubjectStatus.class, patient.getSubjectStatusEnum()));
|
||||
}
|
||||
|
||||
return patient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> batchUpdatePatientStatus(List<Long> encounterIds, Integer targetStatus,
|
||||
HttpServletRequest request) {
|
||||
if (ObjectUtil.isEmpty(encounterIds)) {
|
||||
return R.fail("就诊记录ID列表不能为空");
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if (EncounterStatus.getByValue(targetStatus) == null) {
|
||||
return R.fail("无效的状态值");
|
||||
}
|
||||
|
||||
// 执行批量更新
|
||||
int updated = todayOutpatientMapper.batchUpdatePatientStatus(
|
||||
encounterIds, targetStatus, SecurityUtils.getLoginUser().getUserId(), new Date());
|
||||
|
||||
return updated > 0 ? R.ok("更新成功") : R.fail("更新失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> receivePatient(Long encounterId, HttpServletRequest request) {
|
||||
// 调用现有的接诊逻辑
|
||||
// 这里可以复用 DoctorStationMainAppServiceImpl 中的 receiveEncounter 方法
|
||||
// 或者直接调用相应的服务
|
||||
|
||||
return R.ok("接诊成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> completeVisit(Long encounterId, HttpServletRequest request) {
|
||||
// 调用现有的完诊逻辑
|
||||
return R.ok("就诊完成");
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> cancelVisit(Long encounterId, String reason, HttpServletRequest request) {
|
||||
// 调用现有的取消就诊逻辑
|
||||
return R.ok("就诊取消成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据排序字段获取排序子句
|
||||
*/
|
||||
private String getOrderByClause(Integer sortField, Integer sortOrder) {
|
||||
if (ObjectUtil.isEmpty(sortField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String orderBy = "";
|
||||
switch (sortField) {
|
||||
case 1: // 挂号时间
|
||||
orderBy = "enc.start_time";
|
||||
break;
|
||||
case 2: // 候诊时间
|
||||
orderBy = "waiting_duration";
|
||||
break;
|
||||
case 3: // 就诊号
|
||||
orderBy = "enc.encounter_bus_no";
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return orderBy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package com.openhis.web.doctorstation.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.openhis.web.doctorstation.appservice.ITodayOutpatientService;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientPatientDto;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientQueryParam;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientStatsDto;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 今日门诊控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/today-outpatient")
|
||||
public class TodayOutpatientController {
|
||||
|
||||
@Resource
|
||||
private ITodayOutpatientService todayOutpatientService;
|
||||
|
||||
/**
|
||||
* 获取今日门诊统计信息
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 统计信息
|
||||
*/
|
||||
@GetMapping("/stats")
|
||||
public R<TodayOutpatientStatsDto> getTodayOutpatientStats(HttpServletRequest request) {
|
||||
TodayOutpatientStatsDto stats = todayOutpatientService.getTodayOutpatientStats(request);
|
||||
return R.ok(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询今日门诊患者列表
|
||||
*
|
||||
* @param queryParam 查询参数
|
||||
* @param request HTTP请求
|
||||
* @return 分页患者列表
|
||||
*/
|
||||
@GetMapping("/patients")
|
||||
public R<IPage<TodayOutpatientPatientDto>> getTodayOutpatientPatients(
|
||||
TodayOutpatientQueryParam queryParam,
|
||||
HttpServletRequest request) {
|
||||
IPage<TodayOutpatientPatientDto> page = todayOutpatientService.getTodayOutpatientPatients(
|
||||
queryParam, request);
|
||||
return R.ok(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日待就诊患者队列
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 待就诊患者列表
|
||||
*/
|
||||
@GetMapping("/patients/waiting")
|
||||
public R<List<TodayOutpatientPatientDto>> getWaitingPatients(HttpServletRequest request) {
|
||||
List<TodayOutpatientPatientDto> patients = todayOutpatientService.getWaitingPatients(request);
|
||||
return R.ok(patients);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日就诊中患者列表
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 就诊中患者列表
|
||||
*/
|
||||
@GetMapping("/patients/in-progress")
|
||||
public R<List<TodayOutpatientPatientDto>> getInProgressPatients(HttpServletRequest request) {
|
||||
List<TodayOutpatientPatientDto> patients = todayOutpatientService.getInProgressPatients(request);
|
||||
return R.ok(patients);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日已完成就诊患者列表
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 已完成就诊患者列表
|
||||
*/
|
||||
@GetMapping("/patients/completed")
|
||||
public R<List<TodayOutpatientPatientDto>> getCompletedPatients(HttpServletRequest request) {
|
||||
List<TodayOutpatientPatientDto> patients = todayOutpatientService.getCompletedPatients(request);
|
||||
return R.ok(patients);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取患者就诊详情
|
||||
*
|
||||
* @param encounterId 就诊记录ID
|
||||
* @param request HTTP请求
|
||||
* @return 患者就诊详情
|
||||
*/
|
||||
@GetMapping("/patients/{encounterId}")
|
||||
public R<TodayOutpatientPatientDto> getPatientDetail(
|
||||
@PathVariable("encounterId") Long encounterId,
|
||||
HttpServletRequest request) {
|
||||
TodayOutpatientPatientDto patient = todayOutpatientService.getPatientDetail(encounterId, request);
|
||||
return R.ok(patient);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新患者状态
|
||||
*
|
||||
* @param encounterIds 就诊记录ID列表
|
||||
* @param targetStatus 目标状态
|
||||
* @param request HTTP请求
|
||||
* @return 更新结果
|
||||
*/
|
||||
@PostMapping("/patients/batch-update-status")
|
||||
public R<?> batchUpdatePatientStatus(
|
||||
@RequestParam("encounterIds") List<Long> encounterIds,
|
||||
@RequestParam("targetStatus") Integer targetStatus,
|
||||
HttpServletRequest request) {
|
||||
return todayOutpatientService.batchUpdatePatientStatus(encounterIds, targetStatus, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接诊患者
|
||||
*
|
||||
* @param encounterId 就诊记录ID
|
||||
* @param request HTTP请求
|
||||
* @return 接诊结果
|
||||
*/
|
||||
@PostMapping("/patients/{encounterId}/receive")
|
||||
public R<?> receivePatient(
|
||||
@PathVariable("encounterId") Long encounterId,
|
||||
HttpServletRequest request) {
|
||||
return todayOutpatientService.receivePatient(encounterId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成就诊
|
||||
*
|
||||
* @param encounterId 就诊记录ID
|
||||
* @param request HTTP请求
|
||||
* @return 完成结果
|
||||
*/
|
||||
@PostMapping("/patients/{encounterId}/complete")
|
||||
public R<?> completeVisit(
|
||||
@PathVariable("encounterId") Long encounterId,
|
||||
HttpServletRequest request) {
|
||||
return todayOutpatientService.completeVisit(encounterId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消就诊
|
||||
*
|
||||
* @param encounterId 就诊记录ID
|
||||
* @param reason 取消原因
|
||||
* @param request HTTP请求
|
||||
* @return 取消结果
|
||||
*/
|
||||
@PostMapping("/patients/{encounterId}/cancel")
|
||||
public R<?> cancelVisit(
|
||||
@PathVariable("encounterId") Long encounterId,
|
||||
@RequestParam(value = "reason", required = false) String reason,
|
||||
HttpServletRequest request) {
|
||||
return todayOutpatientService.cancelVisit(encounterId, reason, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速接诊 - 医生首页快捷操作
|
||||
*
|
||||
* @param encounterId 就诊记录ID
|
||||
* @param request HTTP请求
|
||||
* @return 接诊结果
|
||||
*/
|
||||
@PostMapping("/quick-receive/{encounterId}")
|
||||
public R<?> quickReceivePatient(
|
||||
@PathVariable("encounterId") Long encounterId,
|
||||
HttpServletRequest request) {
|
||||
// 这里可以添加一些快速接诊的特殊逻辑
|
||||
// 比如自动填充一些默认值,快速状态转换等
|
||||
|
||||
return todayOutpatientService.receivePatient(encounterId, request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.openhis.web.doctorstation.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 今日门诊患者信息DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class TodayOutpatientPatientDto {
|
||||
|
||||
/**
|
||||
* 就诊记录ID
|
||||
*/
|
||||
private Long encounterId;
|
||||
|
||||
/**
|
||||
* 患者ID
|
||||
*/
|
||||
private Long patientId;
|
||||
|
||||
/**
|
||||
* 患者姓名
|
||||
*/
|
||||
private String patientName;
|
||||
|
||||
/**
|
||||
* 患者性别编码
|
||||
*/
|
||||
private Integer genderEnum;
|
||||
|
||||
/**
|
||||
* 患者性别显示文本
|
||||
*/
|
||||
private String genderEnumEnumText;
|
||||
|
||||
/**
|
||||
* 年龄
|
||||
*/
|
||||
private Integer age;
|
||||
|
||||
/**
|
||||
* 身份证号
|
||||
*/
|
||||
private String idCard;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 就诊流水号
|
||||
*/
|
||||
private String encounterBusNo;
|
||||
|
||||
/**
|
||||
* 挂号时间
|
||||
*/
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date registerTime;
|
||||
|
||||
/**
|
||||
* 接诊时间
|
||||
*/
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date receptionTime;
|
||||
|
||||
/**
|
||||
* 就诊状态编码
|
||||
*/
|
||||
private Integer statusEnum;
|
||||
|
||||
/**
|
||||
* 就诊状态显示文本
|
||||
*/
|
||||
private String statusEnumEnumText;
|
||||
|
||||
/**
|
||||
* 就诊对象状态编码
|
||||
*/
|
||||
private Integer subjectStatusEnum;
|
||||
|
||||
/**
|
||||
* 就诊对象状态显示文本
|
||||
*/
|
||||
private String subjectStatusEnumEnumText;
|
||||
|
||||
/**
|
||||
* 候诊时长(分钟)
|
||||
*/
|
||||
private Integer waitingDuration;
|
||||
|
||||
/**
|
||||
* 就诊时长(分钟)
|
||||
*/
|
||||
private Integer visitDuration;
|
||||
|
||||
/**
|
||||
* 患者类型编码
|
||||
*/
|
||||
private String typeCode;
|
||||
|
||||
/**
|
||||
* 患者类型显示文本
|
||||
*/
|
||||
private String typeCodeDictText;
|
||||
|
||||
/**
|
||||
* 是否重点患者(1-是,0-否)
|
||||
*/
|
||||
private Integer importantFlag;
|
||||
|
||||
/**
|
||||
* 是否已开药(1-是,0-否)
|
||||
*/
|
||||
private Integer hasPrescription;
|
||||
|
||||
/**
|
||||
* 是否已检查(1-是,0-否)
|
||||
*/
|
||||
private Integer hasExamination;
|
||||
|
||||
/**
|
||||
* 是否已检验(1-是,0-否)
|
||||
*/
|
||||
private Integer hasLaboratory;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.openhis.web.doctorstation.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 今日门诊查询参数DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class TodayOutpatientQueryParam {
|
||||
|
||||
/**
|
||||
* 搜索关键词(姓名/身份证号/手机号/就诊号)
|
||||
*/
|
||||
private String searchKey;
|
||||
|
||||
/**
|
||||
* 就诊状态
|
||||
* 1-待就诊,2-就诊中,3-已完成,4-已取消
|
||||
*/
|
||||
private Integer statusEnum;
|
||||
|
||||
/**
|
||||
* 患者类型
|
||||
*/
|
||||
private String typeCode;
|
||||
|
||||
/**
|
||||
* 是否重点患者(1-是,0-否)
|
||||
*/
|
||||
private Integer importantFlag;
|
||||
|
||||
/**
|
||||
* 是否已开药(1-是,0-否)
|
||||
*/
|
||||
private Integer hasPrescription;
|
||||
|
||||
/**
|
||||
* 是否已检查(1-是,0-否)
|
||||
*/
|
||||
private Integer hasExamination;
|
||||
|
||||
/**
|
||||
* 是否已检验(1-是,0-否)
|
||||
*/
|
||||
private Integer hasLaboratory;
|
||||
|
||||
/**
|
||||
* 医生ID(可选,默认当前登录医生)
|
||||
*/
|
||||
private Long doctorId;
|
||||
|
||||
/**
|
||||
* 科室ID(可选,默认当前登录科室)
|
||||
*/
|
||||
private Long departmentId;
|
||||
|
||||
/**
|
||||
* 查询日期(格式:yyyy-MM-dd,默认今日)
|
||||
*/
|
||||
private String queryDate;
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
* 1-挂号时间,2-候诊时间,3-就诊号
|
||||
*/
|
||||
private Integer sortField;
|
||||
|
||||
/**
|
||||
* 排序方式
|
||||
* 1-升序,2-降序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
private Integer pageNo;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
|
||||
public TodayOutpatientQueryParam() {
|
||||
this.pageNo = 1;
|
||||
this.pageSize = 10;
|
||||
this.sortField = 1; // 默认按挂号时间排序
|
||||
this.sortOrder = 2; // 默认降序
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.openhis.web.doctorstation.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 今日门诊统计DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class TodayOutpatientStatsDto {
|
||||
|
||||
/**
|
||||
* 今日总挂号数
|
||||
*/
|
||||
private Integer totalRegistered;
|
||||
|
||||
/**
|
||||
* 待就诊数
|
||||
*/
|
||||
private Integer waitingCount;
|
||||
|
||||
/**
|
||||
* 就诊中数
|
||||
*/
|
||||
private Integer inProgressCount;
|
||||
|
||||
/**
|
||||
* 已完成数
|
||||
*/
|
||||
private Integer completedCount;
|
||||
|
||||
/**
|
||||
* 已取消数
|
||||
*/
|
||||
private Integer cancelledCount;
|
||||
|
||||
/**
|
||||
* 平均候诊时间(分钟)
|
||||
*/
|
||||
private Integer averageWaitingTime;
|
||||
|
||||
/**
|
||||
* 平均就诊时间(分钟)
|
||||
*/
|
||||
private Integer averageVisitTime;
|
||||
|
||||
/**
|
||||
* 今日接诊医生数
|
||||
*/
|
||||
private Integer doctorCount;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.openhis.web.doctorstation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientPatientDto;
|
||||
import com.openhis.web.doctorstation.dto.TodayOutpatientStatsDto;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 今日门诊数据访问接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface TodayOutpatientMapper {
|
||||
|
||||
/**
|
||||
* 获取今日门诊统计信息
|
||||
*
|
||||
* @param doctorId 医生ID
|
||||
* @param departmentId 科室ID
|
||||
* @param queryDate 查询日期
|
||||
* @return 统计信息
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT " +
|
||||
" COUNT(DISTINCT enc.id) AS totalRegistered, " +
|
||||
" SUM(CASE WHEN enc.status_enum = #{plannedStatus} THEN 1 ELSE 0 END) AS waitingCount, " +
|
||||
" SUM(CASE WHEN enc.status_enum = #{inProgressStatus} THEN 1 ELSE 0 END) AS inProgressCount, " +
|
||||
" SUM(CASE WHEN enc.status_enum = #{dischargedStatus} THEN 1 ELSE 0 END) AS completedCount, " +
|
||||
" SUM(CASE WHEN enc.status_enum = #{cancelledStatus} THEN 1 ELSE 0 END) AS cancelledCount, " +
|
||||
" AVG(CASE WHEN enc.reception_time IS NOT NULL " +
|
||||
" THEN EXTRACT(EPOCH FROM (enc.reception_time - enc.start_time)) / 60 " +
|
||||
" ELSE NULL END) AS averageWaitingTime, " +
|
||||
" AVG(CASE WHEN enc.end_time IS NOT NULL AND enc.reception_time IS NOT NULL " +
|
||||
" THEN EXTRACT(EPOCH FROM (enc.end_time - enc.reception_time)) / 60 " +
|
||||
" ELSE NULL END) AS averageVisitTime, " +
|
||||
" COUNT(DISTINCT ep.practitioner_id) AS doctorCount " +
|
||||
"FROM adm_encounter enc " +
|
||||
"LEFT JOIN adm_encounter_participant ep ON enc.id = ep.encounter_id " +
|
||||
" AND ep.type_code = #{admitterCode} " +
|
||||
" AND ep.delete_flag = '0' " +
|
||||
" AND ep.tenant_id = #{tenantId} " +
|
||||
" AND ep.tenant_id = #{tenantId} " +
|
||||
"WHERE enc.delete_flag = '0' " +
|
||||
" AND enc.start_time::DATE = #{queryDate}::DATE " +
|
||||
" AND enc.tenant_id = #{tenantId} " +
|
||||
" <if test='departmentId != null'>" +
|
||||
" AND enc.organization_id = #{departmentId} " +
|
||||
" </if>" +
|
||||
" <if test='doctorId != null'>" +
|
||||
" AND ep.practitioner_id = #{practitionerId} " +
|
||||
" </if>" +
|
||||
"</script>")
|
||||
TodayOutpatientStatsDto getTodayOutpatientStats(
|
||||
@Param("doctorId") Long doctorId,
|
||||
@Param("departmentId") Long departmentId,
|
||||
@Param("queryDate") String queryDate,
|
||||
@Param("tenantId") Integer tenantId,
|
||||
@Param("practitionerId") Long practitionerId,
|
||||
@Param("plannedStatus") Integer plannedStatus,
|
||||
@Param("inProgressStatus") Integer inProgressStatus,
|
||||
@Param("dischargedStatus") Integer dischargedStatus,
|
||||
@Param("cancelledStatus") Integer cancelledStatus,
|
||||
@Param("admitterCode") String admitterCode);
|
||||
|
||||
/**
|
||||
* 分页查询今日门诊患者列表
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryWrapper 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT " +
|
||||
" enc.id AS encounterId, " +
|
||||
" enc.patient_id AS patientId, " +
|
||||
" pt.name AS patientName, " +
|
||||
" pt.gender_enum AS genderEnum, " +
|
||||
" pt.birth_date AS birthDate, " +
|
||||
" pt.id_card AS idCard, " +
|
||||
" pt.phone AS phone, " +
|
||||
" enc.bus_no AS encounterBusNo, " +
|
||||
" enc.start_time AS registerTime, " +
|
||||
" enc.reception_time AS receptionTime, " +
|
||||
" enc.status_enum AS statusEnum, " +
|
||||
" enc.subject_status_enum AS subjectStatusEnum, " +
|
||||
" CASE WHEN enc.reception_time IS NOT NULL " +
|
||||
" THEN EXTRACT(EPOCH FROM (enc.reception_time - enc.start_time)) / 60 " +
|
||||
" ELSE EXTRACT(EPOCH FROM (NOW() - enc.start_time)) / 60 END AS waitingDuration, " +
|
||||
" CASE WHEN enc.end_time IS NOT NULL AND enc.reception_time IS NOT NULL " +
|
||||
" THEN EXTRACT(EPOCH FROM (enc.end_time - enc.reception_time)) / 60 " +
|
||||
" ELSE NULL END AS visitDuration, " +
|
||||
" pt.type_code AS typeCode, " +
|
||||
" enc.important_flag AS importantFlag, " +
|
||||
" CASE WHEN EXISTS (SELECT 1 FROM med_medication_dispense WHERE encounter_id = enc.id AND delete_flag = '0') " +
|
||||
" THEN 1 ELSE 0 END AS hasPrescription, " +
|
||||
" CASE WHEN EXISTS (SELECT 1 FROM med_inspection_application WHERE encounter_id = enc.id AND delete_flag = '0') " +
|
||||
" THEN 1 ELSE 0 END AS hasExamination, " +
|
||||
" CASE WHEN EXISTS (SELECT 1 FROM med_test_application WHERE encounter_id = enc.id AND delete_flag = '0') " +
|
||||
" THEN 1 ELSE 0 END AS hasLaboratory " +
|
||||
"FROM adm_encounter enc " +
|
||||
"INNER JOIN adm_patient pt ON enc.patient_id = pt.id AND pt.delete_flag = '0' AND pt.tenant_id = #{tenantId} " +
|
||||
"LEFT JOIN adm_encounter_participant ep ON enc.id = ep.encounter_id " +
|
||||
" AND ep.type_code = #{admitterCode} " +
|
||||
" AND ep.delete_flag = '0' " +
|
||||
" AND ep.tenant_id = #{tenantId} " +
|
||||
"<where>" +
|
||||
" enc.delete_flag = '0' " +
|
||||
" AND enc.tenant_id = #{tenantId} " +
|
||||
" <if test='ew != null and ew.customSqlSegment != null and ew.customSqlSegment != \"\"'>" +
|
||||
" AND ${ew.customSqlSegment.replace('WHERE ', '').replace('tenant_id', 'enc.tenant_id')}" +
|
||||
" </if>" +
|
||||
"</where> " +
|
||||
"</script>")
|
||||
IPage<TodayOutpatientPatientDto> getTodayOutpatientPatients(
|
||||
Page<TodayOutpatientPatientDto> page,
|
||||
@Param("ew") QueryWrapper<TodayOutpatientPatientDto> queryWrapper,
|
||||
@Param("tenantId") Integer tenantId,
|
||||
@Param("admitterCode") String admitterCode);
|
||||
|
||||
/**
|
||||
* 获取患者详情
|
||||
*
|
||||
* @param queryWrapper 查询条件
|
||||
* @return 患者详情
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT " +
|
||||
" enc.id AS encounterId, " +
|
||||
" enc.patient_id AS patientId, " +
|
||||
" pt.name AS patientName, " +
|
||||
" pt.gender_enum AS genderEnum, " +
|
||||
" pt.birth_date AS birthDate, " +
|
||||
" pt.id_card AS idCard, " +
|
||||
" pt.phone AS phone, " +
|
||||
" enc.bus_no AS encounterBusNo, " +
|
||||
" enc.start_time AS registerTime, " +
|
||||
" enc.reception_time AS receptionTime, " +
|
||||
" enc.status_enum AS statusEnum, " +
|
||||
" enc.subject_status_enum AS subjectStatusEnum, " +
|
||||
" CASE WHEN enc.reception_time IS NOT NULL " +
|
||||
" THEN EXTRACT(EPOCH FROM (enc.reception_time - enc.start_time)) / 60 " +
|
||||
" ELSE EXTRACT(EPOCH FROM (NOW() - enc.start_time)) / 60 END AS waitingDuration, " +
|
||||
" CASE WHEN enc.end_time IS NOT NULL AND enc.reception_time IS NOT NULL " +
|
||||
" THEN EXTRACT(EPOCH FROM (enc.end_time - enc.reception_time)) / 60 " +
|
||||
" ELSE NULL END AS visitDuration, " +
|
||||
" pt.type_code AS typeCode, " +
|
||||
" enc.important_flag AS importantFlag, " +
|
||||
" CASE WHEN EXISTS (SELECT 1 FROM med_medication_dispense WHERE encounter_id = enc.id AND delete_flag = '0') " +
|
||||
" THEN 1 ELSE 0 END AS hasPrescription, " +
|
||||
" CASE WHEN EXISTS (SELECT 1 FROM med_inspection_application WHERE encounter_id = enc.id AND delete_flag = '0') " +
|
||||
" THEN 1 ELSE 0 END AS hasExamination, " +
|
||||
" CASE WHEN EXISTS (SELECT 1 FROM med_test_application WHERE encounter_id = enc.id AND delete_flag = '0') " +
|
||||
" THEN 1 ELSE 0 END AS hasLaboratory " +
|
||||
"FROM adm_encounter enc " +
|
||||
"INNER JOIN adm_patient pt ON enc.patient_id = pt.id AND pt.delete_flag = '0' AND pt.tenant_id = #{tenantId} " +
|
||||
"LEFT JOIN adm_encounter_participant ep ON enc.id = ep.encounter_id " +
|
||||
" AND ep.type_code = #{admitterCode} " +
|
||||
" AND ep.delete_flag = '0' " +
|
||||
" AND ep.tenant_id = #{tenantId} " +
|
||||
"<where>" +
|
||||
" enc.delete_flag = '0' " +
|
||||
" AND enc.tenant_id = #{tenantId} " +
|
||||
" <if test='ew != null and ew.customSqlSegment != null and ew.customSqlSegment != \"\"'>" +
|
||||
" AND ${ew.customSqlSegment.replace('WHERE ', '').replace('tenant_id', 'enc.tenant_id')}" +
|
||||
" </if>" +
|
||||
"</where> " +
|
||||
"</script>")
|
||||
TodayOutpatientPatientDto getPatientDetail(
|
||||
@Param("ew") QueryWrapper<TodayOutpatientPatientDto> queryWrapper,
|
||||
@Param("tenantId") Integer tenantId,
|
||||
@Param("admitterCode") String admitterCode);
|
||||
|
||||
/**
|
||||
* 批量更新患者状态
|
||||
*
|
||||
* @param encounterIds 就诊记录ID列表
|
||||
* @param targetStatus 目标状态
|
||||
* @param doctorId 医生ID
|
||||
* @param updateTime 更新时间
|
||||
* @return 更新记录数
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"UPDATE adm_encounter " +
|
||||
"SET status_enum = #{targetStatus}, " +
|
||||
" update_by = #{doctorId}, " +
|
||||
" update_time = #{updateTime} " +
|
||||
"WHERE id IN " +
|
||||
" <foreach collection='encounterIds' item='id' open='(' separator=',' close=')'>" +
|
||||
" #{id} " +
|
||||
" </foreach> " +
|
||||
" AND delete_flag = '0'" +
|
||||
"</script>")
|
||||
int batchUpdatePatientStatus(
|
||||
@Param("encounterIds") List<Long> encounterIds,
|
||||
@Param("targetStatus") Integer targetStatus,
|
||||
@Param("doctorId") Long doctorId,
|
||||
@Param("updateTime") Date updateTime);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.openhis.web.reportmanage.dto;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
@@ -13,8 +13,8 @@ import java.util.Date;
|
||||
* @author yuxj
|
||||
* @date 2025/8/25
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Getter
|
||||
@Setter
|
||||
public class InpatientMedicalRecordHomePageCollectionDto {
|
||||
|
||||
// 组织机构代码 字符 30 必填
|
||||
|
||||
@@ -6,7 +6,7 @@ spring:
|
||||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
url: jdbc:postgresql://47.116.196.11:15432/postgresql?currentSchema=hisdev&characterEncoding=UTF-8&client_encoding=UTF-8
|
||||
url: jdbc:postgresql://192.168.110.252:15432/postgresql?currentSchema=hisdev&characterEncoding=UTF-8&client_encoding=UTF-8
|
||||
username: postgresql
|
||||
password: Jchl1528
|
||||
# 从库数据源
|
||||
@@ -64,9 +64,9 @@ spring:
|
||||
# redis 配置
|
||||
redis:
|
||||
# 地址
|
||||
host: 47.116.196.11
|
||||
host: 192.168.110.252
|
||||
# 端口,默认为6379
|
||||
port: 26379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 1
|
||||
# 密码
|
||||
|
||||
@@ -13,6 +13,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="patientAge" column="patient_age" />
|
||||
<result property="encounterId" column="encounter_id" />
|
||||
<result property="encounterNo" column="encounter_no" />
|
||||
<result property="applyDoctorId" column="apply_doctor_id" />
|
||||
<result property="applyDoctorName" column="apply_doctor_name" />
|
||||
<result property="applyDeptId" column="apply_dept_id" />
|
||||
<result property="applyDeptName" column="apply_dept_name" />
|
||||
<result property="surgeryName" column="surgery_name" />
|
||||
<result property="surgeryCode" column="surgery_code" />
|
||||
<result property="surgeryTypeEnum" column="surgery_type_enum" />
|
||||
@@ -43,6 +47,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="healingLevel_dictText" column="healing_level_dictText" />
|
||||
<result property="operatingRoomId" column="operating_room_id" />
|
||||
<result property="operatingRoomName" column="operating_room_name" />
|
||||
<result property="operatingRoomOrgId" column="operating_room_org_id" />
|
||||
<result property="operatingRoomOrgName" column="operating_room_org_name" />
|
||||
<result property="orgId" column="org_id" />
|
||||
<result property="orgName" column="org_name" />
|
||||
<result property="preoperativeDiagnosis" column="preoperative_diagnosis" />
|
||||
@@ -68,14 +74,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
EXTRACT(YEAR FROM AGE(p.birth_date)) as patient_age,
|
||||
s.encounter_id,
|
||||
e.bus_no as encounter_no,
|
||||
s.apply_doctor_id,
|
||||
s.apply_doctor_name,
|
||||
s.apply_dept_id,
|
||||
s.apply_dept_name,
|
||||
s.surgery_name,
|
||||
s.surgery_code,
|
||||
s.surgery_type_enum,
|
||||
s.surgery_type_enum as surgery_type_enum_dictText,
|
||||
CASE s.surgery_type_enum
|
||||
WHEN 1 THEN '门诊手术'
|
||||
WHEN 2 THEN '住院手术'
|
||||
WHEN 3 THEN '急诊手术'
|
||||
WHEN 4 THEN '择期手术'
|
||||
ELSE '未知'
|
||||
END as surgery_type_enum_dictText,
|
||||
s.surgery_level,
|
||||
s.surgery_level as surgery_level_dictText,
|
||||
CASE s.surgery_level
|
||||
WHEN 1 THEN '一级手术'
|
||||
WHEN 2 THEN '二级手术'
|
||||
WHEN 3 THEN '三级手术'
|
||||
WHEN 4 THEN '四级手术'
|
||||
WHEN 5 THEN '特级手术'
|
||||
ELSE '未知'
|
||||
END as surgery_level_dictText,
|
||||
s.status_enum,
|
||||
s.status_enum as status_enum_dictText,
|
||||
CASE s.status_enum
|
||||
WHEN 0 THEN '待排期'
|
||||
WHEN 1 THEN '已排期'
|
||||
WHEN 2 THEN '手术中'
|
||||
WHEN 3 THEN '已完成'
|
||||
WHEN 4 THEN '已取消'
|
||||
WHEN 5 THEN '暂停'
|
||||
ELSE '未知'
|
||||
END as status_enum_dictText,
|
||||
s.planned_time,
|
||||
s.actual_start_time,
|
||||
s.actual_end_time,
|
||||
@@ -90,14 +121,36 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
s.scrub_nurse_id,
|
||||
s.scrub_nurse_name,
|
||||
s.anesthesia_type_enum,
|
||||
s.anesthesia_type_enum as anesthesia_type_enum_dictText,
|
||||
CASE s.anesthesia_type_enum
|
||||
WHEN 0 THEN '无麻醉'
|
||||
WHEN 1 THEN '局部麻醉'
|
||||
WHEN 2 THEN '区域麻醉'
|
||||
WHEN 3 THEN '全身麻醉'
|
||||
WHEN 4 THEN '脊椎麻醉'
|
||||
WHEN 5 THEN '硬膜外麻醉'
|
||||
WHEN 6 THEN '表面麻醉'
|
||||
ELSE '未知'
|
||||
END as anesthesia_type_enum_dictText,
|
||||
s.body_site,
|
||||
s.incision_level,
|
||||
s.incision_level as incision_level_dictText,
|
||||
CASE s.incision_level
|
||||
WHEN 1 THEN 'I级切口'
|
||||
WHEN 2 THEN 'II级切口'
|
||||
WHEN 3 THEN 'III级切口'
|
||||
WHEN 4 THEN 'IV级切口'
|
||||
ELSE '未知'
|
||||
END as incision_level_dictText,
|
||||
s.healing_level,
|
||||
s.healing_level as healing_level_dictText,
|
||||
CASE s.healing_level
|
||||
WHEN 1 THEN '甲级愈合'
|
||||
WHEN 2 THEN '乙级愈合'
|
||||
WHEN 3 THEN '丙级愈合'
|
||||
ELSE '未知'
|
||||
END as healing_level_dictText,
|
||||
s.operating_room_id,
|
||||
s.operating_room_name,
|
||||
r.organization_id as operating_room_org_id,
|
||||
ro.name as operating_room_org_name,
|
||||
s.org_id,
|
||||
o.name as org_name,
|
||||
s.preoperative_diagnosis,
|
||||
@@ -114,6 +167,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
FROM cli_surgery s
|
||||
LEFT JOIN adm_patient p ON s.patient_id = p.id
|
||||
LEFT JOIN adm_encounter e ON s.encounter_id = e.id
|
||||
LEFT JOIN adm_operating_room r ON s.operating_room_id = r.id
|
||||
LEFT JOIN adm_organization ro ON r.organization_id = ro.id
|
||||
LEFT JOIN adm_organization o ON s.org_id = o.id
|
||||
</sql>
|
||||
|
||||
@@ -122,7 +177,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<where>
|
||||
s.delete_flag = '0'
|
||||
<if test="ew.sqlSegment != null and ew.sqlSegment != ''">
|
||||
AND ${ew.sqlSegment}
|
||||
AND ${ew.sqlSegment.replace('tenant_id', 's.tenant_id').replace('create_time', 's.create_time').replace('surgery_no', 's.surgery_no').replace('surgery_name', 's.surgery_name').replace('patient_name', 'p.name').replace('main_surgeon_name', 's.main_surgeon_name').replace('anesthetist_name', 's.anesthetist_name').replace('org_name', 'o.name')}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@@ -297,7 +297,12 @@ public enum AssignSeqEnum {
|
||||
/**
|
||||
* 自动备份单据号
|
||||
*/
|
||||
AUTO_BACKUP_NO("70", "自动备份单据号", "ABU");
|
||||
AUTO_BACKUP_NO("70", "自动备份单据号", "ABU"),
|
||||
|
||||
/**
|
||||
* 手术室业务编码
|
||||
*/
|
||||
OPERATING_ROOM_BUS_NO("71", "手术室业务编码", "OPR");
|
||||
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
@@ -81,21 +81,29 @@ public class HisQueryUtils {
|
||||
if (entity == null) {
|
||||
return queryWrapper;
|
||||
}
|
||||
// 反射获取实体类的字段
|
||||
Field[] fields = entity.getClass().getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
Object value = field.get(entity);
|
||||
if (value != null && !value.toString().equals("")) {
|
||||
// 将驼峰命名的字段名转换为下划线命名的数据库字段名
|
||||
String fieldName = camelToUnderline(field.getName());
|
||||
// 处理等于条件
|
||||
queryWrapper.eq(fieldName, value);
|
||||
// 反射获取实体类的所有字段(包括父类)
|
||||
Class<?> currentClass = entity.getClass();
|
||||
while (currentClass != null && currentClass != Object.class) {
|
||||
Field[] fields = currentClass.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
// 跳过静态字段,如 serialVersionUID
|
||||
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
Object value = field.get(entity);
|
||||
if (value != null && !value.toString().equals("")) {
|
||||
// 将驼峰命名的字段名转换为下划线命名的数据库字段名
|
||||
String fieldName = camelToUnderline(field.getName());
|
||||
// 处理等于条件
|
||||
queryWrapper.eq(fieldName, value);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
currentClass = currentClass.getSuperclass();
|
||||
}
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.openhis.administration.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import com.openhis.common.enums.LocationStatus;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 手术室管理Entity实体
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-01-04
|
||||
*/
|
||||
@Data
|
||||
@TableName("adm_operating_room")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class OperatingRoom extends HisBaseEntity {
|
||||
|
||||
/** ID */
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/** 编码 */
|
||||
private String busNo;
|
||||
|
||||
/** 手术室名称 */
|
||||
private String name;
|
||||
|
||||
/** 所属机构ID */
|
||||
private Long organizationId;
|
||||
|
||||
/** 位置描述 */
|
||||
private String locationDescription;
|
||||
|
||||
/** 设备配置 */
|
||||
private String equipmentConfig;
|
||||
|
||||
/** 容纳人数 */
|
||||
private Integer capacity;
|
||||
|
||||
/** 状态编码(1-启用,0-停用) */
|
||||
private Integer statusEnum;
|
||||
|
||||
/** 显示顺序 */
|
||||
private Integer displayOrder;
|
||||
|
||||
/** 拼音码 */
|
||||
private String pyStr;
|
||||
|
||||
/** 五笔码 */
|
||||
private String wbStr;
|
||||
|
||||
public OperatingRoom() {
|
||||
this.statusEnum = LocationStatus.ACTIVE.getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.openhis.administration.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.openhis.administration.domain.OperatingRoom;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 手术室Mapper接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-01-04
|
||||
*/
|
||||
@Mapper
|
||||
public interface OperatingRoomMapper extends BaseMapper<OperatingRoom> {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.openhis.administration.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.openhis.administration.domain.OperatingRoom;
|
||||
|
||||
/**
|
||||
* 手术室Service接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-01-04
|
||||
*/
|
||||
public interface IOperatingRoomService extends IService<OperatingRoom> {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.openhis.administration.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.openhis.administration.domain.OperatingRoom;
|
||||
import com.openhis.administration.mapper.OperatingRoomMapper;
|
||||
import com.openhis.administration.service.IOperatingRoomService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 手术室Service实现类
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-01-04
|
||||
*/
|
||||
@Service
|
||||
public class OperatingRoomServiceImpl extends ServiceImpl<OperatingRoomMapper, OperatingRoom>
|
||||
implements IOperatingRoomService {
|
||||
}
|
||||
@@ -38,10 +38,35 @@ public class Surgery extends HisBaseEntity {
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long patientId;
|
||||
|
||||
/** 患者姓名 */
|
||||
@TableField("patient_name")
|
||||
private String patientName;
|
||||
|
||||
/** 就诊ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long encounterId;
|
||||
|
||||
/** 申请医生ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@TableField("apply_doctor_id")
|
||||
private Long applyDoctorId;
|
||||
|
||||
/** 申请医生姓名 */
|
||||
@TableField("apply_doctor_name")
|
||||
private String applyDoctorName;
|
||||
|
||||
/** 申请科室ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@TableField("apply_dept_id")
|
||||
private Long applyDeptId;
|
||||
|
||||
/** 申请科室名称 */
|
||||
@TableField("apply_dept_name")
|
||||
private String applyDeptName;
|
||||
|
||||
/** 手术指征 */
|
||||
private String surgeryIndication;
|
||||
|
||||
/** 手术名称 */
|
||||
private String surgeryName;
|
||||
|
||||
@@ -49,112 +74,141 @@ public class Surgery extends HisBaseEntity {
|
||||
private String surgeryCode;
|
||||
|
||||
/** 手术类型编码 */
|
||||
@TableField("surgery_type_enum")
|
||||
private Integer surgeryTypeEnum;
|
||||
|
||||
/** 手术等级 */
|
||||
@TableField("surgery_level")
|
||||
private Integer surgeryLevel;
|
||||
|
||||
/** 手术状态 */
|
||||
@TableField("status_enum")
|
||||
private Integer statusEnum;
|
||||
|
||||
/** 计划手术时间 */
|
||||
@TableField("planned_time")
|
||||
private Date plannedTime;
|
||||
|
||||
/** 实际开始时间 */
|
||||
@TableField("actual_start_time")
|
||||
private Date actualStartTime;
|
||||
|
||||
/** 实际结束时间 */
|
||||
@TableField("actual_end_time")
|
||||
private Date actualEndTime;
|
||||
|
||||
/** 主刀医生ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@TableField("main_surgeon_id")
|
||||
private Long mainSurgeonId;
|
||||
|
||||
/** 主刀医生姓名 */
|
||||
@TableField("main_surgeon_name")
|
||||
private String mainSurgeonName;
|
||||
|
||||
/** 助手1 ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@TableField("assistant_1_id")
|
||||
private Long assistant1Id;
|
||||
|
||||
/** 助手1 姓名 */
|
||||
@TableField("assistant_1_name")
|
||||
private String assistant1Name;
|
||||
|
||||
/** 助手2 ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@TableField("assistant_2_id")
|
||||
private Long assistant2Id;
|
||||
|
||||
/** 助手2 姓名 */
|
||||
@TableField("assistant_2_name")
|
||||
private String assistant2Name;
|
||||
|
||||
/** 麻醉医生ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@TableField("anesthetist_id")
|
||||
private Long anesthetistId;
|
||||
|
||||
/** 麻醉医生姓名 */
|
||||
@TableField("anesthetist_name")
|
||||
private String anesthetistName;
|
||||
|
||||
/** 巡回护士ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@TableField("scrub_nurse_id")
|
||||
private Long scrubNurseId;
|
||||
|
||||
/** 巡回护士姓名 */
|
||||
@TableField("scrub_nurse_name")
|
||||
private String scrubNurseName;
|
||||
|
||||
/** 麻醉方式编码 */
|
||||
@TableField("anesthesia_type_enum")
|
||||
private Integer anesthesiaTypeEnum;
|
||||
|
||||
/** 手术部位 */
|
||||
@TableField("body_site")
|
||||
private String bodySite;
|
||||
|
||||
/** 手术切口等级 */
|
||||
@TableField("incision_level")
|
||||
private Integer incisionLevel;
|
||||
|
||||
/** 手术切口愈合等级 */
|
||||
@TableField("healing_level")
|
||||
private Integer healingLevel;
|
||||
|
||||
/** 手术室 */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@TableField("operating_room_id")
|
||||
private Long operatingRoomId;
|
||||
|
||||
/** 手术室名称 */
|
||||
@TableField("operating_room_name")
|
||||
private String operatingRoomName;
|
||||
|
||||
/** 执行科室ID */
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@TableField("org_id")
|
||||
private Long orgId;
|
||||
|
||||
/** 执行科室名称 */
|
||||
@TableField("org_name")
|
||||
private String orgName;
|
||||
|
||||
/** 术前诊断 */
|
||||
@TableField("preoperative_diagnosis")
|
||||
private String preoperativeDiagnosis;
|
||||
|
||||
/** 术后诊断 */
|
||||
@TableField("postoperative_diagnosis")
|
||||
private String postoperativeDiagnosis;
|
||||
|
||||
/** 手术经过描述 */
|
||||
@TableField("surgery_description")
|
||||
private String surgeryDescription;
|
||||
|
||||
/** 术后医嘱 */
|
||||
@TableField("postoperative_advice")
|
||||
private String postoperativeAdvice;
|
||||
|
||||
/** 并发症描述 */
|
||||
@TableField("complications")
|
||||
private String complications;
|
||||
|
||||
/** 手术费用 */
|
||||
@TableField("surgery_fee")
|
||||
private BigDecimal surgeryFee;
|
||||
|
||||
/** 麻醉费用 */
|
||||
@TableField("anesthesia_fee")
|
||||
private BigDecimal anesthesiaFee;
|
||||
|
||||
/** 总费用 */
|
||||
@TableField("total_fee")
|
||||
private BigDecimal totalFee;
|
||||
|
||||
/** 备注信息 */
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
/** 租户ID(表不存在此字段,仅用于继承基类) */
|
||||
@TableField(exist = false)
|
||||
private Integer tenantId;
|
||||
}
|
||||
@@ -2,32 +2,26 @@ package com.openhis.clinical.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.core.common.utils.AssignSeqUtil;
|
||||
import com.openhis.clinical.domain.Surgery;
|
||||
import com.openhis.clinical.mapper.SurgeryMapper;
|
||||
import com.openhis.clinical.service.ISurgeryService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* 手术管理Service业务层处理
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-12-30
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SurgeryServiceImpl extends ServiceImpl<SurgeryMapper, Surgery> implements ISurgeryService {
|
||||
|
||||
@Resource
|
||||
private SurgeryMapper surgeryMapper;
|
||||
|
||||
@Resource
|
||||
private AssignSeqUtil assignSeqUtil;
|
||||
|
||||
/**
|
||||
* 新增手术信息
|
||||
*
|
||||
@@ -37,22 +31,53 @@ public class SurgeryServiceImpl extends ServiceImpl<SurgeryMapper, Surgery> impl
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long insertSurgery(Surgery surgery) {
|
||||
// 生成手术编号
|
||||
String surgeryNo = assignSeqUtil.getSeq("SS", 10);
|
||||
// 生成手术单号:OP+年月日+4位随机数
|
||||
String surgeryNo = generateSurgeryNo();
|
||||
surgery.setSurgeryNo(surgeryNo);
|
||||
surgery.setCreateTime(new Date());
|
||||
surgery.setUpdateTime(new Date());
|
||||
surgery.setDeleteFlag("0");
|
||||
|
||||
// 默认状态为待排期
|
||||
// 默认状态为待排期(新开)
|
||||
if (surgery.getStatusEnum() == null) {
|
||||
surgery.setStatusEnum(0);
|
||||
}
|
||||
|
||||
// 添加日志,检查字段的值
|
||||
log.info("准备插入手术记录 - applyDoctorId: {}, applyDoctorName: {}, applyDeptId: {}, applyDeptName: {}",
|
||||
surgery.getApplyDoctorId(), surgery.getApplyDoctorName(),
|
||||
surgery.getApplyDeptId(), surgery.getApplyDeptName());
|
||||
|
||||
surgeryMapper.insert(surgery);
|
||||
|
||||
// 插入后再查询一次,验证是否保存成功
|
||||
Surgery inserted = surgeryMapper.selectById(surgery.getId());
|
||||
log.info("插入后查询结果 - applyDoctorId: {}, applyDoctorName: {}, applyDeptId: {}, applyDeptName: {}",
|
||||
inserted.getApplyDoctorId(), inserted.getApplyDoctorName(),
|
||||
inserted.getApplyDeptId(), inserted.getApplyDeptName());
|
||||
|
||||
return surgery.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成手术单号
|
||||
* 格式:OP+年月日+4位随机数
|
||||
* 示例:OP2025092003
|
||||
*
|
||||
* @return 手术单号
|
||||
*/
|
||||
private String generateSurgeryNo() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||
String dateStr = sdf.format(new Date());
|
||||
|
||||
// 生成4位随机数
|
||||
Random random = new Random();
|
||||
int randomNum = random.nextInt(10000);
|
||||
String randomStr = String.format("%04d", randomNum);
|
||||
|
||||
return "OP" + dateStr + randomStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改手术信息
|
||||
*
|
||||
|
||||
@@ -348,12 +348,6 @@
|
||||
<version>${jsqlparser.version}</version>
|
||||
</dependency>
|
||||
<!-- JSQlParser - MyBatis Plus 3.5.9+ 需要 4.6+ -->
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<version>${jsqlparser.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"name": "My Awesome App",
|
||||
"short_name": "AwesomeApp",
|
||||
"name": "医院信息管理系统",
|
||||
"short_name": "HIS",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"src": "/favicon/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"src": "/favicon/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"theme_color": "#1890ff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
"display": "standalone",
|
||||
"description": "医院信息管理系统 - 提供全面的医疗信息化解决方案"
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
<meta name="author" content="OpenHIS Team" />
|
||||
|
||||
<!-- 安全相关 meta 标签 -->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://at.alicdn.com; style-src 'self' 'unsafe-inline' https://at.alicdn.com; img-src 'self' data: https: https://at.alicdn.com; font-src 'self' 'unsafe-inline' https://at.alicdn.com data:; connect-src 'self' https://at.alicdn.com;">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://at.alicdn.com; style-src 'self' 'unsafe-inline' https://at.alicdn.com; img-src 'self' data: https: https://at.alicdn.com; font-src 'self' 'unsafe-inline' https://at.alicdn.com data:; connect-src 'self' https://at.alicdn.com http://localhost:* ws://localhost:*;">
|
||||
<meta name="referrer" content="no-referrer-when-downgrade">
|
||||
|
||||
<!-- 移动端和 PWA 支持 -->
|
||||
@@ -27,7 +27,7 @@
|
||||
<link rel="icon" type="image/png" href="/favicon/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="/favicon/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="/favicon/favicon-48x48.png" sizes="48x48">
|
||||
<link rel="manifest" href="/favicon/faviconsite.webmanifest">
|
||||
<link rel="manifest" href="/favicon/site.webmanifest">
|
||||
<link rel="icon" type="image/png" href="/favicon/android-chrome-192x192.png" sizes="192x192">
|
||||
<link rel="icon" type="image/png" href="/favicon/android-chrome-512x512.png" sizes="512x512">
|
||||
<link rel="apple-touch-icon" href="/favicon/apple-touch-icon.png">
|
||||
|
||||
90
openhis-ui-vue3/src/api/operatingroom.js
Normal file
90
openhis-ui-vue3/src/api/operatingroom.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 查询手术室列表
|
||||
* @param {Object} query - 查询参数
|
||||
* @returns {Promise} 请求结果
|
||||
*/
|
||||
export function listOperatingRoom(query) {
|
||||
return request({
|
||||
url: '/base-data-manage/operating-room/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询手术室详细
|
||||
* @param {Long} id - 手术室ID
|
||||
* @returns {Promise} 请求结果
|
||||
*/
|
||||
export function getOperatingRoom(id) {
|
||||
return request({
|
||||
url: '/base-data-manage/operating-room/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增手术室
|
||||
* @param {Object} data - 手术室信息
|
||||
* @returns {Promise} 请求结果
|
||||
*/
|
||||
export function addOperatingRoom(data) {
|
||||
return request({
|
||||
url: '/base-data-manage/operating-room',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改手术室
|
||||
* @param {Object} data - 手术室信息
|
||||
* @returns {Promise} 请求结果
|
||||
*/
|
||||
export function updateOperatingRoom(data) {
|
||||
return request({
|
||||
url: '/base-data-manage/operating-room',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除手术室
|
||||
* @param {Long|Array} ids - 手术室ID或ID数组
|
||||
* @returns {Promise} 请求结果
|
||||
*/
|
||||
export function deleteOperatingRoom(ids) {
|
||||
return request({
|
||||
url: '/base-data-manage/operating-room/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用手术室
|
||||
* @param {Array} ids - 手术室ID数组
|
||||
* @returns {Promise} 请求结果
|
||||
*/
|
||||
export function enableOperatingRoom(ids) {
|
||||
return request({
|
||||
url: '/base-data-manage/operating-room/enable',
|
||||
method: 'put',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用手术室
|
||||
* @param {Array} ids - 手术室ID数组
|
||||
* @returns {Promise} 请求结果
|
||||
*/
|
||||
export function disableOperatingRoom(ids) {
|
||||
return request({
|
||||
url: '/base-data-manage/operating-room/disable',
|
||||
method: 'put',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
@@ -78,3 +78,16 @@ export function updateSurgeryStatus(id, statusEnum) {
|
||||
params: { id, statusEnum }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据患者ID查询就诊列表
|
||||
* @param patientId 患者ID
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getEncounterListByPatientId(patientId) {
|
||||
return request({
|
||||
url: '/clinical-manage/surgery/encounter-list',
|
||||
method: 'get',
|
||||
params: { patientId }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M512 128c-70.69 0-128 57.31-128 128 0 70.69 57.31 128 128 70.69 0 128-57.31 128-128 0-70.69-57.31-128-128-128z m0 192c-35.34 0-64-28.66-64-64 0-35.34 28.66-64 64-64 35.34 0 64 28.66 64 64 0 35.34-28.66 64-64 64z" fill="currentColor"/>
|
||||
<path d="M512 128c-70.69 0-128 57.31-128 128 0 70.69 57.31 128 128 128s128-57.31 128-128c0-70.69-57.31-128-128-128z m0 192c-35.34 0-64-28.66-64-64 0-35.34 28.66-64 64-64 35.34 0 64 28.66 64 64 0 35.34-28.66 64-64 64z" fill="currentColor"/>
|
||||
<path d="M832 832c0-88.36-28.7-172.6-80.4-242.4C702 521 608 480 512 480s-190 41-239.6 109.6C221 659.4 192 743.64 192 832v64h640v-64z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 489 B After Width: | Height: | Size: 485 B |
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1656035183065" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3395"
|
||||
width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||
width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("https://at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("https://at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("https://at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||
</style></defs><path d="M958.88 730.06H65.12c-18.28 0-33.12-14.82-33.12-33.12V68.91c0-18.29 14.83-33.12 33.12-33.12h893.77c18.28 0 33.12 14.82 33.12 33.12v628.03c-0.01 18.3-14.84 33.12-33.13 33.12zM98.23 663.83h827.53v-561.8H98.23v561.8z" p-id="3396"></path><path d="M512 954.55c-18.28 0-33.12-14.82-33.12-33.12V733.92c0-18.29 14.83-33.12 33.12-33.12s33.12 14.82 33.12 33.12v187.51c0 18.3-14.84 33.12-33.12 33.12z" p-id="3397"></path><path d="M762.01 988.21H261.99c-18.28 0-33.12-14.82-33.12-33.12 0-18.29 14.83-33.12 33.12-33.12h500.03c18.28 0 33.12 14.82 33.12 33.12-0.01 18.29-14.84 33.12-33.13 33.12zM514.74 578.55c-21.63 0-43.31-3.87-64.21-11.65-45.95-17.13-82.49-51.13-102.86-95.74-5.07-11.08-0.19-24.19 10.89-29.26 11.08-5.09 24.19-0.18 29.26 10.91 15.5 33.88 43.25 59.7 78.14 72.71 34.93 12.99 72.79 11.64 106.66-3.85 33.22-15.17 58.8-42.26 72.03-76.3 4.42-11.37 17.21-17.01 28.57-12.58 11.36 4.42 16.99 17.22 12.57 28.58-17.42 44.82-51.1 80.5-94.82 100.47-24.34 11.12-50.25 16.71-76.23 16.71z" p-id="3398"></path><path d="M325.27 528.78c-1.66 0-3.34-0.18-5.02-0.57-11.88-2.77-19.28-14.63-16.49-26.51l18.84-81c1.34-5.82 5-10.84 10.13-13.92 5.09-3.09 11.3-3.96 17.03-2.41l80.51 21.43c11.79 3.14 18.8 15.23 15.67 27.02-3.15 11.79-15.42 18.75-27.02 15.65l-58.49-15.57-13.69 58.81c-2.37 10.2-11.45 17.07-21.47 17.07zM360.8 351.01c-2.65 0-5.37-0.49-8-1.51-11.36-4.41-16.99-17.21-12.59-28.57 17.4-44.79 51.06-80.47 94.8-100.48 92.15-42.06 201.25-1.39 243.31 90.68 5.07 11.08 0.19 24.19-10.89 29.26-11.13 5.07-24.19 0.17-29.26-10.91-31.97-69.91-114.9-100.82-184.79-68.86-33.22 15.19-58.8 42.28-71.99 76.29-3.41 8.74-11.75 14.1-20.59 14.1z" p-id="3399"></path><path d="M684.68 376.74c-1.47 0-2.95-0.15-4.42-0.44l-81.61-16.68c-11.94-2.45-19.64-14.11-17.21-26.06 2.44-11.96 14.1-19.64 26.04-17.22l59.29 12.12 10.23-59.5c2.05-12 13.52-20.19 25.48-18.01 12.03 2.06 20.09 13.48 18.02 25.5l-14.08 81.96a22.089 22.089 0 0 1-9.29 14.49c-3.7 2.51-8.03 3.84-12.45 3.84z" p-id="3400"></path></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -91,28 +91,7 @@ export const constantRoutes = [
|
||||
{
|
||||
path: '/tpr',
|
||||
component: () => import('@/views/inpatientNurse/tprsheet/index.vue'),
|
||||
},
|
||||
// {
|
||||
// path: '/patientmanagement',
|
||||
// component: Layout,
|
||||
// redirect: '/patientmanagement/patientmanagement',
|
||||
// name: 'PatientManagement',
|
||||
// meta: { title: '患者管理', icon: 'patient' },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'patientmanagement',
|
||||
// component: () => import('@/views/patientmanagement/patientmanagement/index.vue'),
|
||||
// name: 'PatientManagementList',
|
||||
// meta: { title: '患者档案管理', icon: 'patient' },
|
||||
// },
|
||||
// {
|
||||
// path: 'outpatienrecords',
|
||||
// component: () => import('@/views/patientmanagement/outpatienrecords/index.vue'),
|
||||
// name: 'OutpatientRecords',
|
||||
// meta: { title: '门诊就诊记录', icon: 'record' },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
}
|
||||
];
|
||||
|
||||
// 动态路由 - 基于用户权限动态加载的路由
|
||||
@@ -192,58 +171,19 @@ export const dynamicRoutes = [
|
||||
// meta: { title: '系统管理', icon: 'system' },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'user', // 用户管理路由
|
||||
// component: () => import('@/views/system/user/index.vue'),
|
||||
// name: 'User',
|
||||
// meta: { title: '用户管理', icon: 'user', permissions: ['system:user:list'] }
|
||||
// },
|
||||
// {
|
||||
// path: 'role', // 角色管理路由
|
||||
// component: () => import('@/views/system/role/index.vue'),
|
||||
// name: 'Role',
|
||||
// meta: { title: '角色管理', icon: 'role', permissions: ['system:role:list'] }
|
||||
// },
|
||||
// {
|
||||
// path: 'menu', // 菜单管理路由
|
||||
// component: () => import('@/views/system/menu/index.vue'),
|
||||
// name: 'Menu',
|
||||
// meta: { title: '菜单管理', icon: 'menu', permissions: ['system:menu:list'] }
|
||||
// },
|
||||
// {
|
||||
// path: 'dept', // 部门管理路由
|
||||
// component: () => import('@/views/system/dept/index.vue'),
|
||||
// name: 'Dept',
|
||||
// meta: { title: '部门管理', icon: 'dept', permissions: ['system:dept:list'] }
|
||||
// },
|
||||
// {
|
||||
// path: 'post', // 岗位管理路由
|
||||
// component: () => import('@/views/system/post/index.vue'),
|
||||
// name: 'Post',
|
||||
// meta: { title: '岗位管理', icon: 'post', permissions: ['system:post:list'] }
|
||||
// },
|
||||
// {
|
||||
// path: 'dict', // 字典管理路由
|
||||
// component: () => import('@/views/system/dict/index.vue'),
|
||||
// name: 'Dict',
|
||||
// meta: { title: '字典管理', icon: 'dict', permissions: ['system:dict:list'] }
|
||||
// },
|
||||
// {
|
||||
// path: 'config', // 参数配置路由
|
||||
// component: () => import('@/views/system/config/index.vue'),
|
||||
// name: 'Config',
|
||||
// meta: { title: '参数配置', icon: 'config', permissions: ['system:config:list'] }
|
||||
// },
|
||||
// {
|
||||
// path: 'notice', // 通知公告路由
|
||||
// component: () => import('@/views/system/notice/index.vue'),
|
||||
// name: 'Notice',
|
||||
// meta: { title: '通知公告', icon: 'notice', permissions: ['system:notice:list'] }
|
||||
// },
|
||||
// {
|
||||
// path: 'tenant', // 租户管理路由
|
||||
// component: () => import('@/views/system/tenant/index.vue'),
|
||||
// name: 'Tenant',
|
||||
// meta: { title: '租户管理', icon: 'tenant', permissions: ['system:tenant:list'] }
|
||||
// path: 'basicdata',
|
||||
// component: Layout,
|
||||
// redirect: '/system/basicdata/location',
|
||||
// name: 'BasicData',
|
||||
// meta: { title: '基础数据', icon: 'location' },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'operatingroom',
|
||||
// component: () => import('@/views/operatingroom/index.vue'),
|
||||
// name: 'OperatingRoomManage',
|
||||
// meta: { title: '手术室管理' }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
|
||||
@@ -0,0 +1,715 @@
|
||||
<!--
|
||||
* @Description: 门诊手术申请
|
||||
-->
|
||||
<template>
|
||||
<div class="surgery-application-container">
|
||||
<!-- 顶部操作栏 -->
|
||||
<el-row :gutter="10" class="mb8 top-operation-bar">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" icon="Plus" @click="handleAdd" class="add-button">新增手术申请</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Refresh" @click="handleRefresh" class="refresh-button">刷新</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 手术申请记录表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="surgeryList"
|
||||
border
|
||||
row-key="id"
|
||||
:row-class-name="getRowClassName"
|
||||
height="calc(100vh - 250px)"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
|
||||
<!-- 申请日期 -->
|
||||
<el-table-column label="申请日期" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 手术单号 -->
|
||||
<el-table-column label="手术单号" align="center" prop="surgeryNo" width="150" show-overflow-tooltip />
|
||||
|
||||
<!-- 患者姓名 -->
|
||||
<el-table-column label="患者姓名" align="center" prop="patientName" width="100" />
|
||||
|
||||
<!-- 申请医生 -->
|
||||
<el-table-column label="申请医生" align="center" prop="applyDoctorName" width="100" />
|
||||
|
||||
<!-- 申请科室 -->
|
||||
<el-table-column label="申请科室" align="center" prop="applyDeptName" width="120" show-overflow-tooltip />
|
||||
|
||||
<!-- 手术名称 -->
|
||||
<el-table-column label="手术名称" align="center" prop="surgeryName" min-width="150" show-overflow-tooltip />
|
||||
|
||||
<!-- 手术等级 -->
|
||||
<el-table-column label="手术等级" align="center" prop="surgeryLevel_dictText" width="100" />
|
||||
|
||||
<!-- 状态 -->
|
||||
<el-table-column label="状态" align="center" prop="statusEnum_dictText" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.statusEnum)">
|
||||
{{ scope.row.statusEnum_dictText }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 操作 -->
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<!-- 查看:显示手术申请详情(只读模式) -->
|
||||
<el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
|
||||
|
||||
<!-- 编辑:修改手术申请信息(只有状态为新开的能修改) -->
|
||||
<el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.statusEnum === 0">编辑</el-button>
|
||||
|
||||
<!-- 删除:取消手术申请(作废) -->
|
||||
<el-button link type="danger" @click="handleDelete(scope.row)" v-if="scope.row.statusEnum === 0 || scope.row.statusEnum === 1">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 新增或修改手术申请对话框 -->
|
||||
<el-dialog
|
||||
:title="title"
|
||||
v-model="open"
|
||||
width="900px"
|
||||
@close="cancel"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="surgeryRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="id" prop="id" v-show="false">
|
||||
<el-input v-model="form.id" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 患者基本信息区 -->
|
||||
<el-divider content-position="left">患者基本信息</el-divider>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手术单号" prop="surgeryNo">
|
||||
<el-input v-model="form.surgeryNo" disabled placeholder="系统自动生成" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="患者姓名" prop="patientName">
|
||||
<el-input v-model="form.patientName" disabled placeholder="系统自动获取" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="就诊卡号" prop="encounterNo">
|
||||
<el-input v-model="form.encounterNo" disabled placeholder="系统自动获取" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别" prop="patientGender">
|
||||
<el-select v-model="form.patientGender" disabled placeholder="系统自动获取" style="width: 100%">
|
||||
<el-option label="男" value="1" />
|
||||
<el-option label="女" value="2" />
|
||||
<el-option label="其他" value="9" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年龄" prop="patientAge">
|
||||
<el-input-number v-model="form.patientAge" disabled placeholder="系统自动获取" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 手术信息区 -->
|
||||
<el-divider content-position="left">手术信息</el-divider>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手术类型" prop="surgeryTypeEnum">
|
||||
<el-select v-model="form.surgeryTypeEnum" placeholder="请选择手术类型" style="width: 100%">
|
||||
<el-option label="门诊手术" :value="1" />
|
||||
<el-option label="日间手术" :value="2" />
|
||||
<el-option label="急诊手术" :value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手术名称" prop="surgeryName">
|
||||
<el-input v-model="form.surgeryName" placeholder="请选择手术名称">
|
||||
<template #append>
|
||||
<el-button icon="Search" @click="selectSurgeryName" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="拟实施手术日期" prop="plannedTime">
|
||||
<el-date-picker
|
||||
v-model="form.plannedTime"
|
||||
type="datetime"
|
||||
placeholder="选择日期时间"
|
||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 医疗信息区 -->
|
||||
<el-divider content-position="left">医疗信息</el-divider>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手术等级" prop="surgeryLevel">
|
||||
<el-select v-model="form.surgeryLevel" placeholder="请选择手术等级" style="width: 100%">
|
||||
<el-option label="一级手术" :value="1" />
|
||||
<el-option label="二级手术" :value="2" />
|
||||
<el-option label="三级手术" :value="3" />
|
||||
<el-option label="四级手术" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="切口类型" prop="incisionLevel">
|
||||
<el-select v-model="form.incisionLevel" placeholder="请选择切口类型" style="width: 100%">
|
||||
<el-option label="I类切口" :value="1" />
|
||||
<el-option label="II类切口" :value="2" />
|
||||
<el-option label="III类切口" :value="3" />
|
||||
<el-option label="IV类切口" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="麻醉方式" prop="anesthesiaTypeEnum">
|
||||
<el-select v-model="form.anesthesiaTypeEnum" placeholder="请选择麻醉方式" style="width: 100%">
|
||||
<el-option label="局麻" :value="1" />
|
||||
<el-option label="全麻" :value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 人员信息区 -->
|
||||
<el-divider content-position="left">人员信息</el-divider>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="申请医生" prop="applyDoctorName">
|
||||
<el-input v-model="form.applyDoctorName" disabled placeholder="系统自动获取" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主刀医生" prop="mainSurgeonId">
|
||||
<el-select v-model="form.mainSurgeonId" filterable placeholder="请选择主刀医生" style="width: 100%">
|
||||
<el-option v-for="item in doctorList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手术助手" prop="assistantId">
|
||||
<el-select v-model="form.assistantId" filterable clearable placeholder="请选择手术助手" style="width: 100%">
|
||||
<el-option v-for="item in doctorList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="申请科室" prop="applyDeptName">
|
||||
<el-input v-model="form.applyDeptName" disabled placeholder="系统自动获取" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 其他信息区 -->
|
||||
<el-divider content-position="left">其他信息</el-divider>
|
||||
|
||||
<el-form-item label="术前诊断" prop="preoperativeDiagnosis">
|
||||
<el-input v-model="form.preoperativeDiagnosis" disabled placeholder="自动获取门诊诊断的主要诊断名称" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="手术指征" prop="surgeryIndication">
|
||||
<el-input v-model="form.surgeryIndication" placeholder="请输入手术指征" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">提交申请</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看手术详情对话框 -->
|
||||
<el-dialog title="手术申请详情" v-model="viewOpen" width="900px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="手术单号">{{ viewData.surgeryNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请日期">{{ parseTime(viewData.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="患者姓名">{{ viewData.patientName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="患者信息">{{ viewData.patientGender }} / {{ viewData.patientAge }}岁</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊流水号">{{ viewData.encounterNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手术状态">
|
||||
<el-tag :type="getStatusType(viewData.statusEnum)">{{ viewData.statusEnum_dictText }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手术名称">{{ viewData.surgeryName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手术类型">{{ viewData.surgeryTypeEnum_dictText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手术等级">{{ viewData.surgeryLevel_dictText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="麻醉方式">{{ viewData.anesthesiaTypeEnum_dictText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计划时间">{{ viewData.plannedTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主刀医生">{{ viewData.mainSurgeonName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请医生">{{ viewData.applyDoctorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请科室">{{ viewData.applyDeptName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="术前诊断" :span="2">{{ viewData.preoperativeDiagnosis }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手术指征" :span="2">{{ viewData.surgeryIndication }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="SurgeryApplication">
|
||||
import { getCurrentInstance, ref, computed, watch } from 'vue'
|
||||
import { getSurgeryPage, addSurgery, updateSurgery, deleteSurgery, getSurgeryDetail, updateSurgeryStatus } from '@/api/surgerymanage'
|
||||
import { getEncounterDiagnosis } from '../api'
|
||||
import { listUser } from '@/api/system/user'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const userStore = useUserStore()
|
||||
const props = defineProps({
|
||||
patientInfo: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(true)
|
||||
const surgeryList = ref([])
|
||||
const open = ref(false)
|
||||
const viewOpen = ref(false)
|
||||
const isEditMode = ref(false)
|
||||
const form = ref({
|
||||
id: undefined,
|
||||
patientId: undefined,
|
||||
encounterId: undefined,
|
||||
encounterNo: undefined,
|
||||
patientName: undefined,
|
||||
patientGender: undefined,
|
||||
patientAge: undefined,
|
||||
applyDoctorName: undefined,
|
||||
applyDeptName: undefined,
|
||||
surgeryNo: undefined,
|
||||
surgeryName: undefined,
|
||||
surgeryTypeEnum: undefined,
|
||||
surgeryLevel: undefined,
|
||||
plannedTime: undefined,
|
||||
mainSurgeonId: undefined,
|
||||
assistantId: undefined,
|
||||
anesthesiaTypeEnum: undefined,
|
||||
incisionLevel: undefined,
|
||||
preoperativeDiagnosis: undefined,
|
||||
surgeryIndication: undefined
|
||||
})
|
||||
const surgeryRef = ref()
|
||||
const viewData = ref({})
|
||||
const title = ref('')
|
||||
const doctorList = ref([])
|
||||
|
||||
// 字典选项
|
||||
const surgeryStatusOptions = ref([
|
||||
{ value: 0, label: '新开' },
|
||||
{ value: 1, label: '已安排' },
|
||||
{ value: 2, label: '手术中' },
|
||||
{ value: 3, label: '已完成' },
|
||||
{ value: 4, label: '已取消' },
|
||||
{ value: 5, label: '暂停' }
|
||||
])
|
||||
|
||||
const surgeryTypeOptions = ref([
|
||||
{ value: 1, label: '门诊手术' },
|
||||
{ value: 2, label: '日间手术' },
|
||||
{ value: 3, label: '急诊手术' }
|
||||
])
|
||||
|
||||
const rules = ref({
|
||||
surgeryName: [{ required: true, message: '请输入手术名称', trigger: 'blur' }],
|
||||
surgeryTypeEnum: [{ required: true, message: '请选择手术类型', trigger: 'change' }],
|
||||
surgeryLevel: [{ required: true, message: '请选择手术等级', trigger: 'change' }],
|
||||
plannedTime: [{ required: true, message: '请选择计划手术时间', trigger: 'change' }],
|
||||
mainSurgeonId: [{ required: true, message: '请选择主刀医生', trigger: 'change' }],
|
||||
anesthesiaTypeEnum: [{ required: true, message: '请选择麻醉方式', trigger: 'change' }]
|
||||
})
|
||||
|
||||
// 监听患者信息变化
|
||||
watch(() => props.patientInfo, (newVal) => {
|
||||
if (newVal && newVal.encounterId) {
|
||||
getList()
|
||||
loadDiagnosisInfo()
|
||||
loadDoctorList()
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 获取手术申请列表
|
||||
function getList() {
|
||||
if (!props.patientInfo?.encounterId) {
|
||||
surgeryList.value = []
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
getSurgeryPage({
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
encounterId: props.patientInfo.encounterId
|
||||
}).then((res) => {
|
||||
surgeryList.value = res.data.records || []
|
||||
}).catch(error => {
|
||||
console.error('获取手术列表失败:', error)
|
||||
proxy.$message.error('数据加载失败,请稍后重试')
|
||||
surgeryList.value = []
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 加载诊断信息
|
||||
function loadDiagnosisInfo() {
|
||||
if (!props.patientInfo?.encounterId) return
|
||||
|
||||
getEncounterDiagnosis(props.patientInfo.encounterId).then((res) => {
|
||||
if (res.code == 200) {
|
||||
const datas = res.data || []
|
||||
const mainDiagnosis = datas.find(item => item?.maindiseFlag == 1)
|
||||
if (mainDiagnosis) {
|
||||
form.value.preoperativeDiagnosis = mainDiagnosis.name
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取诊断信息失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
// 加载医生列表
|
||||
function loadDoctorList() {
|
||||
listUser({ pageNo: 1, pageSize: 1000 }).then(res => {
|
||||
if (res.code === 200) {
|
||||
doctorList.value = res.data.records || []
|
||||
console.log('加载医生列表成功,数量:', doctorList.value.length)
|
||||
} else {
|
||||
proxy.$modal.msgError('获取医生列表失败')
|
||||
doctorList.value = []
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('加载医生列表失败:', error)
|
||||
proxy.$modal.msgError('获取医生列表失败')
|
||||
doctorList.value = []
|
||||
})
|
||||
}
|
||||
|
||||
// 新增
|
||||
function handleAdd() {
|
||||
if (!props.patientInfo?.encounterId) {
|
||||
proxy.$message.warning('请先选择患者')
|
||||
return
|
||||
}
|
||||
|
||||
title.value = '新增手术申请'
|
||||
open.value = true
|
||||
reset()
|
||||
|
||||
// 自动填充患者信息
|
||||
form.value.patientId = props.patientInfo.patientId
|
||||
form.value.encounterId = props.patientInfo.encounterId
|
||||
form.value.encounterNo = props.patientInfo.busNo
|
||||
form.value.patientName = props.patientInfo.patientName
|
||||
form.value.patientGender = props.patientInfo.genderEnum_enumText
|
||||
form.value.patientAge = props.patientInfo.age
|
||||
form.value.applyDoctorName = userStore.nickName
|
||||
form.value.applyDeptName = props.patientInfo.deptName || ''
|
||||
|
||||
// 加载诊断信息
|
||||
loadDiagnosisInfo()
|
||||
}
|
||||
|
||||
// 编辑
|
||||
function handleEdit(row) {
|
||||
if (row.statusEnum !== 0) {
|
||||
proxy.$modal.msgWarning('当前状态不允许编辑手术,仅新开状态可编辑')
|
||||
return
|
||||
}
|
||||
|
||||
title.value = '编辑手术申请'
|
||||
open.value = true
|
||||
isEditMode.value = true
|
||||
|
||||
getSurgeryDetail(row.id).then(res => {
|
||||
if (res.code === 200) {
|
||||
Object.assign(form.value, res.data)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取手术信息失败:', error)
|
||||
proxy.$modal.msgError('获取手术信息失败')
|
||||
})
|
||||
}
|
||||
|
||||
// 查看
|
||||
function handleView(row) {
|
||||
viewOpen.value = true
|
||||
getSurgeryDetail(row.id).then(res => {
|
||||
if (res.code === 200) {
|
||||
viewData.value = res.data
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取手术信息失败:', error)
|
||||
proxy.$modal.msgError('获取手术信息失败')
|
||||
})
|
||||
}
|
||||
|
||||
// 删除/取消
|
||||
function handleDelete(row) {
|
||||
if (row.statusEnum === 0) {
|
||||
// 新开状态 - 直接删除
|
||||
proxy.$modal.confirm('是否确认删除手术"' + row.surgeryName + '"?').then(() => {
|
||||
return deleteSurgery(row.id)
|
||||
}).then(() => {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
}).catch(error => {
|
||||
console.error('删除手术失败:', error)
|
||||
proxy.$modal.msgError('删除失败')
|
||||
})
|
||||
} else if (row.statusEnum === 1) {
|
||||
// 已安排状态 - 更新为已取消
|
||||
proxy.$modal.confirm('是否确认取消手术"' + row.surgeryName + '"?').then(() => {
|
||||
return updateSurgeryStatus(row.id, 4) // 4 = 已取消
|
||||
}).then(() => {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess('手术已取消')
|
||||
}).catch(error => {
|
||||
console.error('取消手术失败:', error)
|
||||
proxy.$modal.msgError('取消失败')
|
||||
})
|
||||
} else {
|
||||
// 其他状态 - 不允许操作
|
||||
proxy.$modal.msgWarning('当前状态不允许取消手术')
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新
|
||||
function handleRefresh() {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess('刷新成功')
|
||||
}
|
||||
|
||||
// 选择手术名称
|
||||
function selectSurgeryName() {
|
||||
proxy.$modal.msgInfo('请选择手术名称')
|
||||
// TODO: 实现手术名称选择弹窗
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
function submitForm() {
|
||||
proxy.$refs['surgeryRef'].validate((valid) => {
|
||||
if (valid) {
|
||||
if (form.value.id == undefined) {
|
||||
// 新增手术
|
||||
addSurgery(form.value).then((res) => {
|
||||
proxy.$modal.msgSuccess('新增成功')
|
||||
open.value = false
|
||||
getList()
|
||||
}).catch(error => {
|
||||
console.error('新增手术失败:', error)
|
||||
proxy.$message.error('新增手术失败,请检查表单信息')
|
||||
})
|
||||
} else {
|
||||
// 修改手术
|
||||
updateSurgery(form.value).then((res) => {
|
||||
proxy.$modal.msgSuccess('修改成功')
|
||||
open.value = false
|
||||
getList()
|
||||
}).catch(error => {
|
||||
console.error('更新手术失败:', error)
|
||||
proxy.$message.error('更新手术失败,请检查表单信息')
|
||||
})
|
||||
}
|
||||
} else {
|
||||
proxy.$message.error('请检查表单信息,标红字段为必填项')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 取消
|
||||
function cancel() {
|
||||
open.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
function reset() {
|
||||
form.value = {
|
||||
id: undefined,
|
||||
patientId: undefined,
|
||||
encounterId: undefined,
|
||||
encounterNo: undefined,
|
||||
patientName: undefined,
|
||||
patientGender: undefined,
|
||||
patientAge: undefined,
|
||||
applyDoctorName: undefined,
|
||||
applyDeptName: undefined,
|
||||
surgeryNo: undefined,
|
||||
surgeryName: undefined,
|
||||
surgeryTypeEnum: undefined,
|
||||
surgeryLevel: undefined,
|
||||
plannedTime: undefined,
|
||||
mainSurgeonId: undefined,
|
||||
assistantId: undefined,
|
||||
anesthesiaTypeEnum: undefined,
|
||||
incisionLevel: undefined,
|
||||
preoperativeDiagnosis: undefined,
|
||||
surgeryIndication: undefined
|
||||
}
|
||||
if (surgeryRef.value) {
|
||||
surgeryRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
function getStatusType(status) {
|
||||
const typeMap = {
|
||||
0: 'info',
|
||||
1: 'warning',
|
||||
2: 'primary',
|
||||
3: 'success',
|
||||
4: 'danger',
|
||||
5: 'info'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
// 获取表格行样式
|
||||
function getRowClassName({ row }) {
|
||||
return row.statusEnum === 4 ? 'cancelled-row' : ''
|
||||
}
|
||||
|
||||
// 时间格式化函数
|
||||
function parseTime(time, pattern) {
|
||||
if (!time) return ''
|
||||
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||
let date
|
||||
if (typeof time === 'object') {
|
||||
date = time
|
||||
} else {
|
||||
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
|
||||
time = parseInt(time)
|
||||
} else if (typeof time === 'string') {
|
||||
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '')
|
||||
}
|
||||
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||
time = time * 1000
|
||||
}
|
||||
date = new Date(time)
|
||||
}
|
||||
const formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
}
|
||||
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
|
||||
const value = formatObj[key]
|
||||
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
|
||||
return value.toString().padStart(2, '0')
|
||||
})
|
||||
return time_str
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getList
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.surgery-application-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
|
||||
/* 顶部操作栏样式 */
|
||||
.top-operation-bar {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.add-button {
|
||||
background-color: #5b8fb9;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
padding: 0 20px;
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(91, 143, 185, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.refresh-button {
|
||||
background-color: transparent;
|
||||
border: 1px solid #dcdfe6;
|
||||
color: #606266;
|
||||
border-radius: 8px;
|
||||
padding: 0 20px;
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.el-table {
|
||||
::v-deep(.cancelled-row) {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
|
||||
::v-deep(.cell) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 对话框样式 */
|
||||
.el-dialog {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,633 @@
|
||||
<template>
|
||||
<div class="today-outpatient-patient-list">
|
||||
<!-- 搜索过滤区域 -->
|
||||
<div class="filter-section">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true">
|
||||
<el-form-item label="搜索" prop="searchKey">
|
||||
<el-input
|
||||
v-model="queryParams.searchKey"
|
||||
placeholder="姓名/身份证/手机号/就诊号"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="statusEnum">
|
||||
<el-select
|
||||
v-model="queryParams.statusEnum"
|
||||
placeholder="全部状态"
|
||||
clearable
|
||||
style="width: 120px"
|
||||
>
|
||||
<el-option label="待就诊" :value="1" />
|
||||
<el-option label="就诊中" :value="2" />
|
||||
<el-option label="已完成" :value="3" />
|
||||
<el-option label="已取消" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="患者类型" prop="typeCode">
|
||||
<el-select
|
||||
v-model="queryParams.typeCode"
|
||||
placeholder="全部类型"
|
||||
clearable
|
||||
style="width: 120px"
|
||||
>
|
||||
<el-option label="普通" :value="1" />
|
||||
<el-option label="急诊" :value="2" />
|
||||
<el-option label="VIP" :value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
<el-button type="warning" icon="Download" @click="exportData">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 患者列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
:data="patientList"
|
||||
border
|
||||
style="width: 100%"
|
||||
v-loading="loading"
|
||||
:header-cell-style="{ background: '#f5f7fa', fontWeight: 'bold' }"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="patientName" label="患者" min-width="100">
|
||||
<template #default="scope">
|
||||
<div class="patient-name-cell">
|
||||
<span class="name">{{ scope.row.patientName }}</span>
|
||||
<el-tag
|
||||
v-if="scope.row.importantFlag"
|
||||
size="small"
|
||||
type="danger"
|
||||
class="important-tag"
|
||||
>
|
||||
重点
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="genderEnumEnumText" label="性别" width="80" align="center" />
|
||||
<el-table-column prop="age" label="年龄" width="80" align="center" />
|
||||
<el-table-column prop="phone" label="联系电话" width="120" />
|
||||
<el-table-column prop="encounterBusNo" label="就诊号" width="120" align="center" />
|
||||
<el-table-column prop="registerTime" label="挂号时间" width="160" sortable />
|
||||
<el-table-column prop="waitingDuration" label="候诊时长" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<span :class="getWaitingDurationClass(scope.row.waitingDuration)">
|
||||
{{ scope.row.waitingDuration || 0 }} 分钟
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="statusEnumEnumText" label="就诊状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="getStatusTagType(scope.row.statusEnum)"
|
||||
size="small"
|
||||
class="status-tag"
|
||||
>
|
||||
{{ scope.row.statusEnumEnumText }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-button
|
||||
v-if="scope.row.statusEnum === 1"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleReceive(scope.row.encounterId)"
|
||||
class="action-button"
|
||||
>
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
接诊
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.statusEnum === 2"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleComplete(scope.row.encounterId)"
|
||||
class="action-button"
|
||||
>
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
完成
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.statusEnum !== 4"
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="handleCancel(scope.row.encounterId)"
|
||||
class="action-button"
|
||||
>
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
size="small"
|
||||
@click="handleViewDetail(scope.row)"
|
||||
class="action-button"
|
||||
>
|
||||
<el-icon><View /></el-icon>
|
||||
详情
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-section">
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNo"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 批量操作 -->
|
||||
<div class="batch-section" v-if="selectedPatients.length > 0">
|
||||
<el-card shadow="always" class="batch-card">
|
||||
<div class="batch-content">
|
||||
<el-space>
|
||||
<el-text>已选择 {{ selectedPatients.length }} 个患者</el-text>
|
||||
<el-button-group>
|
||||
<el-button
|
||||
v-if="selectedPatients.some(p => p.statusEnum === 1)"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="batchReceive"
|
||||
>
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
批量接诊
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="selectedPatients.some(p => p.statusEnum === 2)"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="batchComplete"
|
||||
>
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
批量完成
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="batchCancel"
|
||||
>
|
||||
<el-icon><Close /></el-icon>
|
||||
批量取消
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
size="small"
|
||||
@click="clearSelection"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
清空选择
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-space>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, defineEmits, defineExpose } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
VideoPlay, CircleCheck, Close, View, Delete
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
getTodayOutpatientPatients,
|
||||
receivePatient,
|
||||
completeVisit,
|
||||
cancelVisit,
|
||||
batchUpdatePatientStatus
|
||||
} from './api.js'
|
||||
|
||||
// 数据
|
||||
const patientList = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const selectedPatients = ref([])
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
searchKey: '',
|
||||
statusEnum: null,
|
||||
typeCode: null,
|
||||
importantFlag: null,
|
||||
hasPrescription: null,
|
||||
hasExamination: null,
|
||||
hasLaboratory: null,
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
sortField: 1,
|
||||
sortOrder: 2
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
refreshList
|
||||
})
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
loadPatients()
|
||||
})
|
||||
|
||||
// 加载患者列表
|
||||
const loadPatients = () => {
|
||||
loading.value = true
|
||||
getTodayOutpatientPatients(queryParams)
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
patientList.value = res.data?.records || []
|
||||
total.value = res.data?.total || 0
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
function refreshList() {
|
||||
loadPatients()
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
loadPatients()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const resetQuery = () => {
|
||||
queryParams.searchKey = ''
|
||||
queryParams.statusEnum = null
|
||||
queryParams.typeCode = null
|
||||
queryParams.importantFlag = null
|
||||
queryParams.hasPrescription = null
|
||||
queryParams.hasExamination = null
|
||||
queryParams.hasLaboratory = null
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 分页大小变化
|
||||
const handleSizeChange = (val) => {
|
||||
queryParams.pageSize = val
|
||||
loadPatients()
|
||||
}
|
||||
|
||||
// 当前页变化
|
||||
const handleCurrentChange = (val) => {
|
||||
queryParams.pageNo = val
|
||||
loadPatients()
|
||||
}
|
||||
|
||||
// 选择变化
|
||||
const handleSelectionChange = (val) => {
|
||||
selectedPatients.value = val
|
||||
}
|
||||
|
||||
// 清空选择
|
||||
const clearSelection = () => {
|
||||
selectedPatients.value = []
|
||||
}
|
||||
|
||||
// 接诊患者
|
||||
const handleReceive = (encounterId) => {
|
||||
ElMessageBox.confirm('确定接诊该患者吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
receivePatient(encounterId).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('接诊成功')
|
||||
refreshList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '接诊失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 完成就诊
|
||||
const handleComplete = (encounterId) => {
|
||||
ElMessageBox.confirm('确定完成该患者的就诊吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
completeVisit(encounterId).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('就诊完成')
|
||||
refreshList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '操作失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 取消就诊
|
||||
const handleCancel = (encounterId) => {
|
||||
ElMessageBox.prompt('请输入取消原因', '取消就诊', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^.{1,200}$/,
|
||||
inputErrorMessage: '取消原因长度在1到200个字符之间'
|
||||
}).then(({ value }) => {
|
||||
cancelVisit(encounterId, value).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('就诊已取消')
|
||||
refreshList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '操作失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = (patient) => {
|
||||
// 在实际应用中,可以打开详细对话框或跳转到详情页面
|
||||
ElMessage.info(`查看患者 ${patient.patientName} 的详情`)
|
||||
}
|
||||
|
||||
// 批量接诊
|
||||
const batchReceive = () => {
|
||||
const waitingIds = selectedPatients.value
|
||||
.filter(p => p.statusEnum === 1)
|
||||
.map(p => p.encounterId)
|
||||
|
||||
if (waitingIds.length === 0) {
|
||||
ElMessage.warning('请选择待就诊的患者')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(`确定批量接诊 ${waitingIds.length} 个患者吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
batchUpdatePatientStatus(waitingIds, 2).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`成功接诊 ${waitingIds.length} 个患者`)
|
||||
refreshList()
|
||||
selectedPatients.value = []
|
||||
} else {
|
||||
ElMessage.error(res.msg || '批量接诊失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 批量完成
|
||||
const batchComplete = () => {
|
||||
const inProgressIds = selectedPatients.value
|
||||
.filter(p => p.statusEnum === 2)
|
||||
.map(p => p.encounterId)
|
||||
|
||||
if (inProgressIds.length === 0) {
|
||||
ElMessage.warning('请选择就诊中的患者')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(`确定批量完成 ${inProgressIds.length} 个患者的就诊吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
batchUpdatePatientStatus(inProgressIds, 3).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`成功完成 ${inProgressIds.length} 个患者的就诊`)
|
||||
refreshList()
|
||||
selectedPatients.value = []
|
||||
} else {
|
||||
ElMessage.error(res.msg || '批量完成失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 批量取消
|
||||
const batchCancel = () => {
|
||||
ElMessageBox.prompt('请输入批量取消的原因', '批量取消就诊', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^.{1,200}$/,
|
||||
inputErrorMessage: '取消原因长度在1到200个字符之间'
|
||||
}).then(({ value }) => {
|
||||
const cancelIds = selectedPatients.value
|
||||
.filter(p => p.statusEnum !== 4)
|
||||
.map(p => p.encounterId)
|
||||
|
||||
if (cancelIds.length === 0) {
|
||||
ElMessage.warning('没有符合条件的患者可以取消')
|
||||
return
|
||||
}
|
||||
|
||||
batchUpdatePatientStatus(cancelIds, 4).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`成功取消 ${cancelIds.length} 个患者的就诊`)
|
||||
refreshList()
|
||||
selectedPatients.value = []
|
||||
} else {
|
||||
ElMessage.error(res.msg || '批量取消失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
const exportData = () => {
|
||||
ElMessage.info('导出功能开发中...')
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusTagType = (status) => {
|
||||
switch (status) {
|
||||
case 1: // 待就诊
|
||||
return 'warning'
|
||||
case 2: // 就诊中
|
||||
return 'primary'
|
||||
case 3: // 已完成
|
||||
return 'success'
|
||||
case 4: // 已取消
|
||||
return 'info'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 获取候诊时长样式
|
||||
const getWaitingDurationClass = (duration) => {
|
||||
if (!duration) return ''
|
||||
if (duration > 60) return 'waiting-long' // 超过1小时
|
||||
if (duration > 30) return 'waiting-medium' // 超过30分钟
|
||||
return 'waiting-short' // 30分钟以内
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.today-outpatient-patient-list {
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.patient-name-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.important-tag {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 候诊时长颜色
|
||||
.waiting-short {
|
||||
color: #67c23a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.waiting-medium {
|
||||
color: #e6a23c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.waiting-long {
|
||||
color: #f56c6c;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-section {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.batch-section {
|
||||
position: sticky;
|
||||
bottom: 20px;
|
||||
z-index: 1000;
|
||||
|
||||
.batch-card {
|
||||
background: linear-gradient(135deg, #f6f9fc, #ffffff);
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
|
||||
.batch-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
|
||||
.el-text {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.el-button-group {
|
||||
gap: 8px;
|
||||
|
||||
.el-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.today-outpatient-patient-list {
|
||||
.filter-section {
|
||||
padding: 12px;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 12px;
|
||||
width: 100%;
|
||||
|
||||
.el-input,
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-section {
|
||||
overflow-x: auto;
|
||||
|
||||
.el-table {
|
||||
min-width: 800px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-section {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<div class="today-outpatient-stats">
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="12" :sm="6" :md="6" :lg="6">
|
||||
<div class="stats-card total-registered">
|
||||
<div class="stats-icon">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<div class="stats-info">
|
||||
<div class="stats-value">{{ stats.totalRegistered || 0 }}</div>
|
||||
<div class="stats-label">今日总挂号</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="12" :sm="6" :md="6" :lg="6">
|
||||
<div class="stats-card waiting">
|
||||
<div class="stats-icon">
|
||||
<el-icon><Clock /></el-icon>
|
||||
</div>
|
||||
<div class="stats-info">
|
||||
<div class="stats-value">{{ stats.waitingCount || 0 }}</div>
|
||||
<div class="stats-label">待就诊</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="12" :sm="6" :md="6" :lg="6">
|
||||
<div class="stats-card in-progress">
|
||||
<div class="stats-icon">
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
</div>
|
||||
<div class="stats-info">
|
||||
<div class="stats-value">{{ stats.inProgressCount || 0 }}</div>
|
||||
<div class="stats-label">就诊中</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="12" :sm="6" :md="6" :lg="6">
|
||||
<div class="stats-card completed">
|
||||
<div class="stats-icon">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
</div>
|
||||
<div class="stats-info">
|
||||
<div class="stats-value">{{ stats.completedCount || 0 }}</div>
|
||||
<div class="stats-label">已完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 时间统计 -->
|
||||
<div class="time-stats">
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="12" :sm="12" :md="12" :lg="12">
|
||||
<div class="time-card waiting-time">
|
||||
<el-icon><Timer /></el-icon>
|
||||
<div class="time-info">
|
||||
<div class="time-value">{{ stats.averageWaitingTime || 0 }} 分钟</div>
|
||||
<div class="time-label">平均候诊时间</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="12" :sm="12" :md="12" :lg="12">
|
||||
<div class="time-card visit-time">
|
||||
<el-icon><Watch /></el-icon>
|
||||
<div class="time-info">
|
||||
<div class="time-value">{{ stats.averageVisitTime || 0 }} 分钟</div>
|
||||
<div class="time-label">平均就诊时间</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, defineEmits, defineExpose } from 'vue'
|
||||
import { User, Clock, VideoPlay, CircleCheck, Timer, Watch } from '@element-plus/icons-vue'
|
||||
import { getTodayOutpatientStats } from './api.js'
|
||||
|
||||
// 数据
|
||||
const stats = ref({})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
refreshStats
|
||||
})
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
loadStats()
|
||||
})
|
||||
|
||||
// 加载统计信息
|
||||
const loadStats = () => {
|
||||
getTodayOutpatientStats().then(res => {
|
||||
if (res.code === 200) {
|
||||
stats.value = res.data || {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新统计信息
|
||||
function refreshStats() {
|
||||
loadStats()
|
||||
emit('refresh')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.today-outpatient-stats {
|
||||
.stats-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stats-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
|
||||
.el-icon {
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-info {
|
||||
flex: 1;
|
||||
|
||||
.stats-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// 不同统计卡片的颜色
|
||||
&.total-registered {
|
||||
.stats-icon {
|
||||
background: linear-gradient(135deg, #409eff, #79bbff);
|
||||
}
|
||||
}
|
||||
|
||||
&.waiting {
|
||||
.stats-icon {
|
||||
background: linear-gradient(135deg, #e6a23c, #fab85c);
|
||||
}
|
||||
}
|
||||
|
||||
&.in-progress {
|
||||
.stats-icon {
|
||||
background: linear-gradient(135deg, #67c23a, #95d475);
|
||||
}
|
||||
}
|
||||
|
||||
&.completed {
|
||||
.stats-icon {
|
||||
background: linear-gradient(135deg, #909399, #b1b3b8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time-stats {
|
||||
margin-top: 20px;
|
||||
|
||||
.time-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
margin-right: 16px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.time-info {
|
||||
.time-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.time-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// 不同时间统计卡片的颜色
|
||||
&.waiting-time {
|
||||
.el-icon {
|
||||
color: #e6a23c;
|
||||
}
|
||||
}
|
||||
|
||||
&.visit-time {
|
||||
.el-icon {
|
||||
color: #67c23a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式间距
|
||||
.el-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-col {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.today-outpatient-stats {
|
||||
.stats-card {
|
||||
padding: 16px;
|
||||
|
||||
.stats-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-info {
|
||||
.stats-value {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time-stats {
|
||||
.time-card {
|
||||
padding: 16px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.time-info {
|
||||
.time-value {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.time-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,95 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 获取今日门诊统计信息
|
||||
export function getTodayOutpatientStats() {
|
||||
return request({
|
||||
url: '/today-outpatient/stats',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 分页查询今日门诊患者列表
|
||||
export function getTodayOutpatientPatients(queryParams) {
|
||||
return request({
|
||||
url: '/today-outpatient/patients',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
}
|
||||
|
||||
// 获取今日待就诊患者队列
|
||||
export function getWaitingPatients() {
|
||||
return request({
|
||||
url: '/today-outpatient/patients/waiting',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取今日就诊中患者列表
|
||||
export function getInProgressPatients() {
|
||||
return request({
|
||||
url: '/today-outpatient/patients/in-progress',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取今日已完成就诊患者列表
|
||||
export function getCompletedPatients() {
|
||||
return request({
|
||||
url: '/today-outpatient/patients/completed',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取患者就诊详情
|
||||
export function getPatientDetail(encounterId) {
|
||||
return request({
|
||||
url: `/today-outpatient/patients/${encounterId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 批量更新患者状态
|
||||
export function batchUpdatePatientStatus(encounterIds, targetStatus) {
|
||||
return request({
|
||||
url: '/today-outpatient/patients/batch-update-status',
|
||||
method: 'post',
|
||||
params: {
|
||||
encounterIds: encounterIds.join(','),
|
||||
targetStatus
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 接诊患者
|
||||
export function receivePatient(encounterId) {
|
||||
return request({
|
||||
url: `/today-outpatient/patients/${encounterId}/receive`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 完成就诊
|
||||
export function completeVisit(encounterId) {
|
||||
return request({
|
||||
url: `/today-outpatient/patients/${encounterId}/complete`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 取消就诊
|
||||
export function cancelVisit(encounterId, reason) {
|
||||
return request({
|
||||
url: `/today-outpatient/patients/${encounterId}/cancel`,
|
||||
method: 'post',
|
||||
params: { reason }
|
||||
})
|
||||
}
|
||||
|
||||
// 快速接诊
|
||||
export function quickReceivePatient(encounterId) {
|
||||
return request({
|
||||
url: `/today-outpatient/quick-receive/${encounterId}`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,753 @@
|
||||
<template>
|
||||
<div class="today-outpatient-container">
|
||||
<!-- 统计卡片区域 -->
|
||||
<div class="stats-cards">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-card class="stats-card" shadow="hover">
|
||||
<div class="stats-content">
|
||||
<div class="stats-icon" style="background-color: #409eff;">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<div class="stats-info">
|
||||
<div class="stats-label">今日总挂号</div>
|
||||
<div class="stats-value">{{ stats.totalRegistered || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="stats-card" shadow="hover">
|
||||
<div class="stats-content">
|
||||
<div class="stats-icon" style="background-color: #e6a23c;">
|
||||
<el-icon><Clock /></el-icon>
|
||||
</div>
|
||||
<div class="stats-info">
|
||||
<div class="stats-label">待就诊</div>
|
||||
<div class="stats-value">{{ stats.waitingCount || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="stats-card" shadow="hover">
|
||||
<div class="stats-content">
|
||||
<div class="stats-icon" style="background-color: #67c23a;">
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
</div>
|
||||
<div class="stats-info">
|
||||
<div class="stats-label">就诊中</div>
|
||||
<div class="stats-value">{{ stats.inProgressCount || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="stats-card" shadow="hover">
|
||||
<div class="stats-content">
|
||||
<div class="stats-icon" style="background-color: #909399;">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
</div>
|
||||
<div class="stats-info">
|
||||
<div class="stats-label">已完成</div>
|
||||
<div class="stats-value">{{ stats.completedCount || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 时间统计 -->
|
||||
<el-row :gutter="20" class="time-stats">
|
||||
<el-col :span="12">
|
||||
<el-card class="time-card" shadow="hover">
|
||||
<div class="time-content">
|
||||
<el-icon class="time-icon"><Timer /></el-icon>
|
||||
<div class="time-info">
|
||||
<div class="time-label">平均候诊时间</div>
|
||||
<div class="time-value">{{ stats.averageWaitingTime || 0 }} 分钟</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card class="time-card" shadow="hover">
|
||||
<div class="time-content">
|
||||
<el-icon class="time-icon"><Watch /></el-icon>
|
||||
<div class="time-info">
|
||||
<div class="time-label">平均就诊时间</div>
|
||||
<div class="time-value">{{ stats.averageVisitTime || 0 }} 分钟</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和过滤区域 -->
|
||||
<div class="search-filter">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true">
|
||||
<el-form-item label="搜索" prop="searchKey">
|
||||
<el-input
|
||||
v-model="queryParams.searchKey"
|
||||
placeholder="姓名/身份证/手机号/就诊号"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="statusEnum">
|
||||
<el-select
|
||||
v-model="queryParams.statusEnum"
|
||||
placeholder="全部状态"
|
||||
clearable
|
||||
style="width: 120px"
|
||||
>
|
||||
<el-option label="待就诊" :value="1" />
|
||||
<el-option label="就诊中" :value="2" />
|
||||
<el-option label="已完成" :value="3" />
|
||||
<el-option label="已取消" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="患者类型" prop="typeCode">
|
||||
<el-select
|
||||
v-model="queryParams.typeCode"
|
||||
placeholder="全部类型"
|
||||
clearable
|
||||
style="width: 120px"
|
||||
>
|
||||
<el-option label="普通" :value="1" />
|
||||
<el-option label="急诊" :value="2" />
|
||||
<el-option label="VIP" :value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 患者列表 -->
|
||||
<div class="patient-list">
|
||||
<el-table
|
||||
:data="patientList"
|
||||
border
|
||||
style="width: 100%"
|
||||
v-loading="loading"
|
||||
:header-cell-style="{ background: '#f5f7fa', fontWeight: 'bold' }"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="patientName" label="患者" min-width="100" />
|
||||
<el-table-column prop="genderEnumEnumText" label="性别" width="80" align="center" />
|
||||
<el-table-column prop="age" label="年龄" width="80" align="center" />
|
||||
<el-table-column prop="phone" label="联系电话" width="120" />
|
||||
<el-table-column prop="encounterBusNo" label="就诊号" width="120" align="center" />
|
||||
<el-table-column prop="registerTime" label="挂号时间" width="160" sortable />
|
||||
<el-table-column prop="waitingDuration" label="候诊时长" width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.waitingDuration || 0 }} 分钟
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="statusEnumEnumText" label="就诊状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="getStatusTagType(scope.row.statusEnum)"
|
||||
size="small"
|
||||
>
|
||||
{{ scope.row.statusEnumEnumText }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="subjectStatusEnumEnumText" label="就诊对象状态" width="120" align="center" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.statusEnum === 1"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleReceive(scope.row.encounterId)"
|
||||
>
|
||||
接诊
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.statusEnum === 2"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleComplete(scope.row.encounterId)"
|
||||
>
|
||||
完成
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.statusEnum !== 4"
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="handleCancel(scope.row.encounterId)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
size="small"
|
||||
@click="handleViewDetail(scope.row.encounterId)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNo"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 批量操作 -->
|
||||
<div class="batch-actions" v-if="selectedPatients.length > 0">
|
||||
<el-space>
|
||||
<el-text>已选择 {{ selectedPatients.length }} 个患者</el-text>
|
||||
<el-button
|
||||
v-if="selectedPatients.some(p => p.statusEnum === 1)"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="batchReceive"
|
||||
>
|
||||
批量接诊
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="selectedPatients.some(p => p.statusEnum === 2)"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="batchComplete"
|
||||
>
|
||||
批量完成
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="batchCancel"
|
||||
>
|
||||
批量取消
|
||||
</el-button>
|
||||
</el-space>
|
||||
</div>
|
||||
|
||||
<!-- 患者详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="detailDialogVisible"
|
||||
title="患者就诊详情"
|
||||
width="800px"
|
||||
:before-close="handleDetailDialogClose"
|
||||
>
|
||||
<div v-loading="detailLoading">
|
||||
<el-descriptions v-if="patientDetail" :column="2" border>
|
||||
<el-descriptions-item label="患者姓名">{{ patientDetail.patientName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ patientDetail.genderEnumEnumText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="年龄">{{ patientDetail.age }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ patientDetail.idCard }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ patientDetail.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊号">{{ patientDetail.encounterBusNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="挂号时间">{{ patientDetail.registerTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="接诊时间">{{ patientDetail.receptionTime || '未接诊' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊状态">
|
||||
<el-tag :type="getStatusTagType(patientDetail.statusEnum)" size="small">
|
||||
{{ patientDetail.statusEnumEnumText }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊对象状态">{{ patientDetail.subjectStatusEnumEnumText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="候诊时长">{{ patientDetail.waitingDuration || 0 }} 分钟</el-descriptions-item>
|
||||
<el-descriptions-item label="就诊时长">{{ patientDetail.visitDuration || 0 }} 分钟</el-descriptions-item>
|
||||
<el-descriptions-item label="是否重点患者">
|
||||
<el-tag :type="patientDetail.importantFlag ? 'danger' : 'info'" size="small">
|
||||
{{ patientDetail.importantFlag ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="是否已开药">
|
||||
<el-tag :type="patientDetail.hasPrescription ? 'success' : 'info'" size="small">
|
||||
{{ patientDetail.hasPrescription ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="是否已检查">
|
||||
<el-tag :type="patientDetail.hasExamination ? 'success' : 'info'" size="small">
|
||||
{{ patientDetail.hasExamination ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="是否已检验">
|
||||
<el-tag :type="patientDetail.hasLaboratory ? 'success' : 'info'" size="small">
|
||||
{{ patientDetail.hasLaboratory ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-empty v-else description="暂无患者详情" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
<el-button
|
||||
v-if="patientDetail && patientDetail.statusEnum === 1"
|
||||
type="primary"
|
||||
@click="handleReceive(patientDetail.encounterId)"
|
||||
>
|
||||
接诊患者
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="patientDetail && patientDetail.statusEnum === 2"
|
||||
type="success"
|
||||
@click="handleComplete(patientDetail.encounterId)"
|
||||
>
|
||||
完成就诊
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
User, Clock, VideoPlay, CircleCheck, Timer, Watch
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
getTodayOutpatientStats,
|
||||
getTodayOutpatientPatients,
|
||||
receivePatient,
|
||||
completeVisit,
|
||||
cancelVisit,
|
||||
batchUpdatePatientStatus
|
||||
} from './api.js'
|
||||
|
||||
// 数据
|
||||
const stats = ref({})
|
||||
const patientList = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const selectedPatients = ref([])
|
||||
const patientDetail = ref(null)
|
||||
|
||||
// 对话框控制
|
||||
const detailDialogVisible = ref(false)
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
searchKey: '',
|
||||
statusEnum: null,
|
||||
typeCode: null,
|
||||
importantFlag: null,
|
||||
hasPrescription: null,
|
||||
hasExamination: null,
|
||||
hasLaboratory: null,
|
||||
doctorId: null,
|
||||
departmentId: null,
|
||||
queryDate: null,
|
||||
sortField: 1,
|
||||
sortOrder: 2,
|
||||
pageNo: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
loadStats()
|
||||
loadPatients()
|
||||
})
|
||||
|
||||
// 加载统计信息
|
||||
const loadStats = () => {
|
||||
getTodayOutpatientStats().then(res => {
|
||||
if (res.code === 200) {
|
||||
stats.value = res.data || {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 加载患者列表
|
||||
const loadPatients = () => {
|
||||
loading.value = true
|
||||
getTodayOutpatientPatients(queryParams)
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
patientList.value = res.data?.records || []
|
||||
total.value = res.data?.total || 0
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
loadPatients()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const resetQuery = () => {
|
||||
queryParams.searchKey = ''
|
||||
queryParams.statusEnum = null
|
||||
queryParams.typeCode = null
|
||||
queryParams.importantFlag = null
|
||||
queryParams.hasPrescription = null
|
||||
queryParams.hasExamination = null
|
||||
queryParams.hasLaboratory = null
|
||||
queryParams.sortField = 1
|
||||
queryParams.sortOrder = 2
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 分页大小变化
|
||||
const handleSizeChange = (val) => {
|
||||
queryParams.pageSize = val
|
||||
loadPatients()
|
||||
}
|
||||
|
||||
// 当前页变化
|
||||
const handleCurrentChange = (val) => {
|
||||
queryParams.pageNo = val
|
||||
loadPatients()
|
||||
}
|
||||
|
||||
// 选择变化
|
||||
const handleSelectionChange = (val) => {
|
||||
selectedPatients.value = val
|
||||
}
|
||||
|
||||
// 接诊患者
|
||||
const handleReceive = (encounterId) => {
|
||||
ElMessageBox.confirm('确定接诊该患者吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
receivePatient(encounterId).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('接诊成功')
|
||||
loadStats()
|
||||
loadPatients()
|
||||
if (detailDialogVisible.value) {
|
||||
handleViewDetail(encounterId)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.msg || '接诊失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 完成就诊
|
||||
const handleComplete = (encounterId) => {
|
||||
ElMessageBox.confirm('确定完成该患者的就诊吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
completeVisit(encounterId).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('就诊完成')
|
||||
loadStats()
|
||||
loadPatients()
|
||||
if (detailDialogVisible.value) {
|
||||
handleViewDetail(encounterId)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.msg || '操作失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 取消就诊
|
||||
const handleCancel = (encounterId) => {
|
||||
ElMessageBox.prompt('请输入取消原因', '取消就诊', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^.{1,200}$/,
|
||||
inputErrorMessage: '取消原因长度在1到200个字符之间'
|
||||
}).then(({ value }) => {
|
||||
cancelVisit(encounterId, value).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('就诊已取消')
|
||||
loadStats()
|
||||
loadPatients()
|
||||
if (detailDialogVisible.value) {
|
||||
handleViewDetail(encounterId)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.msg || '操作失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = (encounterId) => {
|
||||
detailDialogVisible.value = true
|
||||
detailLoading.value = true
|
||||
|
||||
// 模拟获取详情数据,实际应该调用API
|
||||
setTimeout(() => {
|
||||
const patient = patientList.value.find(p => p.encounterId === encounterId)
|
||||
if (patient) {
|
||||
patientDetail.value = { ...patient }
|
||||
}
|
||||
detailLoading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 关闭详情对话框
|
||||
const handleDetailDialogClose = (done) => {
|
||||
patientDetail.value = null
|
||||
done()
|
||||
}
|
||||
|
||||
// 批量接诊
|
||||
const batchReceive = () => {
|
||||
const waitingIds = selectedPatients.value
|
||||
.filter(p => p.statusEnum === 1)
|
||||
.map(p => p.encounterId)
|
||||
|
||||
if (waitingIds.length === 0) {
|
||||
ElMessage.warning('请选择待就诊的患者')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(`确定批量接诊 ${waitingIds.length} 个患者吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
batchUpdatePatientStatus(waitingIds, 2).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`成功接诊 ${waitingIds.length} 个患者`)
|
||||
loadStats()
|
||||
loadPatients()
|
||||
selectedPatients.value = []
|
||||
} else {
|
||||
ElMessage.error(res.msg || '批量接诊失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 批量完成
|
||||
const batchComplete = () => {
|
||||
const inProgressIds = selectedPatients.value
|
||||
.filter(p => p.statusEnum === 2)
|
||||
.map(p => p.encounterId)
|
||||
|
||||
if (inProgressIds.length === 0) {
|
||||
ElMessage.warning('请选择就诊中的患者')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(`确定批量完成 ${inProgressIds.length} 个患者的就诊吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
batchUpdatePatientStatus(inProgressIds, 3).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`成功完成 ${inProgressIds.length} 个患者的就诊`)
|
||||
loadStats()
|
||||
loadPatients()
|
||||
selectedPatients.value = []
|
||||
} else {
|
||||
ElMessage.error(res.msg || '批量完成失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 批量取消
|
||||
const batchCancel = () => {
|
||||
ElMessageBox.prompt('请输入批量取消的原因', '批量取消就诊', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^.{1,200}$/,
|
||||
inputErrorMessage: '取消原因长度在1到200个字符之间'
|
||||
}).then(({ value }) => {
|
||||
const cancelIds = selectedPatients.value
|
||||
.filter(p => p.statusEnum !== 4)
|
||||
.map(p => p.encounterId)
|
||||
|
||||
if (cancelIds.length === 0) {
|
||||
ElMessage.warning('没有符合条件的患者可以取消')
|
||||
return
|
||||
}
|
||||
|
||||
batchUpdatePatientStatus(cancelIds, 4).then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`成功取消 ${cancelIds.length} 个患者的就诊`)
|
||||
loadStats()
|
||||
loadPatients()
|
||||
selectedPatients.value = []
|
||||
} else {
|
||||
ElMessage.error(res.msg || '批量取消失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusTagType = (status) => {
|
||||
switch (status) {
|
||||
case 1: // 待就诊
|
||||
return 'warning'
|
||||
case 2: // 就诊中
|
||||
return 'primary'
|
||||
case 3: // 已完成
|
||||
return 'success'
|
||||
case 4: // 已取消
|
||||
return 'info'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.today-outpatient-container {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
|
||||
.stats-cards {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.stats-card {
|
||||
border-radius: 8px;
|
||||
|
||||
.stats-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.stats-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
|
||||
.el-icon {
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-info {
|
||||
.stats-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time-stats {
|
||||
margin-top: 20px;
|
||||
|
||||
.time-card {
|
||||
border-radius: 8px;
|
||||
|
||||
.time-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.time-icon {
|
||||
font-size: 32px;
|
||||
color: #409eff;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.time-info {
|
||||
.time-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-filter {
|
||||
background: white;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.patient-list {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: white;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.today-outpatient-container {
|
||||
padding: 10px;
|
||||
|
||||
.stats-cards {
|
||||
.el-col {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-filter {
|
||||
.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -164,6 +164,9 @@
|
||||
<el-tab-pane label="检验" name="inspection">
|
||||
<inspectionApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="inspectionRef" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="手术申请" name="surgery">
|
||||
<surgeryApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="surgeryRef" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="电子处方" name="eprescription">
|
||||
<eprescriptionlist :patientInfo="patientInfo" ref="eprescriptionRef" />
|
||||
</el-tab-pane>
|
||||
@@ -217,6 +220,7 @@ import eprescriptionlist from './components/eprescriptionlist.vue';
|
||||
import HospitalizationDialog from './components/hospitalizationDialog.vue';
|
||||
import tcmAdvice from './components/tcm/tcmAdvice.vue';
|
||||
import inspectionApplication from './components/inspection/inspectionApplication.vue';
|
||||
import surgeryApplication from './components/surgery/surgeryApplication.vue';
|
||||
import {formatDate, formatDateStr} from '@/utils/index';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import {nextTick} from 'vue';
|
||||
@@ -266,6 +270,7 @@ const patientDrawerRef = ref();
|
||||
const prescriptionRef = ref();
|
||||
const tcmRef = ref();
|
||||
const inspectionRef = ref();
|
||||
const surgeryRef = ref();
|
||||
const emrRef = ref();
|
||||
const diagnosisRef = ref();
|
||||
const waitCount = ref(0);
|
||||
@@ -402,6 +407,9 @@ function handleClick(tab) {
|
||||
case 'inspection':
|
||||
// 检验tab点击处理逻辑可以在这里添加
|
||||
break;
|
||||
case 'surgery':
|
||||
surgeryRef.value.getList();
|
||||
break;
|
||||
case 'eprescription':
|
||||
eprescriptionRef.value.getList();
|
||||
break;
|
||||
@@ -460,6 +468,7 @@ function handleCardClick(item, index) {
|
||||
prescriptionRef.value.getListInfo();
|
||||
tcmRef.value.getListInfo();
|
||||
inspectionRef.value.getList();
|
||||
surgeryRef.value.getList();
|
||||
diagnosisRef.value.getList();
|
||||
eprescriptionRef.value.getList();
|
||||
// emrRef.value.getDetail(item.encounterId);
|
||||
|
||||
372
openhis-ui-vue3/src/views/doctorstation/todayOutpatient.vue
Normal file
372
openhis-ui-vue3/src/views/doctorstation/todayOutpatient.vue
Normal file
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<div :class="['today-outpatient-page', { 'fullscreen-mode': isFullscreen }]">
|
||||
<!-- 页面头部工具栏 -->
|
||||
<div class="page-header-toolbar">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="18">
|
||||
<div class="toolbar-left">
|
||||
<h2 style="margin: 0; line-height: 32px;">
|
||||
<i class="el-icon-calendar" style="margin-right: 8px;"></i>
|
||||
今日门诊
|
||||
<span v-if="isFullscreen" style="margin-left: 12px; font-size: 14px; color: #666;">
|
||||
(全屏模式)
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="toolbar-right">
|
||||
<el-button-group>
|
||||
<el-tooltip :content="isFullscreen ? '退出全屏模式' : '进入全屏模式'" placement="top">
|
||||
<el-button
|
||||
:type="isFullscreen ? 'warning' : 'primary'"
|
||||
:icon="isFullscreen ? 'CloseBold' : 'FullScreen'"
|
||||
@click="toggleFullscreen"
|
||||
:loading="refreshing"
|
||||
>
|
||||
{{ isFullscreen ? '退出全屏' : '全屏' }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="刷新数据" placement="top">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="Refresh"
|
||||
@click="refreshData"
|
||||
:loading="refreshing"
|
||||
>
|
||||
刷新
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="页面设置" placement="top">
|
||||
<el-button
|
||||
type="info"
|
||||
icon="Setting"
|
||||
@click="showSettings = true"
|
||||
>
|
||||
设置
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="content-area">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-section">
|
||||
<today-outpatient-stats />
|
||||
</div>
|
||||
|
||||
<!-- 患者列表 -->
|
||||
<div class="patient-list-section">
|
||||
<today-outpatient-patient-list />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置对话框 -->
|
||||
<el-dialog
|
||||
v-model="showSettings"
|
||||
title="今日门诊设置"
|
||||
width="500px"
|
||||
>
|
||||
<el-form :model="settingsForm" label-width="100px">
|
||||
<el-form-item label="默认显示">
|
||||
<el-select v-model="settingsForm.defaultView" placeholder="请选择默认视图">
|
||||
<el-option label="待就诊" value="waiting" />
|
||||
<el-option label="就诊中" value="inProgress" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="全部" value="all" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="每页显示">
|
||||
<el-input-number
|
||||
v-model="settingsForm.pageSize"
|
||||
:min="5"
|
||||
:max="100"
|
||||
:step="5"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="自动刷新">
|
||||
<el-switch v-model="settingsForm.autoRefresh" />
|
||||
<div style="margin-top: 8px; font-size: 12px; color: #999;">
|
||||
启用后每30秒自动刷新数据
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showSettings = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveSettings">保存</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import TodayOutpatientStats from './components/todayOutpatient/TodayOutpatientStats.vue'
|
||||
import TodayOutpatientPatientList from './components/todayOutpatient/TodayOutpatientPatientList.vue'
|
||||
|
||||
// 数据
|
||||
const showSettings = ref(false)
|
||||
const refreshTimer = ref(null)
|
||||
const refreshing = ref(false)
|
||||
const isFullscreen = ref(false)
|
||||
const settingsForm = ref({
|
||||
defaultView: 'waiting',
|
||||
pageSize: 10,
|
||||
autoRefresh: false
|
||||
})
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
loadSettings()
|
||||
})
|
||||
|
||||
// 页面卸载
|
||||
onUnmounted(() => {
|
||||
if (refreshTimer.value) {
|
||||
clearInterval(refreshTimer.value)
|
||||
}
|
||||
})
|
||||
|
||||
// 加载设置
|
||||
const loadSettings = () => {
|
||||
const savedSettings = localStorage.getItem('todayOutpatientSettings')
|
||||
if (savedSettings) {
|
||||
settingsForm.value = JSON.parse(savedSettings)
|
||||
}
|
||||
|
||||
// 启动自动刷新
|
||||
if (settingsForm.value.autoRefresh) {
|
||||
startAutoRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
const saveSettings = () => {
|
||||
localStorage.setItem('todayOutpatientSettings', JSON.stringify(settingsForm.value))
|
||||
|
||||
// 更新自动刷新
|
||||
if (settingsForm.value.autoRefresh) {
|
||||
startAutoRefresh()
|
||||
} else {
|
||||
stopAutoRefresh()
|
||||
}
|
||||
|
||||
ElMessage.success('设置保存成功')
|
||||
showSettings.value = false
|
||||
}
|
||||
|
||||
// 开始自动刷新
|
||||
const startAutoRefresh = () => {
|
||||
stopAutoRefresh() // 先停止现有的定时器
|
||||
refreshTimer.value = setInterval(() => {
|
||||
refreshData()
|
||||
}, 30000) // 30秒刷新一次
|
||||
}
|
||||
|
||||
// 停止自动刷新
|
||||
const stopAutoRefresh = () => {
|
||||
if (refreshTimer.value) {
|
||||
clearInterval(refreshTimer.value)
|
||||
refreshTimer.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
const refreshData = () => {
|
||||
refreshing.value = true
|
||||
// 触发子组件的刷新方法
|
||||
// 在实际应用中,可以通过事件总线或provide/inject传递刷新方法
|
||||
setTimeout(() => {
|
||||
refreshing.value = false
|
||||
ElMessage.success('数据已刷新')
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 全屏切换功能
|
||||
const toggleFullscreen = () => {
|
||||
if (!isFullscreen.value) {
|
||||
// 进入全屏模式
|
||||
const elem = document.documentElement
|
||||
if (elem.requestFullscreen) {
|
||||
elem.requestFullscreen()
|
||||
} else if (elem.webkitRequestFullscreen) { /* Safari */
|
||||
elem.webkitRequestFullscreen()
|
||||
} else if (elem.msRequestFullscreen) { /* IE11 */
|
||||
elem.msRequestFullscreen()
|
||||
}
|
||||
isFullscreen.value = true
|
||||
ElMessage.success('已进入全屏模式')
|
||||
} else {
|
||||
// 退出全屏模式
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.webkitExitFullscreen) { /* Safari */
|
||||
document.webkitExitFullscreen()
|
||||
} else if (document.msExitFullscreen) { /* IE11 */
|
||||
document.msExitFullscreen()
|
||||
}
|
||||
isFullscreen.value = false
|
||||
ElMessage.success('已退出全屏模式')
|
||||
}
|
||||
}
|
||||
|
||||
// 监听全屏状态变化
|
||||
const handleFullscreenChange = () => {
|
||||
isFullscreen.value = !!(
|
||||
document.fullscreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.msFullscreenElement
|
||||
)
|
||||
}
|
||||
|
||||
// 监听键盘事件(ESC退出全屏)
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Escape' && isFullscreen.value) {
|
||||
toggleFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时添加监听器
|
||||
onMounted(() => {
|
||||
loadSettings()
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
||||
document.addEventListener('webkitfullscreenchange', handleFullscreenChange)
|
||||
document.addEventListener('msfullscreenchange', handleFullscreenChange)
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
|
||||
// 页面卸载时移除监听器
|
||||
onUnmounted(() => {
|
||||
if (refreshTimer.value) {
|
||||
clearInterval(refreshTimer.value)
|
||||
}
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
|
||||
document.removeEventListener('msfullscreenchange', handleFullscreenChange)
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.today-outpatient-page {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f7fa;
|
||||
|
||||
// 全屏模式样式
|
||||
&.fullscreen-mode {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
background: white;
|
||||
padding: 0 !important;
|
||||
|
||||
.page-header-toolbar {
|
||||
background: #fff !important;
|
||||
border-bottom: 1px solid #dcdfe6 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header-toolbar {
|
||||
background: #f5f7fa;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
margin-bottom: 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.toolbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
|
||||
.el-icon-calendar {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
.el-button-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 0 20px 20px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
margin-bottom: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.patient-list-section {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// 全屏模式下的特定样式
|
||||
:fullscreen .today-outpatient-page,
|
||||
:-webkit-full-screen .today-outpatient-page,
|
||||
:-moz-full-screen .today-outpatient-page,
|
||||
:-ms-fullscreen .today-outpatient-page {
|
||||
&.fullscreen-mode {
|
||||
.content-area {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.today-outpatient-page {
|
||||
.page-header-toolbar {
|
||||
padding: 12px;
|
||||
|
||||
.toolbar-left h2 {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-area {
|
||||
padding: 0 12px 12px 12px;
|
||||
}
|
||||
|
||||
.patient-list-section {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -409,9 +409,12 @@ const handleStatClick = (stat) => {
|
||||
} else if (stat.key === 'todayRevenue' || stat.key === 'todayPayments') {
|
||||
// 跳转到收费页面
|
||||
router.push('/charge/cliniccharge')
|
||||
} else if (stat.key === 'appointments' || stat.key === 'todayAppointments') {
|
||||
} else if (stat.key === 'appointments') {
|
||||
// 跳转到预约管理页面
|
||||
router.push('/appoinmentmanage')
|
||||
} else if (stat.key === 'todayAppointments') {
|
||||
// 跳转到今日门诊模块
|
||||
router.push('/doctorstation/today-outpatient')
|
||||
} else if (stat.key === 'pendingApprovals' || stat.key === 'pendingReview') {
|
||||
// 跳转到待审核页面
|
||||
router.push('/clinicmanagement/ePrescribing')
|
||||
|
||||
501
openhis-ui-vue3/src/views/operatingroom/index.vue
Normal file
501
openhis-ui-vue3/src/views/operatingroom/index.vue
Normal file
@@ -0,0 +1,501 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 查询表单 -->
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" class="query-form">
|
||||
<el-form-item label="手术室名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入手术室名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="statusEnum">
|
||||
<el-select v-model="queryParams.statusEnum" placeholder="请选择状态" clearable style="width: 150px">
|
||||
<el-option
|
||||
v-for="item in statusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增手术室</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">
|
||||
批量删除
|
||||
</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="operatingRoomList"
|
||||
@selection-change="handleSelectionChange"
|
||||
row-key="id"
|
||||
:row-class-name="getRowClassName"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" type="index" align="center" width="60" />
|
||||
<el-table-column label="手术室编码" align="center" prop="busNo" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="手术室名称" align="center" prop="name" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="位置描述" align="center" prop="locationDescription" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="设备配置" align="center" prop="equipmentConfig" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="容纳人数" align="center" prop="capacity" width="100" />
|
||||
<el-table-column label="状态" align="center" prop="statusEnum" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.statusEnum)">
|
||||
{{ scope.row.statusEnum === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="显示顺序" align="center" prop="displayOrder" width="100" />
|
||||
<el-table-column label="操作" align="center" width="180" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)">查看</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 新增或修改手术室对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="700px" append-to-body>
|
||||
<el-form ref="operatingRoomRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手术室名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入手术室名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="容纳人数" prop="capacity">
|
||||
<el-input-number
|
||||
v-model="form.capacity"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 100%"
|
||||
placeholder="请输入容纳人数"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属科室" prop="organizationId">
|
||||
<el-select
|
||||
v-model="form.organizationId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择所属科室"
|
||||
:loading="deptLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in flatDeptOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="statusEnum">
|
||||
<el-select v-model="form.statusEnum" placeholder="请选择状态" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in statusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="显示顺序" prop="displayOrder">
|
||||
<el-input-number
|
||||
v-model="form.displayOrder"
|
||||
:min="0"
|
||||
:max="999"
|
||||
style="width: 100%"
|
||||
placeholder="请输入显示顺序"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="位置描述" prop="locationDescription">
|
||||
<el-input
|
||||
v-model="form.locationDescription"
|
||||
type="textarea"
|
||||
placeholder="请输入位置描述"
|
||||
:rows="2"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="设备配置" prop="equipmentConfig">
|
||||
<el-input
|
||||
v-model="form.equipmentConfig"
|
||||
type="textarea"
|
||||
placeholder="请输入设备配置,如:麻醉机、监护仪、手术台等"
|
||||
:rows="3"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注"
|
||||
:rows="2"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看手术室详情对话框 -->
|
||||
<el-dialog title="手术室详情" v-model="viewOpen" width="700px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="手术室编码">{{ viewData.busNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手术室名称">{{ viewData.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="位置描述">{{ viewData.locationDescription }}</el-descriptions-item>
|
||||
<el-descriptions-item label="容纳人数">{{ viewData.capacity }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属科室">{{ viewData.organizationName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="显示顺序">{{ viewData.displayOrder }}</el-descriptions-item>
|
||||
<el-descriptions-item label="设备配置" :span="2">{{ viewData.equipmentConfig }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="getStatusType(viewData.statusEnum)">
|
||||
{{ viewData.statusEnum === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ viewData.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="OperatingRoomManage">
|
||||
import { listOperatingRoom, getOperatingRoom, addOperatingRoom, updateOperatingRoom, deleteOperatingRoom } from '@/api/operatingroom'
|
||||
import { deptTreeSelect } from '@/api/system/user'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const loading = ref(true)
|
||||
const showSearch = ref(true)
|
||||
const operatingRoomList = ref([])
|
||||
const ids = ref([])
|
||||
const single = ref(true)
|
||||
const multiple = ref(true)
|
||||
const total = ref(0)
|
||||
const title = ref('')
|
||||
const open = ref(false)
|
||||
const viewOpen = ref(false)
|
||||
const deptOptions = ref([]) // 科室选项,始终是数组
|
||||
const deptLoading = ref(false) // 科室数据加载状态
|
||||
|
||||
// 将树形科室数据扁平化为一维数组,用于可搜索的下拉框
|
||||
const flatDeptOptions = computed(() => {
|
||||
const flatten = (nodes) => {
|
||||
const result = []
|
||||
nodes.forEach(node => {
|
||||
result.push({
|
||||
id: node.id,
|
||||
name: node.name
|
||||
})
|
||||
if (node.children && node.children.length > 0) {
|
||||
result.push(...flatten(node.children))
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
return flatten(deptOptions.value)
|
||||
})
|
||||
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
statusEnum: undefined
|
||||
})
|
||||
|
||||
const form = ref({
|
||||
id: undefined,
|
||||
busNo: undefined,
|
||||
name: undefined,
|
||||
organizationId: undefined,
|
||||
locationDescription: undefined,
|
||||
equipmentConfig: undefined,
|
||||
capacity: 1, // 默认值为1,而不是undefined
|
||||
statusEnum: 1,
|
||||
displayOrder: 0, // 默认值为0
|
||||
remark: undefined
|
||||
})
|
||||
|
||||
const operatingRoomRef = ref()
|
||||
const viewData = ref({})
|
||||
|
||||
const statusOptions = ref([
|
||||
{ value: 1, label: '启用' },
|
||||
{ value: 0, label: '停用' }
|
||||
])
|
||||
|
||||
const rules = ref({
|
||||
name: [{ required: true, message: '手术室名称不能为空', trigger: 'blur' }],
|
||||
capacity: [
|
||||
{ required: true, message: '容纳人数不能为空', trigger: 'blur' },
|
||||
{ type: 'number', message: '容纳人数必须为数字', trigger: 'blur' }
|
||||
],
|
||||
organizationId: [{ required: true, message: '请选择所属科室', trigger: 'change' }]
|
||||
})
|
||||
|
||||
/** 查询手术室列表 */
|
||||
function getList() {
|
||||
loading.value = true
|
||||
const params = { ...queryParams.value }
|
||||
listOperatingRoom(params).then(res => {
|
||||
if (res.code === 200) {
|
||||
operatingRoomList.value = res.data.records || []
|
||||
total.value = res.data.total || 0
|
||||
} else {
|
||||
proxy.$modal.msgError(res.msg || '获取手术室列表失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取手术室列表失败:', error)
|
||||
proxy.$modal.msgError('获取手术室列表失败,请稍后重试')
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/** 查询科室树 */
|
||||
function getDeptTree() {
|
||||
deptLoading.value = true
|
||||
deptTreeSelect().then(res => {
|
||||
if (res.code === 200) {
|
||||
// 后端返回的是分页对象,需要从 records 中获取实际数据
|
||||
const data = res.data
|
||||
if (data && Array.isArray(data.records)) {
|
||||
deptOptions.value = data.records
|
||||
} else if (Array.isArray(data)) {
|
||||
deptOptions.value = data
|
||||
} else {
|
||||
deptOptions.value = []
|
||||
console.warn('科室数据格式不正确:', data)
|
||||
}
|
||||
} else {
|
||||
deptOptions.value = []
|
||||
console.warn('获取科室树失败:', res.msg)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取科室树失败:', error)
|
||||
deptOptions.value = []
|
||||
}).finally(() => {
|
||||
deptLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消按钮 */
|
||||
function cancel() {
|
||||
open.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
/** 表单重置 */
|
||||
function reset() {
|
||||
form.value = {
|
||||
id: undefined,
|
||||
busNo: undefined,
|
||||
name: undefined,
|
||||
organizationId: undefined,
|
||||
locationDescription: undefined,
|
||||
equipmentConfig: undefined,
|
||||
capacity: 1, // 默认值为1
|
||||
statusEnum: 1,
|
||||
displayOrder: 0, // 默认值为0
|
||||
remark: undefined
|
||||
}
|
||||
proxy.resetForm('operatingRoomRef')
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
proxy.resetForm('queryRef')
|
||||
queryParams.value = {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
statusEnum: undefined
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map(item => item.id)
|
||||
single.value = selection.length !== 1
|
||||
multiple.value = !selection.length
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
reset()
|
||||
open.value = true
|
||||
title.value = '新增手术室'
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
function handleEdit(row) {
|
||||
reset()
|
||||
const id = row.id
|
||||
getOperatingRoom(id).then(res => {
|
||||
if (res.code === 200) {
|
||||
form.value = res.data
|
||||
open.value = true
|
||||
title.value = '编辑手术室'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 查看按钮操作 */
|
||||
function handleView(row) {
|
||||
const id = row.id
|
||||
listOperatingRoom({ id }).then(res => {
|
||||
if (res.code === 200 && res.data.records.length > 0) {
|
||||
viewData.value = res.data.records[0]
|
||||
viewOpen.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
proxy.$refs['operatingRoomRef'].validate(valid => {
|
||||
if (valid) {
|
||||
// 确保所有必填字段都有值
|
||||
const submitData = {
|
||||
...form.value,
|
||||
name: form.value.name?.trim() || '',
|
||||
organizationId: form.value.organizationId || null,
|
||||
capacity: form.value.capacity || 1,
|
||||
displayOrder: form.value.displayOrder || 0
|
||||
}
|
||||
|
||||
if (form.value.id) {
|
||||
updateOperatingRoom(submitData).then(res => {
|
||||
if (res.code === 200) {
|
||||
proxy.$modal.msgSuccess('修改成功')
|
||||
open.value = false
|
||||
getList()
|
||||
} else {
|
||||
proxy.$modal.msgError(res.msg || '修改失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('修改手术室失败:', error)
|
||||
proxy.$modal.msgError('修改失败,请稍后重试')
|
||||
})
|
||||
} else {
|
||||
addOperatingRoom(submitData).then(res => {
|
||||
if (res.code === 200) {
|
||||
proxy.$modal.msgSuccess('新增成功')
|
||||
open.value = false
|
||||
getList()
|
||||
} else {
|
||||
proxy.$modal.msgError(res.msg || '新增失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('新增手术室失败:', error)
|
||||
proxy.$modal.msgError('新增失败,请稍后重试')
|
||||
})
|
||||
}
|
||||
} else {
|
||||
proxy.$modal.msgError('请完善表单信息')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
const ids = row.id || ids.value
|
||||
proxy.$modal
|
||||
.confirm('是否确认删除选中的手术室?')
|
||||
.then(function () {
|
||||
return deleteOperatingRoom(ids)
|
||||
})
|
||||
.then(() => {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
/** 获取状态标签类型 */
|
||||
function getStatusType(status) {
|
||||
const typeMap = {
|
||||
1: 'success',
|
||||
0: 'info'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
/** 获取行样式 */
|
||||
function getRowClassName({ row }) {
|
||||
if (row.statusEnum === 0) {
|
||||
return 'disabled-row'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
getDeptTree()
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.disabled-row {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@@ -49,10 +49,11 @@
|
||||
clearable
|
||||
style="width: 120px"
|
||||
>
|
||||
<el-option label="待就诊" :value="1" />
|
||||
<el-option label="就诊中" :value="2" />
|
||||
<el-option label="已完成" :value="3" />
|
||||
<el-option label="已取消" :value="4" />
|
||||
<el-option label="已到达" :value="1" />
|
||||
<el-option label="已分诊" :value="2" />
|
||||
<el-option label="已看诊" :value="3" />
|
||||
<el-option label="已离开" :value="4" />
|
||||
<el-option label="已完成" :value="5" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="医生" prop="doctorName">
|
||||
@@ -181,16 +182,18 @@ function getList() {
|
||||
|
||||
/** 根据状态获取标签类型 */
|
||||
function getStatusTagType(status) {
|
||||
// 假设状态值:1-待就诊,2-就诊中,3-已完成,4-已取消
|
||||
// 状态值对应后端 EncounterSubjectStatus 枚举:1-已到达,2-已分诊,3-已看诊,4-已离开,5-已完成
|
||||
switch (status) {
|
||||
case 1:
|
||||
return 'warning'; // 待就诊 - 黄色
|
||||
return 'warning'; // 已到达 - 黄色
|
||||
case 2:
|
||||
return 'primary'; // 就诊中 - 蓝色
|
||||
return 'primary'; // 已分诊 - 蓝色
|
||||
case 3:
|
||||
return 'success'; // 已完成 - 绿色
|
||||
return 'success'; // 已看诊 - 绿色
|
||||
case 4:
|
||||
return 'info'; // 已取消 - 灰色
|
||||
return 'info'; // 已离开 - 灰色
|
||||
case 5:
|
||||
return 'success'; // 已完成 - 绿色
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
13
sql/手术室管理表结构.sql
Normal file
13
sql/手术室管理表结构.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
apply_doctor_id,
|
||||
apply_doctor_name,
|
||||
apply_dept_id,
|
||||
apply_dept_name,
|
||||
surgery_name,
|
||||
create_time
|
||||
FROM cli_surgery
|
||||
WHERE delete_flag = '0'
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 1;
|
||||
56
verify_surgery_name_fields.sql
Normal file
56
verify_surgery_name_fields.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- 验证手术表中所有名称字段的填充情况
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
patient_id,
|
||||
patient_name,
|
||||
main_surgeon_id,
|
||||
main_surgeon_name,
|
||||
anesthetist_id,
|
||||
anesthetist_name,
|
||||
assistant_1_id,
|
||||
assistant_1_name,
|
||||
assistant_2_id,
|
||||
assistant_2_name,
|
||||
scrub_nurse_id,
|
||||
scrub_nurse_name,
|
||||
operating_room_id,
|
||||
operating_room_name,
|
||||
org_id,
|
||||
org_name,
|
||||
apply_doctor_id,
|
||||
apply_doctor_name,
|
||||
apply_dept_id,
|
||||
apply_dept_name,
|
||||
create_time
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0'
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 10;
|
||||
|
||||
-- 统计名称字段的填充情况
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
COUNT(patient_name) as has_patient_name_count,
|
||||
COUNT(main_surgeon_name) as has_main_surgeon_name_count,
|
||||
COUNT(anesthetist_name) as has_anesthetist_name_count,
|
||||
COUNT(assistant_1_name) as has_assistant_1_name_count,
|
||||
COUNT(assistant_2_name) as has_assistant_2_name_count,
|
||||
COUNT(scrub_nurse_name) as has_scrub_nurse_name_count,
|
||||
COUNT(operating_room_name) as has_operating_room_name_count,
|
||||
COUNT(org_name) as has_org_name_count,
|
||||
COUNT(apply_doctor_name) as has_apply_doctor_name_count,
|
||||
COUNT(apply_dept_name) as has_apply_dept_name_count,
|
||||
-- 计算填写率
|
||||
ROUND(COUNT(patient_name) * 100.0 / COUNT(*), 2) as patient_name_fill_rate,
|
||||
ROUND(COUNT(main_surgeon_name) * 100.0 / COUNT(*), 2) as main_surgeon_name_fill_rate,
|
||||
ROUND(COUNT(anesthetist_name) * 100.0 / COUNT(*), 2) as anesthetist_name_fill_rate,
|
||||
ROUND(COUNT(assistant_1_name) * 100.0 / COUNT(*), 2) as assistant_1_name_fill_rate,
|
||||
ROUND(COUNT(assistant_2_name) * 100.0 / COUNT(*), 2) as assistant_2_name_fill_rate,
|
||||
ROUND(COUNT(scrub_nurse_name) * 100.0 / COUNT(*), 2) as scrub_nurse_name_fill_rate,
|
||||
ROUND(COUNT(operating_room_name) * 100.0 / COUNT(*), 2) as operating_room_name_fill_rate,
|
||||
ROUND(COUNT(org_name) * 100.0 / COUNT(*), 2) as org_name_fill_rate,
|
||||
ROUND(COUNT(apply_doctor_name) * 100.0 / COUNT(*), 2) as apply_doctor_name_fill_rate,
|
||||
ROUND(COUNT(apply_dept_name) * 100.0 / COUNT(*), 2) as apply_dept_name_fill_rate
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0';
|
||||
18
填充手术人员字段姓名.sql
Normal file
18
填充手术人员字段姓名.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
main_surgeon_id,
|
||||
main_surgeon_name,
|
||||
anesthetist_id,
|
||||
anesthetist_name,
|
||||
assistant_1_id,
|
||||
assistant_1_name,
|
||||
assistant_2_id,
|
||||
assistant_2_name,
|
||||
operating_room_id,
|
||||
operating_room_name,
|
||||
org_id,
|
||||
org_name
|
||||
FROM cli_surgery
|
||||
WHERE delete_flag = '0'
|
||||
ORDER BY create_time DESC
|
||||
254
手术人员字段不显示问题解决方案.md
Normal file
254
手术人员字段不显示问题解决方案.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# 手术人员字段不显示问题解决方案
|
||||
|
||||
## 问题描述
|
||||
主刀医生、麻醉医生、助手1、助手2、执行科这些字段在手术查看页面中没有显示数据。
|
||||
|
||||
## 问题原因
|
||||
这些字段在数据库中可能为 **null 或空值**,虽然保存了 ID(如 `main_surgeon_id`),但没有保存对应的姓名(如 `main_surgeon_name`)。
|
||||
|
||||
## 解决步骤
|
||||
|
||||
### 步骤 1:检查数据库中字段的实际值
|
||||
|
||||
执行以下 SQL 查看当前数据:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
main_surgeon_id,
|
||||
main_surgeon_name,
|
||||
anesthetist_id,
|
||||
anesthetist_name,
|
||||
assistant_1_id,
|
||||
assistant_1_name,
|
||||
assistant_2_id,
|
||||
assistant_2_name,
|
||||
operating_room_id,
|
||||
operating_room_name,
|
||||
org_id,
|
||||
org_name
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0'
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 5;
|
||||
```
|
||||
|
||||
**请告诉我结果**:特别是 `main_surgeon_name`、`anesthetist_name`、`assistant_1_name`、`assistant_2_name`、`operating_room_name`、`org_name` 这些字段的值。
|
||||
|
||||
### 步骤 2:检查用户表结构
|
||||
|
||||
执行以下 SQL 查看用户表的结构:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
column_name,
|
||||
data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'sys_user'
|
||||
AND column_name IN ('user_id', 'nick_name', 'user_name', 'practitioner_id')
|
||||
ORDER BY column_name;
|
||||
```
|
||||
|
||||
**目的**:确定人员ID和姓名的对应关系。
|
||||
|
||||
### 步骤 3:填充人员姓名字段(推荐方法)
|
||||
|
||||
使用以下 SQL 脚本填充人员姓名:
|
||||
|
||||
```sql
|
||||
-- 填充主刀医生姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET main_surgeon_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.main_surgeon_id = u.user_id
|
||||
AND s.main_surgeon_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充麻醉医生姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET anesthetist_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.anesthetist_id = u.user_id
|
||||
AND s.anesthetist_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充助手1姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET assistant_1_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.assistant_1_id = u.user_id
|
||||
AND s.assistant_1_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充助手2姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET assistant_2_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.assistant_2_id = u.user_id
|
||||
AND s.assistant_2_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充手术室名称
|
||||
UPDATE public.cli_surgery s
|
||||
SET operating_room_name = r.name
|
||||
FROM public.cli_operating_room r
|
||||
WHERE s.operating_room_id = r.id
|
||||
AND s.operating_room_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 填充执行科室名称
|
||||
UPDATE public.cli_surgery s
|
||||
SET org_name = o.name
|
||||
FROM public.adm_organization o
|
||||
WHERE s.org_id = o.id
|
||||
AND s.org_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
```
|
||||
|
||||
### 步骤 4:验证更新结果
|
||||
|
||||
执行以下 SQL 验证是否更新成功:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
main_surgeon_id,
|
||||
main_surgeon_name,
|
||||
anesthetist_id,
|
||||
anesthetist_name,
|
||||
assistant_1_id,
|
||||
assistant_1_name,
|
||||
assistant_2_id,
|
||||
assistant_2_name,
|
||||
operating_room_id,
|
||||
operating_room_name,
|
||||
org_id,
|
||||
org_name
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0'
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 5;
|
||||
```
|
||||
|
||||
**预期结果**:所有 `*_name` 字段都应该有值。
|
||||
|
||||
### 步骤 5:刷新前端页面
|
||||
|
||||
1. 刷新手术管理页面
|
||||
2. 点击某个手术记录的"查看"按钮
|
||||
3. 检查详情对话框中是否显示这些字段
|
||||
|
||||
## 前端代码检查
|
||||
|
||||
### 1. 检查详情对话框显示
|
||||
|
||||
打开 `surgerymanage/index.vue` 文件,查看详情对话框部分:
|
||||
|
||||
```vue
|
||||
<el-descriptions-item label="主刀医生">{{ viewData.mainSurgeonName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="麻醉医生">{{ viewData.anesthetistName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="助手1">{{ viewData.assistant1Name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="助手2">{{ viewData.assistant2Name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手术室">{{ viewData.operatingRoomName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="执行科室">{{ viewData.orgName }}</el-descriptions-item>
|
||||
```
|
||||
|
||||
**确认**:这些字段名是否正确(注意驼峰命名)。
|
||||
|
||||
### 2. 检查浏览器控制台
|
||||
|
||||
1. 打开浏览器开发者工具(F12)
|
||||
2. 切换到 Console 标签
|
||||
3. 点击"查看"按钮
|
||||
4. 查看是否有 JavaScript 错误
|
||||
|
||||
### 3. 检查 Network 响应
|
||||
|
||||
1. 切换到 Network 标签
|
||||
2. 点击"查看"按钮
|
||||
3. 找到 `/clinical-manage/surgery/surgery-detail` 请求
|
||||
4. 查看响应内容
|
||||
|
||||
**检查**:响应数据中是否包含这些字段,值是什么。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 问题 1:UPDATE SQL 执行失败
|
||||
|
||||
**症状**:报错 "relation does not exist" 或 "column does not exist"
|
||||
|
||||
**解决**:
|
||||
1. 检查表名是否正确(sys_user 或 adm_practitioner)
|
||||
2. 检查字段名是否正确(user_id 或 practitioner_id)
|
||||
|
||||
### 问题 2:UPDATE 后字段仍为 null
|
||||
|
||||
**症状**:UPDATE 执行成功,但字段仍为 null
|
||||
|
||||
**原因**:关联表中没有对应的记录
|
||||
|
||||
**解决**:检查人员ID是否存在于人员表中
|
||||
|
||||
```sql
|
||||
-- 检查主刀医生ID是否存在
|
||||
SELECT s.main_surgeon_id, u.nick_name
|
||||
FROM public.cli_surgery s
|
||||
LEFT JOIN public.sys_user u ON s.main_surgeon_id = u.user_id
|
||||
WHERE s.main_surgeon_id IS NOT NULL
|
||||
AND u.user_id IS NULL
|
||||
LIMIT 5;
|
||||
```
|
||||
|
||||
### 问题 3:前端仍然不显示
|
||||
|
||||
**症状**:数据库中有值,但前端不显示
|
||||
|
||||
**原因**:
|
||||
1. 前端字段名不匹配
|
||||
2. 前端数据绑定有问题
|
||||
|
||||
**解决**:
|
||||
1. 检查 MyBatis XML 映射是否正确
|
||||
2. 检查后端返回的 JSON 数据结构
|
||||
3. 检查前端变量名是否正确
|
||||
|
||||
## 后续改进建议
|
||||
|
||||
### 1. 保存时自动填充姓名
|
||||
|
||||
在前端或后端保存手术信息时,根据选择的医生ID自动查询并填充姓名字段。
|
||||
|
||||
### 2. 提供人员选择功能
|
||||
|
||||
在前端提供医生、科室等选择下拉框,而不是手动输入ID。
|
||||
|
||||
### 3. 添加数据完整性校验
|
||||
|
||||
在保存前检查:如果选择了人员ID,必须填充对应的姓名字段。
|
||||
|
||||
## 相关文件
|
||||
|
||||
1. **检查和填充脚本**:`e:/his/检查和填充手术人员字段.sql`
|
||||
2. **填充脚本**:`e:/his/填充手术人员字段姓名.sql`
|
||||
3. **MyBatis 映射**:`e:/his/openhis-server-new/openhis-application/src/main/resources/mapper/clinicalmanage/SurgeryMapper.xml`
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] 数据库查询显示字段为 null
|
||||
- [ ] 执行了填充 SQL 脚本
|
||||
- [ ] 验证更新后字段有值
|
||||
- [ ] 刷新前端页面
|
||||
- [ ] 详情对话框中正确显示
|
||||
|
||||
## 联系支持
|
||||
|
||||
如果以上步骤都无法解决问题,请提供:
|
||||
|
||||
1. **步骤 1 的查询结果**:当前数据库中这些字段的值
|
||||
2. **步骤 2 的查询结果**:sys_user 表的结构
|
||||
3. **UPDATE SQL 执行结果**:是否有错误,更新了多少条记录
|
||||
4. **步骤 4 的验证结果**:更新后的字段值
|
||||
5. **浏览器控制台错误**:是否有 JavaScript 错误
|
||||
6. **Network 响应数据**:后端返回的完整数据
|
||||
215
手术申请医生科室字段保存问题解决方案.md
Normal file
215
手术申请医生科室字段保存问题解决方案.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# 手术申请医生科室字段保存问题解决方案
|
||||
|
||||
## 问题确认
|
||||
数据库中只保存了 ID 字段(`apply_doctor_id`、`apply_dept_id`),但没有保存名称字段(`apply_doctor_name`、`apply_dept_name`)。
|
||||
|
||||
## 根本原因
|
||||
**数据库表中缺少 `apply_doctor_name` 和 `apply_dept_name` 这两个字段!**
|
||||
|
||||
虽然 MyBatis 映射文件和实体类都配置了这些字段,但如果数据库表中不存在这些列,MyBatis 在插入时会静默忽略这些字段(不会报错),导致只有 ID 被保存。
|
||||
|
||||
## 解决步骤
|
||||
|
||||
### 步骤 1:执行数据库迁移脚本(必须!)
|
||||
|
||||
使用 Navicat Premium 17 执行以下 SQL:
|
||||
|
||||
```sql
|
||||
-- 方法1:使用 IF NOT EXISTS 语法(推荐)
|
||||
ALTER TABLE public.cli_surgery ADD COLUMN IF NOT EXISTS apply_doctor_name VARCHAR(100);
|
||||
COMMENT ON COLUMN public.cli_surgery.apply_doctor_name IS '申请医生姓名';
|
||||
|
||||
ALTER TABLE public.cli_surgery ADD COLUMN IF NOT EXISTS apply_dept_name VARCHAR(100);
|
||||
COMMENT ON COLUMN public.cli_surgery.apply_dept_name IS '申请科室名称';
|
||||
|
||||
-- 验证字段是否添加成功
|
||||
SELECT column_name, data_type, character_maximum_length
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name IN ('apply_doctor_name', 'apply_dept_name');
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
```
|
||||
apply_doctor_name | character varying | 100
|
||||
apply_dept_name | character varying | 100
|
||||
```
|
||||
|
||||
### 步骤 2:重启后端服务
|
||||
|
||||
执行数据库迁移后,必须重启后端服务以重新加载表结构。
|
||||
|
||||
### 步骤 3:新增手术并查看日志
|
||||
|
||||
1. 打开后端控制台或日志文件
|
||||
2. 在前端新增一条手术记录
|
||||
3. 查看后端日志,应该能看到:
|
||||
|
||||
```
|
||||
设置申请医生信息 - doctorId: 123, doctorName: 张医生, deptId: 456, deptName: 普外科
|
||||
前端提交的数据 - applyDoctorId: 123, applyDoctorName: 张医生, applyDeptId: 456, applyDeptName: 普外科
|
||||
准备插入手术记录 - applyDoctorId: 123, applyDoctorName: 张医生, applyDeptId: 456, deptName: 普外科
|
||||
准备插入手术记录 - applyDoctorId: 123, applyDoctorName: 张医生, applyDeptId: 456, deptName: 普外科
|
||||
插入后查询结果 - applyDoctorId: 123, applyDoctorName: 张医生, applyDeptId: 456, deptName: 普外科
|
||||
手术记录插入成功 - surgeryId: 1234567890123456789, surgeryNo: OP202501051234
|
||||
```
|
||||
|
||||
**关键检查点**:
|
||||
- `准备插入手术记录` 这行日志中,`applyDoctorName` 和 `applyDeptName` 必须有值(不能为 null)
|
||||
- `插入后查询结果` 这行日志中,这两个字段也必须有值
|
||||
|
||||
### 步骤 4:验证数据库
|
||||
|
||||
执行以下 SQL 查询最新插入的记录:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
apply_doctor_id,
|
||||
apply_doctor_name,
|
||||
apply_dept_id,
|
||||
apply_dept_name,
|
||||
surgery_name,
|
||||
create_time
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0'
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
- `apply_doctor_id`:有值(例如:123)
|
||||
- `apply_doctor_name`:有值(例如:张医生)
|
||||
- `apply_dept_id`:有值(例如:456)
|
||||
- `apply_dept_name`:有值(例如:普外科)
|
||||
|
||||
### 步骤 5:测试前端显示
|
||||
|
||||
1. 刷新手术管理页面
|
||||
2. 查看列表中是否显示申请医生和申请科室列
|
||||
3. 点击"查看"或"编辑"按钮,检查详情对话框是否显示这些信息
|
||||
|
||||
## 常见问题和解决
|
||||
|
||||
### 问题 1:执行 SQL 后报错 "column does not exist"
|
||||
|
||||
**原因**:数据库表结构可能不同,或者表名不是 `cli_surgery`
|
||||
|
||||
**解决**:先执行以下 SQL 检查表名:
|
||||
|
||||
```sql
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_name LIKE '%surgery%'
|
||||
AND table_schema = 'public';
|
||||
```
|
||||
|
||||
### 问题 2:执行 SQL 后字段仍然不存在
|
||||
|
||||
**原因**:可能是权限问题或 SQL 语法问题
|
||||
|
||||
**解决**:尝试使用更简单的方式:
|
||||
|
||||
```sql
|
||||
-- 先检查表结构
|
||||
\d public.cli_surgery
|
||||
|
||||
-- 手动添加字段(如果不存在)
|
||||
-- 注意:如果字段已存在,这个语句会报错,这是正常的
|
||||
ALTER TABLE public.cli_surgery ADD COLUMN apply_doctor_name VARCHAR(100);
|
||||
ALTER TABLE public.cli_surgery ADD COLUMN apply_dept_name VARCHAR(100);
|
||||
```
|
||||
|
||||
### 问题 3:字段添加成功,但插入时仍然为空
|
||||
|
||||
**原因**:MyBatis 或 MyBatis-Plus 配置问题
|
||||
|
||||
**解决**:
|
||||
1. 检查实体类字段是否有 `@TableField` 注解
|
||||
2. 检查字段名是否与数据库列名一致
|
||||
3. 查看后端日志中的 `插入后查询结果`
|
||||
|
||||
### 问题 4:后端日志显示字段为 null
|
||||
|
||||
**原因**:后端代码中 `applyDoctorName` 或 `applyDeptName` 被设置为 null
|
||||
|
||||
**解决**:
|
||||
1. 检查 `SecurityUtils.getLoginUser().getUser().getNickName()` 是否返回 null
|
||||
2. 检查 `SecurityUtils.getLoginUser().getOrgId()` 是否返回 null
|
||||
3. 检查 `organizationService.getById(orgId)` 是否返回 null
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] 数据库迁移脚本已执行
|
||||
- [ ] 数据库字段已添加(步骤 1 验证 SQL 有结果)
|
||||
- [ ] 后端服务已重启
|
||||
- [ ] 后端日志显示 `准备插入手术记录` 且字段有值
|
||||
- [ ] 后端日志显示 `插入后查询结果` 且字段有值
|
||||
- [ ] 数据库查询显示字段有值(步骤 4)
|
||||
- [ ] 前端列表正确显示
|
||||
- [ ] 前端详情正确显示
|
||||
|
||||
## 调试 SQL 脚本
|
||||
|
||||
如果需要手动测试插入功能,可以执行:
|
||||
|
||||
```sql
|
||||
-- 测试插入(确保字段存在)
|
||||
INSERT INTO public.cli_surgery (
|
||||
surgery_no,
|
||||
patient_id,
|
||||
encounter_id,
|
||||
apply_doctor_id,
|
||||
apply_doctor_name,
|
||||
apply_dept_id,
|
||||
apply_dept_name,
|
||||
surgery_name,
|
||||
status_enum,
|
||||
delete_flag,
|
||||
create_time,
|
||||
update_time
|
||||
) VALUES (
|
||||
'TEST202501050002',
|
||||
(SELECT id FROM public.adm_patient WHERE delete_flag = '0' LIMIT 1),
|
||||
(SELECT id FROM public.adm_encounter WHERE delete_flag = '0' LIMIT 1),
|
||||
999,
|
||||
'手动测试医生',
|
||||
999,
|
||||
'手动测试科室',
|
||||
'手动测试手术',
|
||||
0,
|
||||
'0',
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
|
||||
-- 查询刚才插入的数据
|
||||
SELECT
|
||||
surgery_no,
|
||||
apply_doctor_id,
|
||||
apply_doctor_name,
|
||||
apply_dept_id,
|
||||
apply_dept_name,
|
||||
surgery_name
|
||||
FROM public.cli_surgery
|
||||
WHERE surgery_no = 'TEST202501050002';
|
||||
|
||||
-- 清理测试数据
|
||||
-- DELETE FROM public.cli_surgery WHERE surgery_no = 'TEST202501050002';
|
||||
```
|
||||
|
||||
## 联系支持
|
||||
|
||||
如果以上步骤都无法解决问题,请提供:
|
||||
|
||||
1. **数据库表结构查询结果**:
|
||||
```sql
|
||||
\d public.cli_surgery
|
||||
```
|
||||
|
||||
2. **后端日志**:特别是 `准备插入手术记录` 和 `插入后查询结果` 这两行
|
||||
|
||||
3. **数据库查询结果**:执行步骤 4 中的 SQL,告诉我结果
|
||||
|
||||
4. **错误信息**:如果有任何错误提示
|
||||
194
手术申请医生科室数据保存问题排查指南.md
Normal file
194
手术申请医生科室数据保存问题排查指南.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# 手术申请医生科室数据保存问题排查指南
|
||||
|
||||
## 问题现象
|
||||
新增手术后,列表页面和编辑查看页面没有显示申请医生名称和科室名称。
|
||||
|
||||
## 排查步骤
|
||||
|
||||
### 步骤1:检查数据库字段是否存在
|
||||
执行以下 SQL 检查字段是否已添加:
|
||||
|
||||
```sql
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name IN ('apply_doctor_name', 'apply_dept_name');
|
||||
```
|
||||
|
||||
**预期结果**:应该返回两条记录
|
||||
```
|
||||
apply_doctor_name | character varying
|
||||
apply_dept_name | character varying
|
||||
```
|
||||
|
||||
**如果结果为空**:说明字段未添加,需要执行迁移脚本。
|
||||
|
||||
### 步骤2:检查数据库迁移脚本是否执行
|
||||
执行迁移脚本(如果未执行):
|
||||
|
||||
```sql
|
||||
-- 检查并添加申请医生姓名字段
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name = 'apply_doctor_name'
|
||||
) THEN
|
||||
ALTER TABLE public.cli_surgery ADD COLUMN apply_doctor_name VARCHAR(100);
|
||||
COMMENT ON COLUMN public.cli_surgery.apply_doctor_name IS '申请医生姓名';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 检查并添加申请科室名称字段
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name = 'apply_dept_name'
|
||||
) THEN
|
||||
ALTER TABLE public.cli_surgery ADD COLUMN apply_dept_name VARCHAR(100);
|
||||
COMMENT ON COLUMN public.cli_surgery.apply_dept_name IS '申请科室名称';
|
||||
END IF;
|
||||
END $$;
|
||||
```
|
||||
|
||||
### 步骤3:重启后端服务
|
||||
执行数据库迁移后,必须重启后端服务。
|
||||
|
||||
### 步骤4:新增手术并查看后端日志
|
||||
1. 打开后端控制台或日志文件
|
||||
2. 在前端新增一条手术记录
|
||||
3. 查看后端日志,应该能看到以下信息:
|
||||
|
||||
```
|
||||
设置申请医生信息 - doctorId: 123, doctorName: 张医生, deptId: 456, deptName: 普外科
|
||||
前端提交的数据 - applyDoctorId: 123, applyDoctorName: 张医生, applyDeptId: 456, applyDeptName: 普外科
|
||||
准备插入手术记录 - applyDoctorId: 123, applyDoctorName: 张医生, applyDeptId: 456, deptName: 普外科
|
||||
手术记录插入成功 - surgeryId: 1234567890123456789, surgeryNo: OP202501051234
|
||||
```
|
||||
|
||||
**如果看不到这些日志**:说明代码没有执行到这里,检查是否有异常抛出。
|
||||
|
||||
**如果看到 "前端提交的数据 - applyDoctorName: null"**:说明前端提交的数据为空,需要检查前端代码。
|
||||
|
||||
### 步骤5:检查数据库中是否保存成功
|
||||
执行以下 SQL 查询最新插入的记录:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
patient_id,
|
||||
apply_doctor_id,
|
||||
apply_doctor_name,
|
||||
apply_dept_id,
|
||||
apply_dept_name,
|
||||
surgery_name,
|
||||
status_enum,
|
||||
create_time
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0'
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 5;
|
||||
```
|
||||
|
||||
**如果 apply_doctor_name 和 apply_dept_name 字段为空**:说明数据没有保存成功。
|
||||
|
||||
**如果字段有值**:说明保存成功,问题出在前端显示。
|
||||
|
||||
### 步骤6:检查前端 API 响应
|
||||
1. 打开浏览器开发者工具(F12)
|
||||
2. 切换到 Network 标签
|
||||
3. 新增手术
|
||||
4. 找到 `/clinical-manage/surgery/surgery-page` 请求
|
||||
5. 点击查看响应内容
|
||||
|
||||
检查响应数据中是否包含 `applyDoctorName` 和 `applyDeptName` 字段。
|
||||
|
||||
**如果响应中没有这些字段**:说明 MyBatis 映射有问题,检查 XML 配置。
|
||||
|
||||
**如果响应中有这些字段但值为 null**:说明数据库中为空,回到步骤5。
|
||||
|
||||
### 步骤7:检查前端表格显示
|
||||
查看前端代码中的表格列配置:
|
||||
|
||||
```vue
|
||||
<el-table-column label="申请医生" align="center" prop="applyDoctorName" width="100" />
|
||||
<el-table-column label="申请科室" align="center" prop="applyDeptName" width="120" show-overflow-tooltip />
|
||||
```
|
||||
|
||||
确保 `prop` 属性与后端返回的字段名一致(注意大小写)。
|
||||
|
||||
## 常见问题和解决方案
|
||||
|
||||
### 问题1:数据库字段未添加
|
||||
**症状**:后端报错 "column apply_doctor_name does not exist"
|
||||
**解决**:执行数据库迁移脚本
|
||||
|
||||
### 问题2:后端日志显示 applyDoctorName 为 null
|
||||
**症状**:日志中 "前端提交的数据 - applyDoctorName: null"
|
||||
**原因**:前端提交数据时,disabled 字段没有被包含
|
||||
**解决**:检查前端 submitForm 函数,确保手动设置了这些字段
|
||||
|
||||
### 问题3:数据库中有值,但前端不显示
|
||||
**症状**:数据库查询有值,前端响应也有值,但表格不显示
|
||||
**原因**:
|
||||
1. 前端 prop 属性名与后端字段名不一致(大小写问题)
|
||||
2. 前端数据未正确绑定
|
||||
**解决**:
|
||||
1. 检查 prop 属性名,确保与后端返回的 JSON 字段名一致
|
||||
2. 检查浏览器控制台是否有 JavaScript 错误
|
||||
|
||||
### 问题4:MyBatis 映射未生效
|
||||
**症状**:后端保存成功,但查询时字段为 null
|
||||
**原因**:XML 映射文件未正确配置
|
||||
**解决**:
|
||||
1. 检查 SurgeryMapper.xml 中的 resultMap 配置
|
||||
2. 检查 SQL 查询中是否包含这些字段
|
||||
3. 重启后端服务
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] 数据库迁移脚本已执行
|
||||
- [ ] 数据库字段已添加(步骤1)
|
||||
- [ ] 后端服务已重启
|
||||
- [ ] 后端日志显示申请医生信息(步骤4)
|
||||
- [ ] 数据库中已保存数据(步骤5)
|
||||
- [ ] 前端 API 响应包含这些字段(步骤6)
|
||||
- [ ] 前端表格正确显示(步骤7)
|
||||
|
||||
## 附加 SQL 脚本
|
||||
|
||||
### 查看统计信息
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
COUNT(apply_doctor_name) as has_doctor_name_count,
|
||||
COUNT(apply_dept_name) as has_dept_name_count
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0';
|
||||
```
|
||||
|
||||
### 手动更新测试数据
|
||||
如果需要手动更新已有的测试数据:
|
||||
|
||||
```sql
|
||||
UPDATE public.cli_surgery
|
||||
SET apply_doctor_name = '测试医生',
|
||||
apply_dept_name = '测试科室'
|
||||
WHERE apply_doctor_name IS NULL
|
||||
AND delete_flag = '0';
|
||||
```
|
||||
|
||||
## 联系支持
|
||||
|
||||
如果以上步骤都无法解决问题,请提供以下信息:
|
||||
1. 数据库字段查询结果(步骤1)
|
||||
2. 后端日志截图(步骤4)
|
||||
3. 数据库查询结果(步骤5)
|
||||
4. 浏览器 Network 响应截图(步骤6)
|
||||
5. 浏览器 Console 错误信息
|
||||
113
检查和填充手术人员字段.sql
Normal file
113
检查和填充手术人员字段.sql
Normal file
@@ -0,0 +1,113 @@
|
||||
-- 检查和填充手术表中的人员字段
|
||||
-- 执行时间:2025-01-05
|
||||
|
||||
-- 1. 查询最近10条手术记录,检查人员字段的填写情况
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
main_surgeon_id,
|
||||
main_surgeon_name,
|
||||
anesthetist_id,
|
||||
anesthetist_name,
|
||||
assistant_1_id,
|
||||
assistant_1_name,
|
||||
assistant_2_id,
|
||||
assistant_2_name,
|
||||
operating_room_id,
|
||||
operating_room_name,
|
||||
org_id,
|
||||
org_name,
|
||||
create_time
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0'
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 10;
|
||||
|
||||
-- 2. 统计人员字段的填写情况
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
COUNT(main_surgeon_name) as has_main_surgeon_name_count,
|
||||
COUNT(anesthetist_name) as has_anesthetist_name_count,
|
||||
COUNT(assistant_1_name) as has_assistant_1_name_count,
|
||||
COUNT(assistant_2_name) as has_assistant_2_name_count,
|
||||
COUNT(operating_room_name) as has_operating_room_name_count,
|
||||
COUNT(org_name) as has_org_name_count,
|
||||
-- 计算填写率
|
||||
ROUND(COUNT(main_surgeon_name) * 100.0 / COUNT(*), 2) as main_surgeon_name_fill_rate,
|
||||
ROUND(COUNT(anesthetist_name) * 100.0 / COUNT(*), 2) as anesthetist_name_fill_rate,
|
||||
ROUND(COUNT(assistant_1_name) * 100.0 / COUNT(*), 2) as assistant_1_name_fill_rate,
|
||||
ROUND(COUNT(assistant_2_name) * 100.0 / COUNT(*), 2) as assistant_2_name_fill_rate,
|
||||
ROUND(COUNT(operating_room_name) * 100.0 / COUNT(*), 2) as operating_room_name_fill_rate,
|
||||
ROUND(COUNT(org_name) * 100.0 / COUNT(*), 2) as org_name_fill_rate
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0';
|
||||
|
||||
-- 3. 根据 ID 查询医生表,填充主刀医生姓名
|
||||
-- 注意:这只是一个示例,实际需要根据您的医生表结构调整
|
||||
UPDATE public.cli_surgery s
|
||||
SET main_surgeon_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.main_surgeon_id = u.user_id
|
||||
AND s.main_surgeon_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 4. 根据 ID 查询医生表,填充麻醉医生姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET anesthetist_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.anesthetist_id = u.user_id
|
||||
AND s.anesthetist_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 5. 根据 ID 查询医生表,填充助手1姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET assistant_1_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.assistant_1_id = u.user_id
|
||||
AND s.assistant_1_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 6. 根据 ID 查询医生表,填充助手2姓名
|
||||
UPDATE public.cli_surgery s
|
||||
SET assistant_2_name = u.nick_name
|
||||
FROM public.sys_user u
|
||||
WHERE s.assistant_2_id = u.user_id
|
||||
AND s.assistant_2_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 7. 根据 ID 查询手术室表,填充手术室名称
|
||||
UPDATE public.cli_surgery s
|
||||
SET operating_room_name = r.name
|
||||
FROM public.cli_operating_room r
|
||||
WHERE s.operating_room_id = r.id
|
||||
AND s.operating_room_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 8. 根据 ID 查询机构表,填充执行科室名称
|
||||
UPDATE public.cli_surgery s
|
||||
SET org_name = o.name
|
||||
FROM public.adm_organization o
|
||||
WHERE s.org_id = o.id
|
||||
AND s.org_name IS NULL
|
||||
AND s.delete_flag = '0';
|
||||
|
||||
-- 9. 再次查询,验证更新结果
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
main_surgeon_id,
|
||||
main_surgeon_name,
|
||||
anesthetist_id,
|
||||
anesthetist_name,
|
||||
assistant_1_id,
|
||||
assistant_1_name,
|
||||
assistant_2_id,
|
||||
assistant_2_name,
|
||||
operating_room_id,
|
||||
operating_room_name,
|
||||
org_id,
|
||||
org_name
|
||||
FROM public.cli_surgery
|
||||
WHERE delete_flag = '0'
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 10;
|
||||
58
测试手术表插入.sql
Normal file
58
测试手术表插入.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- 测试手术表的插入,验证 apply_doctor_name 和 apply_dept_name 字段
|
||||
-- 执行时间:2025-01-05
|
||||
|
||||
-- 1. 先检查字段是否存在
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
character_maximum_length,
|
||||
is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name IN ('apply_doctor_id', 'apply_doctor_name', 'apply_dept_id', 'apply_dept_name')
|
||||
ORDER BY column_name;
|
||||
|
||||
-- 2. 插入测试数据(如果字段存在)
|
||||
INSERT INTO cli_surgery (
|
||||
surgery_no,
|
||||
patient_id,
|
||||
encounter_id,
|
||||
apply_doctor_id,
|
||||
apply_doctor_name,
|
||||
apply_dept_id,
|
||||
apply_dept_name,
|
||||
surgery_name,
|
||||
status_enum,
|
||||
delete_flag,
|
||||
create_time,
|
||||
update_time
|
||||
) VALUES (
|
||||
'TEST202501050001',
|
||||
(SELECT id FROM public.adm_patient WHERE delete_flag = '0' LIMIT 1),
|
||||
(SELECT id FROM public.adm_encounter WHERE delete_flag = '0' LIMIT 1),
|
||||
1,
|
||||
'测试医生',
|
||||
1,
|
||||
'测试科室',
|
||||
'测试手术',
|
||||
0,
|
||||
'0',
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 3. 查询刚才插入的测试数据
|
||||
SELECT
|
||||
id,
|
||||
surgery_no,
|
||||
apply_doctor_id,
|
||||
apply_doctor_name,
|
||||
apply_dept_id,
|
||||
apply_dept_name,
|
||||
surgery_name
|
||||
FROM public.cli_surgery
|
||||
WHERE surgery_no = 'TEST202501050001';
|
||||
|
||||
-- 4. 清理测试数据(如果测试成功)
|
||||
-- DELETE FROM public.cli_surgery WHERE surgery_no = 'TEST202501050001';
|
||||
12
迁移记录-DB变更记录/20250104_update_cli_surgery_tenant_id.sql
Normal file
12
迁移记录-DB变更记录/20250104_update_cli_surgery_tenant_id.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- 更新 cli_surgery 表的 tenant_id 字段
|
||||
-- 创建时间: 2025-01-04
|
||||
-- 说明: 为已存在的手术记录设置默认租户ID
|
||||
|
||||
-- 将所有 tenant_id 为 NULL 的记录设置为默认租户ID(1)
|
||||
UPDATE cli_surgery
|
||||
SET tenant_id = 1
|
||||
WHERE tenant_id IS NULL;
|
||||
|
||||
-- 添加非空约束(可选,根据业务需求决定是否执行)
|
||||
-- ALTER TABLE cli_surgery ALTER COLUMN tenant_id SET NOT NULL;
|
||||
-- ALTER TABLE cli_surgery ALTER COLUMN tenant_id SET DEFAULT 1;
|
||||
@@ -0,0 +1 @@
|
||||
-- 检查手术表中所有字段是否存在
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 为手术表添加申请医生名称和申请科室名称字段
|
||||
-- 执行时间:2025-01-05
|
||||
-- 说明:用于存储申请医生的姓名和科室名称,避免每次都需要关联查询
|
||||
|
||||
-- 检查字段是否存在,如果不存在则添加
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name = 'apply_doctor_name'
|
||||
) THEN
|
||||
ALTER TABLE public.cli_surgery ADD COLUMN apply_doctor_name VARCHAR(100);
|
||||
COMMENT ON COLUMN public.cli_surgery.apply_doctor_name IS '申请医生姓名';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name = 'apply_dept_name'
|
||||
) THEN
|
||||
ALTER TABLE public.cli_surgery ADD COLUMN apply_dept_name VARCHAR(100);
|
||||
COMMENT ON COLUMN public.cli_surgery.apply_dept_name IS '申请科室名称';
|
||||
END IF;
|
||||
END $$;
|
||||
12
迁移记录-DB变更记录/fix_cli_surgery_missing_columns.sql
Normal file
12
迁移记录-DB变更记录/fix_cli_surgery_missing_columns.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- 方法1:使用 IF NOT EXISTS 语法(推荐)
|
||||
ALTER TABLE cli_surgery ADD COLUMN IF NOT EXISTS apply_doctor_name VARCHAR(100);
|
||||
COMMENT ON COLUMN cli_surgery.apply_doctor_name IS '申请医生姓名';
|
||||
|
||||
ALTER TABLE cli_surgery ADD COLUMN IF NOT EXISTS apply_dept_name VARCHAR(100);
|
||||
COMMENT ON COLUMN cli_surgery.apply_dept_name IS '申请科室名称';
|
||||
|
||||
-- 验证字段是否添加成功
|
||||
SELECT column_name, data_type, character_maximum_length
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name IN ('apply_doctor_name', 'apply_dept_name');
|
||||
4
验证手术申请医生科室数据.sql
Normal file
4
验证手术申请医生科室数据.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cli_surgery'
|
||||
AND column_name IN ('apply_doctor_name', 'apply_dept_name');
|
||||
Reference in New Issue
Block a user