实现门诊换卡的整体逻辑

This commit is contained in:
2025-11-17 13:24:49 +08:00
parent 93103f7f40
commit a68c4402de
3 changed files with 236 additions and 93 deletions

View File

@@ -1,6 +1,14 @@
package com.openhis.web.charge.patientcardrenewal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import com.openhis.administration.domain.PatientIdentifier;
import com.openhis.administration.service.IPatientIdentifierService;
import com.openhis.common.enums.IdentifierStatusEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
@@ -14,30 +22,50 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PatientCardRenewalServiceImpl implements PatientCardRenewalService {
@Autowired
private IPatientIdentifierService patientIdentifierService;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean renewCard(RenewalRequest request) {
// TODO: 这里应该实现真实的换卡业务逻辑
log.info("执行患者换卡操作: 患者ID={}, 旧卡号={}, 新卡号={}, 原因={}",
request.getPatientId(), request.getOldCardNo(), request.getNewCardNo(), request.getReason());
// 1. 验证参数合法性
if (StringUtils.isEmpty(request.getPatientId())) {
throw new IllegalArgumentException("患者ID不能为空");
}
if (StringUtils.isEmpty(request.getNewCardNo())) {
throw new IllegalArgumentException("新卡号不能为空");
}
// 2. 检查新卡号是否已被使用
// 3. 更新患者主表中的卡号信息
// 4. 记录换卡日志
// 5. 处理相关业务系统的卡号更新
// 目前返回模拟结果
log.info("模拟执行患者换卡操作: 旧卡号={}, 新卡号={}, 原因={}",
request.getOldCardNo(), request.getNewCardNo(), request.getReason());
// 简单验证:确保旧卡号和新卡号不为空且不相同
if (request.getOldCardNo() == null || request.getNewCardNo() == null ||
request.getOldCardNo().isEmpty() || request.getNewCardNo().isEmpty()) {
throw new IllegalArgumentException("卡号不能为空");
LambdaQueryWrapper<PatientIdentifier> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(PatientIdentifier::getIdentifierNo, request.getNewCardNo());
PatientIdentifier existingIdentifier = patientIdentifierService.getOne(queryWrapper);
if (existingIdentifier != null) {
throw new IllegalArgumentException("新卡号已被其他患者使用,请更换新卡号");
}
if (request.getOldCardNo().equals(request.getNewCardNo())) {
throw new IllegalArgumentException("新卡号不能与旧卡号相同");
// 3. 直接使用患者ID作为查询条件
Long patientId = Long.parseLong(request.getPatientId());
// 4. 通过患者ID查询现有标识信息
PatientIdentifier patientIdentifier = patientIdentifierService.selectByPatientId(patientId);
if (patientIdentifier != null) {
// 5. 只更新就诊卡号这一个参数
patientIdentifier.setIdentifierNo(request.getNewCardNo());
patientIdentifierService.updateById(patientIdentifier);
log.info("患者ID={} 换卡成功,已更新就诊卡号", patientId);
} else {
throw new IllegalArgumentException("未找到患者标识信息,无法进行换卡操作");
}
// 模拟成功结果
// 4. 记录换卡日志 - 可以根据需要扩展日志记录功能
// 5. 处理相关业务系统的卡号更新 - 可以根据需要扩展
return true;
}
}

View File

@@ -12,7 +12,7 @@ spring:
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
enabled:
url:
username:
password:

View File

@@ -142,14 +142,32 @@
<!-- 患者列表对话框 -->
<el-dialog
v-model="showPatientList"
title="患者列表"
:title="null"
width="800px"
:close-on-click-modal="false"
class="custom-patient-dialog"
>
<!-- 自定义标题栏和按钮区域 -->
<template #header>
<div style="width: 100%; background-color: #e6f4ff;">
<!-- 标题行 -->
<div style="display: flex; justify-content: flex-start; align-items: center; padding: 10px 20px;">
<h3 style="margin: 0; font-size: 16px; font-weight: 500; color: #303133;">病人档案查询</h3>
</div>
<!-- 按钮行 -->
<div style="display: flex; justify-content: flex-start; gap: 10px; padding: 10px 20px; background-color: #e6f4ff;">
<el-button type="primary" @click="confirmSelectPatient" style="background-color: #409eff; border-color: #409eff; padding: 8px 16px; font-size: 14px;">确认(Q)</el-button>
<el-button @click="showPatientList = false; selectedPatient = null" style="background-color: #f56c6c; border-color: #f56c6c; color: white; padding: 8px 16px; font-size: 14px;">关闭(C)</el-button>
</div>
</div>
</template>
<el-table
:data="patientList"
style="width: 100%"
@row-click="selectPatient"
:row-key="row => row.identifierNo || row.patientId || row.cardNo"
:current-row-key="selectedPatient?.identifierNo || selectedPatient?.patientId || selectedPatient?.cardNo"
highlight-current-row
>
<el-table-column label="序号" width="60" type="index">
@@ -196,7 +214,7 @@
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:page-sizes="[5, 10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@@ -204,10 +222,9 @@
/>
</div>
<!-- 移除底部按钮 -->
<template #footer>
<span class="dialog-footer">
<el-button @click="showPatientList = false">取消</el-button>
</span>
<span></span>
</template>
</el-dialog>
@@ -233,10 +250,16 @@
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import { ref, reactive, onMounted, onUnmounted, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import { doCardRenewal, getPatientList } from './components/api.js';
// 获取路由实例
const router = useRouter();
// 获取当前组件实例以访问全局属性
const { proxy } = getCurrentInstance();
// 搜索表单
const searchForm = reactive({
patientName: '',
@@ -250,9 +273,10 @@ const patientInfo = ref(null)
// 患者列表
const patientList = ref([])
const showPatientList = ref(false)
const selectedPatient = ref(null)
// 分页相关状态
const currentPage = ref(1)
const pageSize = ref(10)
const pageSize = ref(5)
const total = ref(0)
// 换卡表单
@@ -270,6 +294,7 @@ const renewalSuccessVisible = ref(false)
const handleSizeChange = (newSize) => {
pageSize.value = newSize
currentPage.value = 1 // 重置为第一页
selectedPatient.value = null // 切换分页时重置选中状态
if (showPatientList.value) {
handlePatientSearch() // 重新查询
}
@@ -278,6 +303,7 @@ const handleSizeChange = (newSize) => {
// 当前页码变化处理
const handleCurrentChange = (newPage) => {
currentPage.value = newPage
selectedPatient.value = null // 切换分页时重置选中状态
if (showPatientList.value) {
handlePatientSearch() // 重新查询
}
@@ -310,28 +336,26 @@ const handleCurrentChange = (newPage) => {
if (response.data && response.data.records && response.data.records.length > 0) {
// 更新总条数
total.value = response.data.total || 0
selectedPatient.value = null // 查询时重置选中状态
// 如果只有一条记录且是第一页,直接显示
// 如果只有一条记录且是第一页,自动选中
if (response.data.records.length === 1 && currentPage.value === 1) {
const patient = response.data.records[0]
// 获取门诊号码优先使用identifierNo或patientId
const outpatientNo = patient.identifierNo || patient.cardNo || patient.card_number || patient.就诊卡号 || patient.outpatientNumber || patient.outpatientNo || patient.门诊号码 || patient.卡号 || patient.card || patient.patientNo || patient.patient_id;
// 获取性别优先使用genderEnum_enumText
const gender = patient.genderEnum_enumText || patient.gender || patient.sex || patient.性别 || patient.xb || patient.sexCode || patient.GENDER || patient.SEX;
patientInfo.value = {
outpatientNo: outpatientNo,
patientName: patient.patientName || patient.name,
idCard: patient.idCard || patient.id_card || patient.idNo,
phoneNumber: patient.phoneNumber || patient.phone || patient.mobile || patient.mobilePhone,
gender: gender,
age: patient.age,
patientId: patient.patientId || outpatientNo
// 确保patient对象中同时包含id和patientId字段
if (patient.id && !patient.patientId) {
patient.patientId = patient.id;
}
// 设置为选中状态,但不自动确认
selectedPatient.value = patient
ElMessage.warning('已自动选中唯一患者,请点击确定')
} else {
// 如果有多条记录或不是第一页,显示患者列表供选择
patientList.value = response.data.records
// 确保每条患者记录都包含patientId字段优先使用id字段
patientList.value = response.data.records.map(patient => ({
...patient,
patientId: patient.patientId || patient.id
}))
showPatientList.value = true
}
} else {
@@ -344,8 +368,11 @@ const handleCurrentChange = (newPage) => {
}
} catch (error) {
ElMessage.error('查询失败,请稍后重试')
// 使用公共错误处理函数处理错误信息
const errorMessage = processErrorMessage(error, '查询失败,请稍后重试');
ElMessage.error(errorMessage);
// 可以在这里添加错误监控或日志记录
console.error('患者查询错误:', error);
} finally {
loading.value = false
}
@@ -384,31 +411,66 @@ const handleConfirm = async () => {
if (res && res.code === 200) {
renewalSuccessVisible.value = true
// 更新患者信息中的门诊号码为新号码
if (patientInfo.value) {
patientInfo.value.outpatientNo = renewalForm.newOutpatientNo
}
ElMessage.success('换卡成功!')
} else {
ElMessage.error('换卡失败:' + (res?.msg || '未知错误'))
}
} catch (error) {
ElMessage.error('换卡失败,请稍后重试')
ElMessage.error('换卡失败,卡号已存在')
// 可以在这里添加错误监控或日志记录
} finally {
loading.value = false
}
}
// 关闭窗口
// 关闭窗口,同时关闭标签页并返回上一级页面
const handleClose = () => {
if (confirm('确定要关闭换卡窗口吗?')) {
resetForm()
// 如果是在弹窗中打开的,可以添加关闭弹窗的逻辑
// 否则可以导航回上一页
// this.$router.back()
// 显示确认对话框
ElMessageBox.confirm('确定要关闭此页面吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 用户确认后,使用全局$tab对象关闭当前标签页
if (proxy && proxy.$tab) {
proxy.$tab.closePage()
} else {
// 降级处理:如果$tab不可用仍然使用router.back()
router.back()
}
}).catch(() => {
// 用户取消操作,不执行任何操作
// 可以选择显示一个提示消息
ElMessage.info('已取消关闭操作')
})
}
// 键盘事件处理
const handleKeydown = (event) => {
// 只有在患者列表对话框显示时才处理对话框快捷键
if (showPatientList.value) {
// 忽略在输入框中按下的按键
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
return
}
const key = event.key.toLowerCase()
if (key === 'q') {
event.preventDefault()
confirmSelectPatient()
} else if (key === 'c') {
event.preventDefault()
showPatientList.value = false
selectedPatient.value = null
}
return
}
// Alt + Q 查询
if (event.altKey && event.key.toLowerCase() === 'q') {
event.preventDefault()
@@ -429,6 +491,17 @@ const handleKeydown = (event) => {
// 组件挂载时添加键盘事件监听
onMounted(() => {
document.addEventListener('keydown', handleKeydown)
// 设置默认患者数据,方便用户直接进行换卡操作
patientInfo.value = {
outpatientNo: '20231001001',
patientName: '张三',
idCard: '110101199001011234',
phoneNumber: '13800138000',
gender: '男',
age: '33岁',
patientId: '1001'
}
})
// 组件卸载时移除键盘事件监听
@@ -438,8 +511,20 @@ onUnmounted(() => {
// 移除了调试功能
// 选择患者
// 选择患者(仅设置选中状态)
const selectPatient = (row) => {
selectedPatient.value = row
ElMessage.warning('已选择患者,请点击确定')
}
// 确认选择患者
const confirmSelectPatient = () => {
if (!selectedPatient.value) {
ElMessage.warning('请先选择患者')
return
}
const row = selectedPatient.value
// 获取门诊号码优先使用identifierNo
const outpatientNo = row.identifierNo || row.cardNo || row.card_number || row.就诊卡号 || row.outpatientNumber || row.outpatientNo || row.门诊号码 || row.卡号 || row.card || row.patientNo || row.patient_id;
// 获取性别优先使用genderEnum_enumText
@@ -452,10 +537,13 @@ onUnmounted(() => {
phoneNumber: row.phoneNumber || row.phone || row.mobile || row.mobilePhone,
gender: gender,
age: row.age,
patientId: row.patientId || outpatientNo
patientId: row.patientId || row.id || outpatientNo
}
showPatientList.value = false
ElMessage.success('已选择患者:' + (row.patientName || row.name))
// 重置选中状态
selectedPatient.value = null
}
// 重置表单
@@ -466,10 +554,11 @@ onUnmounted(() => {
patientInfo.value = null
patientList.value = []
showPatientList.value = false
selectedPatient.value = null
renewalForm.newOutpatientNo = ''
// 重置分页状态
currentPage.value = 1
pageSize.value = 10
pageSize.value = 5
total.value = 0
}
@@ -491,27 +580,28 @@ onUnmounted(() => {
</script>
<style scoped>
/* 对话框标题样式 */
.el-dialog__header {
background-color: #fff;
padding: 15px 20px;
border-bottom: 1px solid #ebeef5;
margin: 0;
/* 自定义对话框样式 */
.custom-patient-dialog .el-dialog__header {
padding: 0;
border-bottom: none;
height: auto;
}
.el-dialog__title {
font-size: 16px;
color: #303133;
font-weight: 500;
/* 隐藏默认的header样式 */
.custom-header {
display: none;
}
/* 按钮样式 */
.dialog-footer {
display: flex;
justify-content: center;
padding: 15px 20px;
background-color: #fff;
border-top: 1px solid #ebeef5;
.custom-patient-dialog .el-dialog__body {
padding: 0;
max-height: 60vh;
overflow-y: auto;
}
/* 隐藏默认底部区域 */
.custom-patient-dialog .el-dialog__footer {
padding: 0;
border-top: none;
}
.dialog-footer .el-button {
@@ -541,7 +631,24 @@ onUnmounted(() => {
text-align: left;
}
/* 恢复默认表格样式 */
/* 表格样式优化 */
.el-table {
margin: 0;
}
.el-table th {
background-color: #f5f7fa;
font-weight: 500;
color: #303133;
text-align: center;
border-bottom: 1px solid #ebeef5;
}
.el-table td {
text-align: center;
border-bottom: 1px solid #ebeef5;
}
.el-table tr:hover > td {
background-color: #f5f7fa;
}
@@ -550,6 +657,14 @@ onUnmounted(() => {
background-color: #fafafa;
}
/* 分页样式优化 */
.el-pagination {
margin-top: 10px;
padding: 10px 20px;
background-color: #fafafa;
border-top: 1px solid #ebeef5;
}
.card-renewal-container {
width: 100%;
max-width: 800px;