fix(appointments): prioritize calendar and collapse data notices

This commit is contained in:
2026-10-10 16:08:25 +08:00
parent c69115f1b7
commit e404fe1049
4 changed files with 41 additions and 28 deletions
@@ -35,8 +35,8 @@ const selectedSource = ref<PlanningSource | null>(null)
const linkError = ref<ApiErrorDetails | null>(null)
const linkLoading = ref(false)
let linkRequest = 0
const workView = ref<'calendar' | 'cargo' | 'containers'>('containers')
const calendarVisited = ref(false)
const workView = ref<'calendar' | 'cargo' | 'containers'>('calendar')
const calendarVisited = ref(true)
const showTripLayer = ref(false)
const cargoVisited = ref(false)
watch(workView, value => {
@@ -50,6 +50,7 @@ const pickupNotice = ref('')
const tripEvents = ref<CalendarEvent[]>([])
const unresolvedTrips = ref<TripCalendarEntry[]>([])
const tripNotice = ref('')
const unresolvedDialogOpen = ref(false)
const unresolvedPage = ref(1), unresolvedSize = ref(10)
const unresolvedRows = computed(() => unresolvedTrips.value.slice((unresolvedPage.value - 1) * unresolvedSize.value, unresolvedPage.value * unresolvedSize.value))
const unresolvedColumns: DataTableColumn[] = [
@@ -155,6 +156,7 @@ const events = computed<CalendarEvent[]>(() => [...arrivals.value.map(arrival =>
})), ...tripEvents.value])
async function load(nextRange = range.value) {
unresolvedDialogOpen.value = false
range.value = nextRange
const request = ++calendarRequest
calendarController?.abort()
@@ -369,7 +371,7 @@ function arrangeLinkedSource() {
}
watch(() => [route.query.sourceKey, route.query.customerId, route.query.destinationKey], resolveLink, { immediate: true })
onMounted(() => { if (!access.can(permission.lastMileOrderList)) workView.value = 'calendar' })
onMounted(() => { void load(); void loadUnscheduled() })
onBeforeUnmount(() => { ++unscheduledRequest; ++calendarRequest; ++candidateRequest; ++detailRequest; ++warehouseRequest; ++linkRequest; calendarController?.abort() })
</script>
@@ -380,9 +382,9 @@ onBeforeUnmount(() => { ++unscheduledRequest; ++calendarRequest; ++candidateRequ
<AppointmentFollowupDialog v-if="linkedSource" :source="linkedSource.source" :coverage="linkedSource.coverage" :can-write="access.can('platform:appointment:followup')" :can-arrange="access.can(permission.inboundOrderAppointment)" @arrange="arrangeLinkedSource" @close="linkedSource = null" />
<div class="appointment-heading">
<div class="density-status-tabs" role="group" aria-label="预约工作视图">
<button type="button" :class="{ 'is-active': workView === 'calendar' }" :aria-pressed="workView === 'calendar'" @click="workView = 'calendar'">预约日历</button>
<button v-if="access.can(permission.lastMileOrderList)" type="button" :class="{ 'is-active': workView === 'containers' }" :aria-pressed="workView === 'containers'" @click="workView = 'containers'">提柜与到仓</button>
<button v-if="access.can(permission.lastMileOrderList)" type="button" :class="{ 'is-active': workView === 'cargo' }" :aria-pressed="workView === 'cargo'" @click="workView = 'cargo'">送仓与排车</button>
<button type="button" :class="{ 'is-active': workView === 'calendar' }" :aria-pressed="workView === 'calendar'" @click="workView = 'calendar'">预约日历</button>
</div>
</div>
<div v-if="cargoVisited" v-show="workView === 'cargo'"><AppointmentCargoPanel :external-source="selectedSource" @source-accepted="selectedSource = null" @created="load(); loadUnscheduled()" /></div>
@@ -392,18 +394,17 @@ onBeforeUnmount(() => { ++unscheduledRequest; ++calendarRequest; ++candidateRequ
<LoadErrorState v-for="error in sourceErrors" :key="error.title" :title="error.title" :message="error.details.message" :loading="loading" @retry="load()"><template #details><ApiErrorMeta :details="error.details" /></template></LoadErrorState>
<LoadErrorState v-if="unscheduledError" title="未定时安排加载失败" :message="unscheduledError.message" @retry="loadUnscheduled" />
<details v-if="unscheduledTotal" class="appointment-extra"><summary>未定时安排({{ unscheduledTotal }})</summary><DataTable v-model:page="unscheduledPage" v-model:page-size="unscheduledSize" :data="unscheduled" :columns="unscheduledColumns" :total="unscheduledTotal" row-key="appointmentNo" @update:page="loadUnscheduled" @update:page-size="unscheduledPage = 1; loadUnscheduled()"><template #actions="{ row }"><el-button @click="openEdit({key: row.appointmentNo})">查看 / 继续</el-button></template></DataTable></details>
<details v-if="pickupNotice" class="calendar-notice"><summary>部分提柜记录待核实</summary><p>{{ pickupNotice }}</p></details>
<details v-if="showTripLayer && tripNotice" class="calendar-notice"><summary>部分用车计划待核实</summary><p>{{ tripNotice }}</p></details>
<details v-if="showTripLayer && unresolvedTrips.length" class="appointment-extra">
<summary>待核实地点的车次({{ unresolvedTrips.length }})</summary>
<p>检索窗口内的车次,尚无法确定当地日期。原计划时间保留 UTC,不用浏览器时区换算。</p>
<AppointmentCalendarPanel :notices="[pickupNotice, showTripLayer ? tripNotice : ''].filter(Boolean)" :events="events" :loading="loading" :incomplete="sourceErrors.length > 0 || !!pickupNotice || (showTripLayer && !!tripNotice)" :can-create="access.can(permission.inboundOrderAppointment)" @layers="showTripLayer = $event.trips" @range="load" @create="openCreate" @edit="openEdit" @delete="remove" @open="openSource">
<template #notice-actions><el-button v-if="showTripLayer && unresolvedTrips.length" link type="primary" @click="unresolvedDialogOpen = true">查看待核实车次({{ unresolvedTrips.length }})</el-button></template>
</AppointmentCalendarPanel>
</div>
<AppDialog v-if="unresolvedDialogOpen" v-model="unresolvedDialogOpen" :title="`待核实车次(${unresolvedTrips.length})`" width="min(1000px, calc(100vw - 48px))" :show-confirm="false" class="admin-density-dialog">
<p>这些车次缺少地点或时区,暂未计入日历。原计划时间保留 UTC。</p>
<DataTable v-model:page="unresolvedPage" v-model:page-size="unresolvedSize" :data="unresolvedRows" :columns="unresolvedColumns" :total="unresolvedTrips.length" row-key="tripNo" @update:page-size="unresolvedPage = 1">
<template #actions="{ row }"><el-button class="density-action" @click="router.push(`/overseas-master-data/trips/${encodeURIComponent(row.tripNo)}`)">查看来源</el-button></template>
<template #issues="{ row }">{{ tripIssues(row as TripCalendarEntry) }}</template>
</DataTable>
</details>
<AppointmentCalendarPanel :events="events" :loading="loading" :incomplete="sourceErrors.length > 0 || !!pickupNotice || (showTripLayer && !!tripNotice)" :can-create="access.can(permission.inboundOrderAppointment)" @layers="showTripLayer = $event.trips" @range="load" @create="openCreate" @edit="openEdit" @delete="remove" @open="openSource" />
</div>
</AppDialog>
<AppDialog v-model="dialogOpen" class="admin-density-dialog" :title="editing ? '编辑预约' : '新增预约'" width="min(1080px, calc(100vw - 48px))" :confirm-text="pendingSave ? '重试本次保存' : '保存'" :confirm-loading="saving" :confirm-disabled="detailLoading || !detailReady || (!editing && (!warehouse || !selected.length || candidateLoading || !!candidateError))" @confirm="save">
<LoadErrorState v-if="dialogError" title="预约操作失败" :message="dialogError.message" :show-retry="false"><template #details><ApiErrorMeta :details="dialogError" /></template></LoadErrorState>
<div v-loading="detailLoading">
@@ -436,9 +437,6 @@ onBeforeUnmount(() => { ++unscheduledRequest; ++calendarRequest; ++candidateRequ
</template>
<style scoped>
.calendar-notice { margin: var(--admin-space-2) 0; font-size: var(--admin-font-caption); color: var(--el-text-color-secondary); }
.calendar-notice summary { cursor: pointer; }
.calendar-notice p { margin: var(--admin-space-2) 0; }
.appointment-heading { display: flex; align-items: center; flex-wrap: wrap; gap: var(--admin-space-5); }
.appointment-heading .density-status-tabs { flex: 1; margin: 0; }
.calendar-form { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 var(--admin-space-6); }