feat(menu): 优化菜单路径唯一性校验并更新前端界面

- 在SysLoginController中添加optionMap数据返回
- 添加JSQLParser依赖支持MyBatis Plus功能
- 实现selectMenuByPathExcludeId方法用于排除当前菜单的路径唯一性校验
- 在SysMenuServiceImpl中添加日志记录并优化路径唯一性判断逻辑
- 在SysMenuMapper.xml中添加LIMIT 1限制并实现排除ID查询
- 在前端路由中注释患者管理相关路由配置
- 在用户store中添加optionMap配置项并优先从optionMap获取医院名称
- 重构检查项目设置页面的操作按钮样式为统一的圆形按钮设计
- 更新检查项目设置页面的导航栏样式和交互体验
- 优化门诊记录页面的搜索条件和表格展示功能
- 添加性别和状态筛选条件并改进数据加载逻辑
This commit is contained in:
2026-01-03 23:47:09 +08:00
parent 61f4020487
commit 0c35044231
54 changed files with 5871 additions and 510 deletions

View File

@@ -86,6 +86,7 @@ public class SysLoginController {
}
AjaxResult ajax = AjaxResult.success();
ajax.put("optionJson", loginUser.getOptionJson());
ajax.put("optionMap", loginUser.getOptionMap());
ajax.put("practitionerId", String.valueOf(loginUser.getPractitionerId()));
ajax.put("user", user);
ajax.put("roles", roles);

View File

