feat: 完成营业时间模块拆分并补充页面注释规范

This commit is contained in:
2026-02-16 09:59:44 +08:00
parent 4be997df63
commit 14857549ba
31 changed files with 3726 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
// Mock 数据入口,仅在开发环境下使用
import './store';
import './store-hours';
console.warn('[Mock] Mock 数据已启用');

View File

@@ -0,0 +1,417 @@
import Mock from 'mockjs';
const Random = Mock.Random;
/** mockjs 请求回调参数 */
interface MockRequestOptions {
url: string;
type: string;
body: null | string;
}
interface TimeSlotMock {
id: string;
type: number;
startTime: string;
endTime: string;
capacity?: number;
remark?: string;
}
interface DayHoursMock {
dayOfWeek: number;
isOpen: boolean;
slots: TimeSlotMock[];
}
interface HolidayMock {
id: string;
startDate: string;
endDate: string;
type: number;
startTime?: string;
endTime?: string;
reason: string;
remark?: string;
}
interface StoreHoursState {
holidays: HolidayMock[];
weeklyHours: DayHoursMock[];
}
function parseUrlParams(url: string) {
const parsed = new URL(url, 'http://localhost');
const params: Record<string, string> = {};
parsed.searchParams.forEach((value, key) => {
params[key] = value;
});
return params;
}
function parseBody(options: MockRequestOptions) {
if (!options.body) return {};
try {
return JSON.parse(options.body);
} catch (error) {
console.error('[mock-store-hours] parseBody error:', error);
return {};
}
}
function normalizeDate(date?: string) {
if (!date) return '';
return String(date).slice(0, 10);
}
function normalizeTime(time?: string) {
if (!time) return '';
const matched = /(\d{2}:\d{2})/.exec(time);
return matched?.[1] ?? '';
}
function sortSlots(slots: TimeSlotMock[]) {
return [...slots].toSorted((a, b) => {
const startA = a.startTime;
const startB = b.startTime;
if (startA !== startB) return startA.localeCompare(startB);
return a.type - b.type;
});
}
function sortHolidays(holidays: HolidayMock[]) {
return [...holidays].toSorted((a, b) => {
const dateCompare = a.startDate.localeCompare(b.startDate);
if (dateCompare !== 0) return dateCompare;
return a.id.localeCompare(b.id);
});
}
function cloneWeeklyHours(weeklyHours: DayHoursMock[]) {
return weeklyHours.map((day) => ({
...day,
slots: day.slots.map((slot) => ({ ...slot })),
}));
}
function cloneHolidays(holidays: HolidayMock[]) {
return holidays.map((holiday) => ({ ...holiday }));
}
function createDefaultWeeklyHours(): DayHoursMock[] {
const weekdays = [
{
dayOfWeek: 0,
bizEnd: '22:00',
delEnd: '21:30',
delCap: 50,
pickEnd: '21:00',
},
{
dayOfWeek: 1,
bizEnd: '22:00',
delEnd: '21:30',
delCap: 50,
pickEnd: '21:00',
},
{
dayOfWeek: 2,
bizEnd: '22:00',
delEnd: '21:30',
delCap: 50,
pickEnd: '21:00',
},
{
dayOfWeek: 3,
bizEnd: '22:00',
delEnd: '21:30',
delCap: 50,
pickEnd: '21:00',
},
{
dayOfWeek: 4,
bizEnd: '23:00',
delEnd: '22:30',
delCap: 80,
pickEnd: '22:00',
},
{
dayOfWeek: 5,
bizEnd: '23:00',
delEnd: '22:30',
delCap: 80,
pickEnd: '22:00',
},
];
const result = weekdays.map((day) => ({
dayOfWeek: day.dayOfWeek,
isOpen: true,
slots: [
{ id: Random.guid(), type: 1, startTime: '09:00', endTime: day.bizEnd },
{
id: Random.guid(),
type: 2,
startTime: '10:00',
endTime: day.delEnd,
capacity: day.delCap,
},
{ id: Random.guid(), type: 3, startTime: '09:00', endTime: day.pickEnd },
],
}));
result.push({
dayOfWeek: 6,
isOpen: true,
slots: [
{ id: Random.guid(), type: 1, startTime: '10:00', endTime: '22:00' },
{
id: Random.guid(),
type: 2,
startTime: '10:30',
endTime: '21:30',
capacity: 60,
},
],
});
return result;
}
function createDefaultHolidays(): HolidayMock[] {
return [
{
id: Random.guid(),
startDate: '2026-02-17',
endDate: '2026-02-19',
type: 1,
reason: '春节假期',
},
{
id: Random.guid(),
startDate: '2026-04-05',
endDate: '2026-04-05',
type: 1,
reason: '清明节',
},
{
id: Random.guid(),
startDate: '2026-02-14',
endDate: '2026-02-14',
type: 2,
startTime: '09:00',
endTime: '23:30',
reason: '情人节延长营业',
},
{
id: Random.guid(),
startDate: '2026-05-01',
endDate: '2026-05-01',
type: 2,
startTime: '10:00',
endTime: '20:00',
reason: '劳动节缩短营业',
},
];
}
function normalizeWeeklyHoursInput(list: any): DayHoursMock[] {
const dayMap = new Map<number, DayHoursMock>();
if (Array.isArray(list)) {
for (const item of list) {
const dayOfWeek = Number(item?.dayOfWeek);
if (!Number.isInteger(dayOfWeek) || dayOfWeek < 0 || dayOfWeek > 6)
continue;
const slots: TimeSlotMock[] = Array.isArray(item?.slots)
? item.slots.map((slot: any) => ({
id: String(slot?.id || Random.guid()),
type: Number(slot?.type) || 1,
startTime: normalizeTime(slot?.startTime) || '09:00',
endTime: normalizeTime(slot?.endTime) || '22:00',
capacity:
Number(slot?.type) === 2 && slot?.capacity !== undefined
? Number(slot.capacity)
: undefined,
remark: slot?.remark || undefined,
}))
: [];
dayMap.set(dayOfWeek, {
dayOfWeek,
isOpen: Boolean(item?.isOpen),
slots: sortSlots(slots),
});
}
}
return Array.from({ length: 7 }).map((_, dayOfWeek) => {
return (
dayMap.get(dayOfWeek) ?? {
dayOfWeek,
isOpen: false,
slots: [],
}
);
});
}
function normalizeHolidayInput(holiday: any): HolidayMock {
const type = Number(holiday?.type) === 2 ? 2 : 1;
return {
id: String(holiday?.id || Random.guid()),
startDate:
normalizeDate(holiday?.startDate) || normalizeDate(holiday?.date),
endDate:
normalizeDate(holiday?.endDate) ||
normalizeDate(holiday?.startDate) ||
normalizeDate(holiday?.date),
type,
startTime:
type === 2 ? normalizeTime(holiday?.startTime) || undefined : undefined,
endTime:
type === 2 ? normalizeTime(holiday?.endTime) || undefined : undefined,
reason: holiday?.reason || '',
remark: holiday?.remark || undefined,
};
}
const storeHoursMap = new Map<string, StoreHoursState>();
function ensureStoreState(storeId = '') {
const key = storeId || 'default';
let state = storeHoursMap.get(key);
if (!state) {
state = {
weeklyHours: createDefaultWeeklyHours(),
holidays: createDefaultHolidays(),
};
storeHoursMap.set(key, state);
}
return state;
}
// 获取门店营业时间
Mock.mock(/\/store\/hours(?:\?|$)/, 'get', (options: MockRequestOptions) => {
const params = parseUrlParams(options.url);
const storeId = params.storeId || '';
const state = ensureStoreState(storeId);
return {
code: 200,
data: {
storeId,
weeklyHours: cloneWeeklyHours(state.weeklyHours),
holidays: cloneHolidays(state.holidays),
},
};
});
// 保存每周营业时间
Mock.mock(/\/store\/hours\/weekly/, 'post', (options: MockRequestOptions) => {
const body = parseBody(options);
const storeId = String(body.storeId || '');
const state = ensureStoreState(storeId);
state.weeklyHours = normalizeWeeklyHoursInput(body.weeklyHours);
return { code: 200, data: null };
});
// 删除特殊日期
Mock.mock(
/\/store\/hours\/holiday\/delete/,
'post',
(options: MockRequestOptions) => {
const body = parseBody(options);
const holidayId = String(body.id || '');
if (!holidayId) return { code: 200, data: null };
for (const [, state] of storeHoursMap) {
const index = state.holidays.findIndex(
(holiday) => holiday.id === holidayId,
);
if (index !== -1) {
state.holidays.splice(index, 1);
break;
}
}
return { code: 200, data: null };
},
);
// 新增 / 编辑特殊日期
Mock.mock(
/\/store\/hours\/holiday(?!\/delete)/,
'post',
(options: MockRequestOptions) => {
const body = parseBody(options);
const storeId = String(body.storeId || '');
const state = ensureStoreState(storeId);
const incomingHoliday = normalizeHolidayInput(body.holiday);
const existingIndex = state.holidays.findIndex(
(item) => item.id === incomingHoliday.id,
);
if (existingIndex === -1) {
state.holidays.push(incomingHoliday);
} else {
state.holidays[existingIndex] = incomingHoliday;
}
state.holidays = sortHolidays(state.holidays);
return {
code: 200,
data: { ...incomingHoliday },
};
},
);
// 复制营业时间
Mock.mock(/\/store\/hours\/copy/, 'post', (options: MockRequestOptions) => {
const body = parseBody(options);
const sourceStoreId = String(body.sourceStoreId || '');
const targetStoreIds: string[] = Array.isArray(body.targetStoreIds)
? body.targetStoreIds.map(String).filter(Boolean)
: [];
if (!sourceStoreId || targetStoreIds.length === 0) {
return {
code: 200,
data: { copiedCount: 0 },
};
}
const includeWeeklyHours = body.includeWeeklyHours !== false;
const includeHolidays = body.includeHolidays !== false;
const sourceState = ensureStoreState(sourceStoreId);
const uniqueTargets = [...new Set<string>(targetStoreIds)].filter(
(id) => id !== sourceStoreId,
);
for (const targetId of uniqueTargets) {
const targetState = ensureStoreState(targetId);
if (includeWeeklyHours) {
targetState.weeklyHours = cloneWeeklyHours(sourceState.weeklyHours);
}
if (includeHolidays) {
targetState.holidays = cloneHolidays(sourceState.holidays).map(
(holiday) => ({
...holiday,
id: Random.guid(),
}),
);
}
}
return {
code: 200,
data: {
copiedCount: uniqueTargets.length,
includeHolidays,
includeWeeklyHours,
},
};
});