修改样式

This commit is contained in:
2026-06-05 15:03:32 +08:00
parent 7d891c9f7b
commit f2cde43bf4
58 changed files with 2762 additions and 939 deletions
+59 -158
View File
@@ -1,75 +1,54 @@
<script setup lang="ts">
import {EventEnum} from "@/constant/enums";
import { EventEnum } from "@/constant/enums";
const { t } = useI18n();
import dayjs from "dayjs";
// 店铺的营业时间(示例:周一到周五营业,周六周日不营业)
// 测试用的营业时间字符串
// MONDAY/TUESDAY/WEDNESDAY 09:00-18:00;THURSDAY/FRIDAY 08:00-09:00; // 周一到周五9点到18点,周四到周五8点到9点
// MONDAY/TUESDAY/WEDNESDAY 09:00-18:00;THURSDAY/FRIDAY 08:00-12:00;SATURDAY/SUNDAY 10:00-20:00 // 周六周日10点到20点
// MONDAY/TUESDAY/WEDNESDAY 09:00-16:00
// MONDAY 09:00-18:00;TUESDAY 09:00-10:00;WEDNESDAY 09:00-18:00;THURSDAY 09:00-18:00;FRIDAY 08:00-09:00
const storeBusinessHours = ref('');
// 是否仅选择日期(当前需求:只选哪一天配送,不选具体时间段)
const onlySelectDay = ref(true);
// 业务规则:只能预约周一 / 周四 / 周五
// JS 中:0-周日 1-周一 ... 4-周四 5-周五
const allowedWeekdays = [1, 4, 5];
import { parseDeliveryScheduleTimes, isDeliveryScheduleDay } from '@/utils/deliverySchedule';
const allowedWeekdays = ref<number[]>([]);
const isAllowedDay = (date: Date): boolean => {
const dayIndex = date.getDay();
return allowedWeekdays.includes(dayIndex);
return isDeliveryScheduleDay(date, allowedWeekdays.value);
};
// 解析商家营业时间的接口
interface BusinessHours {
days: string[]; // 营业的星期几
startTime: string; // 开始时间 HH:mm
endTime: string; // 结束时间 HH:mm
days: string[];
startTime: string;
endTime: string;
}
/**
* 解析商家营业时间字符串
* @param businessHoursStr 营业时间字符串,支持多种格式:
* - MONDAY/TUESDAY/WEDNESDAY 09:00-18:00;THURSDAY/FRIDAY/SATURDAY/SUNDAY 10:00-20:00
* - MONDAY/TUESDAY/WEDNESDAY 09:00-18:00
* - MONDAY 09:00-18:00;TUESDAY 09:00-10:00;WEDNESDAY 09:00-18:00
* @returns 解析后的营业时间数组
*/
const parseBusinessHours = (businessHoursStr: string): BusinessHours[] => {
if (!businessHoursStr) return [];
const businessHours: BusinessHours[] = [];
// 使用分号分割不同的营业时间段
const segments = businessHoursStr.split(";");
segments.forEach((segment) => {
const trimmedSegment = segment.trim();
if (!trimmedSegment) return;
// 使用空格分割星期几和时间
const parts = trimmedSegment.split(" ");
if (parts.length !== 2) return;
const dayStr = parts[0].trim().toUpperCase();
const timeStr = parts[1];
// 解析时间范围
const timeRange = timeStr.split("-");
if (timeRange.length !== 2) return;
const startTime = timeRange[0].trim();
const endTime = timeRange[1].trim();
// 按斜杠分割星期几,支持 MONDAY/TUESDAY/WEDNESDAY 格式
const days = dayStr
.split("/")
.map((day) => day.trim())
.filter((day) => day);
businessHours.push({
days, // 支持多个星期几共享同一营业时间
days,
startTime,
endTime,
});
@@ -78,17 +57,11 @@ const parseBusinessHours = (businessHoursStr: string): BusinessHours[] => {
return businessHours;
};
/**
* 获取指定日期的营业时间
* @param date 日期
* @returns 营业时间对象,如果不营业返回null
*/
const getBusinessHoursForDate = (date: Date): BusinessHours | null => {
if (!storeBusinessHours.value) return null;
const businessHours = parseBusinessHours(storeBusinessHours.value);
// 获取星期几的英文名称(确保是大写)(不要国际化导致判断失效)
const dayNames = [
"SUNDAY",
"MONDAY",
@@ -100,44 +73,29 @@ const getBusinessHoursForDate = (date: Date): BusinessHours | null => {
];
const dayName = dayNames[date.getDay()];
// 查找包含当前星期几的营业时间
return businessHours.find((hours) => hours.days.includes(dayName)) || null;
};
/**
* 检查日期是否营业
* @param date 日期
* @returns 是否营业
*/
const isDateOpen = (date: Date): boolean => {
if (!storeBusinessHours.value) return true; // 如果没有营业时间限制,默认营业
if (!storeBusinessHours.value) return true;
return getBusinessHoursForDate(date) !== null;
};
/**
* 检查日期是否应该显示为可选择状态(营业且有可用时间段)
* @param date 日期
* @returns 是否可选择
*/
const isDateSelectable = (date: Date): boolean => {
// 新增:限制只能预约周一 / 周四 / 周五
if (!isAllowedDay(date)) {
return false;
}
// 如果只选日期模式,营业即可选择
if (onlySelectDay.value) {
if (!storeBusinessHours.value) return true;
return isDateOpen(date);
}
// 原逻辑:需要营业且有可用时间段
if (!storeBusinessHours.value) return true; // 如果没有营业时间限制,默认可选择
if (!storeBusinessHours.value) return true;
if (!isDateOpen(date)) return false;
return hasAvailableTimeSlots(date);
};
// 生成未来 7 天:设计稿为「本周」5 个圆 +「下周」2 个圆
const dateOptions = computed(() => {
const dates: Date[] = [];
const today = new Date();
@@ -154,10 +112,8 @@ const dateOptions = computed(() => {
const thisWeekDates = computed(() => dateOptions.value.slice(0, 5));
const nextWeekDates = computed(() => dateOptions.value.slice(5, 7));
// 状态管理 - 初始化为第一个营业日期
const selectedDate = ref<Date>();
// 检查指定时间是否在营业时间内
const isTimeInBusinessHours = (
hour: number,
minute: number,
@@ -169,35 +125,28 @@ const isTimeInBusinessHours = (
return timeStr >= businessHours.startTime && timeStr <= businessHours.endTime;
};
// 检查指定日期是否有可用时间段
const hasAvailableTimeSlots = (date: Date): boolean => {
const now = new Date();
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
// 获取指定日期的营业时间
const businessHours = getBusinessHoursForDate(date);
// 如果没有营业时间,返回false
if (!businessHours) {
return false;
}
// 解析营业时间的开始和结束时间
const [startHour, startMinute] = businessHours.startTime
.split(":")
.map(Number);
const [endHour, endMinute] = businessHours.endTime.split(":").map(Number);
// 检查是否有可用时间段
for (let hour = startHour; hour <= endHour; hour++) {
for (let minute = 0; minute < 60; minute += 30) {
// 检查时间段开始时间是否在营业时间内
if (!isTimeInBusinessHours(hour, minute, businessHours)) {
continue;
}
// 计算时间段结束时间
let nextHour = hour;
let nextMinute = minute + 30;
if (nextMinute >= 60) {
@@ -205,7 +154,6 @@ const hasAvailableTimeSlots = (date: Date): boolean => {
nextMinute = 0;
}
// 检查时间段结束时间是否超出营业时间
if (!isTimeInBusinessHours(nextHour, nextMinute, businessHours)) {
if (
nextHour > endHour ||
@@ -216,12 +164,10 @@ const hasAvailableTimeSlots = (date: Date): boolean => {
}
}
// 避免生成开始时间和结束时间相同的无效时间段
if (hour === nextHour && minute === nextMinute) {
continue;
}
// 如果是今天,过滤掉已经过去的时间
if (date.toDateString() === now.toDateString()) {
if (
hour < currentHour ||
@@ -231,7 +177,6 @@ const hasAvailableTimeSlots = (date: Date): boolean => {
}
}
// 如果能到这里,说明有可用时间段
return true;
}
}
@@ -239,69 +184,55 @@ const hasAvailableTimeSlots = (date: Date): boolean => {
return false;
};
// 初始化选中日期为第一个有可用时间段的营业日期
const initializeSelectedDate = () => {
if (onlySelectDay.value) {
// 仅选日期模式:选择第一个“允许预约且营业”的日期(或第一个允许的日期)
const firstOpen = dateOptions.value.find(
(d) => isAllowedDay(d) && isDateOpen(d)
);
const firstAllowed = firstOpen || dateOptions.value.find((d) => isAllowedDay(d));
selectedDate.value = firstAllowed || dateOptions.value[0];
const userPickedDate = ref(false);
/** 从今天起查找最近可配送日(配送日 + 营业时间) */
const findNearestDeliverableDate = (maxDays = 30): Date | null => {
const today = new Date();
today.setHours(0, 0, 0, 0);
for (let i = 0; i <= maxDays; i++) {
const d = new Date(today);
d.setDate(today.getDate() + i);
if (isDateSelectable(d)) {
return d;
}
}
return null;
};
const initializeSelectedDate = (resetUserPick = false) => {
if (resetUserPick) {
userPickedDate.value = false;
}
if (userPickedDate.value) {
return;
}
// 非仅选日期模式:保留原逻辑
for (const date of dateOptions.value) {
if (isDateSelectable(date)) {
selectedDate.value = date;
return;
}
const nearest = findNearestDeliverableDate(30);
if (nearest) {
selectedDate.value = nearest;
selectedTimeSlot.value = "";
return;
}
const today = new Date();
const nextBusinessDate = findNextBusinessDate(today);
if (nextBusinessDate) {
selectedDate.value = nextBusinessDate;
nextTick(() => {
uni.showToast({
title: t('pages.address.reservationTime.currentTimeExpired'),
icon: "none",
duration: 2000,
});
});
} else {
selectedDate.value = dateOptions.value[0];
}
selectedDate.value = dateOptions.value[0] ?? new Date();
selectedTimeSlot.value = "";
};
// 监听dateOptions变化,初始化选中日期
watch(
dateOptions,
() => [...allowedWeekdays.value],
() => {
if (!selectedDate.value) {
initializeSelectedDate();
}
},
{ immediate: true }
initializeSelectedDate();
}
);
// 监听营业时间字符串变化,重新初始化选中日期
watch(
storeBusinessHours,
() => {
if (storeBusinessHours.value) {
initializeSelectedDate();
}
},
{ immediate: true }
);
watch(storeBusinessHours, () => {
initializeSelectedDate();
});
const selectedTimeSlot = ref<string>("");
/** 圆圈内上行:星期(与全局 dayjs 语言一致) */
const formatWeekdayCircle = (date: Date) => dayjs(date).format("dddd");
/** 圆圈内下行:MM/DD;不可选时显示文案 */
const formatCircleSubLine = (date: Date) => {
if (!isDateSelectable(date)) {
return t("pages.address.reservationTime.notAvailable");
@@ -309,34 +240,21 @@ const formatCircleSubLine = (date: Date) => {
return dayjs(date).format("MM/DD");
};
/**
* 检查时间是否在营业时间内
* @param hour 小时
* @param minute 分钟
* @param businessHours 营业时间对象
* @returns 是否在营业时间内
*/
// 生成时间段选项(根据商家营业时间过滤)
const timeSlots = computed(() => {
const slots: string[] = [];
const now = new Date();
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
// 如果还没有选中日期,返回空数组
if (!selectedDate.value) {
return slots;
}
// 获取选中日期的营业时间
const businessHours = getBusinessHoursForDate(selectedDate.value);
// 如果没有营业时间限制,使用原有逻辑(0-24小时)
if (!businessHours) {
for (let hour = 0; hour < 24; hour++) {
for (let minute = 0; minute < 60; minute += 30) {
// 如果是今天,过滤掉已经过去的时间
if (selectedDate.value.toDateString() === now.toDateString()) {
if (
hour < currentHour ||
@@ -361,21 +279,17 @@ const timeSlots = computed(() => {
return slots;
}
// 解析营业时间的开始和结束时间
const [startHour, startMinute] = businessHours.startTime
.split(":")
.map(Number);
const [endHour, endMinute] = businessHours.endTime.split(":").map(Number);
// 生成营业时间内的时间段
for (let hour = startHour; hour <= endHour; hour++) {
for (let minute = 0; minute < 60; minute += 30) {
// 检查时间段开始时间是否在营业时间内
if (!isTimeInBusinessHours(hour, minute, businessHours)) {
continue;
}
// 计算时间段结束时间
let nextHour = hour;
let nextMinute = minute + 30;
if (nextMinute >= 60) {
@@ -383,9 +297,7 @@ const timeSlots = computed(() => {
nextMinute = 0;
}
// 检查时间段结束时间是否超出营业时间
if (!isTimeInBusinessHours(nextHour, nextMinute, businessHours)) {
// 如果结束时间超出营业时间,但开始时间在营业时间内,则调整结束时间为营业结束时间
if (
nextHour > endHour ||
(nextHour === endHour && nextMinute > endMinute)
@@ -395,12 +307,10 @@ const timeSlots = computed(() => {
}
}
// 避免生成开始时间和结束时间相同的无效时间段(如 18:00 - 18:00
if (hour === nextHour && minute === nextMinute) {
continue;
}
// 如果是今天,过滤掉已经过去的时间
if (selectedDate.value.toDateString() === now.toDateString()) {
if (
hour < currentHour ||
@@ -423,11 +333,9 @@ const timeSlots = computed(() => {
return slots;
});
// 监听时间段变化,如果当前选中日期没有可用时间段,自动选择下一个营业日期
watch(timeSlots, (newSlots) => {
if (onlySelectDay.value) return; // 仅选日期模式不需要处理时间段
if (onlySelectDay.value) return;
if (selectedDate.value && newSlots.length === 0) {
// 当前选中日期没有可用时间段,寻找下一个营业日期
const nextBusinessDate = findNextBusinessDate(selectedDate.value);
if (nextBusinessDate) {
selectedDate.value = nextBusinessDate;
@@ -441,14 +349,12 @@ watch(timeSlots, (newSlots) => {
}
});
// 寻找下一个有可用时间段的营业日期
const findNextBusinessDate = (currentDate: Date): Date | null => {
const maxDays = 30; // 最多向前查找30天
const maxDays = 30;
for (let i = 1; i <= maxDays; i++) {
const nextDate = new Date(currentDate);
nextDate.setDate(currentDate.getDate() + i);
// 检查是否在dateOptions范围内
const isInRange = dateOptions.value.some((date) =>
dayjs(date).isSame(dayjs(nextDate), "day")
);
@@ -465,9 +371,7 @@ const findNextBusinessDate = (currentDate: Date): Date | null => {
return null;
};
// 选择日期
const selectDate = (date: Date) => {
// 检查日期是否可选择,如果不可选择则不允许选择
if (!isDateSelectable(date)) {
uni.showToast({
title: t('pages.address.reservationTime.dateNotSelectable'),
@@ -476,12 +380,11 @@ const selectDate = (date: Date) => {
return;
}
userPickedDate.value = true;
selectedDate.value = date;
// 选择新日期后,清空已选择的时间段
selectedTimeSlot.value = "";
};
// 选择时间段
const selectTimeSlot = (timeSlot: string) => {
selectedTimeSlot.value = timeSlot;
};
@@ -489,14 +392,12 @@ const selectTimeSlot = (timeSlot: string) => {
const isDateSelected = (date: Date) =>
!!selectedDate.value && dayjs(selectedDate.value).isSame(dayjs(date), "day");
// 提交预约
const submitReservation = () => {
const dateVal = selectedDate.value;
if (!dateVal) {
return;
}
// 非仅选日期模式,需要选择时间段
if (!onlySelectDay.value) {
if (!selectedTimeSlot.value) {
uni.showToast({
@@ -507,13 +408,11 @@ const submitReservation = () => {
}
}
// 计算开始/结束时间
const selectedDateDayjs = dayjs(dateVal);
let startTime: dayjs.Dayjs;
let endTime: dayjs.Dayjs;
if (onlySelectDay.value) {
// 仅选日期:优先使用营业时间范围;若无营业时间限制,则使用当天起止
const bh = getBusinessHoursForDate(dateVal);
if (bh) {
const [startHour, startMinute] = bh.startTime.split(':').map(Number);
@@ -533,7 +432,6 @@ const submitReservation = () => {
endTime = selectedDateDayjs.endOf('day');
}
} else {
// 选择了时间段:解析并生成起止时间
const [startTimeStr, endTimeStr] = selectedTimeSlot.value.split(' - ');
const [startHour, startMinute] = startTimeStr.split(':').map(Number);
const [endHour, endMinute] = endTimeStr.split(':').map(Number);
@@ -557,6 +455,7 @@ const submitReservation = () => {
});
uni.$emit(EventEnum.CHOOSE_APPOINTMENT_TIME, {
merchantId: storeId.value,
date: dateVal,
timeSlot: onlySelectDay.value ? '' : selectedTimeSlot.value,
startTime: startTime.valueOf(),
@@ -573,19 +472,23 @@ const submitReservation = () => {
const storeId = ref(null);
// 页面加载时处理参数
onLoad((options: any) => {
if (options.storeId) {
if (options.merchantId) {
storeId.value = options.merchantId;
} else if (options.storeId) {
storeId.value = options.storeId;
}
if (options.deliveryScheduleTimes) {
allowedWeekdays.value = parseDeliveryScheduleTimes(
decodeURIComponent(options.deliveryScheduleTimes)
);
}
if (options.storeBusinessHours) {
storeBusinessHours.value = options.storeBusinessHours;
// 如果传递了该参数,进入仅选日期模式
storeBusinessHours.value = decodeURIComponent(options.storeBusinessHours);
onlySelectDay.value = true;
}
// 无论是否传参,统一初始化选中日期,避免首屏未选导致不显示时间段
nextTick(() => {
initializeSelectedDate();
initializeSelectedDate(true);
});
});
</script>
@@ -635,7 +538,6 @@ onLoad((options: any) => {
</view>
</view>
</view>
<!-- 时间段选择区域在仅选日期模式下隐藏 -->
<view v-if="!onlySelectDay" class="pb-138rpx mx-40rpx bg-white rounded-24rpx overflow-hidden mt-24rpx">
<view
v-for="(timeSlot, index) in timeSlots"
@@ -648,7 +550,6 @@ onLoad((options: any) => {
@click="selectTimeSlot(timeSlot)"
>
<text class="text-32rpx font-regular">{{ timeSlot }}</text>
<!-- 单选按钮 -->
<image
:src="
selectedTimeSlot === timeSlot