<template>
|
<!--
|
预约时段维护(queue_sequence)
|
|
入口:检查项目管理列表 → 操作列「预约时段」按钮
|
功能:对**某一个检查项目**的预约时段规则做增 / 删 / 改
|
|
背景:该表原先每天自动生成,现已改为手工维护,此前没有维护界面。
|
-->
|
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1000px">
|
<!-- 顶部说明:这些字段的含义与"每日重置"的关系 -->
|
<el-alert type="info" :closable="false" class="mb-10px">
|
<template #title>
|
<div class="text-13px leading-6">
|
正在维护检查项目:<b>{{ checkTypeName }}</b>
|
<span class="text-gray-500">(检查项目值 {{ checkTypeValue }},共 {{ list.length }} 个时段)</span>
|
</div>
|
</template>
|
<div class="text-12px leading-5">
|
· <b>起始序号</b>:每天自动重置时回到该值;<b>上限</b>:到达后该时段视为「已满」,患者签到会顺延到后续时段。<br />
|
· <b>当前序号</b>:已发到几号,一般无需手工改(每日重置会自动回到"起始序号")。<br />
|
· <b>序号上限必须大于起始序号</b>,否则该时段永远无法取号。<br />
|
· 同一检查项目下<b>同一时段只能有一条规则</b>,重复添加会被拒绝。
|
</div>
|
</el-alert>
|
|
<div class="mb-10px">
|
<el-button type="primary" @click="openForm('create')" :disabled="!checkTypeValue">
|
<Icon icon="ep:plus" class="mr-5px" /> 新增时段
|
</el-button>
|
<el-button @click="getList" :loading="loading">
|
<Icon icon="ep:refresh" class="mr-5px" /> 刷新
|
</el-button>
|
<span v-if="changedCount > 0" class="ml-10px text-13px text-orange-500">
|
本次已修改 {{ changedCount }} 个时段,点「刷新」可放弃未保存的展示改动
|
</span>
|
</div>
|
|
<el-table v-loading="loading" :data="list" :stripe="true" border height="420px">
|
<el-table-column label="时段" align="center" min-width="130px">
|
<template #default="scope">
|
<b>{{ formatTimeSlot(scope.row.timeSlot) }}</b>
|
<div class="text-11px text-gray-400">{{ scope.row.timeSlot }}</div>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="普通-起始序号" align="center" min-width="120px">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.queueSet"
|
:min="0"
|
:controls="false"
|
size="small"
|
style="width: 100%"
|
@change="markChanged(scope.row)"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="普通-上限" align="center" min-width="110px">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.queueFull"
|
:min="1"
|
:controls="false"
|
size="small"
|
style="width: 100%"
|
@change="markChanged(scope.row)"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="普通-当前序号" align="center" min-width="115px">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.queueNo"
|
:min="0"
|
:controls="false"
|
size="small"
|
style="width: 100%"
|
@change="markChanged(scope.row)"
|
/>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="VIP-起始序号" align="center" min-width="115px">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.queueVipSet"
|
:min="0"
|
:controls="false"
|
size="small"
|
style="width: 100%"
|
@change="markChanged(scope.row)"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="VIP-上限" align="center" min-width="105px">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.queueVipFull"
|
:min="1"
|
:controls="false"
|
size="small"
|
style="width: 100%"
|
@change="markChanged(scope.row)"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="VIP-当前序号" align="center" min-width="110px">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.queueVipNo"
|
:min="0"
|
:controls="false"
|
size="small"
|
style="width: 100%"
|
@change="markChanged(scope.row)"
|
/>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="状态" align="center" width="90px">
|
<template #default="scope">
|
<el-tag v-if="isFull(scope.row)" type="danger" size="small">已满</el-tag>
|
<el-tag v-else type="success" size="small">可取号</el-tag>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="操作" align="center" width="140px" fixed="right">
|
<template #default="scope">
|
<el-button
|
link
|
type="primary"
|
:loading="savingId === scope.row.id"
|
@click="handleRowSave(scope.row)"
|
>
|
保存
|
</el-button>
|
<el-button link type="primary" @click="openForm('update', scope.row)">编辑</el-button>
|
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 新增 / 编辑表单 -->
|
<Dialog v-model="formVisible" :title="formTitle" width="520px" append-to-body>
|
<el-form
|
ref="formRef"
|
:model="formData"
|
:rules="formRules"
|
label-width="120px"
|
v-loading="formLoading"
|
>
|
<el-form-item label="时段" prop="timeSlot">
|
<el-select
|
v-model="formData.timeSlot"
|
placeholder="请选择预约时段"
|
class="w-full"
|
filterable
|
allow-create
|
default-first-option
|
>
|
<el-option
|
v-for="opt in timeSlotOptions"
|
:key="opt.value"
|
:label="opt.label"
|
:value="opt.value"
|
/>
|
</el-select>
|
<div class="text-12px text-gray-500 leading-5 mt-1">
|
选项来自字典 <code>ecg_book_timeslot</code>;也支持直接输入起始/结束时间后回车新建。
|
</div>
|
</el-form-item>
|
<el-form-item label="普通-起始序号" prop="queueSet">
|
<el-input-number v-model="formData.queueSet" :min="0" class="w-full" />
|
</el-form-item>
|
<el-form-item label="普通-上限" prop="queueFull">
|
<el-input-number v-model="formData.queueFull" :min="1" class="w-full" />
|
</el-form-item>
|
<el-form-item label="VIP-起始序号" prop="queueVipSet">
|
<el-input-number v-model="formData.queueVipSet" :min="0" class="w-full" />
|
</el-form-item>
|
<el-form-item label="VIP-上限" prop="queueVipFull">
|
<el-input-number v-model="formData.queueVipFull" :min="1" class="w-full" />
|
</el-form-item>
|
<el-form-item label="普通-当前序号">
|
<el-input-number v-model="formData.queueNo" :min="0" class="w-full" />
|
<div class="text-12px text-gray-500 leading-5 mt-1">
|
新增时段时建议与「普通-起始序号」一致。
|
</div>
|
</el-form-item>
|
<el-form-item label="VIP-当前序号">
|
<el-input-number v-model="formData.queueVipNo" :min="0" class="w-full" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
<el-button @click="formVisible = false">取 消</el-button>
|
</template>
|
</Dialog>
|
</Dialog>
|
</template>
|
|
<script setup lang="ts">
|
import { ElMessageBox } from 'element-plus'
|
import {
|
QueueSequenceApi,
|
QueueSequenceVO,
|
formatTimeSlot,
|
parseTimeSlot
|
} from '@/api/ecg/queuesequence'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
|
/** 预约时段维护 弹窗 */
|
defineOptions({ name: 'QueueSequenceForm' })
|
|
const message = useMessage() // 消息弹窗
|
|
const dialogVisible = ref(false) // 主弹窗
|
const loading = ref(false) // 列表加载中
|
const list = ref<QueueSequenceVO[]>([]) // 某检查项目下的全部时段
|
const checkTypeValue = ref<number>() // 当前维护的检查项目值
|
const checkTypeName = ref<string>('') // 检查项目名称(仅用于展示)
|
const changedRows = ref<Set<number>>(new Set()) // 已被改动但未保存的行 id
|
const savingId = ref<number>() // 正在保存的行 id
|
|
const dialogTitle = computed(
|
() => `预约时段维护${checkTypeName.value ? ' - ' + checkTypeName.value : ''}`
|
)
|
const changedCount = computed(() => changedRows.value.size)
|
|
/** 时段下拉选项:取自字典 ecg_book_timeslot(value 为 HHMMHHMM 数字字符串,label 形如 08:00-08:30) */
|
const timeSlotOptions = computed(() => {
|
const opts = getIntDictOptions(DICT_TYPE.ECG_BOOK_TIMESLOT) || []
|
return opts
|
.map((d: any) => {
|
const value = Number(d.value)
|
const label = (d.label || '').trim()
|
return {
|
value,
|
label: label ? `${label}(${value})` : `${formatTimeSlot(value)}(${value})`
|
}
|
})
|
.filter((o: any) => !Number.isNaN(o.value))
|
})
|
|
// ==================== 表单(新增/编辑) ====================
|
const formVisible = ref(false)
|
const formTitle = ref('')
|
const formLoading = ref(false)
|
const formRef = ref()
|
const formData = ref<QueueSequenceVO>({})
|
|
const formRules = reactive({
|
timeSlot: [{ required: true, message: '时段不能为空', trigger: 'change' }],
|
queueSet: [{ required: true, message: '普通起始序号不能为空', trigger: 'blur' }],
|
queueFull: [
|
{ required: true, message: '普通序号上限不能为空', trigger: 'blur' },
|
{
|
validator: (_rule: any, value: any, callback: any) => {
|
const set = formData.value.queueSet
|
if (value !== undefined && value !== null && set !== undefined && set !== null && value <= set) {
|
callback(new Error('上限必须大于起始序号,否则该时段无法取号'))
|
} else {
|
callback()
|
}
|
},
|
trigger: 'blur'
|
}
|
]
|
})
|
|
/** 打开维护弹窗 */
|
const open = async (checkTypeVO: any) => {
|
checkTypeValue.value = checkTypeVO?.value
|
checkTypeName.value = checkTypeVO?.name ?? ''
|
changedRows.value = new Set()
|
dialogVisible.value = true
|
await getList()
|
}
|
|
defineExpose({ open })
|
|
/** 加载该检查项目下的全部时段 */
|
const getList = async () => {
|
if (!checkTypeValue.value) {
|
list.value = []
|
return
|
}
|
loading.value = true
|
try {
|
const data = await QueueSequenceApi.getTimeslotByCheckType(checkTypeValue.value)
|
// 后端按 id 返回;这里按 timeSlot 升序,便于按时间顺序检视
|
list.value = (data || []).sort(
|
(a: QueueSequenceVO, b: QueueSequenceVO) => (a.timeSlot ?? 0) - (b.timeSlot ?? 0)
|
)
|
changedRows.value = new Set()
|
} finally {
|
loading.value = false
|
}
|
}
|
|
/** 行内改动标记 */
|
const markChanged = (row: QueueSequenceVO) => {
|
if (row.id) {
|
const s = new Set(changedRows.value)
|
s.add(row.id)
|
changedRows.value = s
|
}
|
}
|
|
/** 该时段是否已满(当前序号 >= 上限) */
|
const isFull = (row: QueueSequenceVO) =>
|
(row.queueNo ?? 0) >= (row.queueFull ?? 0) && (row.queueFull ?? 0) > 0
|
|
/** 行内保存(只改数值,不动时段) */
|
const handleRowSave = async (row: QueueSequenceVO) => {
|
if (!row.id) return
|
if ((row.queueFull ?? 0) <= (row.queueSet ?? 0)) {
|
message.error(`时段 ${formatTimeSlot(row.timeSlot)}:上限必须大于起始序号`)
|
return
|
}
|
savingId.value = row.id
|
try {
|
await QueueSequenceApi.updateQueueSequence({ ...row, checkType: checkTypeValue.value })
|
message.success('保存成功')
|
const s = new Set(changedRows.value)
|
s.delete(row.id)
|
changedRows.value = s
|
} finally {
|
savingId.value = undefined
|
}
|
}
|
|
/** 打开新增/编辑表单 */
|
const openForm = (type: string, row?: QueueSequenceVO) => {
|
formVisible.value = true
|
formTitle.value = type === 'create' ? '新增预约时段' : '编辑预约时段'
|
if (type === 'update' && row) {
|
formData.value = { ...row }
|
} else {
|
formData.value = {
|
checkType: checkTypeValue.value,
|
timeSlot: undefined,
|
queueSet: 0,
|
queueFull: 10,
|
queueNo: 0,
|
queueVipSet: 0,
|
queueVipFull: 10,
|
queueVipNo: 0
|
}
|
}
|
}
|
|
/** 提交表单 */
|
const submitForm = async () => {
|
if (!formRef.value) return
|
await formRef.value.validate()
|
formLoading.value = true
|
try {
|
const data = { ...formData.value, checkType: checkTypeValue.value }
|
if (data.id) {
|
await QueueSequenceApi.updateQueueSequence(data)
|
message.success('修改成功')
|
} else {
|
await QueueSequenceApi.createQueueSequence(data)
|
message.success('新增成功')
|
}
|
formVisible.value = false
|
await getList()
|
} finally {
|
formLoading.value = false
|
}
|
}
|
|
/** 删除时段 */
|
const handleDelete = async (row: QueueSequenceVO) => {
|
if (!row.id) return
|
try {
|
await ElMessageBox.confirm(
|
`确认删除时段【${formatTimeSlot(row.timeSlot)}】的预约规则吗?` +
|
`删除后 HIS 若仍下发该时段,签到将自动顺延到后续可用时段。`,
|
'提示',
|
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
)
|
} catch {
|
return // 用户取消
|
}
|
await QueueSequenceApi.deleteQueueSequence(row.id)
|
message.success('删除成功')
|
await getList()
|
}
|
</script>
|