diff --git a/apps/web-antd/src/views/store/pickup/components/PickupPreviewSection.vue b/apps/web-antd/src/views/store/pickup/components/PickupPreviewSection.vue index d1ca2f1..0281a86 100644 --- a/apps/web-antd/src/views/store/pickup/components/PickupPreviewSection.vue +++ b/apps/web-antd/src/views/store/pickup/components/PickupPreviewSection.vue @@ -22,7 +22,7 @@ const emit = defineEmits<{ (event: 'selectDate', date: string): void; }>(); -const previewSubtitle = '根据规则自动生成,以下为预览效果'; +const previewSubtitle = '来自后端规则计算(未扣减实际预约)'; function getPreviewStatusText(slot: PickupPreviewSlotDto) { if (slot.status === 'expired') return '已过期'; diff --git a/apps/web-antd/src/views/store/pickup/composables/pickup-page/constants.ts b/apps/web-antd/src/views/store/pickup/composables/pickup-page/constants.ts index 94960b6..011d8fa 100644 --- a/apps/web-antd/src/views/store/pickup/composables/pickup-page/constants.ts +++ b/apps/web-antd/src/views/store/pickup/composables/pickup-page/constants.ts @@ -7,7 +7,6 @@ import type { PickupBasicSettingsDto, PickupFineRuleDto, PickupMode, - PickupSlotDto, PickupWeekDay, } from '#/api/store-pickup'; import type { PickupWeekDayOption } from '#/views/store/pickup/types'; @@ -49,64 +48,6 @@ export const DEFAULT_PICKUP_BASIC_SETTINGS: PickupBasicSettingsDto = { maxItemsPerOrder: 20, }; -export const DEFAULT_BIG_SLOTS: PickupSlotDto[] = [ - { - id: 'slot-morning', - name: '上午时段', - startTime: '09:00', - endTime: '11:30', - cutoffMinutes: 30, - capacity: 20, - reservedCount: 5, - dayOfWeeks: [...WEEKDAY_ONLY], - enabled: true, - }, - { - id: 'slot-noon', - name: '午间时段', - startTime: '11:30', - endTime: '14:00', - cutoffMinutes: 20, - capacity: 30, - reservedCount: 12, - dayOfWeeks: [...ALL_WEEK_DAYS], - enabled: true, - }, - { - id: 'slot-afternoon', - name: '下午时段', - startTime: '14:00', - endTime: '17:00', - cutoffMinutes: 30, - capacity: 15, - reservedCount: 3, - dayOfWeeks: [...WEEKDAY_ONLY], - enabled: true, - }, - { - id: 'slot-evening', - name: '晚间时段', - startTime: '17:00', - endTime: '20:30', - cutoffMinutes: 30, - capacity: 25, - reservedCount: 8, - dayOfWeeks: [...ALL_WEEK_DAYS], - enabled: true, - }, - { - id: 'slot-weekend', - name: '周末特惠', - startTime: '10:00', - endTime: '15:00', - cutoffMinutes: 45, - capacity: 40, - reservedCount: 18, - dayOfWeeks: [...WEEKEND_ONLY], - enabled: false, - }, -]; - export const DEFAULT_FINE_RULE: PickupFineRuleDto = { intervalMinutes: 30, slotCapacity: 5, diff --git a/apps/web-antd/src/views/store/pickup/composables/pickup-page/data-actions.ts b/apps/web-antd/src/views/store/pickup/composables/pickup-page/data-actions.ts index 45fb0c2..73590be 100644 --- a/apps/web-antd/src/views/store/pickup/composables/pickup-page/data-actions.ts +++ b/apps/web-antd/src/views/store/pickup/composables/pickup-page/data-actions.ts @@ -26,7 +26,6 @@ import { } from '#/api/store-pickup'; import { - DEFAULT_BIG_SLOTS, DEFAULT_FINE_RULE, DEFAULT_PICKUP_BASIC_SETTINGS, DEFAULT_PICKUP_MODE, @@ -37,7 +36,6 @@ import { cloneFineRule, clonePreviewDays, createSettingsSnapshot, - generatePreviewDays, sortSlots, } from './helpers'; @@ -90,9 +88,9 @@ export function createDataActions(options: CreateDataActionsOptions) { function applyDefaultSettings() { options.mode.value = DEFAULT_PICKUP_MODE; syncBasicSettings(cloneBasicSettings(DEFAULT_PICKUP_BASIC_SETTINGS)); - options.bigSlots.value = sortSlots(cloneBigSlots(DEFAULT_BIG_SLOTS)); + options.bigSlots.value = []; syncFineRule(cloneFineRule(DEFAULT_FINE_RULE)); - options.previewDays.value = generatePreviewDays(options.fineRule); + options.previewDays.value = []; } /** 应用快照到当前页面状态。 */ @@ -118,9 +116,7 @@ export function createDataActions(options: CreateDataActionsOptions) { ...result.basicSettings, }); options.bigSlots.value = sortSlots( - result.bigSlots?.length - ? result.bigSlots - : cloneBigSlots(DEFAULT_BIG_SLOTS), + result.bigSlots?.length ? result.bigSlots : [], ); syncFineRule({ ...DEFAULT_FINE_RULE, @@ -129,7 +125,7 @@ export function createDataActions(options: CreateDataActionsOptions) { options.previewDays.value = result.previewDays?.length > 0 ? clonePreviewDays(result.previewDays) - : generatePreviewDays(options.fineRule); + : []; options.snapshot.value = buildCurrentSnapshot(); } catch (error) { @@ -189,12 +185,13 @@ export function createDataActions(options: CreateDataActionsOptions) { if (!options.selectedStoreId.value) return; options.isSavingBasic.value = true; try { + const currentStoreId = options.selectedStoreId.value; await savePickupBasicSettingsApi({ - storeId: options.selectedStoreId.value, + storeId: currentStoreId, mode: options.mode.value, basicSettings: cloneBasicSettings(options.basicSettings), }); - options.snapshot.value = buildCurrentSnapshot(); + await loadStoreSettings(currentStoreId); message.success('基本设置已保存'); } catch (error) { console.error(error); @@ -208,12 +205,13 @@ export function createDataActions(options: CreateDataActionsOptions) { if (!options.selectedStoreId.value) return; options.isSavingSlots.value = true; try { + const currentStoreId = options.selectedStoreId.value; await savePickupSlotsApi({ - storeId: options.selectedStoreId.value, + storeId: currentStoreId, mode: options.mode.value, slots: cloneBigSlots(options.bigSlots.value), }); - options.snapshot.value = buildCurrentSnapshot(); + await loadStoreSettings(currentStoreId); message.success('大时段配置已保存'); } catch (error) { console.error(error); @@ -227,13 +225,13 @@ export function createDataActions(options: CreateDataActionsOptions) { if (!options.selectedStoreId.value) return; options.isSavingFineRule.value = true; try { + const currentStoreId = options.selectedStoreId.value; await savePickupFineRuleApi({ - storeId: options.selectedStoreId.value, + storeId: currentStoreId, mode: options.mode.value, fineRule: cloneFineRule(options.fineRule), }); - options.previewDays.value = generatePreviewDays(options.fineRule); - options.snapshot.value = buildCurrentSnapshot(); + await loadStoreSettings(currentStoreId); message.success('精细规则已保存'); } catch (error) { console.error(error); diff --git a/apps/web-antd/src/views/store/pickup/composables/pickup-page/fine-rule-actions.ts b/apps/web-antd/src/views/store/pickup/composables/pickup-page/fine-rule-actions.ts index 577e55f..a5f050c 100644 --- a/apps/web-antd/src/views/store/pickup/composables/pickup-page/fine-rule-actions.ts +++ b/apps/web-antd/src/views/store/pickup/composables/pickup-page/fine-rule-actions.ts @@ -5,20 +5,15 @@ import type { Ref } from 'vue'; * 1. 管理精细规则字段与适用星期选择。 * 2. 处理规则校验与保存提交流程。 */ -import type { - PickupFineRuleDto, - PickupPreviewDayDto, - PickupWeekDay, -} from '#/api/store-pickup'; +import type { PickupFineRuleDto, PickupWeekDay } from '#/api/store-pickup'; import { message } from 'ant-design-vue'; import { ALL_WEEK_DAYS, WEEKDAY_ONLY, WEEKEND_ONLY } from './constants'; -import { generatePreviewDays, parseTimeToMinutes } from './helpers'; +import { parseTimeToMinutes } from './helpers'; interface CreateFineRuleActionsOptions { fineRule: PickupFineRuleDto; - previewDays: Ref; saveFineRule: () => Promise; selectedPreviewDate: Ref; } @@ -29,22 +24,18 @@ export function createFineRuleActions(options: CreateFineRuleActionsOptions) { 5, Math.floor(Number(value || 5)), ); - refreshPreviewDays(); } function setFineSlotCapacity(value: number) { options.fineRule.slotCapacity = Math.max(1, Math.floor(Number(value || 1))); - refreshPreviewDays(); } function setFineDayStartTime(value: string) { options.fineRule.dayStartTime = value; - refreshPreviewDays(); } function setFineDayEndTime(value: string) { options.fineRule.dayEndTime = value; - refreshPreviewDays(); } function setFineMinAdvanceHours(value: number) { @@ -52,7 +43,6 @@ export function createFineRuleActions(options: CreateFineRuleActionsOptions) { 0, Math.floor(Number(value || 0)), ); - refreshPreviewDays(); } function isFineDaySelected(day: PickupWeekDay) { @@ -63,7 +53,6 @@ export function createFineRuleActions(options: CreateFineRuleActionsOptions) { options.fineRule.dayOfWeeks = options.fineRule.dayOfWeeks.includes(day) ? options.fineRule.dayOfWeeks.filter((item) => item !== day) : [...options.fineRule.dayOfWeeks, day].toSorted((a, b) => a - b); - refreshPreviewDays(); } function quickSelectFineDays(mode: 'all' | 'weekday' | 'weekend') { @@ -74,29 +63,12 @@ export function createFineRuleActions(options: CreateFineRuleActionsOptions) { } else { options.fineRule.dayOfWeeks = [...WEEKEND_ONLY]; } - refreshPreviewDays(); } function setSelectedPreviewDate(date: string) { options.selectedPreviewDate.value = date; } - /** 重新计算预览,保障规则修改后页面即时反馈。 */ - function refreshPreviewDays() { - options.previewDays.value = generatePreviewDays(options.fineRule); - if (options.previewDays.value.length === 0) { - options.selectedPreviewDate.value = ''; - return; - } - const hasCurrent = options.previewDays.value.some( - (day) => day.date === options.selectedPreviewDate.value, - ); - if (!hasCurrent) { - const firstDay = options.previewDays.value[0]; - options.selectedPreviewDate.value = firstDay?.date ?? ''; - } - } - /** 校验精细规则并提交保存。 */ async function handleSaveFineRule() { // 1. 核心字段校验。 @@ -128,7 +100,6 @@ export function createFineRuleActions(options: CreateFineRuleActionsOptions) { handleSaveFineRule, isFineDaySelected, quickSelectFineDays, - refreshPreviewDays, setFineDayEndTime, setFineDayStartTime, setFineIntervalMinutes, diff --git a/apps/web-antd/src/views/store/pickup/composables/pickup-page/helpers.ts b/apps/web-antd/src/views/store/pickup/composables/pickup-page/helpers.ts index 4c30133..6e77516 100644 --- a/apps/web-antd/src/views/store/pickup/composables/pickup-page/helpers.ts +++ b/apps/web-antd/src/views/store/pickup/composables/pickup-page/helpers.ts @@ -1,14 +1,12 @@ /** * 文件职责:自提设置页面纯函数工具。 * 1. 负责克隆、格式化、校验、排序等纯逻辑。 - * 2. 负责根据精细规则生成预览数据。 + * 2. 不直接请求接口,不生成 mock 预览数据。 */ import type { PickupBasicSettingsDto, PickupFineRuleDto, PickupPreviewDayDto, - PickupPreviewSlotDto, - PickupPreviewStatus, PickupSlotDto, PickupWeekDay, } from '#/api/store-pickup'; @@ -150,142 +148,7 @@ export function validateSlotForm(payload: { return ''; } -/** 生成 3 天预览数据。 */ -export function generatePreviewDays( - fineRule: PickupFineRuleDto, - baseDate = new Date(), -) { - const startMinutes = parseTimeToMinutes(fineRule.dayStartTime); - const endMinutes = parseTimeToMinutes(fineRule.dayEndTime); - if (!Number.isFinite(startMinutes) || !Number.isFinite(endMinutes)) return []; - if (endMinutes <= startMinutes || fineRule.intervalMinutes <= 0) return []; - - return Array.from({ length: 3 }).map((_, index) => { - const date = addDays(baseDate, index); - const dateKey = toDateOnly(date); - const dayOfWeek = toPickupWeekDay(date); - const isEnabledDay = fineRule.dayOfWeeks.includes(dayOfWeek); - - const slots = isEnabledDay - ? generateDaySlots({ - date, - dateKey, - fineRule, - }) - : []; - - return { - date: dateKey, - label: `${date.getMonth() + 1}/${date.getDate()}`, - subLabel: resolvePreviewSubLabel(index, dayOfWeek), - slots, - }; - }); -} - -/** 是否为有效自提日索引。 */ -function toPickupWeekDay(date: Date): PickupWeekDay { - const jsDay = date.getDay(); // 0=周日 - const mapping: PickupWeekDay[] = [6, 0, 1, 2, 3, 4, 5]; - return mapping[jsDay] ?? 0; -} - -/** 生成某日预览时段。 */ -function generateDaySlots(payload: { - date: Date; - dateKey: string; - fineRule: PickupFineRuleDto; -}): PickupPreviewSlotDto[] { - const startMinutes = parseTimeToMinutes(payload.fineRule.dayStartTime); - const endMinutes = parseTimeToMinutes(payload.fineRule.dayEndTime); - const interval = payload.fineRule.intervalMinutes; - const total = Math.floor((endMinutes - startMinutes) / interval); - - return Array.from({ length: total + 1 }).map((_, index) => { - const minutes = startMinutes + index * interval; - const time = `${String(Math.floor(minutes / 60)).padStart(2, '0')}:${String( - minutes % 60, - ).padStart(2, '0')}`; - const booked = calcMockBookedCount( - `${payload.dateKey}|${time}`, - payload.fineRule.slotCapacity, - ); - const remainingCount = Math.max(0, payload.fineRule.slotCapacity - booked); - - const status = resolvePreviewStatus({ - date: payload.date, - fineRule: payload.fineRule, - remainingCount, - time, - }); - - return { - time, - status, - remainingCount, - }; - }); -} - -/** 计算预览时段状态。 */ -function resolvePreviewStatus(payload: { - date: Date; - fineRule: PickupFineRuleDto; - remainingCount: number; - time: string; -}): PickupPreviewStatus { - const now = new Date(); - const today = toDateOnly(now); - const dateKey = toDateOnly(payload.date); - const slotMinutes = parseTimeToMinutes(payload.time); - const nowMinutes = now.getHours() * 60 + now.getMinutes(); - const minAdvanceMinutes = payload.fineRule.minAdvanceHours * 60; - - if (dateKey < today) return 'expired'; - if (dateKey === today && slotMinutes - nowMinutes <= minAdvanceMinutes) { - return 'expired'; - } - if (payload.remainingCount <= 0) return 'full'; - if (payload.remainingCount <= 1) return 'almost'; - return 'available'; -} - -/** 简单哈希,保障预览可复现。 */ -function calcMockBookedCount(seed: string, capacity: number) { - if (capacity <= 0) return 0; - let hash = 0; - for (const char of seed) { - hash = (hash * 31 + (char.codePointAt(0) ?? 0)) >>> 0; - } - if (hash % 7 === 0) return capacity; - if (hash % 5 === 0) return Math.max(0, capacity - 1); - return hash % (capacity + 1); -} - function isSameDaySet(a: PickupWeekDay[], b: PickupWeekDay[]) { if (a.length !== b.length) return false; return a.every((day, index) => day === b[index]); } - -function toDateOnly(date: Date) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; -} - -function addDays(baseDate: Date, days: number) { - const next = new Date(baseDate); - next.setDate(baseDate.getDate() + days); - return next; -} - -function resolvePreviewSubLabel(offset: number, dayOfWeek: PickupWeekDay) { - const dayText = WEEKDAY_OPTIONS.find( - (item) => item.value === dayOfWeek, - )?.label; - if (offset === 0) return `${dayText} 今天`; - if (offset === 1) return `${dayText} 明天`; - if (offset === 2) return `${dayText} 后天`; - return dayText ?? ''; -} diff --git a/apps/web-antd/src/views/store/pickup/composables/useStorePickupPage.ts b/apps/web-antd/src/views/store/pickup/composables/useStorePickupPage.ts index a457394..e55d81e 100644 --- a/apps/web-antd/src/views/store/pickup/composables/useStorePickupPage.ts +++ b/apps/web-antd/src/views/store/pickup/composables/useStorePickupPage.ts @@ -5,7 +5,11 @@ import type { StoreListItemDto } from '#/api/store'; * 2. 组合数据加载、复制、大时段与精细规则动作。 * 3. 对外暴露视图层可直接消费的状态与方法。 */ -import type { PickupBasicSettingsDto, PickupSlotDto } from '#/api/store-pickup'; +import type { + PickupBasicSettingsDto, + PickupPreviewDayDto, + PickupSlotDto, +} from '#/api/store-pickup'; import type { PickupDrawerMode, PickupSettingsSnapshot, @@ -16,7 +20,6 @@ import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue'; import { ALL_WEEK_DAYS, - DEFAULT_BIG_SLOTS, DEFAULT_FINE_RULE, DEFAULT_PICKUP_BASIC_SETTINGS, DEFAULT_PICKUP_MODE, @@ -30,13 +33,10 @@ import { createFineRuleActions } from './pickup-page/fine-rule-actions'; import { calcReservedPercent, cloneBasicSettings, - cloneBigSlots, cloneFineRule, clonePreviewDays, createSlotId, formatDayOfWeeksText, - generatePreviewDays, - sortSlots, } from './pickup-page/helpers'; import { createSlotActions } from './pickup-page/slot-actions'; @@ -56,11 +56,9 @@ export function useStorePickupPage() { const basicSettings = reactive( cloneBasicSettings(DEFAULT_PICKUP_BASIC_SETTINGS), ); - const bigSlots = ref( - sortSlots(cloneBigSlots(DEFAULT_BIG_SLOTS)), - ); + const bigSlots = ref([]); const fineRule = reactive(cloneFineRule(DEFAULT_FINE_RULE)); - const previewDays = ref(generatePreviewDays(fineRule)); + const previewDays = ref([]); const selectedPreviewDate = ref(previewDays.value[0]?.date ?? ''); const snapshot = ref(null); @@ -187,7 +185,6 @@ export function useStorePickupPage() { handleSaveFineRule, isFineDaySelected, quickSelectFineDays, - refreshPreviewDays, setFineDayEndTime, setFineDayStartTime, setFineIntervalMinutes, @@ -197,7 +194,6 @@ export function useStorePickupPage() { toggleFineDay, } = createFineRuleActions({ fineRule, - previewDays, saveFineRule, selectedPreviewDate, }); @@ -252,7 +248,7 @@ export function useStorePickupPage() { previewDays.value = snapshot.value?.previewDays && snapshot.value.previewDays.length > 0 ? clonePreviewDays(snapshot.value.previewDays) - : generatePreviewDays(fineRule); + : []; selectedPreviewDate.value = previewDays.value[0]?.date ?? ''; } @@ -265,9 +261,9 @@ export function useStorePickupPage() { basicSettings.bookingDays = DEFAULT_PICKUP_BASIC_SETTINGS.bookingDays; basicSettings.maxItemsPerOrder = DEFAULT_PICKUP_BASIC_SETTINGS.maxItemsPerOrder; - bigSlots.value = sortSlots(cloneBigSlots(DEFAULT_BIG_SLOTS)); + bigSlots.value = []; Object.assign(fineRule, cloneFineRule(DEFAULT_FINE_RULE)); - previewDays.value = generatePreviewDays(fineRule); + previewDays.value = []; selectedPreviewDate.value = previewDays.value[0]?.date ?? ''; snapshot.value = null; return; @@ -276,6 +272,20 @@ export function useStorePickupPage() { selectedPreviewDate.value = previewDays.value[0]?.date ?? ''; }); + watch(previewDays, (days) => { + if (days.length === 0) { + selectedPreviewDate.value = ''; + return; + } + + const hasCurrent = days.some( + (item) => item.date === selectedPreviewDate.value, + ); + if (!hasCurrent) { + selectedPreviewDate.value = days[0]?.date ?? ''; + } + }); + // 9. 页面首屏初始化。 onMounted(loadStores); // 10. 路由回到当前页时刷新门店列表,避免使用旧缓存。 @@ -316,7 +326,6 @@ export function useStorePickupPage() { previewDays, quickSelectFineDays, quickSelectSlotDays, - refreshPreviewDays, resetBasicSettings, resetFineRule, resetFromSnapshot,