叫号显示屏页面开发,诊疗目录新增或修改时添加医保编码唯一性校验。
This commit is contained in:
@@ -116,7 +116,11 @@
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="医保编码" prop="conditionCode">
|
||||
<el-input v-model="form.ybNo" placeholder="" />
|
||||
<el-input
|
||||
v-model="form.ybNo"
|
||||
placeholder=""
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -363,6 +367,7 @@ import {
|
||||
deptTreeSelect,
|
||||
editDiagnosisTreatment,
|
||||
getDiagnosisTreatmentList,
|
||||
getDiseaseTreatmentByYbNo,
|
||||
locationTreeSelect,
|
||||
} from './diagnosistreatment';
|
||||
import PopoverList from '@/components/OpenHis/popoverList/index.vue';
|
||||
@@ -454,6 +459,7 @@ const treatmentItems = ref([
|
||||
const medicineSearchKey = ref('');
|
||||
const isFirstOpen = ref(true); // 标记是否首次打开弹窗
|
||||
const totalPrice = ref('0.00'); // 总价
|
||||
const isValidatingYbNo = ref(false); // 标记是否正在校验医保编码
|
||||
|
||||
// 计算总价
|
||||
function calculateTotalPrice() {
|
||||
@@ -597,8 +603,31 @@ function reset() {
|
||||
proxy.resetForm('diagnosisTreatmentRef');
|
||||
}
|
||||
|
||||
async function validateYbNoUnique(ybNo, currentId = null) {
|
||||
if (!ybNo || ybNo.trim() === '') {
|
||||
return true; // 空值不进行校验
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await getDiseaseTreatmentByYbNo(ybNo);
|
||||
const data = response.data;
|
||||
|
||||
if (data && data.length > 0) {
|
||||
// 检查是否存在相同的医保编码,排除当前编辑的记录
|
||||
const existingRecord = data.find(item => item.id !== currentId);
|
||||
if (existingRecord) {
|
||||
return false; // 医保编码已存在
|
||||
}
|
||||
}
|
||||
return true; // 医保编码唯一
|
||||
} catch (error) {
|
||||
console.error('医保编码校验失败:', error);
|
||||
return true; // 校验失败时允许提交,由后端处理
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
async function submitForm() {
|
||||
form.value.ybFlag ? (form.value.ybFlag = 1) : (form.value.ybFlag = 0);
|
||||
form.value.ybMatchFlag ? (form.value.ybMatchFlag = 1) : (form.value.ybMatchFlag = 0);
|
||||
form.value.ruleId ? (form.value.ruleId = 1) : (form.value.ruleId = 0);
|
||||
@@ -606,8 +635,26 @@ function submitForm() {
|
||||
treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId != ''
|
||||
? JSON.stringify(treatmentItems.value)
|
||||
: undefined;
|
||||
proxy.$refs['diagnosisTreatmentRef'].validate((valid) => {
|
||||
proxy.$refs['diagnosisTreatmentRef'].validate(async (valid) => {
|
||||
if (valid) {
|
||||
// 医保编码唯一性校验
|
||||
if (form.value.ybNo) {
|
||||
try {
|
||||
isValidatingYbNo.value = true;
|
||||
const isUnique = await validateYbNoUnique(form.value.ybNo, form.value.id);
|
||||
if (!isUnique) {
|
||||
proxy.$modal.msgWarning('医保编码已存在,请输入其他医保编码');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('医保编码校验失败:', error);
|
||||
proxy.$modal.msgError('医保编码校验失败,请稍后重试');
|
||||
return;
|
||||
} finally {
|
||||
isValidatingYbNo.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (form.value.id != undefined) {
|
||||
editDiagnosisTreatment(form.value).then((response) => {
|
||||
// 触发自定义事件,并传递数据给父组件
|
||||
|
||||
@@ -104,3 +104,13 @@ export function getYbDiagnosisTreatmentList (queryParams) {
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
// 根据医保编码查询诊疗目录(用于医保编码唯一性校验)
|
||||
export function getDiseaseTreatmentByYbNo (ybNo) {
|
||||
return request ({
|
||||
url: '/data-dictionary/diagnosis-treatment/information/' + ybNo,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,556 @@
|
||||
<template>
|
||||
<div class="call-number-display">
|
||||
<!-- 头部区域 -->
|
||||
<div class="header">
|
||||
<h1>{{ departmentName }}</h1>
|
||||
<div class="time">{{ currentTime }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前呼叫区 -->
|
||||
<div class="current-call">
|
||||
<div class="call-box">
|
||||
<div class="call-text">
|
||||
请 <span class="highlight">{{ currentCall?.number || '-' }}</span> 号
|
||||
<span class="highlight">{{ currentCall?.name || '-' }}</span>
|
||||
到 <span class="highlight">{{ currentCall?.room || '-' }}</span> 诊室就诊
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 候诊信息区 -->
|
||||
<div class="waiting-area">
|
||||
<h2 class="section-title">候诊信息</h2>
|
||||
<div class="table-container" ref="tableContainer">
|
||||
<table class="waiting-table">
|
||||
<thead style="position: sticky; top: 0; background: #f0f7ff; z-index: 1;">
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>患者</th>
|
||||
<th>诊室</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(group, doctorName) in groupedPatients" :key="doctorName">
|
||||
<!-- 医生分组标题 -->
|
||||
<tr class="doctor-header">
|
||||
<td colspan="4">{{ doctorName }} 医生 (诊室: {{ getDoctorRoom(doctorName) }})</td>
|
||||
</tr>
|
||||
<!-- 患者列表 -->
|
||||
<tr v-for="(patient, index) in group" :key="patient.id">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ formatPatientName(patient.name) }}</td>
|
||||
<td>{{ getDoctorRoom(doctorName) }}</td>
|
||||
<td :style="{ color: index === 0 ? '#e74c3c' : '#27ae60' }">
|
||||
{{ index === 0 ? '就诊中' : '等待' }}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页控制 -->
|
||||
<div class="pagination-controls">
|
||||
<button
|
||||
id="prevPage"
|
||||
@click="previousPage"
|
||||
:disabled="currentPage === 1 || loading"
|
||||
>上一页</button>
|
||||
<span id="pageInfo">{{ currentPage }}/{{ totalPages }}</span>
|
||||
<button
|
||||
id="nextPage"
|
||||
@click="nextPage"
|
||||
:disabled="currentPage === totalPages || loading"
|
||||
>下一页</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 辅助信息区 -->
|
||||
<div class="info-bar">
|
||||
<div class="info-item">
|
||||
<span class="icon">⏱</span>
|
||||
<span>当前时间: {{ currentTime }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="icon">🔢</span>
|
||||
<span>当前号: {{ currentCall?.number || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="icon">👥</span>
|
||||
<span>等待人数: {{ waitingCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, onUnmounted, nextTick, watchEffect } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
// 响应式数据
|
||||
const currentTime = ref('')
|
||||
const currentCall = ref({
|
||||
number: '1',
|
||||
name: '李*四',
|
||||
room: '3号'
|
||||
})
|
||||
const patients = ref([])
|
||||
const loading = ref(false)
|
||||
const currentPage = ref(1)
|
||||
const patientsPerPage = 5
|
||||
const autoScrollInterval = ref(null)
|
||||
const scrollInterval = 5000 // 5秒自动翻页
|
||||
|
||||
// 科室名称
|
||||
const departmentName = ref('心内科叫号显示屏幕')
|
||||
|
||||
// 计算属性
|
||||
const groupedPatients = computed(() => {
|
||||
const grouped = {}
|
||||
patients.value.forEach(patient => {
|
||||
if (!grouped[patient.doctor]) {
|
||||
grouped[patient.doctor] = []
|
||||
}
|
||||
grouped[patient.doctor].push(patient)
|
||||
})
|
||||
return grouped
|
||||
})
|
||||
|
||||
const waitingCount = computed(() => {
|
||||
let count = 0
|
||||
Object.values(groupedPatients.value).forEach(group => {
|
||||
count += Math.max(0, group.length - 1) // 排除每个医生组中第一个就诊中的患者
|
||||
})
|
||||
return count
|
||||
})
|
||||
|
||||
const totalPages = computed(() => {
|
||||
const totalPatients = patients.value.length
|
||||
return Math.ceil(totalPatients / patientsPerPage) || 1
|
||||
})
|
||||
|
||||
// 方法
|
||||
const updateTime = () => {
|
||||
const now = dayjs()
|
||||
currentTime.value = now.format('YYYY-MM-DD HH:mm')
|
||||
}
|
||||
|
||||
const formatPatientName = (name) => {
|
||||
if (!name || typeof name !== 'string') return '-'
|
||||
if (name.length === 0) return '-'
|
||||
return name.charAt(0) + '*' + name.slice(-1)
|
||||
}
|
||||
|
||||
const getDoctorRoom = (doctorName) => {
|
||||
// 根据医生获取固定诊室
|
||||
const doctorRooms = {
|
||||
'张医生': '3号',
|
||||
'李医生': '1号',
|
||||
'王医生': '2号'
|
||||
}
|
||||
return doctorRooms[doctorName] || '1号'
|
||||
}
|
||||
|
||||
const generateWaitingData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
// 确保数组已正确初始化
|
||||
if (!Array.isArray(patients.value)) {
|
||||
patients.value = []
|
||||
}
|
||||
|
||||
// 模拟API调用获取候诊数据
|
||||
// 实际项目中这里应该调用真实API
|
||||
const mockData = [
|
||||
{ id: 13, name: '李四', type: '专家', doctor: '张医生', status: '就诊中' },
|
||||
{ id: 14, name: '王五', type: '普通', doctor: '李医生', status: '候诊中' },
|
||||
{ id: 15, name: '赵六', type: '专家', doctor: '张医生', status: '候诊中' },
|
||||
{ id: 16, name: '钱七', type: '普通', doctor: '王医生', status: '候诊中' },
|
||||
{ id: 17, name: '孙八', type: '专家', doctor: '李医生', status: '候诊中' },
|
||||
{ id: 18, name: '周九', type: '普通', doctor: '王医生', status: '候诊中' },
|
||||
{ id: 19, name: '吴十', type: '专家', doctor: '张医生', status: '候诊中' },
|
||||
{ id: 20, name: '郑一', type: '普通', doctor: '李医生', status: '候诊中' },
|
||||
{ id: 21, name: '王二', type: '专家', doctor: '王医生', status: '候诊中' },
|
||||
{ id: 22, name: '李三', type: '普通', doctor: '张医生', status: '候诊中' },
|
||||
{ id: 23, name: '赵四', type: '专家', doctor: '李医生', status: '候诊中' },
|
||||
{ id: 24, name: '钱五', type: '普通', doctor: '王医生', status: '候诊中' }
|
||||
]
|
||||
|
||||
// 模拟网络延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
patients.value = mockData
|
||||
} catch (error) {
|
||||
console.error('获取候诊数据失败:', error)
|
||||
ElMessage.error('获取候诊数据失败')
|
||||
// 出错时设置为空数组
|
||||
patients.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const previousPage = () => {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
if (currentPage.value < totalPages.value) {
|
||||
currentPage.value++
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
const scrollToTop = () => {
|
||||
nextTick(() => {
|
||||
const container = document.querySelector('.table-container')
|
||||
if (container && container.scrollTo) {
|
||||
container.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const startAutoScroll = () => {
|
||||
stopAutoScroll()
|
||||
autoScrollInterval.value = setInterval(() => {
|
||||
if (currentPage.value < totalPages.value) {
|
||||
currentPage.value++
|
||||
} else {
|
||||
currentPage.value = 1
|
||||
}
|
||||
scrollToTop()
|
||||
}, scrollInterval)
|
||||
}
|
||||
|
||||
const stopAutoScroll = () => {
|
||||
if (autoScrollInterval.value) {
|
||||
clearInterval(autoScrollInterval.value)
|
||||
autoScrollInterval.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(async () => {
|
||||
// 初始化时间
|
||||
updateTime()
|
||||
// 每分钟更新时间
|
||||
const timeInterval = setInterval(updateTime, 60000)
|
||||
|
||||
// 获取候诊数据
|
||||
await generateWaitingData()
|
||||
|
||||
// 启动自动滚动
|
||||
startAutoScroll()
|
||||
|
||||
// 鼠标悬停时暂停自动滚动
|
||||
const tableContainer = document.querySelector('.table-container')
|
||||
if (tableContainer) {
|
||||
tableContainer.addEventListener('mouseenter', stopAutoScroll)
|
||||
tableContainer.addEventListener('mouseleave', startAutoScroll)
|
||||
}
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
clearInterval(timeInterval)
|
||||
stopAutoScroll()
|
||||
if (tableContainer) {
|
||||
tableContainer.removeEventListener('mouseenter', stopAutoScroll)
|
||||
tableContainer.removeEventListener('mouseleave', startAutoScroll)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 组件卸载时的清理工作
|
||||
stopAutoScroll()
|
||||
})
|
||||
|
||||
// 监听页面变化,重置滚动位置
|
||||
watchEffect(() => {
|
||||
scrollToTop()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.call-number-display {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
background-color: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
border: 1px solid #eaeaea;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #4a90e2, #5fa3e8);
|
||||
color: white;
|
||||
padding: 20px 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 5px 15px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 当前呼叫区 */
|
||||
.current-call {
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.call-box {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 5px 20px rgba(74, 144, 226, 0.15);
|
||||
border: 1px solid #e0e7ff;
|
||||
animation: pulse 2s infinite;
|
||||
|
||||
.call-text {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
color: #4a90e2;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 候诊信息区 */
|
||||
.waiting-area {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.section-title {
|
||||
font-size: 1.4rem;
|
||||
color: #555;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid #4a90e2;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
.waiting-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
|
||||
th,
|
||||
.doctor-header td {
|
||||
background-color: #f0f7ff;
|
||||
color: #4a90e2;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
padding: 15px 20px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #fafcff;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
|
||||
.doctor-header {
|
||||
background-color: #f0f7ff !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 15px;
|
||||
gap: 10px;
|
||||
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 4px;
|
||||
background: #f8f9fa;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #e9ecef;
|
||||
border-color: #4a90e2;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
padding: 8px 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 辅助信息区 */
|
||||
.info-bar {
|
||||
background: #2c3e50;
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-radius: 12px;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.1rem;
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(74, 144, 226, 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 15px rgba(74, 144, 226, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(74, 144, 226, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.call-number-display {
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 10px;
|
||||
padding: 15px 20px;
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.current-call {
|
||||
padding: 15px;
|
||||
|
||||
.call-box {
|
||||
padding: 15px;
|
||||
|
||||
.call-text {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-bar {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
|
||||
.info-item {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.waiting-table {
|
||||
th, td {
|
||||
padding: 10px 15px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.current-call .call-box .call-text {
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.waiting-table th,
|
||||
.waiting-table td {
|
||||
padding: 8px 12px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user