fix: show overnight appointments on each covered calendar day
This commit is contained in:
@@ -17,6 +17,19 @@ describe('appointment calendar interactions', () => {
|
|||||||
expect(wrapper.get('[aria-label="当天安排"]').text()).toContain('当天暂无到港或送仓预约')
|
expect(wrapper.get('[aria-label="当天安排"]').text()).toContain('当天暂无到港或送仓预约')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('shows an overnight slot on both days without fetching again and excludes midnight ends', async () => {
|
||||||
|
const wrapper = render()
|
||||||
|
await wrapper.setProps({ events: [
|
||||||
|
{ key: 'overnight', type: 'delivery', date: '2026-09-23', endAt: '2026-09-24T01:00:00', time: '23:00–01:00', title: '跨日送仓', fields: [] },
|
||||||
|
{ key: 'midnight', type: 'delivery', date: '2026-09-23', endAt: '2026-09-24T00:00:00', time: '23:00–00:00', title: '午夜结束', fields: [] },
|
||||||
|
] })
|
||||||
|
expect(wrapper.find('[aria-label="2026-09-23,2 项安排"]').exists()).toBe(true)
|
||||||
|
expect(wrapper.find('[aria-label="2026-09-24,1 项安排"]').exists()).toBe(true)
|
||||||
|
expect(wrapper.get('[aria-label="当天安排"]').text()).toContain('跨日送仓')
|
||||||
|
expect(wrapper.get('[aria-label="当天安排"]').text()).not.toContain('午夜结束')
|
||||||
|
expect(wrapper.emitted('range')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('requests exactly one range for a view switch and navigates seven days', async () => {
|
it('requests exactly one range for a view switch and navigates seven days', async () => {
|
||||||
const wrapper = render()
|
const wrapper = render()
|
||||||
await wrapper.get('[aria-label="预约视图"]').findAll('button')[1]!.trigger('click')
|
await wrapper.get('[aria-label="预约视图"]').findAll('button')[1]!.trigger('click')
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
import '@/styles/adminDensity.css'
|
import '@/styles/adminDensity.css'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import AppointmentInboundCodes from './AppointmentInboundCodes.vue'
|
import AppointmentInboundCodes from './AppointmentInboundCodes.vue'
|
||||||
import { addDays, calendarDates, calendarRange, dateKey, moveCalendar, type CalendarMode } from './calendar'
|
import { addDays, calendarDates, calendarRange, dateKey, moveCalendar, occursOnDate, type CalendarMode } from './calendar'
|
||||||
|
|
||||||
export interface CalendarEvent {
|
export interface CalendarEvent {
|
||||||
key: string
|
key: string
|
||||||
date: string
|
date: string
|
||||||
|
endAt?: string
|
||||||
time: string
|
time: string
|
||||||
type: 'arrival' | 'delivery' | 'pickup' | 'trip'
|
type: 'arrival' | 'delivery' | 'pickup' | 'trip'
|
||||||
title: string
|
title: string
|
||||||
@@ -38,9 +39,12 @@ const dates = computed(() => calendarDates(selectedDate.value, mode.value))
|
|||||||
const grouped = computed(() => {
|
const grouped = computed(() => {
|
||||||
const result = new Map<string, CalendarEvent[]>()
|
const result = new Map<string, CalendarEvent[]>()
|
||||||
for (const event of props.events) {
|
for (const event of props.events) {
|
||||||
const group = result.get(event.date) || []
|
for (const date of dates.value) {
|
||||||
group.push(event)
|
if (!occursOnDate(event.date, event.endAt, date)) continue
|
||||||
result.set(event.date, group)
|
const group = result.get(date) || []
|
||||||
|
group.push(event)
|
||||||
|
result.set(date, group)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const group of result.values()) group.sort((a, b) => a.time.localeCompare(b.time) || a.key.localeCompare(b.key))
|
for (const group of result.values()) group.sort((a, b) => a.time.localeCompare(b.time) || a.key.localeCompare(b.key))
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -132,12 +132,14 @@ const events = computed<CalendarEvent[]>(() => [...arrivals.value.map(arrival =>
|
|||||||
],
|
],
|
||||||
})), ...appointments.value.map(appointment => ({
|
})), ...appointments.value.map(appointment => ({
|
||||||
key: appointment.appointmentNo, type: 'delivery' as const, date: (appointment.appointmentAt || appointment.plannedAt || appointment.plannedDate || '').slice(0, 10),
|
key: appointment.appointmentNo, type: 'delivery' as const, date: (appointment.appointmentAt || appointment.plannedAt || appointment.plannedDate || '').slice(0, 10),
|
||||||
time: (appointment.appointmentAt || appointment.plannedAt || '').slice(11, 16) + (appointment.appointmentEndAt ? `–${appointment.appointmentEndAt.slice(11, 16)}` : ''), title: `${appointment.fbaWarehouseCode} ${appointment.appointmentStatus === 'SUCCESS' ? '已确认送仓' : '计划送仓'}`,
|
endAt: (appointment.appointmentAt ? appointment.appointmentEndAt : appointment.plannedEndAt) || undefined,
|
||||||
|
time: (appointment.appointmentAt || appointment.plannedAt || '').slice(11, 16) + ((appointment.appointmentAt ? appointment.appointmentEndAt : appointment.plannedEndAt) ? `–${(appointment.appointmentAt ? appointment.appointmentEndAt : appointment.plannedEndAt)!.slice(11, 16)}` : ''), title: `${appointment.fbaWarehouseCode} ${appointment.appointmentStatus === 'SUCCESS' ? '已确认送仓' : '计划送仓'}`,
|
||||||
fields: [
|
fields: [
|
||||||
{ label: '预约仓库', value: appointment.fbaWarehouseCode },
|
{ label: '预约仓库', value: appointment.fbaWarehouseCode },
|
||||||
{ label: '系统单号', value: appointment.appointmentNo },
|
{ label: '系统单号', value: appointment.appointmentNo },
|
||||||
{ label: '外部约号', value: appointment.confirmationNo },
|
{ label: '外部约号', value: appointment.confirmationNo },
|
||||||
{ label: '地点时区', value: appointment.timezone || '待核实' },
|
{ label: '地点时区', value: appointment.timezone || '待核实' },
|
||||||
|
{ label: '完整时段', value: `${appointment.appointmentAt || appointment.plannedAt || appointment.plannedDate || '待定'}${(appointment.appointmentAt ? appointment.appointmentEndAt : appointment.plannedEndAt) ? ` — ${appointment.appointmentAt ? appointment.appointmentEndAt : appointment.plannedEndAt}` : ''}` },
|
||||||
{ label: '预约状态', value: statusLabels[appointment.appointmentStatus] || '—' },
|
{ label: '预约状态', value: statusLabels[appointment.appointmentStatus] || '—' },
|
||||||
{ label: '货物信息', value: `${appointment.selectedCount} 票 / ${new Set(appointment.items.map(item => item.customerId)).size} 个客户` },
|
{ label: '货物信息', value: `${appointment.selectedCount} 票 / ${new Set(appointment.items.map(item => item.customerId)).size} 个客户` },
|
||||||
{ label: '预约备注', value: appointment.appointmentRemark },
|
{ label: '预约备注', value: appointment.appointmentRemark },
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
export type CalendarMode = 'month' | 'week'
|
export type CalendarMode = 'month' | 'week'
|
||||||
|
|
||||||
|
/** End times are exclusive; a slot ending at midnight does not occupy the next day. */
|
||||||
|
export function occursOnDate(startDate: string, endAt: string | undefined, date: string): boolean {
|
||||||
|
if (date === startDate) return true
|
||||||
|
if (date < startDate || !endAt) return false
|
||||||
|
const endDate = endAt.slice(0, 10)
|
||||||
|
return date < endDate || (date === endDate && /[1-9]/.test(endAt.slice(11)))
|
||||||
|
}
|
||||||
|
|
||||||
/** Calendar dates are warehouse-local dates, never UTC conversions. */
|
/** Calendar dates are warehouse-local dates, never UTC conversions. */
|
||||||
export function dateKey(date: Date): string {
|
export function dateKey(date: Date): string {
|
||||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
|
||||||
|
|||||||
Reference in New Issue
Block a user