This commit is contained in:
Your Name
2026-08-12 18:09:57 +08:00
parent a1092c02c3
commit b2eb18c75f
391 changed files with 1666 additions and 638 deletions
@@ -1770,6 +1770,17 @@
</div>
</template>
<div v-loading="prescriptionViewLoading" class="rx-wrap">
<!-- 药房联 / 处方联切换 -->
<el-tabs
v-if="prescriptionViewData"
v-model="prescriptionTabType"
class="mx-4 mt-2"
style="margin-bottom: 0; z-index: 1; position: relative"
>
<el-tab-pane label="药房联" name="internal" />
<el-tab-pane label="处方联" name="user" />
</el-tabs>
<!-- 状态条不进入打印/导出范围 -->
<div class="rx-statusbar" v-if="prescriptionViewData">
<el-tag
@@ -1822,6 +1833,9 @@
v-if="prescriptionViewData"
ref="rxSlipRef"
:data="prescriptionViewData"
:variant="prescriptionTabType"
:aux-library-name="slipAuxLibraryName"
:title="SLIP_TITLE"
/>
<!-- 审核痕迹不进入导出 -->
@@ -4466,6 +4480,8 @@ const prescriptionViewLoading = ref(false)
const prescriptionViewData = ref<any>(null)
/** 药房联辅方标题:处方库中该辅方模板的 prescription_name(导入或药材匹配解析) */
const slipAuxLibraryName = ref('')
/** 查看处方联次:药房联 / 处方联 */
const prescriptionTabType = ref<'internal' | 'user'>('internal')
const rxSlipRef = ref<InstanceType<typeof PrescriptionSlip> | null>(null)
const prescriptionSlipExporting = ref(false)
@@ -4864,6 +4880,7 @@ async function openPrescriptionView(row: any) {
prescriptionViewLoading.value = true
prescriptionViewData.value = null
slipAuxLibraryName.value = ''
prescriptionTabType.value = 'internal'
try {
const res: any = await prescriptionDetail({ id: row.prescription_id })
@@ -2342,6 +2342,17 @@
</div>
</template>
<div v-loading="prescriptionViewLoading" class="rx-wrap">
<!-- 药房联 / 处方联切换 -->
<el-tabs
v-if="prescriptionViewData"
v-model="prescriptionTabType"
class="mx-4 mt-2"
style="margin-bottom: 0; z-index: 1; position: relative"
>
<el-tab-pane label="药房联" name="internal" />
<el-tab-pane label="处方联" name="user" />
</el-tabs>
<!-- 状态条不进入打印/导出范围 -->
<div class="rx-statusbar" v-if="prescriptionViewData">
<el-tag
@@ -2394,6 +2405,8 @@
v-if="prescriptionViewData"
ref="rxSlipRef"
:data="prescriptionViewData"
:variant="prescriptionTabType"
:title="SLIP_TITLE"
/>
<!-- 审核痕迹不进入导出 -->
@@ -5296,6 +5309,8 @@ const SLIP_ADDRESS_LINE = '地址:四川省成都市双流区黄甲街道黄
const prescriptionViewVisible = ref(false)
const prescriptionViewLoading = ref(false)
const prescriptionViewData = ref<any>(null)
/** 查看处方联次:药房联 / 处方联 */
const prescriptionTabType = ref<'internal' | 'user'>('internal')
const rxSlipRef = ref<InstanceType<typeof PrescriptionSlip> | null>(null)
const prescriptionSlipExporting = ref(false)
@@ -5563,7 +5578,8 @@ async function openPrescriptionView(row: any) {
prescriptionViewVisible.value = true
prescriptionViewLoading.value = true
prescriptionViewData.value = null
prescriptionTabType.value = 'internal'
try {
const res: any = await prescriptionDetail({ id: row.prescription_id })
const base = res?.data ?? res ?? null
@@ -1,92 +1,100 @@
<template>
<section class="embedded-panel" v-loading="loading">
<div class="panel-toolbar">
<div>
<h2>医生排班</h2>
<p>查看近 7 日出诊医生及可约时段与医生排班管理同口径</p>
</div>
<div class="toolbar-actions">
<el-button
:icon="Refresh"
:loading="refreshing"
:disabled="!selectedDoctorId || !form.date"
@click="handleRefreshSlots"
>
刷新时段
</el-button>
</div>
</div>
<el-form :model="form" label-width="88px" class="paiban-form">
<el-form-item label="选择医生">
<div class="doctor-list">
<el-radio-group v-model="selectedDoctorId" @change="handleDoctorChange">
<el-radio
v-for="doctor in doctorList"
:key="doctor.id"
:value="doctor.id"
class="doctor-radio"
>
{{ doctor.name }}
</el-radio>
</el-radio-group>
<el-empty
v-if="!loading && doctorList.length === 0"
description="暂无可用医生"
:image-size="64"
/>
<div class="paiban-container" v-loading="loading">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>医生排班管理</span>
<el-button
text
type="primary"
size="small"
:loading="refreshing"
:disabled="!selectedDoctorId || !form.date"
@click="handleRefreshSlots"
>
<template #icon>
<Refresh />
</template>
刷新
</el-button>
</div>
</el-form-item>
</template>
<el-form-item label="排班时间">
<el-empty v-if="!selectedDoctorId" description="请先选择医生" :image-size="72" />
<el-empty
v-else-if="selectedDoctorId && doctorRosterDates.length === 0"
description="该医生暂无排班"
:image-size="72"
/>
<div v-else class="paiban-time-container">
<div class="date-selector">
<el-button
v-for="dateOption in dateOptions"
:key="dateOption.date"
:type="form.date === dateOption.date ? 'primary' : ''"
class="date-button"
@click="selectDate(dateOption.date)"
>
{{ dateOption.label }}
</el-button>
</div>
<div v-if="form.date" class="time-slots-container">
<div class="time-slots-grid">
<div
v-for="slot in filteredTimeSlots"
:key="slot.time"
class="time-slot-item"
:class="{
available: slot.available,
unavailable: !slot.available,
selected: form.selectedTime === slot.time
}"
@click="selectTimeSlot(slot)"
<el-form :model="form" label-width="100px" class="paiban-form">
<!-- 选择医生 -->
<el-form-item label="选择医生:">
<div class="doctor-list">
<el-radio-group v-model="selectedDoctorId" @change="handleDoctorChange">
<el-radio
v-for="doctor in doctorList"
:key="doctor.id"
:value="doctor.id"
class="doctor-radio"
>
<div class="slot-time">{{ slot.time }}</div>
<div class="slot-status" :class="{ 'status-available': slot.available }">
{{ slot.available ? '可约' : slot.hasAppointment ? '已约' : '空号' }}
{{ doctor.name }}
</el-radio>
</el-radio-group>
</div>
</el-form-item>
<!-- 排班时间 -->
<el-form-item label="排班时间:">
<!-- 未选择医生提示 -->
<el-empty
v-if="!selectedDoctorId"
description="请先选择医生"
:image-size="80"
/>
<!-- 医生无排班提示 -->
<el-empty
v-else-if="selectedDoctorId && doctorRosterDates.length === 0"
description="该医生暂无排班"
:image-size="80"
/>
<!-- 有排班时显示日期和时间段 -->
<div v-else class="paiban-time-container">
<!-- 日期选择 -->
<div class="date-selector">
<el-button
v-for="dateOption in dateOptions"
:key="dateOption.date"
:type="form.date === dateOption.date ? 'primary' : ''"
class="date-button"
@click="selectDate(dateOption.date)"
>
{{ dateOption.label }}
</el-button>
</div>
<!-- 时间段区域 -->
<div v-if="form.date" class="time-slots-container">
<!-- 时间段网格 -->
<div class="time-slots-grid">
<div
v-for="slot in filteredTimeSlots"
:key="slot.time"
class="time-slot-item"
:class="{
available: slot.available,
unavailable: !slot.available,
selected: form.selectedTime === slot.time
}"
@click="selectTimeSlot(slot)"
>
<div class="slot-time">{{ slot.time }}</div>
<div class="slot-status" :class="{ 'status-available': slot.available }">
{{ slot.available ? '可约' : slot.hasAppointment ? '已约' : '空号' }}
</div>
</div>
</div>
</div>
<el-empty
v-if="filteredTimeSlots.length === 0"
description="当前日期暂无可展示时段"
:image-size="64"
/>
</div>
</div>
</el-form-item>
</el-form>
</section>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
@@ -164,17 +172,21 @@ const filteredTimeSlots = computed(() => {
const slotDateTime = dayjs(`${form.date} ${slot.time}`)
const isPast = slotDateTime.isBefore(now) || slotDateTime.isSame(now, 'minute')
if (isPast) {
return { ...slot, available: false }
return {
...slot,
available: false
}
}
return slot
})
})
async function loadDoctors() {
const loadDoctors = async () => {
try {
loading.value = true
const res = await getDoctors()
doctorList.value = res || []
if (doctorList.value.length > 0 && !selectedDoctorId.value) {
selectedDoctorId.value = doctorList.value[0].id
await handleDoctorChange()
@@ -187,25 +199,28 @@ async function loadDoctors() {
}
}
async function handleDoctorChange() {
const handleDoctorChange = async () => {
form.selectedTime = ''
form.date = ''
timeSlots.value = []
doctorRosterDates.value = []
if (selectedDoctorId.value) {
await loadDoctorRoster()
}
}
async function loadDoctorRoster() {
const loadDoctorRoster = async () => {
if (!selectedDoctorId.value) {
return
}
try {
loading.value = true
const startDate = dayjs().format('YYYY-MM-DD')
const endDate = dayjs().add(6, 'day').format('YYYY-MM-DD')
const res = await rosterLists({
doctor_id: selectedDoctorId.value,
start_date: startDate,
@@ -216,6 +231,7 @@ async function loadDoctorRoster() {
if (res?.lists && res.lists.length > 0) {
doctorRosterDates.value = [...new Set(res.lists.map((item: any) => item.date))] as string[]
doctorRosterDates.value.sort()
await nextTick()
if (doctorRosterDates.value.length > 0) {
const today = dayjs().format('YYYY-MM-DD')
@@ -237,7 +253,7 @@ async function loadDoctorRoster() {
}
}
function selectDate(date: string) {
const selectDate = (date: string) => {
form.date = date
form.selectedTime = ''
if (selectedDoctorId.value) {
@@ -245,16 +261,19 @@ function selectDate(date: string) {
}
}
async function loadTimeSlots(silent = false) {
const loadTimeSlots = async (silent = false) => {
if (!selectedDoctorId.value || !form.date) {
return
}
if (isLoadingSlots) {
console.log('正在加载中,跳过本次请求')
return
}
try {
isLoadingSlots = true
if (!silent) {
loading.value = true
}
@@ -285,7 +304,7 @@ async function loadTimeSlots(silent = false) {
}
}
function selectTimeSlot(slot: TimeSlot) {
const selectTimeSlot = (slot: TimeSlot) => {
if (!slot.available) {
feedback.msgWarning('该时间段不可预约')
return
@@ -293,10 +312,11 @@ function selectTimeSlot(slot: TimeSlot) {
form.selectedTime = slot.time
}
async function handleRefreshSlots() {
const handleRefreshSlots = async () => {
if (!selectedDoctorId.value || !form.date) {
return
}
if (isLoadingSlots) {
feedback.msgWarning('正在刷新中,请稍候')
return
@@ -306,13 +326,16 @@ async function handleRefreshSlots() {
refreshing.value = true
const previousSelection = form.selectedTime
form.selectedTime = ''
await loadTimeSlots()
if (previousSelection) {
const slot = timeSlots.value.find((s) => s.time === previousSelection)
if (slot?.available) {
if (slot && slot.available) {
form.selectedTime = previousSelection
}
}
feedback.msgSuccess('刷新成功')
} catch (error) {
console.error('刷新失败:', error)
@@ -322,8 +345,9 @@ async function handleRefreshSlots() {
}
}
function startAutoRefresh() {
const startAutoRefresh = () => {
stopAutoRefresh()
autoRefreshTimer = window.setInterval(() => {
if (selectedDoctorId.value && form.date) {
loadTimeSlots(true)
@@ -331,7 +355,7 @@ function startAutoRefresh() {
}, 5000)
}
function stopAutoRefresh() {
const stopAutoRefresh = () => {
if (autoRefreshTimer) {
clearInterval(autoRefreshTimer)
autoRefreshTimer = null
@@ -362,36 +386,16 @@ onUnmounted(() => {
</script>
<style scoped lang="scss">
.embedded-panel {
padding-top: 4px;
.paiban-container {
padding: 0;
}
.panel-toolbar {
.card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
h2 {
margin: 0;
font-size: 18px;
font-weight: 700;
color: #1f2a37;
}
p {
margin: 6px 0 0;
color: #667085;
font-size: 13px;
}
}
.toolbar-actions {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
font-size: 16px;
font-weight: 600;
}
.paiban-form {
@@ -404,10 +408,14 @@ onUnmounted(() => {
display: flex;
flex-wrap: wrap;
gap: 12px;
width: 100%;
.doctor-radio {
margin-right: 0;
:deep(.el-radio__label) {
display: flex;
align-items: center;
}
}
}
@@ -419,7 +427,7 @@ onUnmounted(() => {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 16px;
margin-bottom: 20px;
.date-button {
min-width: 130px;
@@ -441,6 +449,19 @@ onUnmounted(() => {
padding: 16px;
}
.time-slots-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.header-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
}
.time-slots-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
@@ -467,7 +488,7 @@ onUnmounted(() => {
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 8px;
padding: 0px 8px;
border: 2px solid #e4e7ed;
border-radius: 8px;
cursor: pointer;
@@ -485,7 +506,7 @@ onUnmounted(() => {
.slot-status {
font-size: 12px;
color: #909399;
padding: 0 8px;
padding: 0px 8px;
border-radius: 4px;
background-color: #f4f4f5;