feat(admin): streamline appointments and fleet intake

This commit is contained in:
2026-10-09 22:05:51 +08:00
parent 88dc4852c6
commit e1457a8e57
39 changed files with 1215 additions and 165 deletions
@@ -1,6 +1,7 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, ref } from 'vue'
import AppointmentInboundCodes from './AppointmentInboundCodes.vue'
import { addDays, calendarDates, calendarRange, dateKey, moveCalendar, type CalendarMode } from './calendar'
export interface CalendarEvent {
@@ -11,6 +12,7 @@ export interface CalendarEvent {
title: string
subtitle?: string
fields: Array<{ label: string; value?: string | null }>
inboundCodes?: Array<string | null | undefined>
canEdit?: boolean
canDelete?: boolean
}
@@ -18,9 +20,10 @@ export interface CalendarEvent {
const props = withDefaults(defineProps<{
events: CalendarEvent[]
loading?: boolean
incomplete?: boolean
canCreate?: boolean
initialDate?: string
}>(), { loading: false, canCreate: false, initialDate: '' })
}>(), { loading: false, incomplete: false, canCreate: false, initialDate: '' })
const emit = defineEmits<{
range: [range: { from: string; to: string }]
create: [date: string]
@@ -84,10 +87,10 @@ defineExpose({ selectedDate, mode })
<div v-loading="loading" class="calendar-grid" :class="{ 'week-grid': mode === 'week' }">
<button v-for="date in dates" :key="date" type="button" class="calendar-day"
:class="{ selected: selectedDate === date, outside: mode === 'month' && date.slice(0, 7) !== selectedDate.slice(0, 7) }"
:title="`${date},共 ${(grouped.get(date) || []).length} 条安排`" :aria-label="`${date},${(grouped.get(date) || []).length} 项安排`" :aria-pressed="selectedDate === date" @click="select(date)">
:title="`${date},${incomplete ? '已加载' : '共'} ${(grouped.get(date) || []).length} 条安排${incomplete ? '(部分来源未加载)' : ''}`" :aria-label="`${date},${(grouped.get(date) || []).length} 项安排${incomplete ? '(部分来源未加载)' : ''}`" :aria-pressed="selectedDate === date" @click="select(date)">
<span class="day-number">{{ mode === 'week' ? `${Number(date.slice(5, 7))}月` : '' }}{{ Number(date.slice(8)) }}</span>
<span class="day-events">
<span v-for="event in (grouped.get(date) || []).slice(0, 3)" :key="event.key" class="event-chip" :class="event.type" :title="`${event.time} ${event.title} · 当天共 ${(grouped.get(date) || []).length} 条安排`">
<span v-for="event in (grouped.get(date) || []).slice(0, 3)" :key="event.key" class="event-chip" :class="event.type" :title="`${event.time} ${event.title} · 当天${incomplete ? '已加载' : '共'} ${(grouped.get(date) || []).length} 条安排${incomplete ? '(部分来源未加载)' : ''}`">
{{ event.title }}<small v-if="mode === 'week'">{{ event.time }}</small>
</span>
<span v-if="(grouped.get(date) || []).length > 3" class="event-more">还有 {{ (grouped.get(date) || []).length - 3 }} 条</span>
@@ -96,13 +99,14 @@ defineExpose({ selectedDate, mode })
</div>
</div>
<aside class="calendar-detail" aria-label="当天安排">
<header><strong>{{ selectedDate }}</strong><p>当天共有 <b>{{ selectedEvents.length }}</b> 项安排</p></header>
<header><strong>{{ selectedDate }}</strong><p>{{ incomplete ? '当天已加载' : '当天共有' }} <b>{{ selectedEvents.length }}</b> 项安排<span v-if="incomplete">(部分来源未加载)</span></p></header>
<div class="detail-events" aria-live="polite">
<p v-if="!selectedEvents.length && !loading" class="empty-day">当天暂无到港或送仓预约</p>
<p v-if="!selectedEvents.length && !loading" class="empty-day">{{ incomplete ? '部分来源未加载,请重试后确认当天安排' : '当天暂无到港或送仓预约' }}</p>
<article v-for="event in selectedEvents" :key="event.key" class="detail-event">
<div class="event-heading"><span class="event-type" :class="event.type">{{ event.type === 'arrival' ? '船舶到港' : '预约送仓' }}</span><span>{{ event.time || '—' }}</span></div>
<h3>{{ event.title }}</h3><p v-if="event.subtitle" class="event-subtitle">{{ event.subtitle }}</p>
<dl><div v-for="field in event.fields" :key="field.label"><dt>{{ field.label }}</dt><dd>{{ field.value || '—' }}</dd></div></dl>
<AppointmentInboundCodes v-if="event.inboundCodes" :codes="event.inboundCodes" />
<footer v-if="event.canEdit || event.canDelete" class="density-actions">
<el-button v-if="event.canEdit" class="density-action" :disabled="loading" @click="emit('edit', event)">编辑</el-button>
<el-button v-if="event.canDelete" class="density-action" data-tone="danger" :disabled="loading" @click="emit('delete', event)">删除</el-button>