实现门诊换卡的整体逻辑

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; package com.openhis.web.charge.patientcardrenewal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; 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; import lombok.extern.slf4j.Slf4j;
@@ -14,30 +22,50 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class PatientCardRenewalServiceImpl implements PatientCardRenewalService { public class PatientCardRenewalServiceImpl implements PatientCardRenewalService {
@Autowired
private IPatientIdentifierService patientIdentifierService;
@Override @Override
@Transactional(rollbackFor = Exception.class)
public boolean renewCard(RenewalRequest request) { public boolean renewCard(RenewalRequest request) {
// TODO: 这里应该实现真实的换卡业务逻辑 log.info("执行患者换卡操作: 患者ID={}, 旧卡号={}, 新卡号={}, 原因={}",
request.getPatientId(), request.getOldCardNo(), request.getNewCardNo(), request.getReason());
// 1. 验证参数合法性 // 1. 验证参数合法性
if (StringUtils.isEmpty(request.getPatientId())) {
throw new IllegalArgumentException("患者ID不能为空");
}
if (StringUtils.isEmpty(request.getNewCardNo())) {
throw new IllegalArgumentException("新卡号不能为空");
}
// 2. 检查新卡号是否已被使用 // 2. 检查新卡号是否已被使用
// 3. 更新患者主表中的卡号信息 LambdaQueryWrapper<PatientIdentifier> queryWrapper = new LambdaQueryWrapper<>();
// 4. 记录换卡日志 queryWrapper.eq(PatientIdentifier::getIdentifierNo, request.getNewCardNo());
// 5. 处理相关业务系统的卡号更新 PatientIdentifier existingIdentifier = patientIdentifierService.getOne(queryWrapper);
if (existingIdentifier != null) {
// 目前返回模拟结果 throw new IllegalArgumentException("新卡号已被其他患者使用,请更换新卡号");
log.info("模拟执行患者换卡操作: 旧卡号={}, 新卡号={}, 原因={}",
request.getOldCardNo(), request.getNewCardNo(), request.getReason());
// 简单验证:确保旧卡号和新卡号不为空且不相同
if (request.getOldCardNo() == null || request.getNewCardNo() == null ||
request.getOldCardNo().isEmpty() || request.getNewCardNo().isEmpty()) {
throw new IllegalArgumentException("卡号不能为空");
} }
if (request.getOldCardNo().equals(request.getNewCardNo())) { // 3. 直接使用患者ID作为查询条件
throw new IllegalArgumentException("新卡号不能与旧卡号相同"); 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; return true;
} }
} }

View File

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

View File

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