更新
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
<template>
|
||||
<div ref="paperRef" class="rx-paper">
|
||||
<div class="rx-title">{{ isInternal ? '药房联' : slipTitle }}</div>
|
||||
<div class="rx-notice">
|
||||
<span class="rx-notice-text">
|
||||
服药前请核对姓名、电话、医生等信息以及服法、医嘱等要点
|
||||
{{
|
||||
isInternal
|
||||
? '服药前请核对姓名、电话、医生等信息以及服法、医嘱等要点'
|
||||
: '服药前请核对姓名、电话、医生等信息以及医嘱等要点'
|
||||
}}
|
||||
</span>
|
||||
<span class="rx-notice-meta">
|
||||
<span>日期:{{ dateText }}</span>
|
||||
@@ -44,63 +49,98 @@
|
||||
</div>
|
||||
|
||||
<div class="rx-rp">
|
||||
<div class="rx-watermark">药房联</div>
|
||||
<div class="rx-watermark">{{ isInternal ? '药房联' : '处方联' }}</div>
|
||||
<div class="rx-rp-head">
|
||||
<div class="rx-rp-label">Rp.</div>
|
||||
<div class="rx-rp-cols-head">
|
||||
<div class="rx-rp-col-head">
|
||||
<span>用药 (单剂)</span>
|
||||
<span>总量</span>
|
||||
<span>{{ isInternal ? '总量' : '用量' }}</span>
|
||||
</div>
|
||||
<div class="rx-rp-col-head">
|
||||
<span>用药 (单剂)</span>
|
||||
<span>总量</span>
|
||||
<span>{{ isInternal ? '总量' : '用量' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rx-herbs">
|
||||
<template v-if="mainHerbs.length">
|
||||
<div class="rx-herb-section-label">主方</div>
|
||||
<div
|
||||
v-for="(h, i) in mainHerbs"
|
||||
:key="'main-' + i"
|
||||
class="rx-herb-cell"
|
||||
>
|
||||
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||
<span class="rx-herb-total">{{ herbTotal(h.dosage) }}克</span>
|
||||
</div>
|
||||
<template v-if="isInternal">
|
||||
<template v-if="mainHerbs.length">
|
||||
<div class="rx-herb-section-label">主方</div>
|
||||
<div
|
||||
v-for="(h, i) in mainHerbs"
|
||||
:key="'main-' + i"
|
||||
class="rx-herb-cell"
|
||||
>
|
||||
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||
<span class="rx-herb-total">{{ herbTotal(h.dosage) }}克</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="auxHerbs.length">
|
||||
<div class="rx-herb-section-label rx-herb-section-label--aux">
|
||||
辅方
|
||||
<span v-if="auxLibraryName" class="rx-herb-library-name">({{ auxLibraryName }})</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(h, i) in auxHerbs"
|
||||
:key="'aux-' + i"
|
||||
class="rx-herb-cell"
|
||||
>
|
||||
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||
<span class="rx-herb-total">{{ herbTotal(h.dosage) }}克</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="auxHerbs.length">
|
||||
<div class="rx-herb-section-label rx-herb-section-label--aux">辅方</div>
|
||||
<div
|
||||
v-for="(h, i) in auxHerbs"
|
||||
:key="'aux-' + i"
|
||||
class="rx-herb-cell"
|
||||
>
|
||||
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||
<span class="rx-herb-total">{{ herbTotal(h.dosage) }}克</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<template v-if="mainHerbs.length">
|
||||
<div class="rx-herb-section-label">主方</div>
|
||||
<div
|
||||
v-for="(h, i) in mainHerbs"
|
||||
:key="'user-main-' + i"
|
||||
class="rx-herb-cell"
|
||||
>
|
||||
<span class="rx-herb-name">{{ h.name }}</span>
|
||||
<span class="rx-herb-total">{{ h.dosage }}克</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="auxHerbs.length">
|
||||
<div class="rx-herb-section-label rx-herb-section-label--aux">辅方</div>
|
||||
<div
|
||||
v-for="(h, i) in auxHerbs"
|
||||
:key="'user-aux-' + i"
|
||||
class="rx-herb-cell"
|
||||
>
|
||||
<span class="rx-herb-name">{{ h.name }}</span>
|
||||
<span class="rx-herb-total">{{ h.dosage }}克</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rx-text">
|
||||
<p>主方服法:{{ usageText }}</p>
|
||||
<p v-if="auxUsageText">辅方服法:{{ auxUsageText }}</p>
|
||||
<template v-if="isInternal">
|
||||
<p v-if="auxUsageText">主服法:{{ usageText }}</p>
|
||||
<p v-else>服法:{{ usageText }}</p>
|
||||
<p v-if="auxUsageText">辅服法:{{ auxUsageText }}</p>
|
||||
</template>
|
||||
<p v-if="adviceText">医嘱:{{ adviceText }}</p>
|
||||
<p v-if="dietaryText">忌口:{{ dietaryText }}</p>
|
||||
<p v-if="remarkText">备注:{{ remarkText }}</p>
|
||||
<p v-if="isInternal && remarkText">备注:{{ remarkText }}</p>
|
||||
<p v-if="pharmacyRemarkText" class="rx-text-warn">
|
||||
药房备注:{{ pharmacyRemarkText }}
|
||||
</p>
|
||||
<p v-if="outPelletText" class="rx-text-warn">
|
||||
<p
|
||||
v-if="isInternal && outPelletText && data?.prescription_type !== '饮片'"
|
||||
class="rx-text-warn"
|
||||
>
|
||||
出丸:{{ outPelletText }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rx-bottom">
|
||||
<div class="rx-bot-row rx-bot-row-1">
|
||||
<div class="rx-bot-row rx-bot-row-1" :class="{ 'rx-bot-row-1--user': !isInternal }">
|
||||
<div class="rx-bot-cell rx-bot-stack rx-bot-doctor">
|
||||
<div class="rx-bot-label">医师</div>
|
||||
<div class="rx-bot-doctor-body">
|
||||
@@ -115,19 +155,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rx-bot-cell rx-bot-meta">
|
||||
<div v-if="isInternal" class="rx-bot-cell rx-bot-meta">
|
||||
<span class="rx-bot-meta-key">类型:</span>
|
||||
<span class="rx-bot-meta-val">{{ typeText }}</span>
|
||||
</div>
|
||||
<div class="rx-bot-cell rx-bot-meta">
|
||||
<span class="rx-bot-meta-key">天数:</span>
|
||||
<span class="rx-bot-meta-val">{{ data?.dose_count || '—' }}剂</span>
|
||||
<span class="rx-bot-meta-val">{{ daysText }}</span>
|
||||
</div>
|
||||
<div class="rx-bot-cell rx-bot-meta">
|
||||
<span class="rx-bot-meta-key">单剂量:</span>
|
||||
<div v-if="isInternal" class="rx-bot-cell rx-bot-meta">
|
||||
<span class="rx-bot-meta-key">剂量:</span>
|
||||
<span class="rx-bot-meta-val">{{ perDoseAmount }}克</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rx-bot-row rx-hospital-row">
|
||||
<div class="rx-hospital-line">成都双流甄养堂互联网医院有限公司 联系方式:4001667339</div>
|
||||
<div class="rx-hospital-line">地址:四川省成都市双流区黄甲街道黄龙大道二段280号</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -156,12 +200,27 @@ import {
|
||||
|
||||
defineOptions({ name: 'PrescriptionSlip' })
|
||||
|
||||
const props = defineProps<{
|
||||
/** 处方详情 / 列表行(字段与 tcm.prescription/detail 对齐) */
|
||||
data: Record<string, any> | null | undefined
|
||||
}>()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 处方详情 / 列表行(字段与 tcm.prescription/detail 对齐) */
|
||||
data: Record<string, any> | null | undefined
|
||||
/** 药房联 internal / 处方联 user */
|
||||
variant?: 'internal' | 'user'
|
||||
/** 药房联辅方处方库名称 */
|
||||
auxLibraryName?: string
|
||||
/** 抬头(处方联) */
|
||||
title?: string
|
||||
}>(),
|
||||
{
|
||||
variant: 'internal',
|
||||
auxLibraryName: '',
|
||||
title: '成都双流甄养堂互联网医院 处方笺'
|
||||
}
|
||||
)
|
||||
|
||||
const paperRef = ref<HTMLElement | null>(null)
|
||||
const isInternal = computed(() => props.variant !== 'user')
|
||||
const slipTitle = computed(() => props.title || '成都双流甄养堂互联网医院 处方笺')
|
||||
|
||||
const dateText = computed(() => rxDateText(props.data))
|
||||
const serialText = computed(() => rxSerialText(props.data))
|
||||
@@ -179,6 +238,17 @@ const pharmacyRemarkText = computed(() => rxPharmacyRemarkText(props.data))
|
||||
const outPelletText = computed(() => rxOutPelletText(props.data))
|
||||
const typeText = computed(() => rxTypeText(props.data))
|
||||
const perDoseAmount = computed(() => rxPerDoseAmount(props.data))
|
||||
const daysText = computed(() => {
|
||||
const md = props.data?.medication_days
|
||||
if (md != null && String(md).trim() !== '') {
|
||||
return `${md} 天`
|
||||
}
|
||||
const dose = props.data?.dose_count
|
||||
if (dose != null && String(dose).trim() !== '') {
|
||||
return `${dose}剂`
|
||||
}
|
||||
return '—'
|
||||
})
|
||||
|
||||
function herbTotal(dosage: number | string) {
|
||||
return rxHerbTotal(props.data, dosage)
|
||||
@@ -194,9 +264,8 @@ defineExpose({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* ============================================================
|
||||
* 处方笺(A4 药房联)样式
|
||||
* 处方笺(A4 药房联 / 处方联)样式
|
||||
* 严格按 A4:210mm × 297mm,padding 8mm 10mm
|
||||
* 唯一视觉基准:consumer/prescription/index.vue
|
||||
* ============================================================ */
|
||||
.rx-paper {
|
||||
width: 210mm;
|
||||
@@ -214,6 +283,15 @@ defineExpose({
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.rx-title {
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
margin: 0 0 8px;
|
||||
color: #1f1f1f;
|
||||
}
|
||||
|
||||
.rx-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -374,6 +452,11 @@ defineExpose({
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.rx-herb-library-name {
|
||||
font-weight: 500;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.rx-herb-cell {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 64px;
|
||||
@@ -430,10 +513,14 @@ defineExpose({
|
||||
}
|
||||
|
||||
.rx-bot-row-1 {
|
||||
grid-template-columns: 1.4fr 0.6fr 0.6fr 0.6fr 0.6fr 1.7fr 0.95fr 1.25fr;
|
||||
grid-template-columns: 1.6fr 0.9fr 0.9fr 0.9fr;
|
||||
min-height: 70px;
|
||||
}
|
||||
|
||||
.rx-bot-row-1--user {
|
||||
grid-template-columns: 2fr 1fr;
|
||||
}
|
||||
|
||||
.rx-bot-cell {
|
||||
border-right: 1px solid #c8c8c8;
|
||||
padding: 6px 10px;
|
||||
@@ -504,6 +591,18 @@ defineExpose({
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.rx-hospital-row {
|
||||
display: block;
|
||||
padding: 8px 10px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.rx-hospital-line {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
line-height: 1.7;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 全局打印样式:body * 选择器不能放进 scoped -->
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user