From 7db9eb12cbac0cade74d11c971d11ef3fd2516a7 Mon Sep 17 00:00:00 2001 From: Kroytz Date: Fri, 24 Jul 2026 13:47:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(order):=20=E9=A2=84=E4=BC=B0=E9=85=8D?= =?UTF-8?q?=E9=80=81=E8=B4=B9=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yshop-drink-uniapp-vue3/pages/menu/menu.vue | 198 +++++++++++++++++++- 1 file changed, 189 insertions(+), 9 deletions(-) diff --git a/yshop-drink-uniapp-vue3/pages/menu/menu.vue b/yshop-drink-uniapp-vue3/pages/menu/menu.vue index f8cee1b..058484d 100644 --- a/yshop-drink-uniapp-vue3/pages/menu/menu.vue +++ b/yshop-drink-uniapp-vue3/pages/menu/menu.vue @@ -128,14 +128,26 @@ - + - {{ getCartGoodsNumber }} + {{ getCartGoodsNumber }} - ¥{{ getCartGoodsPrice }} - @@ -259,11 +271,13 @@ import { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app' import { formatDateTime,kmUnit } from '@/utils/util' import { shopNearby, - menuGoods + menuGoods, + getDeliveryFee } from '@/api/goods' import { menuAds } from '@/api/market' +import { addressAll } from '@/api/address' const main = useMainStore() const { orderType,address, store,location,isLogin } = storeToRefs(main) const title = ref('米糖烘焙') @@ -289,6 +303,11 @@ const popup = ref() // 点击左侧分类主动滚动时,暂时忽略右侧 scroll 回写,避免高亮回跳到上一项 const menuClickLock = ref(false) let menuClickLockTimer = null +// 外卖配送费提示(默认地址 / 唯一地址预估) +const userAddresses = ref([]) +const menuDeliveryFee = ref(0) +const deliveryTipReady = ref(false) +let deliveryFeeReqId = 0 const measureMenuLayout = () => { } @@ -324,6 +343,73 @@ const spread = computed(() => { //差多少元起送 if (orderType.value != 'takeout') return return parseFloat((store.value.minPrice - getCartGoodsPrice).toFixed(2)) }) +const freeDeliveryAmount = computed(() => parseFloat(store.value?.freeDeliveryAmount) || 0) +const lowestDeliveryFee = computed(() => { + let min = parseFloat(store.value?.deliveryPrice) + if (isNaN(min)) min = 0 + let rules = store.value?.deliveryRules + if (typeof rules === 'string') { + try { + rules = JSON.parse(rules) + } catch (e) { + rules = [] + } + } + if (Array.isArray(rules)) { + rules.forEach((rule) => { + const price = parseFloat(rule?.price) + if (!isNaN(price) && (min <= 0 || price < min)) { + min = price + } + }) + } + return min +}) +// 有默认地址用默认;否则唯一地址;都不满足则不预估 +const estimateAddress = computed(() => { + const list = userAddresses.value || [] + if (!list.length) return null + const preferred = list.find((item) => Number(item.isDefault) === 1) + if (preferred) return preferred + if (list.length === 1) return list[0] + return null +}) +const canShowDeliveryTip = computed(() => { + if (orderType.value !== 'takeout' || !store.value?.id) return false + const list = userAddresses.value || [] + if (!list.length) return false + return list.some((item) => Number(item.isDefault) === 1) || list.length === 1 +}) +const showCartBox = computed(() => { + if (!isCartShow.value) return false + if (cart.value.length > 0) return true + return canShowDeliveryTip.value +}) +const formatMoney = (n) => { + const x = Number(n) || 0 + return Math.round(x * 100) % 100 === 0 ? String(Math.round(x)) : x.toFixed(2) +} +const deliveryTipText = computed(() => { + if (!canShowDeliveryTip.value || !deliveryTipReady.value) return '' + const fee = menuDeliveryFee.value + const freeAt = freeDeliveryAmount.value + const cartAmount = parseFloat(getCartGoodsPrice.value) || 0 + if (fee <= 0) { + return cartAmount > 0 ? '已免配送费' : (freeAt > 0 + ? `另需配送费 0 元(满 ${formatMoney(freeAt)} 元免配送费)` + : '') + } + if (cartAmount <= 0) { + return freeAt > 0 + ? `另需配送费 ${formatMoney(fee)} 元(满 ${formatMoney(freeAt)} 元免配送费)` + : `另需配送费 ${formatMoney(fee)} 元` + } + if (freeAt > 0) { + const need = Math.max(0, parseFloat((freeAt - cartAmount).toFixed(2))) + return `另需配送费 ${formatMoney(fee)} 元(还需 ${formatMoney(need)} 元免配送费)` + } + return `另需配送费 ${formatMoney(fee)} 元` +}) const filteredGoods = computed(() => { const query = keyword.value.trim().toLowerCase() if (!query) return goods.value @@ -363,6 +449,7 @@ onShow(() => { //init(); refreshCart() shopAd.value = uni.getStorageSync('shopAd') + loadAddressesForDelivery() }) const openCartShow = () =>{ @@ -494,7 +581,69 @@ const refreshCart = () =>{ cartPopupVisible.value = false; } } + refreshMenuDeliveryFee() } +const loadAddressesForDelivery = async () => { + if (!isLogin.value) { + userAddresses.value = [] + refreshMenuDeliveryFee() + return + } + try { + const list = await addressAll() + userAddresses.value = list || [] + } catch (e) { + userAddresses.value = [] + } + refreshMenuDeliveryFee() +} +const applyLowestDeliveryFee = () => { + let fee = lowestDeliveryFee.value + const freeAt = freeDeliveryAmount.value + const cartAmount = parseFloat(getCartGoodsPrice.value) || 0 + if (freeAt > 0 && cartAmount >= freeAt) { + fee = 0 + } + menuDeliveryFee.value = fee + deliveryTipReady.value = true +} +const refreshMenuDeliveryFee = async () => { + const reqId = ++deliveryFeeReqId + if (!canShowDeliveryTip.value) { + menuDeliveryFee.value = 0 + deliveryTipReady.value = false + return + } + const addr = estimateAddress.value + const hasDefault = (userAddresses.value || []).some((item) => Number(item.isDefault) === 1) + // 有默认地址:按默认地址预估;无默认地址:展示最低配送费 + if (hasDefault && addr?.longitude && addr?.latitude && store.value?.id) { + try { + const res = await getDeliveryFee({ + shopId: store.value.id, + lng: addr.longitude, + lat: addr.latitude, + amount: getCartGoodsPrice.value + }) + if (reqId !== deliveryFeeReqId) return + if (res && !res.outOfRange) { + menuDeliveryFee.value = parseFloat(res.deliveryPrice) || 0 + deliveryTipReady.value = true + return + } + } catch (e) { + // fall through + } + } + if (reqId !== deliveryFeeReqId) return + applyLowestDeliveryFee() +} +watch( + [orderType, () => store.value?.id, () => store.value?.deliveryPrice, () => store.value?.freeDeliveryAmount, getCartGoodsPrice, canShowDeliveryTip], + () => { + refreshMenuDeliveryFee() + } +) const getAds = async(shop_id) =>{ let data = await menuAds({ shop_id: shop_id ? shop_id : 0 @@ -512,8 +661,8 @@ const takout = (force = false) => { url: '/pages/components/pages/login/login' }) return - } - + } + loadAddressesForDelivery() } const takein = (force = false) => { if (orderType.value == 'takein' && force == false) return @@ -716,6 +865,10 @@ const handleCartItemReduce = (index) => { } uni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value))) } +const onCartPayTap = () => { + if (!cart.value.length) return + toPay() +} const toPay = () => { if (!isLogin.value) { @@ -1397,10 +1550,36 @@ page { .price { flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; color: $text-color-base; + + &__amount { + font-size: 34rpx; + font-weight: 700; + line-height: 1.2; + } + + &__tip { + margin-top: 4rpx; + font-size: 20rpx; + line-height: 1.3; + color: #a8907a; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } } +.cart-box--with-tip { + height: auto; + min-height: $menu-cart-height; + padding: 12rpx 0; +} + .cart-popup { .top { padding: $menu-gap-sm $spacing-row-lg; @@ -1531,9 +1710,10 @@ page { .goods .wrapper .list .category .items .good .right .name { font-size: 31rpx; font-weight: 700; } .goods .wrapper .list .category .items .good .right .price_and_action .price { font-size: 36rpx; color: #ff6817; } .goods .wrapper .list .category .items .good .right .price_and_action .btn-group .btn.property_btn { height: 56rpx; padding: 0 18rpx; line-height: 56rpx; border-radius: 28rpx; background: #ff7417; } - .cart-box { bottom: 28rpx; height: 116rpx; border-radius: 58rpx; box-shadow: 0 8rpx 28rpx rgba(91, 61, 32, .16); } + .cart-box { bottom: 28rpx; min-height: 116rpx; height: auto; border-radius: 58rpx; box-shadow: 0 8rpx 28rpx rgba(91, 61, 32, .16); } .cart-box .cart-img { width: 116rpx; height: 116rpx; } .cart-box .pay-btn { padding: 0 46rpx; border-radius: 58rpx; background: linear-gradient(90deg, #ff9b20, #ff6817); } + .cart-box .price__tip { color: #b08968; } } .store-header-img { flex: 0 0 118rpx; width: 118rpx; height: 118rpx; border-radius: 16rpx; }