@@ -60,6 +60,12 @@
<artifactId>core-system</artifactId>
</dependency>
<!-- JSQLParser - 用于MyBatis Plus -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -91,6 +91,15 @@ public interface SysMenuMapper {
*/
public SysMenu selectMenuByPath(String path);
/**
* 根据路径Path查询信息排除指定菜单ID
*
* @param path 路径
* @param menuId 菜单ID
* @return 菜单信息
*/
public SysMenu selectMenuByPathExcludeId(@Param("path") String path, @Param("menuId") Long menuId);
/**
* 是否存在菜单子节点
*

View File

@@ -14,6 +14,8 @@ import com.core.system.mapper.SysMenuMapper;
import com.core.system.mapper.SysRoleMapper;
import com.core.system.mapper.SysRoleMenuMapper;
import com.core.system.service.ISysMenuService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -27,6 +29,7 @@ import java.util.stream.Collectors;
*/
@Service
public class SysMenuServiceImpl implements ISysMenuService {
private static final Logger log = LoggerFactory.getLogger(SysMenuServiceImpl.class);
public static final String PREMISSION_STRING = "perms[\"{0}\"]";
@Autowired
@@ -281,12 +284,13 @@ public class SysMenuServiceImpl implements ISysMenuService {
*/
@Override
public int updateMenu(SysMenu menu) {
//路径Path唯一性判断
//路径Path唯一性判断(排除当前菜单本身)
String path = menu.getPath();
if (StringUtils.isNotBlank(path)) {
SysMenu sysMenu = menuMapper.selectMenuByPath(menu.getPath());
// 先判断sysMenu是否不为null再比较menuId
if (sysMenu != null && !menu.getMenuId().equals(sysMenu.getMenuId())) {
SysMenu sysMenu = menuMapper.selectMenuByPathExcludeId(menu.getPath(), menu.getMenuId());
if (sysMenu != null) {
log.warn("路由地址已存在 - menuId: {}, path: {}, 存在的menuId: {}",
menu.getMenuId(), menu.getPath(), sysMenu.getMenuId());
return -1; // 路由地址已存在
}
}

View File

@@ -177,6 +177,12 @@
<select id="selectMenuByPath" parameterType="String" resultMap="SysMenuResult">
<include refid="selectMenuVo"/>
where path = #{path}
LIMIT 1
</select>
<select id="selectMenuByPathExcludeId" resultMap="SysMenuResult">
<include refid="selectMenuVo"/>
where path = #{path} and menu_id != #{menuId}
</select>
<select id="selectMenuById" parameterType="Long" resultMap="SysMenuResult">

View File

@@ -0,0 +1,151 @@
package com.openhis.web.administration.controller;
import com.core.common.annotation.Log;
import com.core.common.core.controller.BaseController;
import com.core.common.core.domain.AjaxResult;
import com.core.common.core.page.TableDataInfo;
import com.core.common.enums.BusinessType;
import com.core.common.utils.poi.ExcelUtil;
import com.openhis.administration.domain.PractitionerPatient;
import com.openhis.administration.service.IPractitionerPatientService;
import com.openhis.administration.dto.PractitionerPatientDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
/**
* 医生患者关系管理Controller
*
* @author system
* @date 2026-01-02
*/
@RestController
@RequestMapping("/administration/practitioner-patient")
public class PractitionerPatientController extends BaseController {
@Autowired
private IPractitionerPatientService practitionerPatientService;
/**
* 查询医生患者关系列表
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:list')")
@GetMapping("/list")
public TableDataInfo list(PractitionerPatient practitionerPatient) {
startPage();
List<PractitionerPatient> list = practitionerPatientService.list();
return getDataTable(list);
}
/**
* 导出医生患者关系列表
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:export')")
@Log(title = "医生患者关系", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, PractitionerPatient practitionerPatient) {
List<PractitionerPatient> list = practitionerPatientService.list();
ExcelUtil<PractitionerPatient> util = new ExcelUtil<>(PractitionerPatient.class);
util.exportExcel(response, list, "医生患者关系数据");
}
/**
* 获取医生患者关系详细信息
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return AjaxResult.success(practitionerPatientService.getById(id));
}
/**
* 获取医生的所有有效患者
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:query')")
@GetMapping("/practitioner/{practitionerId}/patients")
public AjaxResult getPatientsByPractitioner(@PathVariable Long practitionerId) {
return AjaxResult.success(practitionerPatientService.getValidPatientsByPractitioner(practitionerId));
}
/**
* 获取患者的所有有效医生
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:query')")
@GetMapping("/patient/{patientId}/practitioners")
public AjaxResult getPractitionersByPatient(@PathVariable Long patientId) {
return AjaxResult.success(practitionerPatientService.getValidPractitionersByPatient(patientId));
}
/**
* 新增医生患者关系
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:add')")
@Log(title = "医生患者关系", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody PractitionerPatientDto dto) {
PractitionerPatient relationship = new PractitionerPatient();
relationship.setPractitionerId(dto.getPractitionerId());
relationship.setPatientId(dto.getPatientId());
relationship.setRelationshipType(dto.getRelationshipType());
relationship.setOrganizationId(dto.getOrganizationId());
relationship.setStartDate(dto.getStartDate());
relationship.setRemark(dto.getRemark());
return toAjax(practitionerPatientService.createRelationship(relationship));
}
/**
* 修改医生患者关系
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:edit')")
@Log(title = "医生患者关系", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody PractitionerPatient practitionerPatient) {
return toAjax(practitionerPatientService.updateById(practitionerPatient));
}
/**
* 终止医生患者关系
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:remove')")
@Log(title = "医生患者关系", businessType = BusinessType.DELETE)
@PostMapping("/terminate/{id}")
public AjaxResult terminate(@PathVariable Long id) {
return toAjax(practitionerPatientService.terminateRelationship(id));
}
/**
* 删除医生患者关系
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:remove')")
@Log(title = "医生患者关系", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(practitionerPatientService.removeByIds(List.of(ids)));
}
/**
* 批量创建医生患者关系
*/
@PreAuthorize("@ss.hasPermi('administration:practitionerPatient:add')")
@Log(title = "批量创建医生患者关系", businessType = BusinessType.INSERT)
@PostMapping("/batch")
public AjaxResult batchAdd(@RequestBody List<PractitionerPatientDto> dtos) {
List<PractitionerPatient> relationships = dtos.stream().map(dto -> {
PractitionerPatient relationship = new PractitionerPatient();
relationship.setPractitionerId(dto.getPractitionerId());
relationship.setPatientId(dto.getPatientId());
relationship.setRelationshipType(dto.getRelationshipType());
relationship.setOrganizationId(dto.getOrganizationId());
relationship.setStartDate(dto.getStartDate());
relationship.setRemark(dto.getRemark());
return relationship;
}).toList();
return toAjax(practitionerPatientService.batchCreateRelationships(relationships));
}
}

View File

@@ -407,8 +407,12 @@ public class PractitionerAppServiceImpl implements IPractitionerAppService {
// iBizUserService.remove(new LambdaQueryWrapper<BizUser>().eq(BizUser::getUserId, userId));
practitionerAppAppMapper.delUser(userId);
practitionerAppAppMapper.delUserRole(userId);
Practitioner one =
iPractitionerService.getOne(new LambdaQueryWrapper<Practitioner>().eq(Practitioner::getUserId, userId));
// 使用list()避免TooManyResultsException异常然后取第一个记录
List<Practitioner> practitionerList = iPractitionerService.list(new LambdaQueryWrapper<Practitioner>().eq(Practitioner::getUserId, userId));
Practitioner one = practitionerList != null && !practitionerList.isEmpty() ? practitionerList.get(0) : null;
if (one == null) {
return R.fail(null, "未找到对应的医生信息");
}
Long practitionerId = one.getId();// 参与者id
iPractitionerService.removeById(practitionerId);
iPractitionerRoleService

View File

@@ -0,0 +1,37 @@
package com.openhis.web.controller;
import com.core.common.core.controller.BaseController;
import com.core.common.core.domain.AjaxResult;
import com.openhis.administration.domain.Encounter;
import com.openhis.web.dto.HomeStatisticsDto;
import com.openhis.web.service.IHomeStatisticsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 首页统计Controller
*
* @author system
* @date 2025-12-31
*/
@RestController
@RequestMapping("/home")
public class HomeStatisticsController extends BaseController {
@Autowired
private IHomeStatisticsService homeStatisticsService;
/**
* 获取首页统计数据
*
* @return 首页统计数据
*/
@GetMapping("/statistics")
public AjaxResult getHomeStatistics() {
HomeStatisticsDto statistics = homeStatisticsService.getHomeStatistics();
return AjaxResult.success(statistics);
}
}

View File

@@ -0,0 +1,62 @@
package com.openhis.web.dto;
import lombok.Data;
/**
* 首页统计数据DTO
*
* @author system
* @date 2025-12-31
*/
@Data
public class HomeStatisticsDto {
/**
* 在院患者数量
*/
private Integer totalPatients;
/**
* 昨日在院患者数量
*/
private Integer yesterdayPatients;
/**
* 相对前日变化百分比
*/
private Double patientTrend;
/**
* 今日收入
*/
private String todayRevenue;
/**
* 昨日收入
*/
private String yesterdayRevenue;
/**
* 相对前日变化百分比
*/
private Double revenueTrend;
/**
* 今日预约数量
*/
private Integer todayAppointments;
/**
* 昨日预约数量
*/
private Integer yesterdayAppointments;
/**
* 相对前日变化百分比
*/
private Double appointmentTrend;
/**
* 待审核数量
*/
private Integer pendingApprovals;
}

View File

@@ -28,6 +28,7 @@ import java.util.HashSet;
* @date 2025/3/15
*/
@Service
@Slf4j
public class OutpatientRecordServiceImpl implements IOutpatientRecordService {
@Resource
@@ -37,24 +38,78 @@ public class OutpatientRecordServiceImpl implements IOutpatientRecordService {
* 分页查询门诊记录
*
* @param outpatientRecordSearchParam 门诊录查询参数
* @param searchKey 搜索关键词(支持身份证号/病人ID/门诊号/姓名)
* @param pageNo 页码默认为1
* @param pageSize 每页大小默认为10
* @return 分页查询
* @param request HTTP请求
* @return 分页查询结果
*/
@Override
public IPage<OutpatientRecordDto> getPatient(OutpatientRecordSearchParam outpatientRecordSearchParam,
String searchKey, Integer pageNo, Integer pageSize, HttpServletRequest request) {
log.info("进入门诊记录查询服务searchKey: {}", searchKey);
if (outpatientRecordSearchParam != null) {
log.info("查询参数searchKey={}, 性别={}, 状态={}, 电话={}, 医生={}, 开始时间={}, 结束时间={}",
searchKey,
outpatientRecordSearchParam.getGenderEnum(),
outpatientRecordSearchParam.getSubjectStatusEnum(),
outpatientRecordSearchParam.getPhone(),
outpatientRecordSearchParam.getDoctorName(),
outpatientRecordSearchParam.getStartTimeSTime(),
outpatientRecordSearchParam.getStartTimeETime());
}
// 构建查询条件
QueryWrapper<OutpatientRecordDto> queryWrapper
= HisQueryUtils.buildQueryWrapper(outpatientRecordSearchParam, searchKey,
new HashSet<>(Arrays.asList(CommonConstants.FieldName.IdCard, CommonConstants.FieldName.Name,
CommonConstants.FieldName.PatientBusNo, CommonConstants.FieldName.EncounterBusNo)),
request);
// 构建查询条件不自动添加tenant_id手动指定表别名
QueryWrapper<OutpatientRecordDto> queryWrapper = new QueryWrapper<>();
// 手动添加带表别名的tenant_id条件
queryWrapper.eq("enc.tenant_id", com.core.common.utils.SecurityUtils.getLoginUser().getTenantId());
// 处理模糊查询关键字searchKey- 用于姓名/身份证号/病人ID/门诊号的模糊搜索
if (searchKey != null && !searchKey.isEmpty()) {
queryWrapper.and(wrapper -> {
wrapper.like("pt.name", searchKey)
.or().like("pt.id_card", searchKey)
.or().like("pt.bus_no", searchKey)
.or().like("enc.bus_no", searchKey);
});
}
// 处理其他筛选条件(这些条件可以与模糊查询或精确查询组合使用)
if (outpatientRecordSearchParam != null) {
// 处理性别筛选
if (outpatientRecordSearchParam.getGenderEnum() != null) {
queryWrapper.eq("pt.gender_enum", outpatientRecordSearchParam.getGenderEnum());
}
// 处理就诊对象状态筛选
if (outpatientRecordSearchParam.getSubjectStatusEnum() != null) {
queryWrapper.eq("enc.status_enum", outpatientRecordSearchParam.getSubjectStatusEnum());
}
// 处理医生姓名查询(支持模糊查询)
if (outpatientRecordSearchParam.getDoctorName() != null && !outpatientRecordSearchParam.getDoctorName().isEmpty()) {
queryWrapper.like("prac.name", outpatientRecordSearchParam.getDoctorName());
}
// 处理电话号码查询(支持模糊查询)
if (outpatientRecordSearchParam.getPhone() != null && !outpatientRecordSearchParam.getPhone().isEmpty()) {
queryWrapper.like("pt.phone", outpatientRecordSearchParam.getPhone());
}
// 处理时间范围查询
if (outpatientRecordSearchParam.getStartTimeSTime() != null && !outpatientRecordSearchParam.getStartTimeSTime().isEmpty()
&& outpatientRecordSearchParam.getStartTimeETime() != null && !outpatientRecordSearchParam.getStartTimeETime().isEmpty()) {
queryWrapper.between("enc.create_time", outpatientRecordSearchParam.getStartTimeSTime(), outpatientRecordSearchParam.getStartTimeETime());
}
}
// 使用接诊医生ADMITTERcode="1")作为参与者类型
IPage<OutpatientRecordDto> outpatientRecordPage = patientManageMapper
.getOutpatientRecord(ParticipantType.ADMITTER.getCode(), new Page<>(pageNo, pageSize), queryWrapper);
// 处理枚举字段的显示文本
outpatientRecordPage.getRecords().forEach(e -> {
// 性别枚举类回显赋值
e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));

View File

@@ -17,6 +17,7 @@ import com.openhis.administration.domain.Patient;
import com.openhis.administration.domain.PatientIdentifier;
import com.openhis.administration.service.IPatientIdentifierService;
import com.openhis.administration.service.IPatientService;
import com.openhis.administration.service.IPractitionerService;
import com.openhis.common.constant.CommonConstants;
import com.openhis.common.constant.PromptMsgConstant;
import com.openhis.common.enums.*;
@@ -54,6 +55,9 @@ public class PatientInformationServiceImpl implements IPatientInformationService
@Autowired
private IPatientService patientService;
@Autowired
private IPractitionerService practitionerService;
@Autowired
private IPatientIdentifierService patientIdentifierService;
@@ -129,11 +133,40 @@ public class PatientInformationServiceImpl implements IPatientInformationService
@Override
public IPage<PatientBaseInfoDto> getPatientInfo(PatientBaseInfoDto patientBaseInfoDto, String searchKey,
Integer pageNo, Integer pageSize, HttpServletRequest request) {
// 构建查询条件
// 获取登录者信息
LoginUser loginUser = SecurityUtils.getLoginUser();
Long userId = loginUser.getUserId();
// 先构建基础查询条件
QueryWrapper<PatientBaseInfoDto> queryWrapper = HisQueryUtils.buildQueryWrapper(
patientBaseInfoDto, searchKey, new HashSet<>(Arrays.asList(CommonConstants.FieldName.Name,
CommonConstants.FieldName.BusNo, CommonConstants.FieldName.PyStr, CommonConstants.FieldName.WbStr)),
request);
// 查询当前用户对应的医生信息
LambdaQueryWrapper<com.openhis.administration.domain.Practitioner> practitionerQuery = new LambdaQueryWrapper<>();
practitionerQuery.eq(com.openhis.administration.domain.Practitioner::getUserId, userId);
// 使用list()避免TooManyResultsException异常然后取第一个记录
List<com.openhis.administration.domain.Practitioner> practitionerList = practitionerService.list(practitionerQuery);
com.openhis.administration.domain.Practitioner practitioner = practitionerList != null && !practitionerList.isEmpty() ? practitionerList.get(0) : null;
// 如果当前用户是医生,添加医生患者过滤条件
if (practitioner != null) {
// 查询该医生作为接诊医生ADMITTER, code="1"和挂号医生REGISTRATION_DOCTOR, code="12"的所有就诊记录的患者ID
List<Long> doctorPatientIds = patientManageMapper.getPatientIdsByPractitionerId(
practitioner.getId(),
Arrays.asList(ParticipantType.ADMITTER.getCode(), ParticipantType.REGISTRATION_DOCTOR.getCode()));
if (doctorPatientIds != null && !doctorPatientIds.isEmpty()) {
// 添加患者ID过滤条件 - 注意:这里使用列名而不是表别名
queryWrapper.in("id", doctorPatientIds);
} else {
// 如果没有相关患者,返回空结果
queryWrapper.eq("id", -1); // 设置一个不存在的ID
}
}
// 如果不是医生,查询所有患者
IPage<PatientBaseInfoDto> patientInformationPage
= patientManageMapper.getPatientPage(new Page<>(pageNo, pageSize), queryWrapper);
// 患者id集合
@@ -141,8 +174,7 @@ public class PatientInformationServiceImpl implements IPatientInformationService
= patientInformationPage.getRecords().stream().map(PatientBaseInfoDto::getId).collect(Collectors.toList());
// 患者身份信息
List<PatientIdInfoDto> patientIdInfo = patientManageMapper.getPatientIdInfo(patientIdList);
// 获取登录者信息
LoginUser loginUser = SecurityUtils.getLoginUser();
patientInformationPage.getRecords().forEach(e -> {
// 性别枚举类回显赋值
e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));

View File

@@ -0,0 +1,96 @@
package com.openhis.web.patientmanage.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.core.common.annotation.Anonymous;
import com.core.common.core.domain.R;
import com.openhis.web.patientmanage.appservice.IOutpatientRecordService;
import com.openhis.web.patientmanage.dto.OutpatientRecordDto;
import com.openhis.web.patientmanage.dto.OutpatientRecordSearchParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* 门诊记录查询控制器
*
* @author system
* @date 2025/12/31
*/
@RestController
@RequestMapping("/patient-manage/records")
@Slf4j
@RequiredArgsConstructor
@Anonymous
public class OutpatientRecordController {
private final IOutpatientRecordService outpatientRecordService;
/**
* 测试接口 - 验证Controller是否被加载
*
* @return 测试消息
*/
@GetMapping("/test")
public R<?> test() {
log.info("OutpatientRecordController.test() 被调用");
return R.ok("OutpatientRecordController 工作正常");
}
/**
* 获取门诊记录初期数据
*
* @return 初期数据
*/
@GetMapping("/init")
public R<?> getInitData() {
return outpatientRecordService.getDoctorNames();
}
/**
* 分页查询门诊记录
*
* @param outpatientRecordSearchParam 门诊记录查询参数
* @param searchKey 查询条件-模糊查询
* @param pageNo 页码默认为1
* @param pageSize 每页大小默认为10
* @param request 请求对象
* @return 分页查询结果
*/
@GetMapping("/outpatient-record-page")
public R<IPage<OutpatientRecordDto>> getOutpatientRecordPage(
OutpatientRecordSearchParam outpatientRecordSearchParam,
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest request) {
log.info("查询门诊记录pageNo: {}, pageSize: {}", pageNo, pageSize);
log.info("searchKey: {}", searchKey);
log.info("outpatientRecordSearchParam: {}", outpatientRecordSearchParam);
if (outpatientRecordSearchParam != null) {
log.info("姓名参数: {}, 身份证参数: {}, 病人ID: {}, 门诊号: {}, 性别: {}, 状态: {}, 电话: {}, 医生: {}, 开始时间: {}, 结束时间: {}",
outpatientRecordSearchParam.getName(),
outpatientRecordSearchParam.getIdCard(),
outpatientRecordSearchParam.getPatientBusNo(),
outpatientRecordSearchParam.getEncounterBusNo(),
outpatientRecordSearchParam.getGenderEnum(),
outpatientRecordSearchParam.getSubjectStatusEnum(),
outpatientRecordSearchParam.getPhone(),
outpatientRecordSearchParam.getDoctorName(),
outpatientRecordSearchParam.getStartTimeSTime(),
outpatientRecordSearchParam.getStartTimeETime());
}
return R.ok(outpatientRecordService.getPatient(outpatientRecordSearchParam, searchKey, pageNo, pageSize, request));
}
/**
* 获取医生名字列表
*
* @return 医生名字列表
*/
@GetMapping("/doctor-names")
public R<?> getDoctorNames() {
return outpatientRecordService.getDoctorNames();
}
}

View File

@@ -19,10 +19,10 @@ import java.util.Date;
public class OutpatientRecordDto {
/**
* ID
* 就诊记录ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private Long encounterId;
/**
* 患者姓名
@@ -50,17 +50,36 @@ public class OutpatientRecordDto {
private Integer genderEnum;
private String genderEnum_enumText;
/**
* 联系电话
*/
private String phone;
/**
* 就诊时间
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date encounterTime;
/**
* 就诊对象状态
*/
private Integer subjectStatusEnum;
private String subjectStatusEnum_enumText;
/**
* 医疗机构名称
*/
private String organizationName;
/**
* 接诊医生姓名
*/
private String doctorName;
/**
* 登记时间
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
// 其他可能需要的字段
}

View File

@@ -43,5 +43,30 @@ public class OutpatientRecordSearchParam {
*/
private Integer subjectStatusEnum;
/**
* 医生姓名
*/
private String doctorName;
/**
* 患者电话
*/
private String phone;
/**
* 搜索关键词(支持身份证号/病人ID/门诊号/姓名)
*/
private String searchKey;
/**
* 开始时间(起始)
*/
private String startTimeSTime;
/**
* 开始时间(结束)
*/
private String startTimeETime;
// 其他可能需要的查询参数
}

View File

@@ -58,4 +58,14 @@ public interface PatientManageMapper extends BaseMapper<Patient> {
* @return 医生名字列表
*/
List<String> getDoctorNames();
/**
* 根据医生ID和参与者类型获取相关的患者ID列表
*
* @param practitionerId 医生ID
* @param typeCodes 参与者类型代码列表
* @return 患者ID列表
*/
List<Long> getPatientIdsByPractitionerId(@Param("practitionerId") Long practitionerId,
@Param("typeCodes") List<String> typeCodes);
}

View File

@@ -0,0 +1,18 @@
package com.openhis.web.service;
import com.openhis.web.dto.HomeStatisticsDto;
/**
* 首页统计Service接口
*
* @author system
* @date 2025-12-31
*/
public interface IHomeStatisticsService {
/**
* 获取首页统计数据
*
* @return 首页统计数据
*/
HomeStatisticsDto getHomeStatistics();
}

View File

@@ -0,0 +1,122 @@
package com.openhis.web.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.utils.DateUtils;
import com.core.common.utils.SecurityUtils;
import com.openhis.administration.domain.Encounter;
import com.openhis.administration.domain.EncounterParticipant;
import com.openhis.administration.domain.Patient;
import com.openhis.administration.domain.Practitioner;
import com.openhis.administration.service.IEncounterParticipantService;
import com.openhis.administration.service.IEncounterService;
import com.openhis.administration.service.IPatientService;
import com.openhis.administration.service.IPractitionerService;
import com.openhis.common.enums.ParticipantType;
import com.openhis.web.dto.HomeStatisticsDto;
import com.openhis.web.service.IHomeStatisticsService;
import com.openhis.web.patientmanage.mapper.PatientManageMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.Arrays;
/**
* 首页统计Service业务层处理
*
* @author system
* @date 2025-12-31
*/
@Service
public class HomeStatisticsServiceImpl implements IHomeStatisticsService {
@Autowired
private IEncounterService encounterService;
@Autowired
private IEncounterParticipantService encounterParticipantService;
@Autowired
private IPractitionerService practitionerService;
@Autowired
private PatientManageMapper patientManageMapper;
@Autowired
private IPatientService patientService;
/**
* 获取首页统计数据
*
* @return 首页统计数据
*/
@Override
public HomeStatisticsDto getHomeStatistics() {
HomeStatisticsDto statistics = new HomeStatisticsDto();
// 获取当前登录用户ID
Long userId = SecurityUtils.getUserId();
// 查询当前用户对应的医生信息
LambdaQueryWrapper<Practitioner> practitionerQuery = new LambdaQueryWrapper<>();
practitionerQuery.eq(Practitioner::getUserId, userId);
// 使用list()避免TooManyResultsException异常然后取第一个记录
List<Practitioner> practitionerList = practitionerService.list(practitionerQuery);
Practitioner practitioner = practitionerList != null && !practitionerList.isEmpty() ? practitionerList.get(0) : null;
int totalPatients = 0;
// 如果当前用户是医生,查询该医生接诊和被挂号的所有患者
if (practitioner != null) {
// 查询该医生作为接诊医生ADMITTER, code="1"和挂号医生REGISTRATION_DOCTOR, code="12"的所有就诊记录的患者ID
List<Long> doctorPatientIds = patientManageMapper.getPatientIdsByPractitionerId(
practitioner.getId(),
Arrays.asList(ParticipantType.ADMITTER.getCode(), ParticipantType.REGISTRATION_DOCTOR.getCode()));
totalPatients = doctorPatientIds != null ? doctorPatientIds.size() : 0;
} else {
// 如果不是医生,查询所有患者(与患者管理页面逻辑保持一致)
LambdaQueryWrapper<Patient> patientQuery = new LambdaQueryWrapper<>();
patientQuery.eq(Patient::getDeleteFlag, "0");
List<Patient> patientList = patientService.list(patientQuery);
totalPatients = patientList != null ? patientList.size() : 0;
}
statistics.setTotalPatients(totalPatients);
// 查询昨日在院患者数量(暂时简化处理)
// TODO: 应该从历史记录表中查询昨天的实际在院患者数
int yesterdayPatients = totalPatients; // 这里应该是从历史表中查询昨天的数据
statistics.setYesterdayPatients(yesterdayPatients);
// 计算相对前日的百分比
double patientTrend = calculateTrend(totalPatients, yesterdayPatients);
statistics.setPatientTrend(patientTrend);
// 今日收入和预约等其他统计暂时设为0后续从相应表查询
statistics.setTodayRevenue("¥ 0");
statistics.setYesterdayRevenue("¥ 0");
statistics.setRevenueTrend(0.0);
statistics.setTodayAppointments(0);
statistics.setYesterdayAppointments(0);
statistics.setAppointmentTrend(0.0);
statistics.setPendingApprovals(0);
return statistics;
}
/**
* 计算相对前日的百分比变化
*
* @param todayValue 今天的值
* @param yesterdayValue 昨天的值
* @return 百分比变化(正数表示增长,负数表示下降)
*/
private double calculateTrend(double todayValue, double yesterdayValue) {
if (yesterdayValue == 0) {
return todayValue > 0 ? 100.0 : 0.0;
}
return ((todayValue - yesterdayValue) / yesterdayValue) * 100;
}
}

View File

@@ -6,7 +6,7 @@ spring:
druid:
# 主库数据源
master:
url: jdbc:postgresql://192.168.110.252:15432/postgresql?currentSchema=hisdev&characterEncoding=UTF-8&client_encoding=UTF-8
url: jdbc:postgresql://47.116.196.11:15432/postgresql?currentSchema=hisdev&characterEncoding=UTF-8&client_encoding=UTF-8
username: postgresql
password: Jchl1528
# 从库数据源
@@ -64,9 +64,9 @@ spring:
# redis 配置
redis:
# 地址
host: 192.168.110.252
host: 47.116.196.11
# 端口默认为6379
port: 6379
port: 26379
# 数据库索引
database: 1
# 密码

View File

@@ -95,5 +95,60 @@
</if>
</select>
<!-- 查询门诊记录 -->
<select id="getOutpatientRecord" resultType="com.openhis.web.patientmanage.dto.OutpatientRecordDto">
SELECT
enc.id as encounterId,
pt.name,
pt.id_card,
pt.bus_no as patientBusNo,
enc.bus_no as encounterBusNo,
pt.gender_enum,
pt.phone,
enc.create_time as encounterTime,
enc.status_enum as subjectStatusEnum,
org.name as organizationName,
prac.name as doctorName
FROM adm_encounter AS enc
LEFT JOIN adm_organization AS org ON enc.organization_id = org.ID AND org.delete_flag = '0'
LEFT JOIN adm_encounter_participant AS ep
ON enc.ID = ep.encounter_id AND ep.type_code = #{participantType} AND ep.delete_flag = '0'
LEFT JOIN adm_practitioner AS prac ON ep.practitioner_id = prac.ID AND prac.delete_flag = '0'
LEFT JOIN adm_patient AS pt ON enc.patient_id = pt.ID AND pt.delete_flag = '0'
<where>
enc.delete_flag = '0'
<if test="ew.sqlSegment != null and ew.sqlSegment != ''">
AND ${ew.sqlSegment}
</if>
</where>
ORDER BY enc.create_time DESC
</select>
</mapper>
<!-- 获取医生名字列表 -->
<select id="getDoctorNames" resultType="java.lang.String">
SELECT DISTINCT prac.name
FROM adm_practitioner AS prac
WHERE prac.delete_flag = '0'
ORDER BY prac.name
</select>
<!-- 根据医生ID和参与者类型获取相关的患者ID列表 -->
<select id="getPatientIdsByPractitionerId" resultType="java.lang.Long">
SELECT DISTINCT enc.patient_id
FROM adm_encounter_participant AS ep
LEFT JOIN adm_encounter AS enc ON ep.encounter_id = enc.ID AND enc.delete_flag = '0'
INNER JOIN adm_patient AS pt ON enc.patient_id = pt.id AND pt.delete_flag = '0'
WHERE ep.delete_flag = '0'
AND ep.practitioner_id = #{practitionerId}
AND ep.tenant_id = 1
AND enc.tenant_id = 1
AND pt.tenant_id = 1
<if test="typeCodes != null and !typeCodes.isEmpty()">
AND ep.type_code IN
<foreach collection="typeCodes" item="typeCode" open="(" separator="," close=")">
#{typeCode}
</foreach>
</if>
</select>
</mapper>

View File

@@ -0,0 +1,58 @@
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.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 医生患者关系管理Entity实体
*
* @author system
* @date 2026-01-02
*/
@Data
@TableName("adm_practitioner_patient")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class PractitionerPatient extends HisBaseEntity {
/** ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 医生ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long practitionerId;
/** 患者ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
/** 关系类型1-主治医生2-签约医生3-管床医生4-家庭医生5-会诊医生6-随访医生 */
private Integer relationshipType;
/** 机构ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long organizationId;
/** 关系开始时间 */
private Date startDate;
/** 关系结束时间 */
private Date endDate;
/** 状态1-有效0-无效 */
private Integer status;
/** 备注信息 */
private String remark;
}

View File

@@ -0,0 +1,38 @@
package com.openhis.administration.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 医生患者关系DTO
*
* @author system
* @date 2026-01-02
*/
@Data
public class PractitionerPatientDto implements Serializable {
private static final long serialVersionUID = 1L;
/** 医生ID */
private Long practitionerId;
/** 患者ID */
private Long patientId;
/** 关系类型1-主治医生2-签约医生3-管床医生4-家庭医生5-会诊医生6-随访医生 */
private Integer relationshipType;
/** 机构ID */
private Long organizationId;
/** 关系开始时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startDate;
/** 备注信息 */
private String remark;
}

View File

@@ -0,0 +1,16 @@
package com.openhis.administration.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.openhis.administration.domain.PractitionerPatient;
import org.springframework.stereotype.Repository;
/**
* 医生患者关系管理Mapper接口
*
* @author system
* @date 2026-01-02
*/
@Repository
public interface PractitionerPatientMapper extends BaseMapper<PractitionerPatient> {
}

View File

@@ -0,0 +1,65 @@
package com.openhis.administration.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.openhis.administration.domain.PractitionerPatient;
import java.util.List;
/**
* 医生患者关系管理Service接口
*
* @author system
* @date 2026-01-02
*/
public interface IPractitionerPatientService extends IService<PractitionerPatient> {
/**
* 获取医生的所有有效患者
*
* @param practitionerId 医生ID
* @return 患者关系列表
*/
List<PractitionerPatient> getValidPatientsByPractitioner(Long practitionerId);
/**
* 获取患者的所有有效医生
*
* @param patientId 患者ID
* @return 医生关系列表
*/
List<PractitionerPatient> getValidPractitionersByPatient(Long patientId);
/**
* 根据关系类型获取医生患者关系
*
* @param practitionerId 医生ID
* @param patientId 患者ID
* @param relationshipType 关系类型
* @return 医生患者关系
*/
PractitionerPatient getRelationship(Long practitionerId, Long patientId, Integer relationshipType);
/**
* 创建医生患者关系
*
* @param practitionerPatient 医生患者关系
* @return 是否成功
*/
boolean createRelationship(PractitionerPatient practitionerPatient);
/**
* 终止医生患者关系
*
* @param id 关系ID
* @return 是否成功
*/
boolean terminateRelationship(Long id);
/**
* 批量创建医生患者关系
*
* @param relationships 关系列表
* @return 是否成功
*/
boolean batchCreateRelationships(List<PractitionerPatient> relationships);
}

View File

@@ -0,0 +1,135 @@
package com.openhis.administration.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.openhis.administration.domain.PractitionerPatient;
import com.openhis.administration.mapper.PractitionerPatientMapper;
import com.openhis.administration.service.IPractitionerPatientService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
/**
* 医生患者关系管理Service实现
*
* @author system
* @date 2026-01-02
*/
@Slf4j
@Service
public class PractitionerPatientServiceImpl extends ServiceImpl<PractitionerPatientMapper, PractitionerPatient>
implements IPractitionerPatientService {
@Override
public List<PractitionerPatient> getValidPatientsByPractitioner(Long practitionerId) {
LambdaQueryWrapper<PractitionerPatient> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PractitionerPatient::getPractitionerId, practitionerId)
.eq(PractitionerPatient::getStatus, 1)
.orderByDesc(PractitionerPatient::getCreateTime);
return list(wrapper);
}
@Override
public List<PractitionerPatient> getValidPractitionersByPatient(Long patientId) {
LambdaQueryWrapper<PractitionerPatient> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PractitionerPatient::getPatientId, patientId)
.eq(PractitionerPatient::getStatus, 1)
.orderByDesc(PractitionerPatient::getCreateTime);
return list(wrapper);
}
@Override
public PractitionerPatient getRelationship(Long practitionerId, Long patientId, Integer relationshipType) {
LambdaQueryWrapper<PractitionerPatient> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PractitionerPatient::getPractitionerId, practitionerId)
.eq(PractitionerPatient::getPatientId, patientId)
.eq(PractitionerPatient::getRelationshipType, relationshipType)
.eq(PractitionerPatient::getStatus, 1);
return getOne(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean createRelationship(PractitionerPatient practitionerPatient) {
// 设置默认值
if (practitionerPatient.getStatus() == null) {
practitionerPatient.setStatus(1);
}
if (practitionerPatient.getStartDate() == null) {
practitionerPatient.setStartDate(new Date());
}
// 检查是否已存在相同的关系
PractitionerPatient existing = getRelationship(
practitionerPatient.getPractitionerId(),
practitionerPatient.getPatientId(),
practitionerPatient.getRelationshipType()
);
if (existing != null) {
// 如果关系已存在,更新结束时间
existing.setEndDate(new Date());
existing.setStatus(0);
updateById(existing);
log.info("已终止旧的医患关系doctorId={}, patientId={}, relationshipType={}",
practitionerPatient.getPractitionerId(),
practitionerPatient.getPatientId(),
practitionerPatient.getRelationshipType());
}
// 创建新关系
boolean result = save(practitionerPatient);
if (result) {
log.info("创建医患关系成功doctorId={}, patientId={}, relationshipType={}",
practitionerPatient.getPractitionerId(),
practitionerPatient.getPatientId(),
practitionerPatient.getRelationshipType());
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean terminateRelationship(Long id) {
PractitionerPatient relationship = getById(id);
if (relationship == null) {
log.warn("医患关系不存在id={}", id);
return false;
}
relationship.setEndDate(new Date());
relationship.setStatus(0);
boolean result = updateById(relationship);
if (result) {
log.info("终止医患关系成功id={}, doctorId={}, patientId={}",
id, relationship.getPractitionerId(), relationship.getPatientId());
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean batchCreateRelationships(List<PractitionerPatient> relationships) {
if (relationships == null || relationships.isEmpty()) {
return false;
}
boolean allSuccess = true;
for (PractitionerPatient relationship : relationships) {
boolean success = createRelationship(relationship);
if (!success) {
allSuccess = false;
log.error("批量创建医患关系失败doctorId={}, patientId={}",
relationship.getPractitionerId(), relationship.getPatientId());
}
}
if (allSuccess) {
log.info("批量创建医患关系成功count={}", relationships.size());
}
return allSuccess;
}
}

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.administration.mapper.PractitionerPatientMapper">
<resultMap type="com.openhis.administration.domain.PractitionerPatient" id="PractitionerPatientResult">
<result property="id" column="id" />
<result property="practitionerId" column="practitioner_id" />
<result property="patientId" column="patient_id" />
<result property="relationshipType" column="relationship_type" />
<result property="organizationId" column="organization_id" />
<result property="startDate" column="start_date" />
<result property="endDate" column="end_date" />
<result property="status" column="status" />
<result property="remark" column="remark" />
<result property="tenantId" column="tenant_id" />
<result property="deleteFlag" column="delete_flag" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectPractitionerPatientVo">
select id, practitioner_id, patient_id, relationship_type, organization_id,
start_date, end_date, status, remark, tenant_id,
delete_flag, create_by, create_time, update_by, update_time
from adm_practitioner_patient
</sql>
<select id="selectPractitionerPatientList" parameterType="com.openhis.administration.domain.PractitionerPatient" resultMap="PractitionerPatientResult">
<include refid="selectPractitionerPatientVo"/>
<where>
delete_flag = '0'
<if test="practitionerId != null">
and practitioner_id = #{practitionerId}
</if>
<if test="patientId != null">
and patient_id = #{patientId}
</if>
<if test="relationshipType != null">
and relationship_type = #{relationshipType}
</if>
<if test="organizationId != null">
and organization_id = #{organizationId}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
order by create_time desc
</select>
<select id="selectPractitionerPatientById" parameterType="Long" resultMap="PractitionerPatientResult">
<include refid="selectPractitionerPatientVo"/>
where id = #{id} and delete_flag = '0'
</select>
<!-- 获取医生的所有有效患者(带详细信息) -->
<select id="getValidPatientsByPractitionerWithDetail" parameterType="Long" resultType="java.util.Map">
SELECT
pp.id as relationship_id,
pp.practitioner_id,
pp.patient_id,
pp.relationship_type,
pp.start_date,
pp.end_date,
pp.status,
pp.remark,
pt.name as patient_name,
pt.bus_no as patient_bus_no,
pt.gender_enum as patient_gender,
pt.phone as patient_phone,
pt.id_card as patient_id_card,
pt.birth_date as patient_birth_date
FROM adm_practitioner_patient pp
LEFT JOIN adm_patient pt ON pp.patient_id = pt.ID AND pt.delete_flag = '0'
WHERE pp.practitioner_id = #{practitionerId}
AND pp.status = 1
AND pp.delete_flag = '0'
ORDER BY pp.create_time DESC
</select>
<!-- 获取患者的所有有效医生(带详细信息) -->
<select id="getValidPractitionersByPatientWithDetail" parameterType="Long" resultType="java.util.Map">
SELECT
pp.id as relationship_id,
pp.practitioner_id,
pp.patient_id,
pp.relationship_type,
pp.start_date,
pp.end_date,
pp.status,
pp.remark,
prac.name as practitioner_name,
prac.bus_no as practitioner_bus_no,
prac.gender_enum as practitioner_gender,
prac.phone as practitioner_phone,
prac.dr_profttl_code as practitioner_title,
org.name as organization_name
FROM adm_practitioner_patient pp
LEFT JOIN adm_practitioner prac ON pp.practitioner_id = prac.ID AND prac.delete_flag = '0'
LEFT JOIN adm_organization org ON pp.organization_id = org.ID AND org.delete_flag = '0'
WHERE pp.patient_id = #{patientId}
AND pp.status = 1
AND pp.delete_flag = '0'
ORDER BY pp.relationship_type, pp.create_time DESC
</select>
</mapper>

View File

@@ -40,7 +40,7 @@
<tomcat.version>9.0.96</tomcat.version>
<logback.version>1.2.13</logback.version>
<lombok.version>1.18.34</lombok.version> <!-- 替换为 -->
<mybatis-plus.version>3.5.3</mybatis-plus.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<flowable.version>6.8.0</flowable.version>
<postgresql.version>42.2.27</postgresql.version>
<aviator.version>5.3.3</aviator.version>
@@ -56,6 +56,7 @@
<itext-asian.version>5.2.0</itext-asian.version>
<mysql-connector-j.version>9.4.0</mysql-connector-j.version>
<jsr250.version>1.3.2</jsr250.version>
<jsqlparser.version>4.5</jsqlparser.version>
</properties>
<!-- 依赖声明 -->
@@ -340,6 +341,19 @@
<version>1.9.10</version>
</dependency>
<!-- JSQlParser - MyBatis Plus 3.5.8 使用4.5版本 -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<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>