diff --git a/env/.env.development b/env/.env.development index 4095854..4bf122d 100644 --- a/env/.env.development +++ b/env/.env.development @@ -4,7 +4,7 @@ NODE_ENV=development VITE_DELETE_CONSOLE=false #本地环境 -VITE_SERVER_BASEURL=https://howhowfresh.com/prod-api -#VITE_SERVER_BASEURL=http://192.168.0.158:8889 +#VITE_SERVER_BASEURL=https://howhowfresh.com/prod-api +VITE_SERVER_BASEURL=http://192.168.0.197:8889 #VITE_SERVER_BASEURL=http://192.168.0.158:8888 #VITE_SERVER_BASEURL=http://liuyao.nat100.top/meiguowaimai \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 08dbd98..b16a1dc 100644 --- a/src/App.vue +++ b/src/App.vue @@ -10,15 +10,12 @@ onLaunch(initConfig); onShow(() => { console.log('%c外卖用户端--用户端', 'background: #00A76D; color: white; padding: 3px; border-radius: 2px;'); - console.log("App Show"); }); onHide(() => { - console.log("App Hide"); }); onError((error: string) => { - console.log('App Error', error) }) @@ -29,12 +26,9 @@ function initApp() { const color = plus.android.newObject("android.graphics.Color"); const ac = plus.android.runtimeMainActivity(); const c2int = plus.android.invoke(color, "parseColor", "#FFFFFF"); - console.log("c2int===" + JSON.stringify(c2int)) const win = plus.android.invoke(ac, "getWindow"); - console.log("win===" + JSON.stringify(win)) plus.android.invoke(win, "setNavigationBarColor", c2int); } catch (e) { - console.log('error', e) } // #endif } @@ -87,12 +81,10 @@ function initConfig() { } if(!configStore.isShowedLanguageSelectPage) { - console.log('未展示过语言选择页面,导航到语言选择页面') uni.navigateTo({ url: '/pages-login/pages/choose-language/index', success() { configStore.isShowedLanguageSelectPage = true - console.log('导航到语言选择页面成功') }, }) } diff --git a/src/components/cc-comment/cc-comment.vue b/src/components/cc-comment/cc-comment.vue index eab49b9..818e376 100644 --- a/src/components/cc-comment/cc-comment.vue +++ b/src/components/cc-comment/cc-comment.vue @@ -236,8 +236,6 @@ let commentPlaceholder = ref("说点什么..."); // 输入框占位符 // 发送评论 function sendClick({ item1, index1, item2, index2 } = replyTemp) { - console.log('replyTemp', replyTemp.item1) - console.log('replyTemp', replyTemp.item2) const data = replyTemp.item2 || replyTemp.item1 appCommentPublishCommentPost({ body: { @@ -248,7 +246,6 @@ function sendClick({ item1, index1, item2, index2 } = replyTemp) { content: commentValue.value, } }).then(res=> { - console.log(res) emit("update"); cPopupShow.value = false; commentValue.value = ""; // 清空输入框值 @@ -263,7 +260,6 @@ function sendClick({ item1, index1, item2, index2 } = replyTemp) { function expandReply(item1) { // item1.childrenShow = item1.children - console.log(item1) appCommentReplyListPost({ params: { pageNum: 1, @@ -273,7 +269,6 @@ function expandReply(item1) { parentId: item1.id, } }).then(res=> { - console.log('回复列表', res) item1.children = res.rows.map(item => { let userInfo = {} // 普通用户 @@ -314,7 +309,6 @@ function shrinkReply(item1) { const delPopupRef = ref(null); let delTemp = reactive({}); // 临时数据 function deleteClick({ item1, index1, item2, index2 }) { - console.log('删123除', item1, index1, item2, index2) message .confirm({ title: t("common.prompt.system-prompt"), @@ -336,7 +330,6 @@ function deleteClick({ item1, index1, item2, index2 }) { id: item2.id ? item2.id : item1.id, } }).then(res=> { - console.log('删除成功', res) emit("deleteFun"); shrinkReply(item1) }) diff --git a/src/locale/en.json b/src/locale/en.json index f85e855..b7f1252 100644 --- a/src/locale/en.json +++ b/src/locale/en.json @@ -231,7 +231,25 @@ "deliveryFee": "Shipping Fee", "featured-on": "Featured on CHEFLINK", "featured-dishes": "Featured Dishes", - "nearby-merchants": "Nearby Merchants" + "nearby-merchants": "Nearby Merchants", + "quickTabs": { + "memberZone": "Member Zone", + "liveSeafoodAir": "Limited Live Seafood", + "mustEatList": "CHEFLINK Must-Eat", + "newCalendar": "New Arrival Calendar", + "newCalendarNav": "Today's New", + "freshSeafoodToday": "Fresh Seafood Today" + }, + "mustEatListTabs": { + "merchant": "Merchant Rank", + "dish": "Product Rank", + "all": "All" + }, + "noticeBar": { + "thursday": "Every Thursday: Fresh seafood + New York store delivery", + "sunday": "Every Sunday: Boston local store delivery", + "freshCatch": "Freshly caught seafood is listed based on daily catch. Limited quantity, while supplies last." + } }, "ai": { "recommend": { @@ -477,6 +495,7 @@ "appointmentDelivery": "Appointment delivery", "appointmentPickup": "Appointment pickup", "chooseTime": "Choose the time", + "chooseTimeForStore": "Please choose a delivery time for \"{name}\"", "chooseTips": "Choose tips", "contactPhone": "Contact phone", "deliveryPreference": "Delivery preference", @@ -634,6 +653,7 @@ "tips3": "membership to enjoy a ", "tips4": "Enjoy delivery service over", "tips5": "Delivery fee", + "deliveryScheduleDays": "Delivery days: {days}", "start": " and up", "title": "The minimum order amount for this store is", "toast": { @@ -706,7 +726,10 @@ "removeProductTitle": "Remove the product?", "removeProductDesc": "Are you sure you want to remove {name} from your shopping cart?", "emptyTitle": "Your cart is empty", - "emptyAction": "Explore Restaurants" + "emptyAction": "Explore Restaurants", + "orderNoticeTitle": "Order Notice", + "multiStoreCheckoutNoticeTitle": "Check delivery dates before checkout", + "multiStoreCheckoutNoticeDesc": "Your cart contains multiple stores, and items may have different delivery dates. Please confirm each item's delivery date before placing the order." }, "choosePaymethod": { "creditCard": "Credit card payment", diff --git a/src/locale/zh-Hans.json b/src/locale/zh-Hans.json index f06e90a..a4bcf0b 100644 --- a/src/locale/zh-Hans.json +++ b/src/locale/zh-Hans.json @@ -231,7 +231,25 @@ "deliveryFee": "配送费", "featured-on": "精选商家", "featured-dishes": "精选菜品", - "nearby-merchants": "附近商家" + "nearby-merchants": "附近商家", + "quickTabs": { + "memberZone": "会员专区", + "liveSeafoodAir": "限量空运活海鲜", + "mustEatList": "CHEFLINK必吃榜", + "newCalendar": "上新日历", + "newCalendarNav": "今日上新", + "freshSeafoodToday": "今日现打海鲜" + }, + "mustEatListTabs": { + "merchant": "商家榜单", + "dish": "产品榜单", + "all": "综合" + }, + "noticeBar": { + "thursday": "每周四:好好鲜生生鲜 + 纽约店铺配送", + "sunday": "每周日:波士顿本地店铺配送", + "freshCatch": "渔船现打海鲜将根据当天捕捞情况临时上新,数量有限,售完即止。" + } }, "ai": { "recommend": { @@ -477,6 +495,7 @@ "appointmentDelivery": "预约配送", "appointmentPickup": "预约自取", "chooseTime": "选择时间", + "chooseTimeForStore": "请为「{name}」选择配送时间", "chooseTips": "选择小费", "contactPhone": "联系电话", "deliveryPreference": "配送偏好", @@ -634,6 +653,7 @@ "tips3": "会员可享受", "tips4": "最低起送金额", "tips5": "配送费", + "deliveryScheduleDays": "配送日:{days}", "start": "起", "title": "这家店的起送金额是", "toast": { @@ -706,7 +726,10 @@ "removeProductTitle": "移除商品?", "removeProductDesc": "确定要将 {name} 从购物车中移除吗?", "emptyTitle": "购物车为空", - "emptyAction": "去逛逛餐厅" + "emptyAction": "去逛逛餐厅", + "orderNoticeTitle": "下单须知", + "multiStoreCheckoutNoticeTitle": "下单前请确认配送日期", + "multiStoreCheckoutNoticeDesc": "当前购物车有多个店铺,商品可能不是同一天配送。请先确认每个商品的配送日期,再提交订单。" }, "choosePaymethod": { "creditCard": "信用卡支付", diff --git a/src/manifest.json b/src/manifest.json index 1cecf37..c3edfab 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,8 +2,8 @@ "name" : "CHEFLINK delivery", "appid" : "__UNI__06509BE", "description" : "", - "versionName" : "3.1.8", - "versionCode" : 318, + "versionName" : "3.1.9", + "versionCode" : 319, "transformPx" : false, /* 5+App特有相关 */ "app-plus" : { diff --git a/src/pages-store/pages/dishes/components/featured-dish-topic-page.vue b/src/pages-store/pages/dishes/components/featured-dish-topic-page.vue new file mode 100644 index 0000000..512037e --- /dev/null +++ b/src/pages-store/pages/dishes/components/featured-dish-topic-page.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/src/pages-store/pages/dishes/components/featured-dish-waterfall.vue b/src/pages-store/pages/dishes/components/featured-dish-waterfall.vue new file mode 100644 index 0000000..c3eedd1 --- /dev/null +++ b/src/pages-store/pages/dishes/components/featured-dish-waterfall.vue @@ -0,0 +1,297 @@ + + + + + diff --git a/src/pages-store/pages/dishes/components/topic-fresh-seafood-today.vue b/src/pages-store/pages/dishes/components/topic-fresh-seafood-today.vue new file mode 100644 index 0000000..3276220 --- /dev/null +++ b/src/pages-store/pages/dishes/components/topic-fresh-seafood-today.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/pages-store/pages/dishes/components/topic-live-seafood-air.vue b/src/pages-store/pages/dishes/components/topic-live-seafood-air.vue new file mode 100644 index 0000000..af17548 --- /dev/null +++ b/src/pages-store/pages/dishes/components/topic-live-seafood-air.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/pages-store/pages/dishes/components/topic-member-zone.vue b/src/pages-store/pages/dishes/components/topic-member-zone.vue new file mode 100644 index 0000000..49834da --- /dev/null +++ b/src/pages-store/pages/dishes/components/topic-member-zone.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/pages-store/pages/dishes/components/topic-must-eat-list.vue b/src/pages-store/pages/dishes/components/topic-must-eat-list.vue new file mode 100644 index 0000000..8d7662c --- /dev/null +++ b/src/pages-store/pages/dishes/components/topic-must-eat-list.vue @@ -0,0 +1,505 @@ + + + + + diff --git a/src/pages-store/pages/dishes/components/topic-new-calendar.vue b/src/pages-store/pages/dishes/components/topic-new-calendar.vue new file mode 100644 index 0000000..159c879 --- /dev/null +++ b/src/pages-store/pages/dishes/components/topic-new-calendar.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/pages-store/pages/dishes/quick-topic.vue b/src/pages-store/pages/dishes/quick-topic.vue new file mode 100644 index 0000000..8482f09 --- /dev/null +++ b/src/pages-store/pages/dishes/quick-topic.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/pages-store/pages/dishes/utils/featured-dish-query.ts b/src/pages-store/pages/dishes/utils/featured-dish-query.ts new file mode 100644 index 0000000..a31adda --- /dev/null +++ b/src/pages-store/pages/dishes/utils/featured-dish-query.ts @@ -0,0 +1,123 @@ +/** 精选菜品列表 POST body,见 docs/app-featured-dish-list.md */ +export type FeaturedDishQueryBody = { + hasMemberPrice?: any + stockMin?: any + stockMax?: any + recipeCategoryId?: any + createBeginTime?: any + createEndTime?: any + salesSort?: 'asc' | 'desc' + isNew?: any +} + +export type RouteQueryMap = Record +/** 将 URL 上可选筛选参数合并进 body(分页仍走 query) */ +export function mergeRouteQueryIntoBody( + base: FeaturedDishQueryBody, + routeQuery: RouteQueryMap = {}, +): FeaturedDishQueryBody { + const body: FeaturedDishQueryBody = { ...base } + const recipeCategoryId = routeQuery.recipeCategoryId + const stockMin = routeQuery.stockMin + const stockMax = routeQuery.stockMax + const createBeginTime = routeQuery.createBeginTime + const createEndTime = routeQuery.createEndTime + const hasMemberPrice = routeQuery.hasMemberPrice + const salesSort = routeQuery.salesSort + + if (recipeCategoryId != null) body.recipeCategoryId = recipeCategoryId + if (stockMin != null) body.stockMin = stockMin + if (stockMax != null) body.stockMax = stockMax + if (createBeginTime != null) body.createBeginTime = createBeginTime + if (createEndTime != null) body.createEndTime = createEndTime + if (hasMemberPrice != null) body.hasMemberPrice = hasMemberPrice + if (salesSort === 'asc' || salesSort === 'desc') body.salesSort = salesSort + + return body +} + +/** 会员专区:仅查有会员价菜品 */ +export function buildMemberZoneQuery(routeQuery?: RouteQueryMap): FeaturedDishQueryBody { + return mergeRouteQueryIntoBody({ hasMemberPrice: true }, routeQuery) +} + +/** 海鲜菜谱分类 ID(保持字符串,勿转 number) */ +const SEAFOOD_RECIPE_CATEGORY_ID = '2037471880338415618' + +/** 限量空运活海鲜:海鲜分类 + 库存范围 */ +export function buildLiveSeafoodAirQuery(routeQuery?: RouteQueryMap): FeaturedDishQueryBody { + return mergeRouteQueryIntoBody( + { + recipeCategoryId: SEAFOOD_RECIPE_CATEGORY_ID, + stockMin: 1, + stockMax: 200, + }, + routeQuery, + ) +} + +/** 必吃榜:按销量降序 */ +export function buildMustEatListQuery(routeQuery?: RouteQueryMap): FeaturedDishQueryBody { + return mergeRouteQueryIntoBody({ salesSort: 'desc' }, routeQuery) +} + +/** 当天 00:00:00 ~ 23:59:59(本地时区,10 位秒级时间戳) */ +export function getTodayCreateTimeRange(): { createBeginTime: number; createEndTime: number } { + const now = new Date() + const createBeginTime = Math.floor( + new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + 0, + 0, + 0, + 0, + ).getTime() / 1000, + ) + const createEndTime = Math.floor( + new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + 23, + 59, + 59, + 0, + ).getTime() / 1000, + ) + return { createBeginTime, createEndTime } +} + +/** 上新日历:当天创建 + 新品(isNew = 1) */ +export function buildNewCalendarQuery(routeQuery?: RouteQueryMap): FeaturedDishQueryBody { + return mergeRouteQueryIntoBody( + { + ...getTodayCreateTimeRange(), + isNew: 1, + }, + routeQuery, + ) +} + +/** 今日现打海鲜:海鲜分类 + 当天创建时间(秒级时间戳) */ +export function buildFreshSeafoodTodayQuery(routeQuery?: RouteQueryMap): FeaturedDishQueryBody { + return mergeRouteQueryIntoBody( + { + recipeCategoryId: SEAFOOD_RECIPE_CATEGORY_ID, + ...getTodayCreateTimeRange(), + }, + routeQuery, + ) +} + +export const TOPIC_QUERY_BUILDERS: Record< + string, + (routeQuery?: RouteQueryMap) => FeaturedDishQueryBody +> = { + 'member-zone': buildMemberZoneQuery, + 'live-seafood-air': buildLiveSeafoodAirQuery, + 'must-eat-list': buildMustEatListQuery, + 'new-calendar': buildNewCalendarQuery, + 'fresh-seafood-today': buildFreshSeafoodTodayQuery, +} diff --git a/src/pages-store/pages/dishes/utils/quick-topic-route.ts b/src/pages-store/pages/dishes/utils/quick-topic-route.ts new file mode 100644 index 0000000..acca6e2 --- /dev/null +++ b/src/pages-store/pages/dishes/utils/quick-topic-route.ts @@ -0,0 +1,44 @@ +/** 首页 tabs-type 对应精选菜品专题 slug(与 quick-topic 子组件一致) */ +export const QUICK_TOPIC_SLUGS = [ + 'member-zone', + 'live-seafood-air', + 'must-eat-list', + 'new-calendar', + 'fresh-seafood-today', +] as const + +export type QuickTopicSlug = (typeof QUICK_TOPIC_SLUGS)[number] + +export function isQuickTopicSlug(value: string): value is QuickTopicSlug { + return (QUICK_TOPIC_SLUGS as readonly string[]).includes(value) +} + +/** + * 将 /app/merchantCategory/list 单项映射为 quick-topic 的 topic。 + * 优先读后端字段 topicKey / code / topic;否则按列表顺序与五个专题一一对应。 + */ +export function resolveQuickTopicFromMerchantCategory( + item: Record, + index: number, +): QuickTopicSlug { + const raw = item.topicKey ?? item.code ?? item.topic + if (typeof raw === 'string' && isQuickTopicSlug(raw)) { + return raw + } + const safeIndex = index >= 0 && index < QUICK_TOPIC_SLUGS.length ? index : 0 + return QUICK_TOPIC_SLUGS[safeIndex] +} + +export function buildQuickTopicUrl( + topic: QuickTopicSlug, + extra: { merchantCategoryId?: string | number; categoryName?: string } = {}, +) { + const parts = [`topic=${encodeURIComponent(topic)}`] + if (extra.merchantCategoryId != null && extra.merchantCategoryId !== '') { + parts.push(`merchantCategoryId=${encodeURIComponent(String(extra.merchantCategoryId))}`) + } + if (extra.categoryName) { + parts.push(`categoryName=${encodeURIComponent(extra.categoryName)}`) + } + return `/pages-store/pages/dishes/quick-topic?${parts.join('&')}` +} diff --git a/src/pages-store/pages/order/checkout.vue b/src/pages-store/pages/order/checkout.vue index 414c7ec..3c062de 100644 --- a/src/pages-store/pages/order/checkout.vue +++ b/src/pages-store/pages/order/checkout.vue @@ -25,21 +25,18 @@ import { } from "@/service"; import useEventEmit from "@/hooks/useEventEmit"; import {EventEnum} from "@/constant/enums"; -import { getDistanceInMiles} from "@/utils/utils"; +import { getDistanceInMiles, parseMerchantCartPayload } from "@/utils/utils"; +import { + buildReservationTimeUrl, + buildDayAppointmentSlot, + findNearestDeliveryScheduleDate, + loadCheckoutMerchantAppointments, + type MerchantAppointmentSlot, +} from "@/utils/deliverySchedule"; const { t } = useI18n(); const configStore = useConfigStore(); const userStore = useUserStore(); -function fillI18nParams(template: string, params: Record) { - let text = template; - Object.keys(params).forEach((key) => { - const value = String(params[key] ?? ""); - text = text.replace(new RegExp(`\\{${key}\\}`, "g"), value); - text = text.replace(new RegExp(`\\$\\{${key}\\}`, "g"), value); - }); - return text; -} - const appDisplayName = computed(() => { const name = String((Config as any)?.appName ?? "").trim(); return name || "CHEFLINK"; @@ -111,43 +108,134 @@ const showDeliveryTime = computed(() => { } return ""; }); -// 切换配送时间点击事件(目前仅支持预约配送) -function toggleDeliveryTimeType(type: number) { - // 统一设置为预约配送 - deliveryTimeType.value = 2; +const merchantAppointmentMap = ref>({}); +const merchantAppointmentDisplay = ref>({}); +const selectingMerchantId = ref(null); - // 跳转到时间选择页面 - const businessHours = (storeDetail.value as any)?.businessHours; - const url = businessHours - ? `/pages/address/reservation-time?storeBusinessHours=${encodeURIComponent( - businessHours - )}` - : "/pages/address/reservation-time"; +function ensureDefaultMerchantAppointment(merchant: { + id?: string | number; + deliveryScheduleTimes?: string; +}) { + const key = String(merchant.id ?? ""); + if (!key || merchantAppointmentMap.value[key]?.startTime) return; + const nearest = findNearestDeliveryScheduleDate( + merchant.deliveryScheduleTimes + ); + if (!nearest) return; + + const slot = buildDayAppointmentSlot(nearest); + merchantAppointmentMap.value[key] = slot; + syncAppointmentDisplay(key, { date: nearest, timeSlot: "" }); +} + +function syncAppointmentDisplay(merchantId: string, data: any) { + const label = + dayjs(data.date).format("MM-DD") + + (data.timeSlot ? ` ${data.timeSlot}` : ""); + merchantAppointmentDisplay.value[merchantId] = label; + if (deliveryMethod.value === 0) { + userSelectedDeliveryTimeDate.value = label; + } else { + userSelectedSelfPickupTimeDate.value = label; + } +} + +async function openReservationForMerchant(merchant: { + id?: string | number; + businessHours?: string; + deliveryScheduleTimes?: string; +}) { + if (!merchant?.id) return; + selectingMerchantId.value = String(merchant.id); + let schedule = merchant.deliveryScheduleTimes; + let hours = merchant.businessHours; + if (!schedule || !hours) { + try { + const res: any = await appMerchantDetailMerchantIdGet({ + params: { merchantId: merchant.id as any }, + }); + schedule = res.data?.deliveryScheduleTimes ?? schedule; + hours = res.data?.businessHours ?? hours; + const mid = String(merchant.id); + const found = cartDataList.value.find( + (m: any) => String(m.id) === mid + ) as any; + if (found) { + found.deliveryScheduleTimes = schedule; + found.businessHours = hours; + } + if (String(storeId.value) === mid) { + storeDetail.value = { + ...storeDetail.value, + deliveryScheduleTimes: schedule, + businessHours: hours, + } as MerchantVo; + } + } catch { + /* ignore */ + } + } uni.navigateTo({ - url, + url: buildReservationTimeUrl({ + merchantId: merchant.id, + deliveryScheduleTimes: schedule, + businessHours: hours, + }), }); } -const diyTime = ref({}) -useEventEmit(EventEnum.CHOOSE_APPOINTMENT_TIME, (data) => { - console.log('CHOOSE_APPOINTMENT_TIME', data) - if(data) { - diyTime.value = data; - deliveryTimeType.value = 2 +// 切换配送时间点击事件(目前仅支持预约配送) +function toggleDeliveryTimeType( + _type: number, + merchant?: { id?: string | number; businessHours?: string; deliveryScheduleTimes?: string } +) { + deliveryTimeType.value = 2; + if (orderType.value === "batch" && merchant) { + void openReservationForMerchant(merchant); + return; + } + void openReservationForMerchant({ + id: storeId.value, + businessHours: (storeDetail.value as any)?.businessHours, + deliveryScheduleTimes: storeDetail.value?.deliveryScheduleTimes, + }); +} - // 配送还是自取 - if(deliveryMethod.value === 0) { - userSelectedDeliveryTimeDate.value = - dayjs(data.date).format('MM-DD') + - (data.timeSlot ? ' ' + data.timeSlot : ''); - } else { - userSelectedSelfPickupTimeDate.value = - dayjs(data.date).format('MM-DD') + - (data.timeSlot ? ' ' + data.timeSlot : ''); +function getMerchantPillText(merchantId: string | number) { + const key = String(merchantId); + if (merchantAppointmentDisplay.value[key]) { + return `${merchantAppointmentDisplay.value[key]} >`; + } + const ap = merchantAppointmentMap.value[key]; + if (ap?.startTime) { + const d = dayjs(Number(ap.startTime)); + if (d.isValid()) { + return `${d.format("dddd")}, ${d.format("MM/DD")} >`; } } -}) + return t("pages-store.checkout.chooseTime"); +} + +const diyTime = ref>({}); +useEventEmit(EventEnum.CHOOSE_APPOINTMENT_TIME, (data: any) => { + if (!data?.startTime || !data?.endTime) return; + const merchantId = String( + data.merchantId || selectingMerchantId.value || storeId.value || "" + ); + if (!merchantId) return; + + merchantAppointmentMap.value[merchantId] = { + date: data.date, + timeSlot: data.timeSlot, + startTime: data.startTime, + endTime: data.endTime, + }; + diyTime.value = merchantAppointmentMap.value[merchantId]; + deliveryTimeType.value = 2; + syncAppointmentDisplay(merchantId, data); + selectingMerchantId.value = null; +}); // 设置配送时间(分钟) const setDeliveryMinutes = (minutes: number) => { @@ -408,6 +496,19 @@ onLoad(async (options: any)=> { // 严格按顺序执行 await safeAwait('getAddressList', getAddressList) await safeAwait('getBatchCartInfo', getBatchCartInfo) + merchantAppointmentMap.value = loadCheckoutMerchantAppointments() + cartDataList.value.forEach((m: any) => { + const key = String(m.id); + const ap = merchantAppointmentMap.value[key]; + if (ap?.startTime) { + syncAppointmentDisplay(key, { + date: ap.date ? new Date(ap.date as any) : new Date(Number(ap.startTime)), + timeSlot: ap.timeSlot, + }); + } else { + ensureDefaultMerchantAppointment(m); + } + }); await safeAwait('appUserCardSelectDefault', appUserCardSelectDefault) } else if(options.storeId) { // 普通下单模式 @@ -469,7 +570,7 @@ async function getCartInfo() { }) console.log('购物车列表', res) - cartDataList.value = res.data + cartDataList.value = parseMerchantCartPayload(res?.data).items as MerchantCartVo[] // 购物车有菜品,查询菜品会员折扣价 + 计算价格(严格串行) if(cartDataList.value.length > 0) { @@ -503,9 +604,9 @@ async function getBatchCartInfo() { } }); console.log(`批量模式-店铺 ${merchant.merchantName} 的商品列表`, cartRes); - - // 只保留选中的商品(根据 cartIds 过滤) - const filteredItems = (cartRes.data || []).filter((item: any) => + + const payload = parseMerchantCartPayload(cartRes?.data); + const filteredItems = payload.items.filter((item: any) => cartIds.includes(String(item.id)) ); @@ -513,6 +614,8 @@ async function getBatchCartInfo() { if (filteredItems.length > 0) { return { ...merchant, + deliveryScheduleTimes: + payload.deliveryScheduleTimes ?? merchant.deliveryScheduleTimes, merchantCartVoList: filteredItems }; } @@ -532,6 +635,7 @@ async function getBatchCartInfo() { if (merchantTipIndexMap.value[id] === undefined) { merchantTipIndexMap.value[id] = 2; } + ensureDefaultMerchantAppointment(m); }); // 批量模式:查询菜品会员折扣价 + 计算价格(严格串行) @@ -616,6 +720,11 @@ async function getStoreDetail() { deliveryMethod.value = 0 showDeliverySwitch.value = true } + + ensureDefaultMerchantAppointment({ + id: storeId.value, + deliveryScheduleTimes: storeDetail.value?.deliveryScheduleTimes, + }); } const priceData = ref({}) @@ -659,13 +768,25 @@ async function appMerchantOrderCalculatePriceCart() { } }) + const startMap: Record = {} + const endMap: Record = {} + cartDataList.value.forEach((m: any) => { + const ap = merchantAppointmentMap.value[String(m.id)] + if (ap?.startTime && ap?.endTime) { + startMap[String(m.id)] = ap.startTime + endMap[String(m.id)] = ap.endTime + } + }) + const res: any = await appMerchantOrderCalculatePriceCartBatchPost({ body: { addressId: targetAddressId, cartIds: cartIds, receiveMethod: deliveryMethod.value === 0 ? 1 : 2, merchantCouponMap: couponMap, - merchantTipMap: tipMap, // 小费金额(美元) + merchantTipMap: tipMap, + merchantStartScheduledTimeMap: startMap, + merchantEndScheduledTimeMap: endMap, phone: formData.value.phone, areaCode: contact.value.areaCode, } @@ -792,13 +913,30 @@ function handleGoSettle() { return } - // 仅支持预约配送:必须先选择预约时间 - if (deliveryTimeType.value === 2 && (!diyTime.value.startTime || !diyTime.value.endTime)) { - uni.showToast({ - title: t('pages-store.checkout.chooseTime'), - icon: 'none' - }) - return + if (deliveryTimeType.value === 2) { + if (orderType.value === "batch") { + for (const m of cartDataList.value as any[]) { + const ap = merchantAppointmentMap.value[String(m.id)]; + if (!ap?.startTime || !ap?.endTime) { + uni.showToast({ + title: t("pages-store.checkout.chooseTimeForStore", { + name: m.merchantName || "", + }), + icon: "none", + }); + return; + } + } + } else { + const ap = merchantAppointmentMap.value[String(storeId.value)]; + if (!ap?.startTime || !ap?.endTime) { + uni.showToast({ + title: t("pages-store.checkout.chooseTime"), + icon: "none", + }); + return; + } + } } // 批量下单 @@ -851,14 +989,22 @@ function handleGoSettle() { needTableware: needTableware.value ? 1 : 2, // 餐具 1是 2否 } - // 如果是预约派送 - if(deliveryTimeType.value === 1) { + if (deliveryTimeType.value === 1) { const result = getTimeStamps(showDeliveryTime.value); data.startScheduledTime = result.startTimestamp data.endScheduledTime = result.endTimestamp } else { - data.startScheduledTime = diyTime.value.startTime - data.endScheduledTime = diyTime.value.endTime + const startMap: Record = {} + const endMap: Record = {} + cartDataList.value.forEach((m: any) => { + const ap = merchantAppointmentMap.value[String(m.id)] + if (ap?.startTime && ap?.endTime) { + startMap[String(m.id)] = ap.startTime + endMap[String(m.id)] = ap.endTime + } + }) + data.merchantStartScheduledTimeMap = startMap + data.merchantEndScheduledTimeMap = endMap } console.log('批量下单参数', data) appMerchantOrderCreateOrderCartBatchPost({ @@ -924,8 +1070,9 @@ function handleGoSettle() { data.startScheduledTime = result.startTimestamp data.endScheduledTime = result.endTimestamp } else { - data.startScheduledTime = diyTime.value.startTime - data.endScheduledTime = diyTime.value.endTime + const ap = merchantAppointmentMap.value[String(storeId.value)] + data.startScheduledTime = ap?.startTime ?? (diyTime.value as any).startTime + data.endScheduledTime = ap?.endTime ?? (diyTime.value as any).endTime } console.log('下单参数', data) appMerchantOrderCreateOrderCartPost({ @@ -1201,22 +1348,10 @@ const serviceFeeAmount = computed(() => { }); const deliveryPillText = computed(() => { - const raw = - deliveryMethod.value === 0 - ? userSelectedDeliveryTimeDate.value - : userSelectedSelfPickupTimeDate.value; - if (!raw) return t('pages-store.checkout.chooseTime'); - const str = String(raw); - const m = str.match(/^(\d{2})-(\d{2})/); - if (m) { - const y = dayjs().year(); - const d = dayjs(`${y}-${m[1]}-${m[2]}`); - if (d.isValid()) { - const w = d.format('dddd'); - return `${w}, ${m[1]}/${m[2]} >`; - } + if (orderType.value === "batch") { + return t("pages-store.checkout.chooseTime"); } - return `${str} >`; + return getMerchantPillText(storeId.value); }); function safeToNumber(val: unknown, fallback = 0) { @@ -1282,24 +1417,24 @@ const scheduledServiceEndLabel = computed(() => { }); const localDeliverySubtitleText = computed(() => - fillI18nParams(t('pages-store.checkout.localDeliverySubtitle'), { name: appDisplayName.value }) + t('pages-store.checkout.localDeliverySubtitle', { name: appDisplayName.value }) ); const itemsGoodsTotalWithPriceText = computed(() => - fillI18nParams(t('pages-store.checkout.itemsGoodsTotalWithPrice'), { + t('pages-store.checkout.itemsGoodsTotalWithPrice', { count: cartTotalPieceCount.value, amount: displayGoodsAmountStr.value, }) ); const scheduledDeliveryWindowText = computed(() => - fillI18nParams(t('pages-store.checkout.scheduledDeliveryWindow'), { + t('pages-store.checkout.scheduledDeliveryWindow', { time: scheduledServiceEndLabel.value, }) ); const deliverBeforeText = computed(() => - fillI18nParams(t('pages-store.checkout.deliverBefore'), { + t('pages-store.checkout.deliverBefore', { time: deliveryMethod.value === 0 ? String(userSelectedDeliveryTimeDate.value ?? '') : String(userSelectedSelfPickupTimeDate.value ?? ''), @@ -1307,17 +1442,17 @@ const deliverBeforeText = computed(() => ); const memberThanksText = computed(() => - fillI18nParams(t('pages-store.checkout.memberThanks'), { name: appDisplayName.value }) + t('pages-store.checkout.memberThanks', { name: appDisplayName.value }) ); const subtotalWithPiecesText = computed(() => - fillI18nParams(t('pages-store.checkout.subtotalWithPieces'), { + t('pages-store.checkout.subtotalWithPieces', { count: cartTotalPieceCount.value, }) ); const subtotalOneLineText = computed(() => - fillI18nParams(t('pages-store.checkout.subtotalOneLine'), { + t('pages-store.checkout.subtotalOneLine', { amount: displayPaidStr.value, }) ); @@ -1588,25 +1723,6 @@ function handleClose(merchantId?: string) { - - - - - {{ deliveryPillText }} - - - - {{ cartTotalPieceCount }} {{ t('pages-user.cart.items') }} · ${{ displayGoodsAmountStr }} - - - {{ t('pages-store.checkout.shippingFee') }} - ${{ listDeliveryFeeStr }} - - ${{ displayDeliveryFeeStr }} - - - - - + {{ merchant.merchantName }} @@ -1630,6 +1746,17 @@ function handleClose(merchantId?: string) { > + + + {{ getMerchantPillText(merchant.id) }} + + @@ -2758,6 +2885,16 @@ $checkout-gutter: 32rpx; border-bottom: 1rpx solid $checkout-border; } +.checkout-merchant-schedule-row { + padding: 20rpx 24rpx; + border-bottom: 1rpx solid $checkout-border; +} + +.checkout-confirm-band-right--full { + width: 100%; + text-align: right; +} + .checkout-merchant-body { padding: 8rpx 24rpx 16rpx; } diff --git a/src/pages-store/pages/order/index.vue b/src/pages-store/pages/order/index.vue index 4e8d8d2..76e69b4 100644 --- a/src/pages-store/pages/order/index.vue +++ b/src/pages-store/pages/order/index.vue @@ -20,16 +20,6 @@ import dayjs from 'dayjs' import useEventEmit from "@/hooks/useEventEmit"; const configStore = useConfigStore(); -function fillI18nParams(template: string, params: Record) { - let text = template - Object.keys(params).forEach((key) => { - const value = String(params[key] ?? '') - text = text.replace(new RegExp(`\\{${key}\\}`, 'g'), value) - text = text.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value) - }) - return text -} - // 价格明细 const priceDetailRef = ref>(); // 打开价格明细 @@ -139,7 +129,7 @@ const orderTotalItemCount = computed(() => { }) const orderTotalItemCountText = computed(() => { - return fillI18nParams(t('pages.order.totalItemCount'), { + return t('pages.order.totalItemCount', { count: orderTotalItemCount.value, }) }) diff --git a/src/pages-store/pages/store/dishes.vue b/src/pages-store/pages/store/dishes.vue index dd65dac..b72cfeb 100644 --- a/src/pages-store/pages/store/dishes.vue +++ b/src/pages-store/pages/store/dishes.vue @@ -17,6 +17,8 @@ import Config from "@/config"; import {useConfigStore, useUserStore} from "@/store"; import DishesSkeleton from "./components/dishes-skeleton.vue"; import {CollectionType} from "@/constant/enums"; +import { formatDeliveryScheduleDays } from "@/utils/deliverySchedule"; +import { parseMerchantCartPayload } from "@/utils/utils"; const configStore = useConfigStore(); const userStore = useUserStore(); @@ -317,18 +319,8 @@ const appDisplayName = computed(() => { return name || "CHEFLINK"; }); -function fillI18nParams(template: string, params: Record) { - let text = template; - Object.keys(params).forEach((key) => { - const value = String(params[key] ?? ""); - text = text.replace(new RegExp(`\\{${key}\\}`, "g"), value); - text = text.replace(new RegExp(`\\$\\{${key}\\}`, "g"), value); - }); - return text; -} - const memberPriceLabelText = computed(() => - fillI18nParams(t("pages-store.store.dishDetail.memberPriceLabel"), { + t("pages-store.store.dishDetail.memberPriceLabel", { name: appDisplayName.value, }) ); @@ -426,7 +418,7 @@ function getCartInfo() { } }).then((res: any)=> { console.log('购物车列表', res) - cartDataList.value = res.data ?? [] + cartDataList.value = parseMerchantCartPayload(res?.data).items as MerchantCartVo[] }) } @@ -658,6 +650,13 @@ function navigateToCart() { // 获取商家详情信息 const storeDetail = ref({}) +const deliveryScheduleDaysText = computed(() => { + const days = formatDeliveryScheduleDays( + storeDetail.value?.deliveryScheduleTimes, + (key) => t(key) + ); + return days ? t("pages-store.store.deliveryScheduleDays", { days }) : ""; +}); function getStoreDetail() { appMerchantDetailMerchantIdGet({ params: { @@ -836,6 +835,10 @@ function getStoreDetail() { {{ storeDetail.merchantName }} + {{ deliveryScheduleDaysText }} {{ @@ -1198,6 +1201,7 @@ function getStoreDetail() { .dish-info { padding: 28rpx 30rpx 24rpx; + position: relative; } .price-block { @@ -1279,6 +1283,9 @@ function getStoreDetail() { flex-wrap: wrap; gap: 12rpx; margin-bottom: 16rpx; + position: absolute; + top: 28rpx; + right: 30rpx; } .meta-tag { @@ -1315,6 +1322,13 @@ function getStoreDetail() { white-space: nowrap; } +.delivery-schedule-hint { + margin-top: 16rpx; + font-size: 24rpx; + line-height: 32rpx; + color: #00a76d; +} + .store-pill__arr { font-size: 32rpx; color: #5a8fd8; diff --git a/src/pages-store/pages/store/index.vue b/src/pages-store/pages/store/index.vue index dd6b2be..2a8a192 100644 --- a/src/pages-store/pages/store/index.vue +++ b/src/pages-store/pages/store/index.vue @@ -14,7 +14,8 @@ import { } from "@/service"; import {CollectionType} from "@/constant/enums"; import {useUserStore} from "@/store"; -import { parseBusinessHoursUtils, getDistanceInMiles } from "@/utils/utils"; +import { parseBusinessHoursUtils, getDistanceInMiles, parseMerchantCartPayload } from "@/utils/utils"; +import { formatDeliveryScheduleDays } from "@/utils/deliverySchedule"; import CouponPopup from './components/coupon-popup.vue' import {getMerchantCouponReceiveListApi} from "@/pages-user/service"; // import type { MerchantVo } from '@/service/types' @@ -77,6 +78,13 @@ onUnmounted(() => { // 获取商家详情信息 const storeDetail = ref({}) +const deliveryScheduleDaysText = computed(() => { + const days = formatDeliveryScheduleDays( + storeDetail.value?.deliveryScheduleTimes, + (key) => t(key) + ); + return days ? t("pages-store.store.deliveryScheduleDays", { days }) : ""; +}); // 闭店提示信息 const closingInfo = ref({ show: false, minutes: 0 }) @@ -232,7 +240,7 @@ function getCartInfo() { } }).then((res: any)=> { console.log('购物车列表', res) - cartDataList.value = res.data + cartDataList.value = parseMerchantCartPayload(res?.data).items as MerchantCartVo[] // 购物车有菜品,查询菜品会员折扣价 if(cartDataList.value.length > 0) { @@ -571,6 +579,10 @@ function handleShare() { {{ storeDetail?.deliveryTime }}{{ daySuffix(storeDetail?.deliveryTime) }} {{ t('pages-store.store.earTime') }} + {{ deliveryScheduleDaysText }} diff --git a/src/pages/home/components/tabbar-home/components/featured-on/index.vue b/src/pages/home/components/tabbar-home/components/featured-on/index.vue index 3f29aaa..0d4df93 100644 --- a/src/pages/home/components/tabbar-home/components/featured-on/index.vue +++ b/src/pages/home/components/tabbar-home/components/featured-on/index.vue @@ -79,14 +79,14 @@ function subtitleLine(item: any): string { @@ -148,9 +148,9 @@ function subtitleLine(item: any): string { flex-shrink: 0; display: flex; flex-direction: row; - width: 620rpx; - height: 256rpx; - min-height: 256rpx; + width: 660rpx; + height: 306rpx; + min-height: 306rpx; margin-left: 16rpx; background: #fff; border-radius: 24rpx; @@ -162,7 +162,7 @@ function subtitleLine(item: any): string { } .featured-card__media { - width: 232rpx; + width: 323rpx; flex-shrink: 0; background: #f0f0f0; display: flex; @@ -204,23 +204,17 @@ function subtitleLine(item: any): string { } .featured-card__name { + display: block; + width: 100%; font-size: 28rpx; line-height: 34rpx; font-weight: 600; color: #1a1a1a; -} -.featured-card__name--primary { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + white-space: normal; + word-break: break-word; } .featured-card__name--secondary { - font-size: 28rpx; - line-height: 34rpx; - color: #1a1a1a; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + font-weight: 500; } .featured-card__media-placeholder { diff --git a/src/pages/home/components/tabbar-home/components/home-skeleton.vue b/src/pages/home/components/tabbar-home/components/home-skeleton.vue index 2d9b776..8b22f5b 100644 --- a/src/pages/home/components/tabbar-home/components/home-skeleton.vue +++ b/src/pages/home/components/tabbar-home/components/home-skeleton.vue @@ -2,17 +2,17 @@ - + - - - - - + + + + + @@ -121,19 +121,22 @@ .header-section { padding: 18rpx 24rpx 0; display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12rpx; + flex-direction: column; + gap: 10rpx; - .header-left { - flex: 1; - min-width: 0; + .header-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12rpx; } .brand-row { display: flex; align-items: center; gap: 14rpx; + flex: 1; + min-width: 0; } .logo-skeleton { @@ -149,10 +152,9 @@ border-radius: 8rpx; } - .delivery-info-skeleton { - margin-top: 12rpx; - width: 300rpx; - height: 30rpx; + .notice-row-skeleton { + width: 100%; + height: 32rpx; border-radius: 8rpx; } @@ -160,6 +162,7 @@ display: flex; align-items: center; gap: 10rpx; + flex-shrink: 0; } .location-pill-skeleton { diff --git a/src/pages/home/components/tabbar-home/components/home-top-header.vue b/src/pages/home/components/tabbar-home/components/home-top-header.vue index ab934fc..73a4a3d 100644 --- a/src/pages/home/components/tabbar-home/components/home-top-header.vue +++ b/src/pages/home/components/tabbar-home/components/home-top-header.vue @@ -1,50 +1,81 @@ @@ -53,6 +84,30 @@ const emit = defineEmits<{ background: #f2f2f2; } +.home-notice-wrap { + width: 100%; + + :deep(.home-notice-bar) { + padding: 0; + font-size: 26rpx; + line-height: 32rpx; + border-radius: 0; + background: transparent !important; + } + + :deep(.wd-notice-bar__wrap) { + height: 32rpx; + line-height: 32rpx; + overflow: hidden; + } + + :deep(.wd-notice-bar__content) { + font-size: 26rpx; + line-height: 32rpx; + white-space: nowrap; + } +} + .home-loc-pill { display: flex; align-items: center; @@ -102,4 +157,3 @@ const emit = defineEmits<{ border-radius: 999rpx; } - diff --git a/src/pages/home/components/tabbar-home/components/msg-box.vue b/src/pages/home/components/tabbar-home/components/msg-box.vue index f6673e6..5b1265f 100644 --- a/src/pages/home/components/tabbar-home/components/msg-box.vue +++ b/src/pages/home/components/tabbar-home/components/msg-box.vue @@ -1,9 +1,6 @@ @@ -72,38 +42,9 @@ function onHandleTouchEnd(e: any) { transform: translateY(-50%) translateX(55rpx); } -.ai-close { - position: absolute; - left: -18rpx; - top: -15rpx; - width: 34rpx; - height: 34rpx; - z-index: 2; - border-radius: 999rpx; - background: #ffffff; - border: 1rpx solid #e5e7eb; - box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - justify-content: center; - color: #7b8794; - font-size: 24rpx; - line-height: 1; -} - -.ai-fab { - width: 92rpx; - height: 92rpx; - border-radius: 50%; - background: #e2e7eb; - box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.18); - display: flex; - align-items: center; - justify-content: center; -} - .ai-icon { - width: 44rpx; - height: 44rpx; + width: 260rpx; + height: 260rpx; + margin-right:-70rpx !important; } \ No newline at end of file diff --git a/src/pages/home/components/tabbar-home/components/tabs-type.vue b/src/pages/home/components/tabbar-home/components/tabs-type.vue index 89f4a8a..2f9ddf8 100644 --- a/src/pages/home/components/tabbar-home/components/tabs-type.vue +++ b/src/pages/home/components/tabbar-home/components/tabs-type.vue @@ -1,5 +1,9 @@ diff --git a/src/pages/home/components/tabbar-home/tabbar-home.vue b/src/pages/home/components/tabbar-home/tabbar-home.vue index c93eedc..f387bb9 100644 --- a/src/pages/home/components/tabbar-home/tabbar-home.vue +++ b/src/pages/home/components/tabbar-home/tabbar-home.vue @@ -1,4 +1,6 @@