Files
his/openhis-ui-vue3/src/views/charge/patientCardRenewal/index.vue
2025-11-24 09:22:11 +08:00

768 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="card-renewal-container">
<!-- 标题栏 -->
<div class="title-bar">
<h2>换卡处理</h2>
</div>
<!-- 按钮栏 - 修改为水平排放 -->
<div class="button-bar">
<el-button
type="primary"
size="large"
@click="handlePatientSearch"
:loading="loading"
icon="el-icon-search"
style="min-width: 140px;"
>
病人查询(Q)
</el-button>
<el-button
type="success"
size="large"
@click="handleConfirm"
:loading="loading"
:disabled="!patientInfo"
style="min-width: 140px;"
>
确定 (O)
</el-button>
<el-button
type="danger"
size="large"
@click="handleClose"
:disabled="loading"
style="min-width: 140px;"
>
关闭 (C)
</el-button>
</div>
<!-- 查询条件 -->
<div class="search-section">
<el-row :gutter="20">
<el-col :span="8">
<div class="input-item">
<label>病人姓名:</label>
<el-input
v-model="searchForm.patientName"
placeholder="请输入"
@keyup.enter="handlePatientSearch"
clearable
/>
</div>
</el-col>
<el-col :span="8">
<div class="input-item">
<label>身份证号码:</label>
<el-input
v-model="searchForm.idCard"
placeholder="请输入"
@keyup.enter="handlePatientSearch"
clearable
/>
</div>
</el-col>
<el-col :span="8">
<div class="input-item">
<label>手机号码:</label>
<el-input
v-model="searchForm.phoneNumber"
placeholder="请输入"
@keyup.enter="handlePatientSearch"
clearable
/>
</div>
</el-col>
</el-row>
</div>
<!-- 患者信息显示 -->
<div class="patient-info-section">
<el-row :gutter="20">
<el-col :span="8">
<div class="info-item">
<label>门诊号码</label>
<div class="info-value">{{ patientInfo?.outpatientNo || '' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<label>病人姓名</label>
<div class="info-value">{{ patientInfo?.patientName || '' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<label>身份证号码</label>
<div class="info-value">{{ patientInfo?.idCard || '' }}</div>
</div>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 15px;">
<el-col :span="8">
<div class="info-item">
<label>手机号码</label>
<div class="info-value">{{ patientInfo?.phoneNumber || '' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<label>性别</label>
<div class="info-value">{{ patientInfo?.gender || '' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<label>年龄</label>
<div class="info-value">{{ patientInfo?.age || '' }}</div>
</div>
</el-col>
</el-row>
</div>
<!-- 新门诊号码输入 -->
<div class="new-number-section">
<div class="input-item">
<label>新门诊号码:</label>
<el-input
v-model="renewalForm.newOutpatientNo"
placeholder="请输入新门诊号码"
@keyup.enter="handleConfirm"
style="width: 250px;"
/>
</div>
</div>
<!-- 按钮区域已移除 -->
<!-- 患者列表对话框 -->
<el-dialog
v-model="showPatientList"
: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">
</el-table-column>
<el-table-column label="病人姓名" width="120">
<template #default="scope">
{{ scope.row.patientName || scope.row.name || '-' }}
</template>
</el-table-column>
<el-table-column label="门诊号码" width="120">
<template #default="scope">
{{ scope.row.identifierNo || scope.row.cardNo || scope.row.card_number || scope.row.就诊卡号 || scope.row.outpatientNumber || scope.row.outpatientNo || scope.row.门诊号码 || scope.row.卡号 || scope.row.card || scope.row.patientNo || scope.row.patient_id || '-' }}
</template>
</el-table-column>
<el-table-column label="身份证号码" width="200">
<template #default="scope">
{{ scope.row.idCard || scope.row.id_card || scope.row.idNo || '-' }}
</template>
</el-table-column>
<el-table-column label="手机号码" width="120">
<template #default="scope">
{{ scope.row.phoneNumber || scope.row.phone || scope.row.mobile || scope.row.mobilePhone || '-' }}
</template>
</el-table-column>
<el-table-column label="性别" width="80">
<template #default="scope">
{{ scope.row.genderEnum_enumText || scope.row.gender || scope.row.sex || scope.row.性别 || scope.row.xb || scope.row.sexCode || scope.row.GENDER || scope.row.SEX || '-' }}
</template>
</el-table-column>
<el-table-column label="年龄" width="80">
<template #default="scope">
{{ scope.row.age || '-' }}
</template>
</el-table-column>
<el-table-column label="出生年月" width="120">
<template #default="scope">
{{ formatDate(scope.row.birthDate || scope.row.birthday || scope.row.出生日期) || '-' }}
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div style="margin-top: 15px; display: flex; justify-content: flex-end;">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[5, 10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 移除底部按钮 -->
<template #footer>
<span></span>
</template>
</el-dialog>
<!-- 换卡成功提示 -->
<el-dialog
v-model="renewalSuccessVisible"
title="换卡成功"
width="400px"
:close-on-click-modal="false"
>
<div style="text-align: center; padding: 20px;">
<div class="success-icon">✓</div>
<p style="margin-top: 15px; font-size: 16px;">患者换卡操作已成功完成!</p>
<p style="margin-top: 10px;">新门诊号码:{{ renewalForm.newOutpatientNo }}</p>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="renewalSuccessVisible = false; resetForm">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
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: '',
idCard: '',
phoneNumber: ''
})
// 患者信息
const patientInfo = ref(null)
// 患者列表
const patientList = ref([])
const showPatientList = ref(false)
const selectedPatient = ref(null)
// 分页相关状态
const currentPage = ref(1)
const pageSize = ref(5)
const total = ref(0)
// 换卡表单
const renewalForm = reactive({
newOutpatientNo: ''
})
// 加载状态
const loading = ref(false)
// 成功弹窗显示状态
const renewalSuccessVisible = ref(false)
// 分页大小变化处理
const handleSizeChange = (newSize) => {
pageSize.value = newSize
currentPage.value = 1 // 重置为第一页
selectedPatient.value = null // 切换分页时重置选中状态
if (showPatientList.value) {
handlePatientSearch() // 重新查询
}
}
// 当前页码变化处理
const handleCurrentChange = (newPage) => {
currentPage.value = newPage
selectedPatient.value = null // 切换分页时重置选中状态
if (showPatientList.value) {
handlePatientSearch() // 重新查询
}
}
// 查询患者信息
const handlePatientSearch = async () => {
// 检查是否至少填写了一个查询条件
if (!searchForm.patientName && !searchForm.idCard && !searchForm.phoneNumber) {
ElMessage.warning('请至少输入一个查询条件')
return
}
loading.value = true
try {
// 构建查询参数,包含分页信息
const queryParams = {
patientName: searchForm.patientName || '',
idCard: searchForm.idCard || '',
phoneNumber: searchForm.phoneNumber || '',
pageNo: currentPage.value,
pageSize: pageSize.value
}
// 调用复用的门诊挂号API获取患者列表
const response = await getPatientList(queryParams)
if (response && response.code === 200) {
// 检查是否有查询结果
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]
// 确保patient对象中同时包含id和patientId字段
if (patient.id && !patient.patientId) {
patient.patientId = patient.id;
}
// 直接构建并设置患者信息,无需用户手动确认
const outpatientNo = patient.identifierNo || patient.cardNo || patient.card_number || patient.就诊卡号 || patient.outpatientNumber || patient.outpatientNo || patient.门诊号码 || patient.卡号 || patient.card || patient.patientNo || patient.patient_id;
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 || patient.id || outpatientNo
}
ElMessage.success('已自动选中唯一患者')
} else {
// 如果有多条记录或不是第一页,显示患者列表供选择
// 确保每条患者记录都包含patientId字段优先使用id字段
patientList.value = response.data.records.map(patient => ({
...patient,
patientId: patient.patientId || patient.id
}))
showPatientList.value = true
}
} else {
// 无数据时重置总条数
total.value = 0
ElMessage.warning('未查询到患者信息')
}
} else {
ElMessage.error('查询失败:' + (response?.msg || '未知错误'))
}
} catch (error) {
// 错误处理
ElMessage.error('查询失败,请稍后重试');
// 可以在这里添加错误监控或日志记录
console.error('患者查询错误:', error);
} finally {
loading.value = false
}
}
// 确定换卡
const handleConfirm = async () => {
if (!patientInfo.value) {
ElMessage.warning('请先查询患者信息')
return
}
if (!renewalForm.newOutpatientNo) {
ElMessage.warning('请输入新门诊号码')
return
}
if (renewalForm.newOutpatientNo === patientInfo.value.outpatientNo) {
ElMessage.warning('新门诊号码不能与原号码相同')
return
}
loading.value = true
try {
// 构建换卡请求参数注意参数名称与API接口保持一致
const params = {
oldCardNo: patientInfo.value.outpatientNo,
newCardNo: renewalForm.newOutpatientNo,
patientId: patientInfo.value.patientId,
reason: '患者换卡',
remark: `${patientInfo.value.outpatientNo}更换到${renewalForm.newOutpatientNo}`
}
// 调用真实的换卡接口
const res = await doCardRenewal(params)
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('换卡失败,卡号已存在')
// 可以在这里添加错误监控或日志记录
} finally {
loading.value = false
}
}
// 关闭窗口,同时关闭标签页并返回上一级页面
const handleClose = () => {
// 显示确认对话框
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()
handlePatientSearch()
}
// Alt + O 确定
if (event.altKey && event.key.toLowerCase() === 'o') {
event.preventDefault()
handleConfirm()
}
// Alt + C 关闭
if (event.altKey && event.key.toLowerCase() === 'c') {
event.preventDefault()
handleClose()
}
}
// 组件挂载时添加键盘事件监听
onMounted(() => {
document.addEventListener('keydown', handleKeydown)
// 设置默认患者数据,方便用户直接进行换卡操作
})
// 组件卸载时移除键盘事件监听
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown)
})
// 移除了调试功能
// 选择患者(仅设置选中状态)
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
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 = () => {
searchForm.patientName = ''
searchForm.idCard = ''
searchForm.phoneNumber = ''
patientInfo.value = null
patientList.value = []
showPatientList.value = false
selectedPatient.value = null
renewalForm.newOutpatientNo = ''
// 重置分页状态
currentPage.value = 1
pageSize.value = 5
total.value = 0
}
// 格式化日期为YYYY-MM-DD格式
const formatDate = (date) => {
if (!date) return ''
// 如果已经是YYYY-MM-DD格式直接返回
if (typeof date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(date)) {
return date
}
// 处理日期对象或其他格式
const d = new Date(date)
if (isNaN(d.getTime())) return ''
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
</script>
<style scoped>
/* 自定义对话框样式 */
.custom-patient-dialog .el-dialog__header {
padding: 0;
border-bottom: none;
height: auto;
}
/* 隐藏默认的header样式 */
.custom-header {
display: none;
}
.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 {
min-width: 80px;
background-color: #f56c6c;
border-color: #f56c6c;
color: white;
}
/* 表格样式 */
.el-dialog__body {
padding: 0;
overflow: hidden;
}
.el-table {
margin: 0;
}
.el-table th {
background-color: #f5f7fa;
font-weight: 500;
color: #303133;
}
.el-table td {
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;
}
.el-table--striped .el-table__body tr:nth-child(2n) {
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;
margin: 0 auto;
padding: 10px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background: #fff;
}
.title-bar {
background: #409eff;
color: white;
padding: 10px 15px;
margin: -10px -10px 15px -10px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.title-bar h2 {
margin: 0;
font-size: 16px;
font-weight: normal;
}
.button-bar {
display: flex;
gap: 15px;
padding: 15px;
background-color: #f5f7fa;
border: 1px solid #ebeef5;
margin-bottom: 20px;
}
.button-bar .el-button {
font-weight: 500;
transition: all 0.3s ease;
}
.button-bar .el-button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.search-section {
margin-bottom: 20px;
}
.input-item {
margin-bottom: 15px;
}
.input-item label {
display: inline-block;
width: 100px;
text-align: right;
margin-right: 10px;
font-weight: 500;
}
.patient-info-section {
background: #f5f7fa;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
}
.info-item label {
display: block;
color: #606266;
font-size: 14px;
margin-bottom: 5px;
}
.info-value {
color: #303133;
font-weight: 500;
}
.new-number-section {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #ebeef5;
}
.success-icon {
width: 50px;
height: 50px;
background: #67c23a;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
margin: 0 auto;
}
</style>