diff --git a/src/config/index.ts b/src/config/index.ts index fb94d8f..2512a63 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -23,6 +23,8 @@ const Config = { shareLink: "https://www.howhowfresh.com/h5/", shareImage: "https://hanguomcn.oss-ap-northeast-2.aliyuncs.com/images/20250901105756_1756695476183_56ac07ac.png", shareDesc: "分享专属二维码邀请新用户注册即邀请成功。一起来注册体验点餐功能。", + // Zelle 支付二维码图片地址 + zellePayPath: "https://www.howhowfresh.com/minio/ruoyi/2026/04/14/ef120c97e50c419da2263a59d6679229.png", // 登录端口 userPort: 1, // 手机区号数组 diff --git a/src/locale/en.json b/src/locale/en.json index 9829614..31ea387 100644 --- a/src/locale/en.json +++ b/src/locale/en.json @@ -666,6 +666,7 @@ "coupon": { "all-merchants": "Applicable to all merchants", "expiry-date": "Expiry date: ", + "merchant-only": "Only available at {name}", "merchant-specific": "For specific merchant use", "no-coupons": "You currently do not have any coupons", "redeem-now": "Redeem now", diff --git a/src/locale/zh-Hans.json b/src/locale/zh-Hans.json index 279eadc..f414640 100644 --- a/src/locale/zh-Hans.json +++ b/src/locale/zh-Hans.json @@ -666,6 +666,7 @@ "coupon": { "all-merchants": "适用于所有商户使用", "expiry-date": "到期日:", + "merchant-only": "仅{name}可用", "merchant-specific": "指定商户使用", "no-coupons": "您目前没有任何优惠券", "redeem-now": "立即兑换", diff --git a/src/manifest.json b/src/manifest.json index 72e82e8..31db2de 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,8 +2,8 @@ "name" : "CHEFLINK delivery", "appid" : "__UNI__06509BE", "description" : "", - "versionName" : "3.0.6", - "versionCode" : 306, + "versionName" : "3.1.0", + "versionCode" : 310, "transformPx" : false, /* 5+App特有相关 */ "app-plus" : { diff --git a/src/pages-store/pages/order/checkout.vue b/src/pages-store/pages/order/checkout.vue index bf6cd30..414c7ec 100644 --- a/src/pages-store/pages/order/checkout.vue +++ b/src/pages-store/pages/order/checkout.vue @@ -2,6 +2,7 @@ import { useConfigStore, useUserStore } from "@/store"; import { dayjs } from "@/plugin/index"; import Config from '@/config/index' +import ChooseImage from '@/components/choose-image/choose-image.vue' import CheckoutSkeleton from "./components/checkout-skeleton.vue"; import ChangePhone from "./components/change-phone.vue"; import PriceDetail from "./components/price-detail.vue"; @@ -19,7 +20,8 @@ import { appMerchantOrderCreateOrderCartBatchPost, appMerchantOrderCalculatePriceCartBatchPost, appMerchantCartListMerchantPost, - appMerchantOrderPayOrderBatchPost + appMerchantOrderPayOrderBatchPost, + appMerchantOrderZipPayVoucherPost } from "@/service"; import useEventEmit from "@/hooks/useEventEmit"; import {EventEnum} from "@/constant/enums"; @@ -268,14 +270,90 @@ const openPriceDetail = () => { function navigateTo(url: string) { uni.navigateTo({ url }) } +function chooseCardForCheckout() { + payMethodSelected.value = 1 + uni.navigateTo({ + url: '/pages-user/pages/select-credit-card/index', + events: { + selectedCard: function(data: any) { + if (data) { + payMethodOptions.value.cardId = data.cardId || '' + payMethodOptions.value.cardNumber = data.cardNumber || '' + } + }, + }, + }) +} // 支付参数 const payMethodOptions = ref({ orderId: '', cardId: '', + cardNumber: '', payMethod: 1, // 支付方式 1信用卡 2余额 payPassword: '', }) +const payMethodPopupVisible = ref(false) +const payMethodSelected = ref(1) +const zellePopupVisible = ref(false) +const zelleOrderIdForVoucher = ref('') +const voucherChooseRef = ref>() +const voucherSubmitting = ref(false) +const zellePayPath = Config.zellePayPath; + +function openPayMethodPopup() { + payMethodSelected.value = Number(payMethodOptions.value.payMethod || 1) + payMethodPopupVisible.value = true +} +function confirmPayMethodForSettle() { + payMethodOptions.value.payMethod = Number(payMethodSelected.value || 1) + payMethodPopupVisible.value = false + handleGoSettle() +} +function openUploadVoucher() { + if (!zelleOrderIdForVoucher.value) { + uni.showToast({ title: t('pages-store.order.voucherUploadFailed'), icon: 'none' }) + return + } + voucherChooseRef.value?.init() +} +function normalizeVoucherUrl(payload: unknown): string { + if (Array.isArray(payload)) { + const first = payload[0] + return typeof first === 'string' ? first : '' + } + return typeof payload === 'string' ? payload : '' +} +async function onVoucherImageUploaded(urls: unknown) { + const zipPayVoucher = normalizeVoucherUrl(urls) + if (!zipPayVoucher || !zelleOrderIdForVoucher.value) { + uni.showToast({ title: t('pages-store.order.voucherUploadFailed'), icon: 'none' }) + return + } + if (voucherSubmitting.value) return + voucherSubmitting.value = true + try { + await appMerchantOrderZipPayVoucherPost({ + body: { + orderId: zelleOrderIdForVoucher.value, + zipPayVoucher, + } + }) + uni.showToast({ title: t('pages-store.order.voucherSubmitSuccess'), icon: 'none' }) + zellePopupVisible.value = false + setTimeout(() => { + if (zelleOrderIdForVoucher.value) { + uni.reLaunch({ + url: `/pages-store/pages/order/index?id=${zelleOrderIdForVoucher.value}` + }) + } + }, 500) + } catch { + uni.showToast({ title: t('pages-store.order.voucherSubmitFailed'), icon: 'none' }) + } finally { + voucherSubmitting.value = false + } +} useEventEmit(EventEnum.CHOOSE_PAYMENT_METHOD, (data) => { if(data) { if(data.payMethod === 1) { @@ -791,6 +869,9 @@ function handleGoSettle() { // 如果是余额支付,弹出支付密码弹窗 if(payMethodOptions.value.payMethod === 2) { passwordInputRef.value?.showPasswordInput() + } else if (payMethodOptions.value.payMethod === 3) { + zelleOrderIdForVoucher.value = resOrderIds.value?.[0] ? String(resOrderIds.value[0]) : '' + zellePopupVisible.value = true } else { appMerchantOrderPayOrderBatch() } @@ -799,6 +880,9 @@ function handleGoSettle() { // 如果是余额支付,弹出支付密码弹窗 if(payMethodOptions.value.payMethod === 2) { passwordInputRef.value?.showPasswordInput() + } else if (payMethodOptions.value.payMethod === 3) { + zelleOrderIdForVoucher.value = resOrderIds.value?.[0] ? String(resOrderIds.value[0]) : '' + zellePopupVisible.value = true } else { appMerchantOrderPayOrderBatch() } @@ -852,6 +936,9 @@ function handleGoSettle() { // 如果是余额支付,弹出支付密码弹窗 if(payMethodOptions.value.payMethod === 2) { passwordInputRef.value?.showPasswordInput() + } else if (payMethodOptions.value.payMethod === 3) { + zelleOrderIdForVoucher.value = String(resOrderId.value || '') + zellePopupVisible.value = true } else { appMerchantOrderPayOrder() } @@ -860,6 +947,9 @@ function handleGoSettle() { // 如果是余额支付,弹出支付密码弹窗 if(payMethodOptions.value.payMethod === 2) { passwordInputRef.value?.showPasswordInput() + } else if (payMethodOptions.value.payMethod === 3) { + zelleOrderIdForVoucher.value = String(resOrderId.value || '') + zellePopupVisible.value = true } else { appMerchantOrderPayOrder() } @@ -1345,27 +1435,6 @@ function handleClose(merchantId?: string) { - - - - - - - - - - - - @@ -1875,7 +1944,7 @@ function handleClose(merchantId?: string) { {{ subtotalOneLineText }} {{ t('pages-store.checkout.pay') }} @@ -1891,6 +1960,89 @@ function handleClose(merchantId?: string) { + + + + {{ t('pages-store.checkout.payMethodSection') }} + + + + + + + + {{ t('pages-user.choosePaymethod.creditCard') }} + {{ payMethodOptions.cardId ? payMethodOptions.cardNumber : t('pages-user.member.creditCard') }} + + + + {{ payMethodOptions.cardId ? t('pages-user.choosePaymethod.replace') : t('pages-user.member.creditCard') }} + + + + + + + + + + {{ t('pages-user.choosePaymethod.wallet') }} + Balance + + + + + + + + + + + Zelle + Scan QR and upload voucher + + + + + + {{ t('common.confirm') }} + + + + + + + Zelle + + + zellePayPath is empty + + + {{ t('pages-store.order.uploadPaidVoucher') }} + + + @@ -2075,6 +2227,122 @@ $checkout-gutter: 32rpx; box-sizing: border-box; } +.checkout-paymethod-popup { + padding: 36rpx 30rpx 20rpx; + background: #fff; + border-radius: 28rpx 28rpx 0 0; +} + +.checkout-paymethod-popup-title { + font-size: 34rpx; + line-height: 1.3; + font-weight: 700; + color: #14181b; + text-align: center; + margin-bottom: 30rpx; +} + +.checkout-paymethod-option { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 128rpx; + padding: 0 24rpx; + border: 2rpx solid #ececec; + border-radius: 22rpx; + background: #fff; + margin-bottom: 18rpx; + transition: all .2s ease; +} + +.checkout-paymethod-option.is-active { + border-color: #14181b; + background: #f7f8fa; + box-shadow: 0 6rpx 20rpx rgba(20, 24, 27, 0.08); +} + +.checkout-paymethod-option-left { + flex: 1; + min-width: 0; + display: flex; + align-items: center; +} + +.checkout-paymethod-dot { + width: 34rpx; + height: 34rpx; + border-radius: 50%; + border: 2rpx solid #d0d0d0; + display: flex; + align-items: center; + justify-content: center; + margin-right: 18rpx; + flex-shrink: 0; +} + +.checkout-paymethod-dot-inner { + width: 16rpx; + height: 16rpx; + border-radius: 50%; + background: transparent; +} + +.checkout-paymethod-option.is-active .checkout-paymethod-dot { + border-color: #14181b; +} + +.checkout-paymethod-option.is-active .checkout-paymethod-dot-inner { + background: #14181b; +} + +.checkout-paymethod-text-wrap { + min-width: 0; + display: flex; + flex-direction: column; + gap: 8rpx; +} + +.checkout-paymethod-name { + font-size: 30rpx; + line-height: 1.25; + font-weight: 600; + color: #1f1f1f; +} + +.checkout-paymethod-desc { + font-size: 24rpx; + line-height: 1.3; + color: #8b8b8b; +} + +.checkout-paymethod-replace-btn { + margin-left: 16rpx; + height: 50rpx; + padding: 0 20rpx; + border-radius: 25rpx; + background: #ffffff; + border: 1rpx solid #e6e6e6; + display: flex; + align-items: center; + justify-content: center; + font-size: 24rpx; + color: #333333; + flex-shrink: 0; +} + +.checkout-paymethod-option:last-of-type { + margin-bottom: 0; +} + +.checkout-paymethod-confirm-btn { + margin-top: 28rpx !important; + height: 96rpx !important; + border-radius: 46rpx !important; + font-size: 30rpx !important; + font-weight: 600 !important; + background: #14181b !important; +} + .checkout-confirm-head { display: flex; flex-direction: row; diff --git a/src/pages-store/pages/order/index.vue b/src/pages-store/pages/order/index.vue index 0a08719..2a228cc 100644 --- a/src/pages-store/pages/order/index.vue +++ b/src/pages-store/pages/order/index.vue @@ -20,6 +20,16 @@ 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>(); // 打开价格明细 @@ -128,6 +138,12 @@ const orderTotalItemCount = computed(() => { return orderDishList.value.reduce((sum, item) => sum + (Number(item?.count) || 0), 0) }) +const orderTotalItemCountText = computed(() => { + return fillI18nParams(t('pages.order.totalItemCount'), { + count: orderTotalItemCount.value, + }) +}) + const orderTotalPrice = computed(() => { const n = orderDetail.value?.paidAmount ?? orderDetail.value?.actualPrice ?? 0 return Number(n).toFixed(2) @@ -292,55 +308,24 @@ const orderNavSecondaryLine = computed(() => { const voucherChooseRef = ref>() const voucherSubmitting = ref(false) -// function openUploadVoucher() { -// voucherChooseRef.value?.init() -// } +function openUploadVoucher() { + voucherChooseRef.value?.init() +} -// function normalizeVoucherUrl(payload: unknown): string { -// if (Array.isArray(payload)) { -// const first = payload[0] -// return typeof first === 'string' ? first : '' -// } -// return typeof payload === 'string' ? payload : '' -// } +function normalizeVoucherUrl(payload: unknown): string { + if (Array.isArray(payload)) { + const first = payload[0] + return typeof first === 'string' ? first : '' + } + return typeof payload === 'string' ? payload : '' +} -// async function onVoucherImageUploaded(urls: unknown) { -// const zipPayVoucher = normalizeVoucherUrl(urls) -// if (!zipPayVoucher) { -// uni.showToast({ title: t('pages-store.order.voucherUploadFailed'), icon: 'none' }) -// return -// } -// const id = orderDetail.value?.id -// if (id == null || String(id).trim() === '') { -// uni.showToast({ title: t('pages-store.order.voucherUploadFailed'), icon: 'none' }) -// return -// } -// if (voucherSubmitting.value) return -// voucherSubmitting.value = true -// try { -// await appMerchantOrderZipPayVoucherPost({ -// body: { -// orderId: Number(id), -// zipPayVoucher, -// }, -// options: { hideErrorToast: true }, -// }) -// uni.showToast({ title: t('pages-store.order.voucherSubmitSuccess'), icon: 'none' }) -// setTimeout(() => { -// appMerchantOrderDetail() -// }, 500) -// } -// catch { -// uni.showToast({ title: t('pages-store.order.voucherSubmitFailed'), icon: 'none' }) -// } -// finally { -// voucherSubmitting.value = false -// } -// } - -const FIXED_VOUCHER_URL = 'https://www.howhowfresh.com/prod-api/resource/oss/download/2037827804362121218' - -async function openUploadVoucher() { +async function onVoucherImageUploaded(urls: unknown) { + const zipPayVoucher = normalizeVoucherUrl(urls) + if (!zipPayVoucher) { + uni.showToast({ title: t('pages-store.order.voucherUploadFailed'), icon: 'none' }) + return + } const id = orderDetail.value?.id if (id == null || String(id).trim() === '') { uni.showToast({ title: t('pages-store.order.voucherUploadFailed'), icon: 'none' }) @@ -352,7 +337,7 @@ async function openUploadVoucher() { await appMerchantOrderZipPayVoucherPost({ body: { orderId: id, - zipPayVoucher: FIXED_VOUCHER_URL, + zipPayVoucher, }, options: { hideErrorToast: true }, }) @@ -575,7 +560,7 @@ async function openUploadVoucher() { ${{ orderTotalPrice }} - {{ t('pages.order.totalItemCount', { count: orderTotalItemCount }) }} + {{ orderTotalItemCountText }} @@ -856,8 +841,8 @@ async function openUploadVoucher() { - - + + diff --git a/src/pages-store/pages/store/dishes.vue b/src/pages-store/pages/store/dishes.vue index 7b2a426..7d94621 100644 --- a/src/pages-store/pages/store/dishes.vue +++ b/src/pages-store/pages/store/dishes.vue @@ -386,6 +386,15 @@ const specPopupDisplayPrice = computed(() => { return centStringToPriceText(baseCent); }); +const detailDisplayPrice = computed(() => { + const dish = dishDetailData.value as any; + const firstSpecPrice = dish?.merchantSideDishVoList?.[0]?.merchantSideDishItemVoList?.[0]?.price; + if (firstSpecPrice != null && String(firstSpecPrice) !== "") { + return firstSpecPrice; + } + return dish?.actualSalePrice; +}); + function openSpecPopup() { const sideList = dishDetailData.value?.merchantSideDishVoList ?? []; for (const item of sideList) { @@ -765,7 +774,7 @@ function getStoreDetail() { $ {{ - dishDetailData?.discountPrice + detailDisplayPrice }} {{ dishDetailData?.dishName }} - + {{ specUnitDisplay }} {{ @@ -856,12 +865,12 @@ function getStoreDetail() { t("pages-store.store.dishDetail.category") }} - + @@ -1335,7 +1344,7 @@ function getStoreDetail() { .spec-grid { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(2, 1fr); gap: 0; border: 1rpx solid #e8e8e8; border-radius: 16rpx; diff --git a/src/pages-user/pages/cart/index.vue b/src/pages-user/pages/cart/index.vue index 5ff6184..8c93c81 100644 --- a/src/pages-user/pages/cart/index.vue +++ b/src/pages-user/pages/cart/index.vue @@ -556,14 +556,20 @@ function feeLine( return { show: true, orig, cur }; } -onMounted(() => { - loading.value = true; - getCartList(); -}); +// onMounted(() => { +// loading.value = true; +// getCartList(); +// }); onShow(() => { if (userStore.isLogin) { userStore.getAppointmentTime(); + // 从结算页返回时强制刷新购物车,避免已结算商品残留 + loading.value = true; + getCartList(); + } else { + dataList.value = []; + loading.value = false; } }); diff --git a/src/pages-user/pages/coupon/index.vue b/src/pages-user/pages/coupon/index.vue index fb8e0f1..35c8502 100644 --- a/src/pages-user/pages/coupon/index.vue +++ b/src/pages-user/pages/coupon/index.vue @@ -42,6 +42,26 @@ function formatCouponDetail(item: any) { return t('pages-store.store.discount') } +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 +} + +function formatCouponMerchantText(item: any) { + const name = String(item?.merchantVo?.merchantName || '').trim() + if (name) { + return fillI18nParams(t('pages-user.coupon.merchant-only'), { name }) + } + return item?.snapshotMerchantId + ? t('pages-user.coupon.merchant-specific') + : t('pages-user.coupon.all-merchants') +} + function getList(pageNum: number, pageSize: number) { return appCouponUserCouponListPost({ params: { @@ -150,7 +170,7 @@ function handleSubmit() { {{ item.snapshotNameZh }} - {{ item.snapshotMerchantId ? t('pages-user.coupon.merchant-specific') : t('pages-user.coupon.all-merchants') }} + {{ formatCouponMerchantText(item) }} {{ dayjs(Number(item.snapshotValidEnd)).format('YYYY-MM-DD HH:mm') }}{{ isEnLocale() ? ' expires' : '到期' }} diff --git a/src/pages/home/components/tabbar-home/tabbar-home.vue b/src/pages/home/components/tabbar-home/tabbar-home.vue index 507d7c0..0dd4454 100644 --- a/src/pages/home/components/tabbar-home/tabbar-home.vue +++ b/src/pages/home/components/tabbar-home/tabbar-home.vue @@ -48,6 +48,13 @@ function getDishPromoLabel(item: Record): string { return typeof raw === 'string' && raw.trim() ? raw.trim() : '' } +function getFeaturedDishDisplayPrice(item: Record) { + const firstSpecPrice = item?.merchantSideDishVoList?.[0]?.merchantSideDishItemVoList?.[0]?.price + if (firstSpecPrice != null && String(firstSpecPrice) !== '') return firstSpecPrice + if (item?.actualSalePrice != null && String(item.actualSalePrice) !== '') return item.actualSalePrice + return item?.discountPrice ?? 0 +} + function navigateTo(url: string) { if(userStore.checkLogin()) { uni.navigateTo({ @@ -445,7 +452,7 @@ const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: Co - US${{ item?.discountPrice }} + US${{ getFeaturedDishDisplayPrice(item) }}