This commit is contained in:
Your Name
2026-08-10 17:29:05 +08:00
parent 2199887c07
commit 9add23e019
129 changed files with 34157 additions and 59 deletions
+4 -2
View File
@@ -89,7 +89,7 @@
>
<div class="slot-time">{{ slot.time }}</div>
<div class="slot-status" :class="{ 'status-available': slot.available }">
{{ slot.available ? '可约' : '已约' }}
{{ slot.available ? '可约' : slot.hasAppointment ? '已约' : '空号' }}
</div>
</div>
</div>
@@ -115,6 +115,7 @@ dayjs.extend(isoWeek)
interface TimeSlot {
time: string
available: boolean
hasAppointment: boolean
quota: number
}
@@ -186,7 +187,7 @@ const filteredTimeSlots = computed(() => {
const slotDateTime = dayjs(`${form.date} ${slot.time}`)
const isPast = slotDateTime.isBefore(now) || slotDateTime.isSame(now, 'minute')
// 如果时间已过,标记为不可用
// 如果时间已过,标记为不可用;保留原始挂号状态以区分“已约”和“空号”
if (isPast) {
return {
...slot,
@@ -315,6 +316,7 @@ const loadTimeSlots = async (silent = false) => {
timeSlots.value = (response?.slots || []).map((slot: any) => ({
time: slot.time,
available: slot.available,
hasAppointment: Boolean(slot.has_appointment ?? (slot.available === false)),
quota: slot.available ? 1 : 0
}))
} catch (error) {
@@ -61,12 +61,23 @@
class="channel-select"
@change="loadDashboard"
>
<el-option
v-for="item in dashboard.filters.media_channels"
:key="item.code"
:label="item.name"
:value="item.code"
/>
<el-option-group
v-for="group in mediaChannelGroups"
:key="group.group_name || '_'"
:label="`${group.group_name || '(未分组)'} · ${group.customer_count}`"
>
<el-option
v-for="item in group.channels"
:key="item.code"
:label="item.name"
:value="item.code"
>
<span class="channel-option">
<span>{{ item.name }}</span>
<span>{{ item.customer_count }} </span>
</span>
</el-option>
</el-option-group>
</el-select>
</div>
<span class="range-text">{{ dashboard.meta.start_date }} {{ dashboard.meta.end_date }}</span>
@@ -246,6 +257,13 @@ import vCharts from 'vue-echarts'
import { firstVisitConversionOverview, type FirstVisitConversionParams } from '@/api/first_visit'
type MetricType = 'count' | 'money' | 'ratio'
type MediaChannelOption = {
code: string
name: string
tag_id?: string
group_name?: string
customer_count?: number
}
const emptyDashboard = () => ({
meta: {
@@ -256,7 +274,7 @@ const emptyDashboard = () => ({
filters: {
departments: [] as any[],
assistants: [] as Array<{ id: number; name: string }>,
media_channels: [] as Array<{ code: string; name: string }>
media_channels: [] as MediaChannelOption[]
},
summary: {} as Record<string, any>,
rankings: { orders: [] as any[], amounts: [] as any[] },
@@ -303,6 +321,23 @@ const scopeDescription = computed(() => {
if (dashboard.meta.selected_media_channel_name) parts.push(`渠道:${dashboard.meta.selected_media_channel_name}`)
return parts.join(' · ')
})
const mediaChannelGroups = computed(() => {
const groups = new Map<string, {
group_name: string
customer_count: number
channels: MediaChannelOption[]
}>()
for (const channel of dashboard.filters.media_channels) {
const groupName = channel.group_name || ''
if (!groups.has(groupName)) {
groups.set(groupName, { group_name: groupName, customer_count: 0, channels: [] })
}
const group = groups.get(groupName)!
group.channels.push(channel)
group.customer_count = Math.max(group.customer_count, Number(channel.customer_count || 0))
}
return Array.from(groups.values())
})
const maxOrderValue = computed(() => Math.max(0, ...dashboard.rankings.orders.map(item => Number(item.value || 0))))
const maxAmountValue = computed(() => Math.max(0, ...dashboard.rankings.amounts.map(item => Number(item.value || 0))))
const targetChartOption = computed(() => ({
@@ -445,6 +480,18 @@ onMounted(loadDashboard)
.employee-select { width: 190px; }
.dept-select { width: 220px; }
.channel-select { width: 180px; }
.channel-option {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
width: 100%;
span:last-child {
color: #98a2b3;
font-size: 12px;
}
}
.metric-grid {
display: grid;
@@ -120,7 +120,7 @@
<section class="board-section queue-section">
<div class="section-heading queue-heading">
<div class="heading-copy">
<h2>候诊列表</h2>
<h2>{{ queueDateLabel }}候诊列表</h2>
<span class="heading-badge">按医生排队</span>
<span class="queue-count"> {{ pager.count }} </span>
</div>
@@ -168,7 +168,7 @@
</template>
</el-table-column>
<template #empty>
<el-empty description="当前权限范围内今日暂无候诊患者" />
<el-empty :description="`当前权限范围内${queueDateLabel}暂无候诊患者`" />
</template>
</el-table>
@@ -196,11 +196,12 @@ const formData = reactive({
end_date: today
})
const { pager, getLists } = usePaging({
const { pager, getLists, resetPage } = usePaging({
fetchFun: myPatientProgressLists as any,
params: formData,
size: 15,
firstLoading: true
firstLoading: true,
latestOnly: true
})
const todayOverview = computed(() => ({
@@ -259,6 +260,10 @@ const selectedScheduleLabel = computed(() => {
const day = selectedScheduleDay.value
return `${day.date_text || ''} ${day.weekday || ''} `
})
const queueDateLabel = computed(() => {
if (selectedScheduleDate.value === today) return '今日'
return selectedScheduleLabel.value.trim() || selectedScheduleDate.value
})
const scopeLabel = computed(() => pager.extend?.scope?.label || '按权限加载')
const scheduleRange = computed(() => {
@@ -274,7 +279,11 @@ function refreshPanel(options?: { silent?: boolean }) {
}
function selectScheduleDay(date: string) {
if (!date) return
selectedScheduleDate.value = date
formData.start_date = date
formData.end_date = date
resetPage()
}
function doctorWindows(doctor: Record<string, any>) {