2839 lines
84 KiB
Vue
2839 lines
84 KiB
Vue
<script setup lang="ts">
|
||
import { useConfigStore, useUserStore } from "@/store";
|
||
import { dayjs } from "@/plugin/index";
|
||
import Config from '@/config/index'
|
||
import CheckoutSkeleton from "./components/checkout-skeleton.vue";
|
||
import ChangePhone from "./components/change-phone.vue";
|
||
import PriceDetail from "./components/price-detail.vue";
|
||
import VisitMethod from "@/components/visit-method/index.vue";
|
||
import {ref} from "vue";
|
||
import {
|
||
appMerchantCartListByMerchantIdPost,
|
||
appMerchantCartCalculateSavingsPost,
|
||
appMerchantDetailMerchantIdGet,
|
||
appUserAddressListPost,
|
||
type MerchantCartVo,
|
||
type MerchantVo,
|
||
appMerchantOrderCalculatePriceCartPost,
|
||
appUserCardSelectDefaultPost, appMerchantOrderCreateOrderCartPost, appMerchantOrderPayOrderPost,
|
||
appMerchantOrderCreateOrderCartBatchPost,
|
||
appMerchantOrderCalculatePriceCartBatchPost,
|
||
appMerchantCartListMerchantPost,
|
||
appMerchantOrderPayOrderBatchPost
|
||
} from "@/service";
|
||
import useEventEmit from "@/hooks/useEventEmit";
|
||
import {EventEnum} from "@/constant/enums";
|
||
import { getDistanceInMiles} from "@/utils/utils";
|
||
const { t } = useI18n();
|
||
const configStore = useConfigStore();
|
||
const userStore = useUserStore();
|
||
|
||
// 送达偏好
|
||
const visitMethodRef = ref();
|
||
// value 与 visit-method 组件保持一致:0-亲自送达 1-放门口
|
||
const visitMethod = ref({
|
||
value: 1,
|
||
label: t("components.visit.putItAtTheDoor"),
|
||
});
|
||
function chooseVisitMethod() {
|
||
visitMethodRef.value?.onOpen(visitMethod.value.value);
|
||
}
|
||
function handleVisitMethodConfirm(data: { value: number; label: string }) {
|
||
visitMethod.value = data;
|
||
}
|
||
|
||
// 联系方式(这里默认值取用户信息的手机号区号)
|
||
const contact = ref({
|
||
phone: userStore.userInfo.phone,
|
||
areaCode: "+1",
|
||
});
|
||
const changePhoneRef = ref<InstanceType<typeof ChangePhone>>();
|
||
function openChangePhone() {
|
||
if (changePhoneRef.value) {
|
||
changePhoneRef.value.onOpen(contact.value);
|
||
}
|
||
}
|
||
function confirmPhone(data: { phone: string; areaCode: string }) {
|
||
contact.value = data;
|
||
formData.value.phone = data.phone;
|
||
}
|
||
|
||
// 配送时间 1 即刻配送 2 预约配送
|
||
// 当前业务仅支持预约配送,默认值设置为 2
|
||
const deliveryTimeType = ref(2);
|
||
// 配送时间配置
|
||
const deliveryMinutes = ref(0); // 预计送达时间(分钟)
|
||
// 自取时间配置
|
||
const selfPickupMinutes = ref(0); // 自取时间(分钟)
|
||
const deliveryWindowMinutes = ref(30); // 配送时间窗口(分钟)
|
||
// 预约配送日期配送
|
||
const userSelectedDeliveryTimeDate = ref(null);
|
||
// 预约自取时间
|
||
const userSelectedSelfPickupTimeDate = ref(null);
|
||
// 显示用的配送时间(即刻配送=当前时间加预计时间)(预约配送=预约时间+预计时间分钟)
|
||
const showDeliveryTime = computed(() => {
|
||
// 配送
|
||
if (deliveryTimeType.value === 1) {
|
||
// 即刻配送:当前时间 + 预计送达时间
|
||
let time = 0
|
||
if(deliveryMethod.value === 0) {
|
||
time = deliveryMinutes.value
|
||
} else {
|
||
time = selfPickupMinutes.value
|
||
}
|
||
const deliveryStart = dayjs().add(Number(time), "minute");
|
||
const deliveryEnd = deliveryStart.add(
|
||
deliveryWindowMinutes.value,
|
||
"minute"
|
||
);
|
||
|
||
return `${deliveryStart.format("HH:mm")}-${deliveryEnd.format("HH:mm")}`;
|
||
}
|
||
if (deliveryTimeType.value === 2) {
|
||
return deliveryMethod.value === 0 ? userSelectedDeliveryTimeDate : '123';
|
||
}
|
||
return "";
|
||
});
|
||
// 切换配送时间点击事件(目前仅支持预约配送)
|
||
function toggleDeliveryTimeType(type: number) {
|
||
// 统一设置为预约配送
|
||
deliveryTimeType.value = 2;
|
||
|
||
// 跳转到时间选择页面
|
||
const businessHours = (storeDetail.value as any)?.businessHours;
|
||
const url = businessHours
|
||
? `/pages/address/reservation-time?storeBusinessHours=${encodeURIComponent(
|
||
businessHours
|
||
)}`
|
||
: "/pages/address/reservation-time";
|
||
|
||
uni.navigateTo({
|
||
url,
|
||
});
|
||
}
|
||
|
||
const diyTime = ref({})
|
||
useEventEmit(EventEnum.CHOOSE_APPOINTMENT_TIME, (data) => {
|
||
console.log('CHOOSE_APPOINTMENT_TIME', data)
|
||
if(data) {
|
||
diyTime.value = data;
|
||
deliveryTimeType.value = 2
|
||
|
||
// 配送还是自取
|
||
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 : '');
|
||
}
|
||
}
|
||
})
|
||
|
||
// 设置配送时间(分钟)
|
||
const setDeliveryMinutes = (minutes: number) => {
|
||
deliveryMinutes.value = minutes;
|
||
};
|
||
|
||
// 设置配送时间窗口(分钟)
|
||
const setDeliveryWindowMinutes = (minutes: number) => {
|
||
deliveryWindowMinutes.value = minutes;
|
||
};
|
||
|
||
const deliveryMethod = ref(0);
|
||
// 配送方式
|
||
const showDeliverySwitch = ref(false)
|
||
const deliveryMethodOptions = [
|
||
t("pages-store.store.delivery"),
|
||
t("pages-store.store.pickup"),
|
||
];
|
||
function handleClickSegmented(index: number) {
|
||
console.log("切换配送方式:", index);
|
||
if(+storeDetail.value.deliveryService !== 1 && index === 0) {
|
||
uni.showToast({
|
||
title: t('pages-store.store.toast.deliveryService'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if(+storeDetail.value.selfPickup !== 1 && index === 1) {
|
||
uni.showToast({
|
||
title: t('pages-store.store.toast.selfPickup'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
if(index !== deliveryMethod.value) {
|
||
deliveryMethod.value = index
|
||
// 重置配送类型为预约配送
|
||
deliveryTimeType.value = 2
|
||
void appMerchantOrderCalculatePriceCart()
|
||
}
|
||
}
|
||
|
||
// 折叠面板
|
||
const collapseValue = ref([""]);
|
||
|
||
// 单个订单的小费(与设计稿一致:默认 $2)
|
||
const selectedTipIndex = ref(2);
|
||
const diyTipValue = ref('')
|
||
// 批量订单:每个店铺的小费索引映射 merchantId -> tipIndex
|
||
const merchantTipIndexMap = ref<Record<string, number>>({})
|
||
// 批量订单:每个店铺的自定义小费值映射 merchantId -> diyTipValue
|
||
const merchantDiyTipValueMap = ref<Record<string, string>>({})
|
||
|
||
const tipOptions = ref([
|
||
{ label: "$ 2", value: 2 },
|
||
{ label: "$ 3", value: 3 },
|
||
{ label: "$ 4", value: 4 },
|
||
]);
|
||
|
||
// 单个订单的小费选择
|
||
function selectedTipChange(item: any) {
|
||
if(item.value === selectedTipIndex.value) return
|
||
diyTipValue.value = ''
|
||
selectedTipIndex.value = item.value
|
||
void appMerchantOrderCalculatePriceCart()
|
||
}
|
||
|
||
// 批量订单:选择某个店铺的小费
|
||
function selectedTipChangeForMerchant(merchantId: string, item: any) {
|
||
if(item.value === merchantTipIndexMap.value[merchantId]) return
|
||
merchantDiyTipValueMap.value[merchantId] = ''
|
||
merchantTipIndexMap.value[merchantId] = item.value
|
||
void appMerchantOrderCalculatePriceCart()
|
||
}
|
||
|
||
// 单个订单的自定义小费确认(金额,单位:美元)
|
||
function handleConfirmTip() {
|
||
// 如果未填写其他小费金额,失去焦点时默认填充为 0
|
||
const val = String(diyTipValue.value || '').trim()
|
||
if (!val) {
|
||
diyTipValue.value = '0'
|
||
}
|
||
// 使用自定义小费时,选中"其他"这一项
|
||
if (selectedTipIndex.value !== 0) {
|
||
selectedTipIndex.value = 0
|
||
}
|
||
void appMerchantOrderCalculatePriceCart()
|
||
}
|
||
|
||
// 批量订单:确认某个店铺的自定义小费(金额,单位:美元)
|
||
function handleConfirmTipForMerchant(merchantId: string) {
|
||
const val = String(merchantDiyTipValueMap.value[merchantId] || '').trim()
|
||
if (!val) {
|
||
merchantDiyTipValueMap.value[merchantId] = '0'
|
||
}
|
||
// 使用自定义小费时,选中"其他"这一项
|
||
if (merchantTipIndexMap.value[merchantId] !== 0) {
|
||
merchantTipIndexMap.value[merchantId] = 0
|
||
}
|
||
void appMerchantOrderCalculatePriceCart()
|
||
}
|
||
|
||
// 是否选择了配送周卡
|
||
const isWeeklyDelivery = ref(false);
|
||
// 切换配送周卡
|
||
const toggleWeeklyDelivery = (value: boolean) => {
|
||
if(value === isWeeklyDelivery.value) return
|
||
isWeeklyDelivery.value = value;
|
||
void appMerchantOrderCalculatePriceCart()
|
||
};
|
||
|
||
// 价格明细
|
||
const priceDetailRef = ref<InstanceType<typeof PriceDetail>>();
|
||
// 打开价格明细
|
||
const openPriceDetail = () => {
|
||
priceDetailRef.value?.onOpen(priceData.value, cartSavingsData.value?.savings || 0);
|
||
};
|
||
|
||
function navigateTo(url: string) {
|
||
uni.navigateTo({ url })
|
||
}
|
||
|
||
// 支付参数
|
||
const payMethodOptions = ref({
|
||
orderId: '',
|
||
cardId: '',
|
||
payMethod: 1, // 支付方式 1信用卡 2余额
|
||
payPassword: '',
|
||
})
|
||
useEventEmit(EventEnum.CHOOSE_PAYMENT_METHOD, (data) => {
|
||
if(data) {
|
||
if(data.payMethod === 1) {
|
||
payMethodOptions.value.cardId = data.cardId
|
||
payMethodOptions.value.cardNumber = data.cardNumber
|
||
payMethodOptions.value.payMethod = 1
|
||
} else {
|
||
payMethodOptions.value.payMethod = 2
|
||
}
|
||
}
|
||
})
|
||
|
||
|
||
const storeId = ref('')
|
||
const orderType = ref('') // 订单类型:batch-批量下单,normal-普通下单
|
||
const batchCartIds = ref([]) // 批量下单的购物车ID列表
|
||
const formData = ref<CreateOrderCartBo>({
|
||
orderRemark: '',
|
||
phone: '',
|
||
receiveMethod: 0,
|
||
startScheduledTime: 0,
|
||
endScheduledTime: 0,
|
||
tipDiscount: 0,
|
||
weeklyDeliveryFee: 0,
|
||
})
|
||
// 是否需要餐具
|
||
const needTableware = ref(false)
|
||
|
||
async function safeAwait<T>(label: string, task: () => Promise<T>): Promise<T | undefined> {
|
||
try {
|
||
return await task()
|
||
} catch (e) {
|
||
console.error(`[checkout] ${label} failed`, e)
|
||
return undefined
|
||
}
|
||
}
|
||
onLoad(async (options: any)=> {
|
||
loading.value = true
|
||
|
||
try {
|
||
// 判断是批量下单还是普通下单
|
||
if(options.type == 'batch' && options.cartIds) {
|
||
// 批量下单模式
|
||
orderType.value = 'batch'
|
||
batchCartIds.value = options.cartIds.split(',')
|
||
console.log("下单类型",options.type);
|
||
|
||
// 默认取用户信息中的手机号作为收货手机号
|
||
formData.value.phone = userStore.userInfo.phone || ''
|
||
contact.value.areaCode = userStore.userInfo.areaCode || ''
|
||
|
||
// 严格按顺序执行
|
||
await safeAwait('getAddressList', getAddressList)
|
||
await safeAwait('getBatchCartInfo', getBatchCartInfo)
|
||
await safeAwait('appUserCardSelectDefault', appUserCardSelectDefault)
|
||
} else if(options.storeId) {
|
||
// 普通下单模式
|
||
orderType.value = 'normal'
|
||
storeId.value = options.storeId as string
|
||
formData.value.orderRemark = options.orderRemark as string
|
||
|
||
needTableware.value = options.needTableware === 'true'
|
||
|
||
// 默认取用户信息中的手机号作为收货手机号
|
||
formData.value.phone = userStore.userInfo.phone || ''
|
||
contact.value.areaCode = userStore.userInfo.areaCode || ''
|
||
|
||
// 严格按顺序执行
|
||
await safeAwait('getAddressList', getAddressList)
|
||
await safeAwait('getCartInfo', getCartInfo)
|
||
await safeAwait('getStoreDetail', getStoreDetail)
|
||
await safeAwait('appUserCardSelectDefault', appUserCardSelectDefault)
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
})
|
||
|
||
onShow(async ()=> {
|
||
// 刷新地址,并在必要时重新计算价格
|
||
await safeAwait('getAddressList', getAddressList)
|
||
const hasCart =
|
||
orderType.value === 'batch'
|
||
? (batchCartIds.value?.length || 0) > 0
|
||
: (cartDataList.value?.length || 0) > 0
|
||
if (hasCart) {
|
||
await safeAwait('appMerchantOrderCalculatePriceCart', appMerchantOrderCalculatePriceCart)
|
||
}
|
||
})
|
||
|
||
// 页面加载状态
|
||
const loading = ref(true);
|
||
onMounted(() => {
|
||
userStore.getUserInfo()
|
||
const currentHour = dayjs().hour();
|
||
if (currentHour >= 11 && currentHour <= 14) {
|
||
setDeliveryMinutes(60);
|
||
} else if (currentHour >= 17 && currentHour <= 20) {
|
||
// 晚餐高峰期,配送时间延长
|
||
setDeliveryMinutes(50);
|
||
} else {
|
||
// 非高峰期,正常配送时间
|
||
setDeliveryMinutes(40);
|
||
}
|
||
});
|
||
|
||
const cartDataList = ref<MerchantCartVo[]>([])
|
||
async function getCartInfo() {
|
||
const res: any = await appMerchantCartListByMerchantIdPost({
|
||
params: {
|
||
merchantId: storeId.value,
|
||
}
|
||
})
|
||
|
||
console.log('购物车列表', res)
|
||
cartDataList.value = res.data
|
||
|
||
// 购物车有菜品,查询菜品会员折扣价 + 计算价格(严格串行)
|
||
if(cartDataList.value.length > 0) {
|
||
await appMerchantCartCalculateSavings()
|
||
await appMerchantOrderCalculatePriceCart()
|
||
}
|
||
}
|
||
|
||
// 批量下单:查询购物车详情
|
||
async function getBatchCartInfo() {
|
||
try {
|
||
// 根据购物车ID列表,按店铺分组查询
|
||
const cartIds = batchCartIds.value
|
||
console.log('批量购物车ID列表', cartIds)
|
||
|
||
// 1. 先获取所有店铺列表
|
||
const merchantRes = await appMerchantCartListMerchantPost({});
|
||
console.log('批量模式-购物车店铺列表', merchantRes);
|
||
|
||
if (!merchantRes.data || merchantRes.data.length === 0) {
|
||
cartDataList.value = [];
|
||
return;
|
||
}
|
||
|
||
// 2. 对每个店铺查询完整的商品信息
|
||
const merchantPromises = merchantRes.data.map(async (merchant: any) => {
|
||
try {
|
||
const cartRes = await appMerchantCartListByMerchantIdPost({
|
||
params: {
|
||
merchantId: merchant.id
|
||
}
|
||
});
|
||
console.log(`批量模式-店铺 ${merchant.merchantName} 的商品列表`, cartRes);
|
||
|
||
// 只保留选中的商品(根据 cartIds 过滤)
|
||
const filteredItems = (cartRes.data || []).filter((item: any) =>
|
||
cartIds.includes(String(item.id))
|
||
);
|
||
|
||
// 如果该店铺有选中的商品,才返回
|
||
if (filteredItems.length > 0) {
|
||
return {
|
||
...merchant,
|
||
merchantCartVoList: filteredItems
|
||
};
|
||
}
|
||
return null;
|
||
} catch (error) {
|
||
console.error(`获取店铺 ${merchant.id} 商品失败`, error);
|
||
return null;
|
||
}
|
||
});
|
||
|
||
// 3. 等待所有店铺的商品信息加载完成,并过滤掉 null
|
||
const results = await Promise.all(merchantPromises);
|
||
cartDataList.value = results.filter(item => item !== null);
|
||
console.log('批量模式-最终购物车数据', cartDataList.value);
|
||
cartDataList.value.forEach((m: any) => {
|
||
const id = String(m.id);
|
||
if (merchantTipIndexMap.value[id] === undefined) {
|
||
merchantTipIndexMap.value[id] = 2;
|
||
}
|
||
});
|
||
|
||
// 批量模式:查询菜品会员折扣价 + 计算价格(严格串行)
|
||
if(cartIds.length > 0) {
|
||
await appMerchantCartCalculateSavings()
|
||
await appMerchantOrderCalculatePriceCart()
|
||
}
|
||
} catch (error) {
|
||
console.error('获取批量购物车详情失败', error)
|
||
cartDataList.value = [];
|
||
}
|
||
}
|
||
|
||
// 查询菜品会员折扣价
|
||
const cartSavingsData = ref({})
|
||
async function appMerchantCartCalculateSavings() {
|
||
const cartIds = orderType.value == 'batch' ? batchCartIds.value : cartDataList.value.map(item => item.id)
|
||
const res: any = await appMerchantCartCalculateSavingsPost({
|
||
body: cartIds
|
||
})
|
||
console.log('菜品会员折扣价', res)
|
||
cartSavingsData.value = res.data
|
||
}
|
||
|
||
// 获取商家详情信息
|
||
// 商家是否支持配送
|
||
const storeIsDeliveryService = computed(()=> {
|
||
if(!storeDetail.value) return false
|
||
return +storeDetail.value.deliveryService === 1
|
||
})
|
||
// 商家是否支持自提
|
||
const storeIsSelfPickup = computed(()=> {
|
||
if(!storeDetail.value) return false
|
||
return +storeDetail.value.selfPickup === 1
|
||
})
|
||
const storeDetail = ref<MerchantVo>({})
|
||
// 用户距离商家的距离信息
|
||
const storeDistance = ref(null)
|
||
async function getStoreDetail() {
|
||
const res: any = await appMerchantDetailMerchantIdGet({
|
||
params: {
|
||
merchantId: storeId.value,
|
||
}
|
||
})
|
||
|
||
console.log('商家详情', res)
|
||
storeDetail.value = res.data as MerchantVo
|
||
|
||
// 配送时间(是否支持配送)
|
||
if(+storeDetail.value.deliveryService === 1) {
|
||
// 配送时间
|
||
deliveryMinutes.value = res.data.deliveryTime || 0
|
||
}
|
||
if(+storeDetail.value.selfPickup === 1) {
|
||
// 自提时间
|
||
selfPickupMinutes.value = res.data.pickupTime
|
||
}
|
||
// 商户的经纬度存在,并且用户的经纬度也存在
|
||
if(res.data.latitude && res.data.longitude && userStore.userLocation.latitude && userStore.userLocation.longitude) {
|
||
let distance = getDistanceInMiles(res.data.latitude, res.data.longitude, userStore.userLocation.latitude, userStore.userLocation.longitude)
|
||
console.log('距离商家距离', distance)
|
||
storeDistance.value = distance
|
||
}
|
||
|
||
// 判断配送和自取的开通状态
|
||
const hasDelivery = +storeDetail.value.deliveryService === 1
|
||
const hasPickup = +storeDetail.value.selfPickup === 1
|
||
|
||
if (!hasDelivery && !hasPickup) {
|
||
// 两个都没开通,不显示切换组件
|
||
showDeliverySwitch.value = false
|
||
} else if (!hasDelivery && hasPickup) {
|
||
// 只开通自取,默认选中自取
|
||
deliveryMethod.value = 1
|
||
showDeliverySwitch.value = true
|
||
} else if (hasDelivery && !hasPickup) {
|
||
// 只开通配送,默认选中配送
|
||
deliveryMethod.value = 0
|
||
showDeliverySwitch.value = true
|
||
} else {
|
||
// 两个都开通,默认选中配送
|
||
deliveryMethod.value = 0
|
||
showDeliverySwitch.value = true
|
||
}
|
||
}
|
||
|
||
const priceData = ref({})
|
||
async function appMerchantOrderCalculatePriceCart() {
|
||
// 优先使用新选择的地址id
|
||
const targetAddressId = selectedAddressId.value || currentAddressId.value
|
||
// if(!targetAddressId) return
|
||
|
||
const cartIds = orderType.value == 'batch' ? batchCartIds.value : cartDataList.value.map(item => item.id)
|
||
|
||
// 批量模式使用批量计算价格接口
|
||
if(orderType.value == 'batch') {
|
||
// 构建 merchantCouponMap: { merchantId: couponId }
|
||
const couponMap: Record<string, number> = {}
|
||
Object.keys(merchantCouponMap.value).forEach(merchantId => {
|
||
if(merchantCouponMap.value[merchantId]?.id) {
|
||
couponMap[merchantId] = merchantCouponMap.value[merchantId].id
|
||
}
|
||
})
|
||
|
||
// 构建 merchantTipMap: { merchantId: tipAmount } (小费金额,单位:美元)
|
||
const tipMap: Record<string, number> = {}
|
||
cartDataList.value.forEach((merchant: any) => {
|
||
const merchantId = String(merchant.id)
|
||
if(deliveryMethod.value === 1) {
|
||
// 自取订单不需要小费
|
||
tipMap[merchantId] = 0
|
||
} else {
|
||
// 配送订单需要小费
|
||
const diyTip = merchantDiyTipValueMap.value[merchantId]
|
||
const tipIndex = merchantTipIndexMap.value[merchantId] || 0
|
||
if(diyTip) {
|
||
// 自定义小费金额(美元)
|
||
tipMap[merchantId] = Number(diyTip) || 0
|
||
} else if(tipIndex > 0) {
|
||
// 选择固定金额选项
|
||
tipMap[merchantId] = tipIndex
|
||
} else {
|
||
tipMap[merchantId] = 0
|
||
}
|
||
}
|
||
})
|
||
|
||
const res: any = await appMerchantOrderCalculatePriceCartBatchPost({
|
||
body: {
|
||
addressId: targetAddressId,
|
||
cartIds: cartIds,
|
||
receiveMethod: deliveryMethod.value === 0 ? 1 : 2,
|
||
merchantCouponMap: couponMap,
|
||
merchantTipMap: tipMap, // 小费金额(美元)
|
||
phone: formData.value.phone,
|
||
areaCode: contact.value.areaCode,
|
||
}
|
||
})
|
||
console.log('批量购物车下单价格', res)
|
||
priceData.value = res.data
|
||
} else {
|
||
// 普通模式使用单店铺计算价格接口
|
||
// 计算小费金额(美元)
|
||
let tipAmount = 0
|
||
if(deliveryMethod.value === 0) {
|
||
// 配送订单需要小费
|
||
if(diyTipValue.value) {
|
||
tipAmount = Number(diyTipValue.value) || 0
|
||
} else if(selectedTipIndex.value > 0) {
|
||
tipAmount = selectedTipIndex.value
|
||
}
|
||
}
|
||
|
||
const res: any = await appMerchantOrderCalculatePriceCartPost({
|
||
body: {
|
||
addressId: targetAddressId,
|
||
cartIds: cartIds,
|
||
receiveMethod: deliveryMethod.value === 0 ? 1 : 2,
|
||
couponId: couponInfo.value ? couponInfo.value.id : '',
|
||
tipDiscount: tipAmount, // 小费金额(美元),自取订单不需要小费
|
||
// weeklyDeliveryFee: isWeeklyDelivery.value ? 1 : 2, // 是否支付周配送费(1-是 2-否)
|
||
phone: formData.value.phone,
|
||
areaCode: contact.value.areaCode,
|
||
}
|
||
})
|
||
console.log('购物车下单价格', res)
|
||
priceData.value = res.data
|
||
}
|
||
}
|
||
|
||
// 获取用户地址列表
|
||
const addressesList = ref([])
|
||
// 当前选中的地址id
|
||
const currentAddressId = ref('')
|
||
// 新选择的地址id(优先使用)
|
||
const selectedAddressId = ref('')
|
||
// 当前选中的地址信息
|
||
const addressInfo = computed(()=> {
|
||
if(addressesList.value.length === 0) return {}
|
||
// 优先使用新选择的地址id,如果没有则使用默认的地址id
|
||
const targetId = selectedAddressId.value || currentAddressId.value
|
||
return addressesList.value.find(item => String(item.id) === String(targetId)) || {};
|
||
});
|
||
|
||
async function getAddressList() {
|
||
const res: any = await appUserAddressListPost({
|
||
params: {
|
||
pageNum: 1,
|
||
pageSize: 100,
|
||
}
|
||
})
|
||
console.log('获取用户地址列表', res)
|
||
addressesList.value = res.rows
|
||
if(addressesList.value.length > 0) {
|
||
currentAddressId.value = addressesList.value[0].id
|
||
// 结算页配送偏好默认「放门口」,不按地址簿 deliveryType 回显
|
||
}
|
||
}
|
||
|
||
function chooseAddress() {
|
||
// 优先使用新选择的地址id
|
||
const targetAddressId = selectedAddressId.value || currentAddressId.value
|
||
uni.navigateTo({
|
||
url: '/pages-store/pages/order/choose-address?id=' + targetAddressId,
|
||
events: {
|
||
// 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据
|
||
acceptDataFromOpenedPage: function(data) {
|
||
console.log('获取被打开页面传送到当前页面的数据', data)
|
||
// 将新选择的地址id存储到selectedAddressId中
|
||
selectedAddressId.value = data.data
|
||
// 配送偏好默认「放门口」,切换地址时保持用户在本页已选或默认值,不回写地址簿
|
||
|
||
// 重新计算价格
|
||
void appMerchantOrderCalculatePriceCart()
|
||
},
|
||
},
|
||
})
|
||
}
|
||
|
||
function appUserCardSelectDefault() {
|
||
return appUserCardSelectDefaultPost({}).then((res: any)=> {
|
||
console.log('查询用户默认信用卡', res)
|
||
payMethodOptions.value.cardId = res.data?.cardId || ''
|
||
payMethodOptions.value.cardNumber = res.data?.cardNumber || ''
|
||
return res
|
||
})
|
||
}
|
||
|
||
// 是否显示周配送费选项
|
||
const isShowWeeklyDelivery = computed(()=> {
|
||
let time = userStore?.userInfo?.weeklyDeliveryExpire;
|
||
// 如果值为null,返回true
|
||
if (time === null) return true;
|
||
// 如果值不存在(如undefined),也返回true(根据你的需求可调整)
|
||
if (!time) return true;
|
||
// 检测是否到期:已过期返回true,未过期返回false
|
||
return dayjs().isAfter(dayjs(Number(time)));
|
||
})
|
||
|
||
const passwordInputRef = ref(null)
|
||
const resOrderId = ref('')
|
||
const resOrderIds = ref([]) // 批量下单返回的订单ID列表
|
||
function handleGoSettle() {
|
||
// 优先使用新选择的地址id
|
||
const targetAddressId = selectedAddressId.value || currentAddressId.value
|
||
if(!targetAddressId) {
|
||
uni.showToast({
|
||
title: t('pages-store.checkout.pleaseSelectAddress'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if(payMethodOptions.value.payMethod === 1 && !payMethodOptions.value.cardId) {
|
||
uni.showToast({
|
||
title: t('pages-store.checkout.pleaseSelectCreditCard'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 仅支持预约配送:必须先选择预约时间
|
||
if (deliveryTimeType.value === 2 && (!diyTime.value.startTime || !diyTime.value.endTime)) {
|
||
uni.showToast({
|
||
title: t('pages-store.checkout.chooseTime'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 批量下单
|
||
if(orderType.value == 'batch') {
|
||
if(resOrderIds.value.length === 0) {
|
||
// 构建 merchantCouponMap: { merchantId: couponId }
|
||
const couponMap: Record<string, number> = {}
|
||
Object.keys(merchantCouponMap.value).forEach(merchantId => {
|
||
if(merchantCouponMap.value[merchantId]?.id) {
|
||
couponMap[merchantId] = merchantCouponMap.value[merchantId].id
|
||
}
|
||
})
|
||
|
||
// 构建 merchantTipMap: { merchantId: tipAmount } (小费金额,单位:美元)
|
||
const tipMap: Record<string, number> = {}
|
||
cartDataList.value.forEach((merchant: any) => {
|
||
const merchantId = String(merchant.id)
|
||
if(deliveryMethod.value === 1) {
|
||
// 自取订单不需要小费
|
||
tipMap[merchantId] = 0
|
||
} else {
|
||
// 配送订单需要小费
|
||
const diyTip = merchantDiyTipValueMap.value[merchantId]
|
||
const tipIndex = merchantTipIndexMap.value[merchantId] || 0
|
||
if(diyTip) {
|
||
// 自定义小费金额(美元)
|
||
tipMap[merchantId] = Number(diyTip) || 0
|
||
} else if(tipIndex > 0) {
|
||
// 选择固定金额选项
|
||
tipMap[merchantId] = tipIndex
|
||
} else {
|
||
tipMap[merchantId] = 0
|
||
}
|
||
}
|
||
})
|
||
|
||
let data = {
|
||
addressId: targetAddressId,
|
||
phone: formData.value.phone,
|
||
areaCode: contact.value.areaCode,
|
||
cartIds: batchCartIds.value,
|
||
merchantCouponMap: couponMap,
|
||
merchantTipMap: tipMap,
|
||
deliveryMethod: visitMethod.value.label, // 派送方式(如放门口或者交到顾客手中)
|
||
deliveryType: deliveryTimeType.value, // 1-立即交付 2-预约交付
|
||
orderRemark: formData.value.orderRemark,
|
||
receiveMethod: deliveryMethod.value === 0 ? 1 : 2, // 收货方式(1-派送 2-自取)
|
||
startScheduledTime: '', //
|
||
endScheduledTime: '', //
|
||
needTableware: needTableware.value ? 1 : 2, // 餐具 1是 2否
|
||
}
|
||
|
||
// 如果是预约派送
|
||
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
|
||
}
|
||
console.log('批量下单参数', data)
|
||
appMerchantOrderCreateOrderCartBatchPost({
|
||
body: data
|
||
}).then(res=> {
|
||
console.log('批量下单成功', res)
|
||
resOrderIds.value = res.data.orderIds || []
|
||
// 如果是余额支付,弹出支付密码弹窗
|
||
if(payMethodOptions.value.payMethod === 2) {
|
||
passwordInputRef.value?.showPasswordInput()
|
||
} else {
|
||
appMerchantOrderPayOrderBatch()
|
||
}
|
||
})
|
||
} else {
|
||
// 如果是余额支付,弹出支付密码弹窗
|
||
if(payMethodOptions.value.payMethod === 2) {
|
||
passwordInputRef.value?.showPasswordInput()
|
||
} else {
|
||
appMerchantOrderPayOrderBatch()
|
||
}
|
||
}
|
||
} else {
|
||
// 普通下单
|
||
if(!resOrderId.value) {
|
||
// 计算小费金额(美元)
|
||
let tipAmount = 0
|
||
if(deliveryMethod.value === 0) {
|
||
// 配送订单需要小费
|
||
if(diyTipValue.value) {
|
||
tipAmount = Number(diyTipValue.value) || 0
|
||
} else if(selectedTipIndex.value > 0) {
|
||
tipAmount = selectedTipIndex.value
|
||
}
|
||
}
|
||
|
||
let data = {
|
||
addressId: targetAddressId,
|
||
phone: formData.value.phone,
|
||
areaCode: contact.value.areaCode,
|
||
cartIds: cartDataList.value.map(item => item.id),
|
||
couponId: couponInfo.value ? couponInfo.value.id : '',
|
||
deliveryMethod: visitMethod.value.label, // 派送方式(如放门口或者交到顾客手中)
|
||
deliveryType: deliveryTimeType.value, // 1-立即交付 2-预约交付
|
||
orderRemark: formData.value.orderRemark,
|
||
receiveMethod: deliveryMethod.value === 0 ? 1 : 2, // 收货方式(1-派送 2-自取)
|
||
startScheduledTime: '', //
|
||
endScheduledTime: '', //
|
||
tipDiscount: tipAmount, // 小费金额(美元),自取订单不需要小费
|
||
// weeklyDeliveryFee: isWeeklyDelivery.value ? 1 : 2, // 是否支付周配送费(1-是 2-否)
|
||
needTableware: needTableware.value ? 1 : 2, // 餐具 1是 2否
|
||
}
|
||
|
||
// 如果是预约派送
|
||
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
|
||
}
|
||
console.log('下单参数', data)
|
||
appMerchantOrderCreateOrderCartPost({
|
||
body: data
|
||
}).then(res=> {
|
||
console.log('下单成功', res)
|
||
resOrderId.value = res.data || ''
|
||
// 如果是余额支付,弹出支付密码弹窗
|
||
if(payMethodOptions.value.payMethod === 2) {
|
||
passwordInputRef.value?.showPasswordInput()
|
||
} else {
|
||
appMerchantOrderPayOrder()
|
||
}
|
||
})
|
||
} else {
|
||
// 如果是余额支付,弹出支付密码弹窗
|
||
if(payMethodOptions.value.payMethod === 2) {
|
||
passwordInputRef.value?.showPasswordInput()
|
||
} else {
|
||
appMerchantOrderPayOrder()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function payPawSuccess(password: string) {
|
||
payMethodOptions.value.payPassword = password
|
||
if(orderType.value === 'batch') {
|
||
appMerchantOrderPayOrderBatch()
|
||
} else {
|
||
appMerchantOrderPayOrder()
|
||
}
|
||
}
|
||
|
||
function appMerchantOrderPayOrder() {
|
||
appMerchantOrderPayOrderPost({
|
||
body: {
|
||
...payMethodOptions.value,
|
||
orderId: resOrderId.value
|
||
}
|
||
}).then(res=> {
|
||
console.log('支付结果', res)
|
||
uni.showToast({
|
||
title: t('pages-store.checkout.paymentSuccess'),
|
||
icon: 'none'
|
||
})
|
||
|
||
setTimeout(()=> {
|
||
// 支付成功后跳转到当前订单详情页
|
||
if (resOrderId.value) {
|
||
uni.reLaunch({
|
||
url: `/pages-store/pages/order/index?id=${resOrderId.value}`
|
||
})
|
||
} else {
|
||
uni.navigateBack({
|
||
delta: 2,
|
||
})
|
||
}
|
||
}, 500)
|
||
})
|
||
}
|
||
|
||
// 批量订单支付
|
||
function appMerchantOrderPayOrderBatch() {
|
||
// 使用批量支付接口
|
||
appMerchantOrderPayOrderBatchPost({
|
||
body: {
|
||
orderIds: resOrderIds.value,
|
||
cardId: payMethodOptions.value.cardId,
|
||
payMethod: payMethodOptions.value.payMethod,
|
||
payPassword: payMethodOptions.value.payPassword
|
||
}
|
||
}).then(res=> {
|
||
console.log('批量支付结果', res)
|
||
uni.showToast({
|
||
title: t('pages-store.checkout.paymentSuccess'),
|
||
icon: 'none'
|
||
})
|
||
|
||
setTimeout(()=> {
|
||
// 批量支付后,优先跳转到第一笔订单详情;若无订单ID则返回
|
||
const firstOrderId = resOrderIds.value && resOrderIds.value.length > 0 ? resOrderIds.value[0] : ''
|
||
if (firstOrderId) {
|
||
uni.redirectTo({
|
||
url: `/pages-store/pages/order/index?id=${firstOrderId}`
|
||
})
|
||
} else {
|
||
uni.navigateBack({
|
||
delta: 2,
|
||
})
|
||
}
|
||
}, 500)
|
||
}).catch(err => {
|
||
console.error('批量支付失败', err)
|
||
uni.showToast({
|
||
title: '支付失败',
|
||
icon: 'none'
|
||
})
|
||
})
|
||
}
|
||
|
||
function getTimeStamps(timeStr: string) {
|
||
// 验证输入是否为空
|
||
if (!timeStr || typeof timeStr !== 'string') {
|
||
throw new Error('输入必须是有效的时间字符串,格式如 "HH:mm-HH:mm"');
|
||
}
|
||
|
||
// 验证格式是否正确
|
||
const timeFormatRegex = /^([01]\d|2[0-3]):([0-5]\d)-([01]\d|2[0-3]):([0-5]\d)$/;
|
||
if (!timeFormatRegex.test(timeStr)) {
|
||
throw new Error('时间格式不正确,请使用 "HH:mm-HH:mm" 格式,例如 "17:57-18:27"');
|
||
}
|
||
|
||
// 分割时间字符串
|
||
const [startTimeStr, endTimeStr] = timeStr.split('-');
|
||
|
||
// 获取当前日期
|
||
const today = dayjs().format('YYYY-MM-DD');
|
||
|
||
// 解析开始和结束时间
|
||
const startTime = dayjs(`${today} ${startTimeStr}`);
|
||
const endTime = dayjs(`${today} ${endTimeStr}`);
|
||
|
||
// 验证时间是否有效
|
||
if (!startTime.isValid()) {
|
||
throw new Error(`开始时间 ${startTimeStr} 无效`);
|
||
}
|
||
|
||
if (!endTime.isValid()) {
|
||
throw new Error(`结束时间 ${endTimeStr} 无效`);
|
||
}
|
||
|
||
// 验证结束时间是否晚于开始时间
|
||
if (endTime.isBefore(startTime)) {
|
||
throw new Error('结束时间不能早于开始时间');
|
||
}
|
||
|
||
// 返回时间戳(毫秒)
|
||
return {
|
||
startTimestamp: startTime.valueOf(),
|
||
endTimestamp: endTime.valueOf()
|
||
};
|
||
}
|
||
|
||
/**
|
||
* CreateOrderCartBo
|
||
*/
|
||
export interface CreateOrderCartBo {
|
||
/**
|
||
* 配送地址id
|
||
*/
|
||
addressId?: number;
|
||
/**
|
||
* 区号
|
||
*/
|
||
areaCode?: string;
|
||
/**
|
||
* 购物车id列表
|
||
*/
|
||
cartIds?: number[];
|
||
/**
|
||
* 优惠券id
|
||
*/
|
||
couponId?: number;
|
||
/**
|
||
* 派送方式(如放门口或者交到顾客手中)
|
||
*/
|
||
deliveryMethod?: string;
|
||
/**
|
||
* 交付时间类型(1-立即交付 2-预约交付)
|
||
*/
|
||
deliveryType?: number;
|
||
/**
|
||
* 订单备注
|
||
*/
|
||
orderRemark?: string;
|
||
/**
|
||
* 手机号
|
||
*/
|
||
phone: string;
|
||
/**
|
||
* 收货方式(1-派送 2-自取)
|
||
*/
|
||
receiveMethod: number;
|
||
/**
|
||
* 预约时间- 开始
|
||
*/
|
||
startScheduledTime?: number;
|
||
/**
|
||
* 预约时间- 结束
|
||
*/
|
||
endScheduledTime?: number;
|
||
/**
|
||
* 小费比例
|
||
*/
|
||
tipDiscount?: number;
|
||
/**
|
||
* 是否支付周配送费(1-是 2-否)
|
||
*/
|
||
weeklyDeliveryFee?: number;
|
||
[property: string]: any;
|
||
}
|
||
|
||
// 单个订单的优惠券信息
|
||
const couponInfo = ref(null)
|
||
// 批量订单:每个店铺的优惠券映射 merchantId -> couponInfo
|
||
const merchantCouponMap = ref<Record<string, any>>({})
|
||
// 批量订单:每个店铺的小费映射 merchantId -> tipAmount (小费金额,单位:美元)
|
||
const merchantTipMap = ref<Record<string, number>>({})
|
||
// 批量订单:每个店铺的小费比例映射 merchantId -> tipPercent (小费比例,0-100)
|
||
const merchantTipPercentMap = ref<Record<string, number>>({})
|
||
|
||
function navigateToCoupon(merchantId?: string) {
|
||
const targetMerchantId = merchantId || storeId.value
|
||
uni.navigateTo({
|
||
url: '/pages-user/pages/coupon/list?id=' + targetMerchantId,
|
||
events: {
|
||
// 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据
|
||
selectedCoupon: function(data) {
|
||
console.log(data)
|
||
if(data) {
|
||
if(orderType.value === 'batch' && merchantId) {
|
||
// 批量模式:存储到对应店铺的优惠券映射
|
||
merchantCouponMap.value[merchantId] = data
|
||
} else {
|
||
// 单个订单模式
|
||
couponInfo.value = data
|
||
}
|
||
// 重新计算价格
|
||
void appMerchantOrderCalculatePriceCart()
|
||
}
|
||
},
|
||
},
|
||
})
|
||
}
|
||
const isUserMemberCheckout = computed(() => {
|
||
const vo = userStore.userInfo.userMembershipVo;
|
||
if (!vo) return false;
|
||
const exp = vo.expireTime;
|
||
if (exp) return dayjs().isBefore(dayjs(Number(exp)));
|
||
return false;
|
||
});
|
||
|
||
const cartTotalPieceCount = computed(() => {
|
||
if (orderType.value === 'batch') {
|
||
let n = 0;
|
||
cartDataList.value.forEach((m: any) => {
|
||
(m.merchantCartVoList || []).forEach((item: any) => {
|
||
n += Number(item.count) || 0;
|
||
});
|
||
});
|
||
return n || batchCartIds.value.length;
|
||
}
|
||
return cartDataList.value.reduce(
|
||
(sum, item: any) => sum + (Number(item.count) || 0),
|
||
0,
|
||
);
|
||
});
|
||
|
||
const serviceFeeAmount = computed(() => {
|
||
const p: any = priceData.value;
|
||
if (!p) return 0;
|
||
const original =
|
||
Number(p.actualAmount ?? p.totalActualAmount ?? 0) || 0;
|
||
const ratio = Number(p.merchantVo?.platformServiceFeeRatio) || 0;
|
||
return Math.round(original * ratio * 100) / 100;
|
||
});
|
||
|
||
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]} >`;
|
||
}
|
||
}
|
||
return `${str} >`;
|
||
});
|
||
|
||
function safeToNumber(val: unknown, fallback = 0) {
|
||
const n = Number(val);
|
||
return Number.isFinite(n) ? n : fallback;
|
||
}
|
||
|
||
function safeMoneyStr(val: unknown, fallback = 0) {
|
||
return safeToNumber(val, fallback).toFixed(2);
|
||
}
|
||
|
||
const displayDeliveryFeeStr = computed(() => {
|
||
const p: any = priceData.value;
|
||
if (!p) return '0.00';
|
||
if (orderType.value === 'batch') {
|
||
return safeMoneyStr(p.totalDeliveryFee, 0);
|
||
}
|
||
return safeMoneyStr(p.deliveryFee, 0);
|
||
});
|
||
|
||
const displayGoodsAmountStr = computed(() => {
|
||
const p: any = priceData.value;
|
||
if (!p) return '0.00';
|
||
if (orderType.value === 'batch') {
|
||
return safeMoneyStr(p.totalActualAmount, 0);
|
||
}
|
||
return safeMoneyStr(p.actualAmount, 0);
|
||
});
|
||
|
||
const displayTaxStr = computed(() => {
|
||
const p: any = priceData.value;
|
||
if (!p) return '0.00';
|
||
if (orderType.value === 'batch') {
|
||
return safeMoneyStr(p.totalTax, 0);
|
||
}
|
||
return safeMoneyStr(p.tax, 0);
|
||
});
|
||
|
||
const displayTipStr = computed(() => {
|
||
const p: any = priceData.value;
|
||
if (!p) return '0.00';
|
||
if (orderType.value === 'batch') {
|
||
return safeMoneyStr(p.totalTip, 0);
|
||
}
|
||
return safeMoneyStr(p.tip, 0);
|
||
});
|
||
|
||
const displayPaidStr = computed(() => {
|
||
const p: any = priceData.value;
|
||
if (!p) return '0.00';
|
||
if (orderType.value === 'batch') {
|
||
return safeMoneyStr(p.totalPaidAmount, 0);
|
||
}
|
||
return safeMoneyStr(p.paidAmount, 0);
|
||
});
|
||
|
||
const scheduledServiceEndLabel = computed(() => {
|
||
const slot = (diyTime.value as any)?.timeSlot;
|
||
if (slot && String(slot).includes(' - ')) {
|
||
return String(slot).split(' - ')[1]?.trim() || '';
|
||
}
|
||
return '';
|
||
});
|
||
|
||
const showAppointmentEntry = computed(() => {
|
||
if (deliveryMethod.value === 0) {
|
||
return storeIsDeliveryService.value || orderType.value === 'batch';
|
||
}
|
||
return storeIsSelfPickup.value || orderType.value === 'batch';
|
||
});
|
||
|
||
/** 商家标价配送费(用于划线价,实际运费可能因活动/距离更低) */
|
||
const listDeliveryFeeAmount = computed(() => {
|
||
const p: any = priceData.value;
|
||
const raw = p?.merchantVo?.deliveryFee;
|
||
if (raw === undefined || raw === null || raw === '') return null;
|
||
const n = Number(raw);
|
||
return Number.isFinite(n) ? n : null;
|
||
});
|
||
|
||
const actualDeliveryFeeNum = computed(() => {
|
||
const p: any = priceData.value;
|
||
if (!p) return 0;
|
||
if (orderType.value === 'batch') {
|
||
return Number(p.totalDeliveryFee ?? 0);
|
||
}
|
||
return Number(p.deliveryFee ?? 0);
|
||
});
|
||
|
||
const showListDeliveryStrike = computed(() => {
|
||
const list = listDeliveryFeeAmount.value;
|
||
if (list == null) return false;
|
||
return list > actualDeliveryFeeNum.value + 0.005;
|
||
});
|
||
|
||
const listDeliveryFeeStr = computed(() => {
|
||
const list = listDeliveryFeeAmount.value;
|
||
return list != null ? list.toFixed(2) : '';
|
||
});
|
||
|
||
function handleClose(merchantId?: string) {
|
||
if(orderType.value === 'batch' && merchantId) {
|
||
// 批量模式:清除对应店铺的优惠券
|
||
delete merchantCouponMap.value[merchantId]
|
||
} else {
|
||
// 单个订单模式
|
||
couponInfo.value = null
|
||
}
|
||
// 重新计算价格
|
||
void appMerchantOrderCalculatePriceCart()
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<view class="checkout-root">
|
||
<navbar
|
||
:title="t('pages-store.checkout.title')"
|
||
circle-back
|
||
custom-class="checkout-page-navbar"
|
||
/>
|
||
<view
|
||
class="animate-in fade-in animate-ease-out animate-duration-300"
|
||
v-show="loading"
|
||
>
|
||
<!-- 骨架屏 -->
|
||
<CheckoutSkeleton
|
||
/></view>
|
||
<view
|
||
class="checkout-page animate-in fade-in animate-ease-in animate-duration-300"
|
||
v-if="!loading"
|
||
>
|
||
<view class="checkout-page-stack">
|
||
<!-- 配送信息:主行(地址)+ 底部分栏(偏好 | 电话) -->
|
||
<view class="checkout-block">
|
||
<view class="checkout-section-label">{{ t('pages-store.checkout.deliveryInfo') }}</view>
|
||
<view class="checkout-card checkout-gutter checkout-card--stacked">
|
||
<template v-if="deliveryMethod === 0">
|
||
<view @click="chooseAddress" class="checkout-delivery-main">
|
||
<view class="flex-1 min-w-0 pr-16rpx">
|
||
<template v-if="addressesList.length > 0 && addressInfo && addressInfo.formattedAddress">
|
||
<text class="checkout-card-primary block line-clamp-2">{{ addressInfo.formattedAddress }}</text>
|
||
<text v-if="addressInfo.displayName" class="checkout-card-secondary block mt-10rpx">{{ addressInfo.displayName }}</text>
|
||
</template>
|
||
<text v-else class="checkout-card-placeholder">{{ t('pages-store.checkout.fillAddressHint') }}</text>
|
||
</view>
|
||
<image src="@img/chef/142.png" mode="aspectFill" class="checkout-chevron" />
|
||
</view>
|
||
<view class="checkout-delivery-extras">
|
||
<view @click.stop="chooseVisitMethod" class="checkout-delivery-extras-cell">
|
||
<text class="checkout-delivery-extras-label">{{ t('pages-store.checkout.deliveryPreference') }}</text>
|
||
<text class="checkout-delivery-extras-value line-clamp-1">{{ visitMethod.label }}</text>
|
||
</view>
|
||
<view class="checkout-delivery-extras-vline" />
|
||
<view @click.stop="openChangePhone" class="checkout-delivery-extras-cell">
|
||
<text class="checkout-delivery-extras-label">{{ t('pages-store.checkout.contactPhone') }}</text>
|
||
<text class="checkout-delivery-extras-value line-clamp-1">{{ contact.areaCode }} {{ formData.phone }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<template v-else>
|
||
<view class="checkout-pickup-info">
|
||
<text class="checkout-pickup-name">{{ storeDetail?.merchantName }}</text>
|
||
<text class="checkout-pickup-addr line-clamp-2">{{ storeDetail?.merchantAddress }}</text>
|
||
</view>
|
||
<view class="checkout-delivery-extras checkout-delivery-extras--full">
|
||
<view class="checkout-delivery-extras-cell checkout-delivery-extras-cell--grow">
|
||
<text class="checkout-delivery-extras-label">{{ t("pages-store.checkout.distance") }}</text>
|
||
<text class="checkout-delivery-extras-value">
|
||
<template v-if="storeDistance">{{ storeDistance }} {{ t('common.mile') }}</template>
|
||
<template v-else>{{ t('pages-store.checkout.enableLocationForDistance') }}</template>
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 支付方式 -->
|
||
<view class="checkout-block">
|
||
<view class="checkout-section-label">{{ t('pages-store.checkout.payMethodSection') }}</view>
|
||
<view
|
||
@click="navigateTo('/pages-user/pages/choose-paymethod/index')"
|
||
class="checkout-card checkout-gutter checkout-pay-row"
|
||
>
|
||
<view class="flex-1 min-w-0 pr-16rpx">
|
||
<template v-if="payMethodOptions.payMethod === 1">
|
||
<text class="checkout-card-primary block">{{ t('pages-user.choosePaymethod.creditCard') }}</text>
|
||
<text v-if="payMethodOptions.cardId" class="checkout-card-secondary block mt-10rpx">{{ payMethodOptions.cardNumber }}</text>
|
||
<text v-else class="checkout-card-secondary block mt-10rpx">{{ t("pages-user.member.creditCard") }}</text>
|
||
</template>
|
||
<template v-else>
|
||
<text class="checkout-card-primary block">{{ t('pages-user.choosePaymethod.wallet') }}</text>
|
||
</template>
|
||
</view>
|
||
<image src="@img/chef/142.png" class="checkout-chevron" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 确认订单(单店) -->
|
||
<view v-if="orderType === 'normal' && cartDataList.length > 0" class="checkout-block">
|
||
<view class="checkout-section-label">{{ t('pages-store.checkout.confirmOrder') }}</view>
|
||
<view class="checkout-card checkout-card--flush checkout-gutter overflow-hidden">
|
||
<view class="checkout-confirm-inner">
|
||
<view class="checkout-confirm-head">
|
||
<view class="checkout-icon-circle">
|
||
<view
|
||
v-if="deliveryMethod === 0"
|
||
class="i-carbon:delivery text-40rpx text-#333"
|
||
/>
|
||
<view
|
||
v-else
|
||
class="i-carbon:store text-40rpx text-#333"
|
||
/>
|
||
</view>
|
||
<view class="checkout-confirm-head-text">
|
||
<text class="checkout-card-title block">{{
|
||
deliveryMethod === 0 ? t('pages-store.checkout.localDelivery') : t('pages-store.checkout.pickupTitle')
|
||
}}</text>
|
||
<text class="checkout-card-subtitle block">{{
|
||
deliveryMethod === 0
|
||
? t('pages-store.checkout.localDeliverySubtitle', { name: Config.appName })
|
||
: t('pages-store.checkout.pickupSubtitle')
|
||
}}</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="showAppointmentEntry" class="checkout-confirm-band">
|
||
<view
|
||
@click.stop="toggleDeliveryTimeType(2)"
|
||
class="checkout-date-pill"
|
||
>
|
||
{{ deliveryPillText }}
|
||
</view>
|
||
<view class="checkout-confirm-band-right">
|
||
<text class="checkout-confirm-band-line1">
|
||
{{ t('pages-store.checkout.itemsGoodsTotalWithPrice', { count: cartTotalPieceCount, amount: displayGoodsAmountStr }) }}
|
||
</text>
|
||
<view v-if="deliveryMethod === 0" class="checkout-confirm-band-line2">
|
||
<text>{{ t('pages-store.checkout.shippingFee') }}</text>
|
||
<text v-if="showListDeliveryStrike" class="checkout-price-strike"> ${{ listDeliveryFeeStr }}</text>
|
||
<text v-if="showListDeliveryStrike"> </text>
|
||
<text class="checkout-confirm-meta-fee">${{ displayDeliveryFeeStr }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<scroll-view
|
||
v-if="deliveryMethod === 0"
|
||
scroll-x
|
||
class="checkout-schedule-scroll checkout-schedule-bleed"
|
||
:show-scrollbar="false"
|
||
>
|
||
<view class="checkout-schedule-inner">
|
||
<view class="checkout-schedule-card checkout-schedule-card--on">
|
||
<view class="checkout-schedule-card-body">
|
||
<view class="checkout-radio-dot" />
|
||
<view class="flex-1 min-w-0">
|
||
<text class="checkout-schedule-title">{{ t('pages-store.checkout.scheduledDeliveryTitle') }}</text>
|
||
<text v-if="scheduledServiceEndLabel" class="checkout-schedule-mid">{{
|
||
t('pages-store.checkout.scheduledDeliveryWindow', { time: scheduledServiceEndLabel })
|
||
}}</text>
|
||
<text class="checkout-schedule-hint">{{
|
||
t('pages-store.checkout.scheduledDeliveryHint')
|
||
}}</text>
|
||
</view>
|
||
</view>
|
||
<text class="checkout-schedule-addon">{{ t('pages-store.checkout.scheduledDeliveryAddon') }}</text>
|
||
</view>
|
||
<view class="checkout-schedule-card checkout-schedule-card--off">
|
||
<view class="checkout-schedule-card-body">
|
||
<view class="checkout-radio-ring" />
|
||
<view class="flex-1 min-w-0">
|
||
<text class="checkout-schedule-title checkout-schedule-title--muted">{{ t('pages-store.checkout.scheduledDeliveryStandardTitle') }}</text>
|
||
<text class="checkout-schedule-hint">{{ t('pages-store.checkout.scheduledDeliveryStandardDesc') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<wd-collapse v-model="collapseValue" custom-class="checkout-order-collapse">
|
||
<wd-collapse-item name="item3">
|
||
<template #title="{ expanded }">
|
||
<scroll-view scroll-x class="checkout-thumb-scroll checkout-thumb-strip" :show-scrollbar="false">
|
||
<view class="flex items-center gap-16rpx py-20rpx pr-28rpx">
|
||
<view
|
||
v-for="cItem in cartDataList"
|
||
:key="cItem.id"
|
||
class="relative shrink-0 w-100rpx h-100rpx rounded-12rpx overflow-hidden"
|
||
>
|
||
<image
|
||
:src="(cItem.merchantDishVo?.dishImage || '').split(',')[0]"
|
||
mode="aspectFill"
|
||
class="w-full h-full"
|
||
/>
|
||
<view
|
||
v-if="Number(cItem.count) > 1"
|
||
class="checkout-qty-badge"
|
||
>x{{ cItem.count }}</view>
|
||
</view>
|
||
<image
|
||
:class="[expanded ? 'rotate--90' : 'rotate-90']"
|
||
src="@img/chef/142.png"
|
||
mode="aspectFill"
|
||
class="w-32rpx h-32rpx shrink-0 transition-all duration-300 ml-8rpx"
|
||
/>
|
||
</view>
|
||
</scroll-view>
|
||
</template>
|
||
<view
|
||
v-for="(item, index) in cartDataList"
|
||
:key="item.id"
|
||
class="checkout-order-line"
|
||
>
|
||
<view class="flex items-center flex-1 min-w-0">
|
||
<view
|
||
class="w-48rpx h-48rpx rounded-8rpx bg-#F2F2F2 center mr-24rpx shrink-0 text-24rpx"
|
||
>{{ index + 1 }}</view>
|
||
<view class="min-w-0 flex-1">
|
||
<view class="text-30rpx lh-36rpx text-#333 font-500 line-clamp-2"
|
||
>{{ item.merchantDishVo?.dishName }}</view
|
||
>
|
||
<view v-if="item.sideDishList?.length > 0"
|
||
class="text-24rpx lh-30rpx text-#7D7D7D font-400 mt-8rpx"
|
||
>
|
||
<view
|
||
v-for="(dish, dIdx) in item.sideDishList"
|
||
:key="dIdx"
|
||
class="inline"
|
||
>
|
||
<text class="mr-6rpx">{{ dish.merchantSideDishItemVo?.name }}</text>
|
||
<text class="text-#00A76D mx-6rpx">+${{ dish.merchantSideDishItemVo?.price }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="text-30rpx text-#333 font-500 shrink-0 ml-16rpx">${{ item.merchantDishVo?.discountPrice }}</view>
|
||
</view>
|
||
</wd-collapse-item>
|
||
</wd-collapse>
|
||
|
||
<view
|
||
v-if="showAppointmentEntry && (userSelectedDeliveryTimeDate || userSelectedSelfPickupTimeDate)"
|
||
class="checkout-deliver-hint"
|
||
>
|
||
{{
|
||
t('pages-store.checkout.deliverBefore', {
|
||
time: deliveryMethod === 0 ? userSelectedDeliveryTimeDate : userSelectedSelfPickupTimeDate,
|
||
})
|
||
}}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 批量模式:多店铺列表 -->
|
||
<view class="checkout-block" v-if="orderType === 'batch' && cartDataList.length > 0">
|
||
<view class="checkout-section-label">{{ t('pages-store.checkout.confirmOrder') }}</view>
|
||
<view v-if="showAppointmentEntry" class="checkout-card checkout-gutter checkout-batch-pill-card">
|
||
<view class="checkout-confirm-band checkout-confirm-band--compact">
|
||
<view @click.stop="toggleDeliveryTimeType(2)" class="checkout-date-pill">
|
||
{{ deliveryPillText }}
|
||
</view>
|
||
<view class="checkout-confirm-band-right">
|
||
<text class="checkout-confirm-band-line1">
|
||
{{ cartTotalPieceCount }} {{ t('pages-user.cart.items') }} · ${{ displayGoodsAmountStr }}
|
||
</text>
|
||
<view v-if="deliveryMethod === 0" class="checkout-confirm-band-line2">
|
||
<text>{{ t('pages-store.checkout.shippingFee') }}</text>
|
||
<text v-if="showListDeliveryStrike" class="checkout-price-strike"> ${{ listDeliveryFeeStr }}</text>
|
||
<text v-if="showListDeliveryStrike"> </text>
|
||
<text class="checkout-batch-shipping-now">${{ displayDeliveryFeeStr }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="checkout-gutter">
|
||
<view
|
||
v-for="merchant in cartDataList"
|
||
:key="merchant.id"
|
||
class="mb-24rpx checkout-card checkout-card--merchant overflow-hidden"
|
||
>
|
||
<!-- 店铺头部 -->
|
||
<view class="checkout-merchant-head">
|
||
<image
|
||
class="w-60rpx h-60rpx rounded-50% mr-20rpx"
|
||
:src="merchant.logo"
|
||
mode="aspectFill"
|
||
/>
|
||
<view>
|
||
<view class="text-28rpx lh-28rpx text-#333 font-500"
|
||
>{{ merchant.merchantName }}</view
|
||
>
|
||
<view
|
||
class="text-24rpx lh-24rpx text-#7D7D7D font-400 mt-8rpx"
|
||
>{{ merchant.merchantCartVoList.length }} {{ t('pages-user.cart.items') }}</view
|
||
>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品列表 -->
|
||
<view class="checkout-merchant-body">
|
||
<view
|
||
v-for="(item, itemIndex) in merchant.merchantCartVoList"
|
||
:key="item.id"
|
||
class="checkout-merchant-item"
|
||
:class="itemIndex < merchant.merchantCartVoList.length - 1 ? 'is-divider' : ''"
|
||
>
|
||
<!-- 商品图片 -->
|
||
<image
|
||
:src="item.merchantDishVo?.dishImage?.split(',')[0]"
|
||
mode="aspectFill"
|
||
class="w-100rpx h-100rpx shrink-0 rounded-12rpx"
|
||
></image>
|
||
|
||
<!-- 商品信息 -->
|
||
<view class="ml-16rpx flex-1">
|
||
<view class="text-[#333333] text-26rpx lh-36rpx font-500 line-clamp-2">{{
|
||
item.merchantDishVo?.dishName
|
||
}}</view>
|
||
|
||
<!-- 配菜信息 -->
|
||
<view class="text-[#7D7D7D] text-22rpx lh-26rpx mt-8rpx" v-if="item.sideDishList?.length > 0">
|
||
<view
|
||
v-for="(dish, dIdx) in item.sideDishList"
|
||
:key="dIdx"
|
||
class="inline"
|
||
>
|
||
<text class="mr-6rpx">
|
||
{{ dish?.merchantSideDishItemVo?.name || '' }}
|
||
</text>
|
||
<text v-if="dish?.merchantSideDishItemVo?.price" class="text-#00A76D mr-10rpx">+${{ dish?.merchantSideDishItemVo?.price }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 价格和数量 -->
|
||
<view class="flex items-center justify-between mt-12rpx">
|
||
<view class="flex items-center">
|
||
<text class="text-[#333333] text-28rpx font-500"
|
||
>${{ item.merchantDishVo?.discountPrice }}</text>
|
||
<text
|
||
v-if="item.merchantDishVo?.originalPrice && item.merchantDishVo?.originalPrice !== item.merchantDishVo?.discountPrice"
|
||
class="text-[#7D7D7D] text-22rpx line-through ml-8rpx"
|
||
>${{ item.merchantDishVo?.originalPrice }}</text>
|
||
</view>
|
||
<view class="text-[#7D7D7D] text-22rpx">
|
||
x{{ item.count }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 批量模式:每个店铺的优惠券和小费选择 -->
|
||
<view v-if="merchant.id" class="checkout-merchant-footer">
|
||
<!-- 优惠券 -->
|
||
<view @click="navigateToCoupon(String(merchant.id))" class="flex-center-sb py-24rpx gap-20rpx">
|
||
<view class="flex-center-sb flex-1">
|
||
<view class="flex items-center">
|
||
<image
|
||
src="@img/chef/105.png"
|
||
mode="aspectFill"
|
||
class="w-36rpx h-36rpx relative z-1"
|
||
/>
|
||
<view class="ml-20rpx text-26rpx lh-26rpx font-500 text-#333">
|
||
{{ merchantCouponMap[String(merchant.id)] ? merchantCouponMap[String(merchant.id)].snapshotNameZh : t('pages-store.checkout.couponInputPlaceholder') }}
|
||
</view>
|
||
</view>
|
||
<view v-if="merchantCouponMap[String(merchant.id)]" @click.stop="handleClose(String(merchant.id))" class="i-carbon:close-filled text-32rpx text-#7D7D7D"></view>
|
||
</view>
|
||
<image
|
||
src="@img/chef/142.png"
|
||
mode="aspectFill"
|
||
class="w-24rpx h-24rpx shrink-0"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 小费(仅配送时显示) -->
|
||
<view v-if="deliveryMethod === 0" class="checkout-merchant-tip-block">
|
||
<text class="checkout-driver-tip-desc">{{
|
||
t('pages-store.checkout.driverTipNote')
|
||
}}</text>
|
||
<view class="flex items-center mb-20rpx">
|
||
<image
|
||
src="@img/chef/1335.png"
|
||
mode="aspectFill"
|
||
class="w-36rpx h-36rpx relative z-1"
|
||
/>
|
||
<view class="ml-20rpx text-26rpx lh-26rpx font-500 text-#333">
|
||
{{ t('pages-store.checkout.driverTip') }}
|
||
</view>
|
||
</view>
|
||
|
||
<view class="checkout-tip-grid checkout-tip-grid--merchant">
|
||
<view
|
||
v-for="(item, index) in tipOptions"
|
||
:key="index"
|
||
@click="selectedTipChangeForMerchant(String(merchant.id), item)"
|
||
:class="[
|
||
merchantTipIndexMap[String(merchant.id)] === item.value
|
||
? 'checkout-tip-pill checkout-tip-pill--on'
|
||
: 'checkout-tip-pill',
|
||
]"
|
||
class="checkout-tip-cell checkout-tip-cell--merchant"
|
||
>
|
||
{{ item.label }}
|
||
</view>
|
||
<view
|
||
@click="selectedTipChangeForMerchant(String(merchant.id), { value: 0 })"
|
||
:class="[
|
||
merchantTipIndexMap[String(merchant.id)] === 0
|
||
? 'checkout-tip-pill checkout-tip-pill--on'
|
||
: 'checkout-tip-pill',
|
||
]"
|
||
class="checkout-tip-cell checkout-tip-cell--merchant checkout-tip-cell--input"
|
||
>
|
||
<view class="px-10rpx center h-full w-full">
|
||
<wd-input
|
||
no-border
|
||
use-prefix-slot
|
||
@blur="handleConfirmTipForMerchant(String(merchant.id))"
|
||
custom-class="!center !text-24rpx !bg-transparent flex-1 !text-center"
|
||
placeholderStyle="font-size: 24rpx;color: #333; text-align: center;"
|
||
:placeholder="t('pages-store.checkout.other')"
|
||
v-model="merchantDiyTipValueMap[String(merchant.id)]"
|
||
@confirm="handleConfirmTipForMerchant(String(merchant.id))"
|
||
>
|
||
<template #suffix>
|
||
<text v-if="merchantDiyTipValueMap[String(merchant.id)] && merchantDiyTipValueMap[String(merchant.id)].length > 0">$</text>
|
||
</template>
|
||
</wd-input>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 批量模式且没有数据时的提示 -->
|
||
<view class="checkout-block" v-if="orderType === 'batch' && cartDataList.length === 0">
|
||
<view class="checkout-section-label">{{ t('pages-store.checkout.confirmOrder') }}</view>
|
||
<view class="checkout-card checkout-gutter py-48rpx">
|
||
<text class="block text-center text-28rpx text-#6d6d6d">已选择 {{ batchCartIds.length }} 件商品</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 使用优惠(单店) -->
|
||
<view class="checkout-block" v-if="orderType === 'normal'">
|
||
<view class="checkout-section-label">{{ t('pages-store.checkout.useDiscount') }}</view>
|
||
<view
|
||
@click="navigateToCoupon()"
|
||
class="checkout-card checkout-gutter checkout-pay-row"
|
||
>
|
||
<view class="flex items-center flex-1 min-w-0 pr-16rpx">
|
||
<text class="flex-1 checkout-card-primary">{{
|
||
couponInfo ? couponInfo.snapshotNameZh : t('pages-store.checkout.couponInputPlaceholder')
|
||
}}</text>
|
||
<view
|
||
v-if="couponInfo"
|
||
@click.stop="handleClose()"
|
||
class="i-carbon:close-filled text-36rpx text-#7D7D7D mr-12rpx"
|
||
></view>
|
||
</view>
|
||
<image src="@img/chef/142.png" mode="aspectFill" class="checkout-chevron" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 司机小费(单店,仅配送) -->
|
||
<view class="checkout-block" v-if="orderType === 'normal' && deliveryMethod === 0">
|
||
<view class="checkout-section-label">{{ t('pages-store.checkout.driverTip') }}</view>
|
||
<view class="checkout-card checkout-gutter checkout-tip-card">
|
||
<text class="checkout-driver-tip-desc">{{
|
||
t('pages-store.checkout.driverTipNote')
|
||
}}</text>
|
||
<view class="checkout-tip-grid">
|
||
<view
|
||
v-for="(item, index) in tipOptions"
|
||
:key="index"
|
||
@click="selectedTipChange(item)"
|
||
:class="[
|
||
selectedTipIndex === item.value
|
||
? 'checkout-tip-pill checkout-tip-pill--on'
|
||
: 'checkout-tip-pill',
|
||
]"
|
||
class="checkout-tip-cell"
|
||
>
|
||
{{ item.label }}
|
||
</view>
|
||
<view
|
||
@click="selectedTipChange({ value: 0 })"
|
||
:class="[
|
||
selectedTipIndex === 0
|
||
? 'checkout-tip-pill checkout-tip-pill--on'
|
||
: 'checkout-tip-pill',
|
||
]"
|
||
class="checkout-tip-cell checkout-tip-cell--input"
|
||
>
|
||
<view class="px-10rpx center w-full h-full">
|
||
<wd-input
|
||
no-border
|
||
use-prefix-slot
|
||
@blur="handleConfirmTip"
|
||
custom-class="!center !text-26rpx !bg-transparent flex-1 !text-center"
|
||
placeholderStyle="font-size: 26rpx;color: #333; text-align: center;"
|
||
:placeholder="t('pages-store.checkout.other')"
|
||
v-model="diyTipValue"
|
||
@confirm="handleConfirmTip"
|
||
>
|
||
<template #suffix>
|
||
<text v-if="diyTipValue.length > 0">$</text>
|
||
</template>
|
||
</wd-input>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单明细 -->
|
||
<view class="checkout-block">
|
||
<view class="checkout-section-label">{{ t('pages-store.checkout.orderBreakdown') }}</view>
|
||
<view class="checkout-card checkout-breakdown checkout-gutter">
|
||
<view v-if="isUserMemberCheckout" class="checkout-member-line">
|
||
<text class="checkout-member-text">{{
|
||
t('pages-store.checkout.memberThanks', { name: Config.appName })
|
||
}}</text>
|
||
<text class="checkout-member-icon">◆</text>
|
||
</view>
|
||
<view class="checkout-breakdown-row">
|
||
<text class="checkout-breakdown-label">{{ t('pages-store.checkout.subtotalWithPieces', { count: cartTotalPieceCount }) }}</text>
|
||
<text class="checkout-breakdown-value">${{ displayGoodsAmountStr }}</text>
|
||
</view>
|
||
<view class="checkout-breakdown-row">
|
||
<text class="checkout-breakdown-label">{{ t('pages-store.checkout.priceDetail.taxation') }}</text>
|
||
<text class="checkout-breakdown-value">${{ displayTaxStr }}</text>
|
||
</view>
|
||
<view class="checkout-breakdown-row checkout-breakdown-row--service">
|
||
<view class="checkout-breakdown-label-wrap">
|
||
<text class="checkout-breakdown-label" @click="openPriceDetail">{{ t('pages-store.checkout.serviceFee') }}</text>
|
||
<image
|
||
src="@img/chef/1336.png"
|
||
class="checkout-breakdown-info-icon"
|
||
mode="aspectFit"
|
||
@click.stop="openPriceDetail"
|
||
/>
|
||
|
||
</view>
|
||
<text v-if="serviceFeeAmount > 0" class="checkout-breakdown-value">${{ serviceFeeAmount.toFixed(2) }}</text>
|
||
<view
|
||
v-else-if="serviceFeeAmount === 0"
|
||
class="checkout-breakdown-value-placeholder checkout-breakdown-service-fee-free"
|
||
>
|
||
<text class="checkout-price-strike checkout-breakdown-strike-fee">$0.00</text>
|
||
<view class="checkout-tag-free">{{ t('pages-store.checkout.freeTag') }}</view>
|
||
</view>
|
||
</view>
|
||
<view
|
||
v-if="deliveryMethod === 0"
|
||
class="checkout-breakdown-row"
|
||
>
|
||
<text class="checkout-breakdown-label">{{ t('pages-store.checkout.shippingFee') }}</text>
|
||
<view class="checkout-breakdown-value-wrap">
|
||
<text v-if="showListDeliveryStrike" class="checkout-price-strike">${{ listDeliveryFeeStr }}</text>
|
||
<text v-if="showListDeliveryStrike" class="checkout-price-gap"> </text>
|
||
<text class="checkout-breakdown-value checkout-price-now">${{ displayDeliveryFeeStr }}</text>
|
||
</view>
|
||
</view>
|
||
<view
|
||
v-if="deliveryMethod === 0"
|
||
class="checkout-breakdown-row"
|
||
>
|
||
<text class="checkout-breakdown-label">{{ t('pages-store.checkout.driverTip') }}</text>
|
||
<text class="checkout-breakdown-value">${{ displayTipStr }}</text>
|
||
</view>
|
||
<view class="checkout-breakdown-row checkout-breakdown-row--emphasis">
|
||
<text class="checkout-breakdown-label">{{ t('pages-store.checkout.orderTotalLine') }}</text>
|
||
<text class="checkout-breakdown-value">${{ displayPaidStr }}</text>
|
||
</view>
|
||
<view class="checkout-breakdown-divider"></view>
|
||
<view class="checkout-breakdown-row checkout-breakdown-row--total">
|
||
<text class="checkout-breakdown-total-label">{{ t('pages-store.checkout.subtotalWithPieces', { count: cartTotalPieceCount }) }}</text>
|
||
<text class="checkout-breakdown-total-value">${{ displayPaidStr }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
</view>
|
||
<!-- /checkout-page-stack -->
|
||
|
||
<view class="checkout-page-bottom-spacer"></view>
|
||
|
||
<!-- 底部支付:浮窗 + 毛玻璃(H5/App 全效果;小程序降级为半透明白底) -->
|
||
<view class="checkout-bottom-dock">
|
||
<view
|
||
v-if="cartSavingsData && cartSavingsData.savings > 0"
|
||
@click="navigateTo('/pages-user/pages/member/index')"
|
||
class="checkout-savings-banner checkout-savings-banner--float"
|
||
>
|
||
<image
|
||
src="@img/chef/1289.png"
|
||
class="mr-10rpx w-32rpx h-32rpx shrink-0"
|
||
></image>
|
||
<text class="checkout-savings-banner-text">
|
||
{{ t('pages-store.store.use') }} {{ Config.appName }} {{ t('pages-store.store.tips3') }} ${{ cartSavingsData?.savings }} {{ t('pages-store.store.discount') }}
|
||
</text>
|
||
</view>
|
||
<view class="checkout-bottom-bar">
|
||
<view class="checkout-bottom-bar-glass"></view>
|
||
<view class="checkout-bottom-bar-inner">
|
||
<view class="checkout-bottom-bar-price">
|
||
<text class="checkout-bottom-bar-one-line">{{
|
||
t('pages-store.checkout.subtotalOneLine', { amount: displayPaidStr })
|
||
}}</text>
|
||
</view>
|
||
<wd-button
|
||
@click="handleGoSettle"
|
||
custom-class="checkout-pay-btn"
|
||
>{{ t('pages-store.checkout.pay') }}
|
||
</wd-button>
|
||
</view>
|
||
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 修改配送偏好 -->
|
||
<visit-method ref="visitMethodRef" @confirm="handleVisitMethodConfirm" />
|
||
<!-- 修改手机号 -->
|
||
<change-phone ref="changePhoneRef" @confirm="confirmPhone" />
|
||
<!-- 价格明细 -->
|
||
<price-detail ref="priceDetailRef" />
|
||
<password-container @success="payPawSuccess" ref="passwordInputRef" />
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<style>
|
||
page {
|
||
background-color: #f8f8f8;
|
||
}
|
||
</style>
|
||
<style scoped lang="scss">
|
||
/* 设计稿:浅灰底 #F8F8F8、主色 #000、次级字 #6D6D6D、卡片白底圆角约 12~20px、内边距 12~16px */
|
||
$checkout-bg: #f8f8f8;
|
||
$checkout-text: #000000;
|
||
$checkout-text-secondary: #6d6d6d;
|
||
$checkout-text-tertiary: #9e9e9e;
|
||
$checkout-card-bg: #ffffff;
|
||
$checkout-card-radius: 24rpx;
|
||
$checkout-border: #ebebeb;
|
||
$checkout-gutter: 32rpx;
|
||
|
||
.checkout-page {
|
||
padding-top: 16rpx;
|
||
padding-bottom: 32rpx;
|
||
min-height: 100vh;
|
||
box-sizing: border-box;
|
||
background: $checkout-bg;
|
||
}
|
||
|
||
.checkout-page-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 40rpx;
|
||
padding-bottom: 8rpx;
|
||
}
|
||
|
||
.checkout-block {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.checkout-block > .checkout-section-label {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.checkout-gutter {
|
||
margin-left: $checkout-gutter;
|
||
margin-right: $checkout-gutter;
|
||
}
|
||
|
||
.checkout-section-label {
|
||
padding: 0 $checkout-gutter;
|
||
margin-bottom: 20rpx;
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: $checkout-text;
|
||
letter-spacing: 0.02em;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.checkout-section-label--spaced {
|
||
margin-top: 40rpx;
|
||
}
|
||
|
||
.checkout-card {
|
||
background: $checkout-card-bg;
|
||
border-radius: $checkout-card-radius;
|
||
border: 1rpx solid $checkout-border;
|
||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.03);
|
||
}
|
||
|
||
.checkout-card--flush {
|
||
border: 1rpx solid $checkout-border;
|
||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.03);
|
||
}
|
||
|
||
.checkout-card--merchant {
|
||
border: 1rpx solid $checkout-border;
|
||
}
|
||
|
||
.checkout-card--stacked {
|
||
padding: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.checkout-delivery-main {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 32rpx 28rpx;
|
||
min-height: 100rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-delivery-extras {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: stretch;
|
||
border-top: 1rpx solid $checkout-border;
|
||
min-height: 100rpx;
|
||
}
|
||
|
||
.checkout-delivery-extras-cell {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding: 20rpx 16rpx 24rpx 24rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-delivery-extras-cell:first-of-type {
|
||
padding-right: 12rpx;
|
||
}
|
||
|
||
.checkout-delivery-extras-cell:last-of-type {
|
||
padding-left: 12rpx;
|
||
padding-right: 24rpx;
|
||
}
|
||
|
||
.checkout-delivery-extras-vline {
|
||
width: 1rpx;
|
||
flex-shrink: 0;
|
||
background: $checkout-border;
|
||
align-self: stretch;
|
||
margin: 16rpx 0;
|
||
}
|
||
|
||
.checkout-delivery-extras-label {
|
||
font-size: 22rpx;
|
||
color: $checkout-text-tertiary;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.checkout-delivery-extras-value {
|
||
font-size: 26rpx;
|
||
font-weight: 500;
|
||
color: $checkout-text;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.checkout-delivery-extras--full {
|
||
border-top: 1rpx solid $checkout-border;
|
||
}
|
||
|
||
.checkout-delivery-extras--full .checkout-delivery-extras-cell--grow {
|
||
flex: 1;
|
||
padding: 20rpx 24rpx 24rpx;
|
||
}
|
||
|
||
.checkout-pickup-info {
|
||
padding: 32rpx 28rpx 24rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-pickup-name {
|
||
display: block;
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: $checkout-text;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.checkout-pickup-addr {
|
||
display: block;
|
||
margin-top: 10rpx;
|
||
font-size: 26rpx;
|
||
color: $checkout-text-secondary;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.checkout-pay-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 32rpx 28rpx;
|
||
min-height: 100rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-confirm-head {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.checkout-confirm-head-text {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.checkout-card-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 32rpx 28rpx;
|
||
gap: 20rpx;
|
||
min-height: 100rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-card-row.is-divider {
|
||
border-top: 1rpx solid $checkout-border;
|
||
}
|
||
|
||
.checkout-card-primary {
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
color: $checkout-text;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.checkout-card-secondary {
|
||
font-size: 26rpx;
|
||
font-weight: 400;
|
||
color: $checkout-text-secondary;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.checkout-card-placeholder {
|
||
font-size: 30rpx;
|
||
font-weight: 400;
|
||
color: $checkout-text-secondary;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.checkout-field-line {
|
||
align-items: center;
|
||
}
|
||
|
||
.checkout-field-line--sub {
|
||
min-height: auto;
|
||
padding-top: 24rpx;
|
||
padding-bottom: 24rpx;
|
||
}
|
||
|
||
.checkout-field-sub-label {
|
||
display: block;
|
||
font-size: 24rpx;
|
||
color: $checkout-text-tertiary;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.checkout-field-sub-value {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
color: $checkout-text;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.checkout-chevron {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
flex-shrink: 0;
|
||
opacity: 0.45;
|
||
}
|
||
|
||
.checkout-chevron--sub {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
}
|
||
|
||
.checkout-price-strike {
|
||
text-decoration: line-through;
|
||
color: $checkout-text-tertiary;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.checkout-price-now {
|
||
font-weight: 700;
|
||
}
|
||
|
||
.checkout-price-gap {
|
||
display: inline;
|
||
font-size: 20rpx;
|
||
}
|
||
|
||
.checkout-batch-shipping-line {
|
||
margin-top: 12rpx;
|
||
font-size: 24rpx;
|
||
color: $checkout-text-secondary;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.checkout-batch-shipping-now {
|
||
font-weight: 700;
|
||
color: $checkout-text;
|
||
}
|
||
|
||
.checkout-card-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: $checkout-text;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.checkout-card-subtitle {
|
||
font-size: 26rpx;
|
||
font-weight: 400;
|
||
color: $checkout-text-secondary;
|
||
line-height: 1.45;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.checkout-confirm-inner {
|
||
padding: 32rpx 28rpx 12rpx;
|
||
}
|
||
|
||
.checkout-icon-circle {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
background: #ececec;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.checkout-confirm-band {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 24rpx;
|
||
width: 100%;
|
||
margin-top: 28rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-confirm-band--compact {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.checkout-confirm-band-right {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
text-align: right;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.checkout-confirm-band-line1 {
|
||
font-size: 28rpx;
|
||
font-weight: 400;
|
||
color: $checkout-text;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.checkout-confirm-band-line2 {
|
||
font-size: 24rpx;
|
||
color: $checkout-text-secondary;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.checkout-confirm-meta-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 24rpx;
|
||
margin-top: 28rpx;
|
||
}
|
||
|
||
.checkout-date-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 18rpx 32rpx;
|
||
font-size: 26rpx;
|
||
color: #fff;
|
||
background: #000;
|
||
border-radius: 999rpx;
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.checkout-confirm-meta-right {
|
||
flex: 1;
|
||
min-width: 200rpx;
|
||
text-align: right;
|
||
}
|
||
|
||
.checkout-confirm-meta-primary {
|
||
font-size: 28rpx;
|
||
font-weight: 400;
|
||
color: $checkout-text;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.checkout-confirm-meta-strong {
|
||
font-weight: 700;
|
||
}
|
||
|
||
.checkout-confirm-meta-gap {
|
||
font-size: 20rpx;
|
||
}
|
||
|
||
.checkout-confirm-meta-sub {
|
||
margin-top: 12rpx;
|
||
font-size: 24rpx;
|
||
color: $checkout-text-secondary;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.checkout-confirm-meta-fee {
|
||
font-weight: 700;
|
||
color: $checkout-text;
|
||
}
|
||
|
||
.checkout-schedule-scroll {
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
margin-top: 28rpx;
|
||
}
|
||
|
||
.checkout-schedule-scroll.checkout-schedule-bleed {
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.checkout-schedule-inner {
|
||
display: inline-flex;
|
||
flex-direction: row;
|
||
align-items: stretch;
|
||
padding: 0 0 8rpx 0;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.checkout-schedule-bleed .checkout-schedule-inner {
|
||
padding: 12rpx 28rpx 12rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-schedule-card {
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
min-width: 460rpx;
|
||
max-width: 560rpx;
|
||
padding: 24rpx 24rpx 20rpx;
|
||
border-radius: 20rpx;
|
||
vertical-align: top;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-schedule-card-body {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.checkout-schedule-card--on {
|
||
border: 2rpx solid #000;
|
||
background: #f0f0f0;
|
||
}
|
||
|
||
.checkout-schedule-card--off {
|
||
border: 2rpx solid #e5e5e5;
|
||
background: #fff;
|
||
}
|
||
|
||
.checkout-radio-dot {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #000;
|
||
flex-shrink: 0;
|
||
margin-top: 4rpx;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
}
|
||
|
||
.checkout-radio-dot::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 18rpx;
|
||
height: 18rpx;
|
||
border-radius: 50%;
|
||
background: #000;
|
||
}
|
||
|
||
.checkout-radio-ring {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #c4c4c4;
|
||
flex-shrink: 0;
|
||
margin-top: 4rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-schedule-title {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: $checkout-text;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.checkout-schedule-title--muted {
|
||
font-weight: 600;
|
||
color: #444;
|
||
}
|
||
|
||
.checkout-schedule-mid {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: #444;
|
||
margin-top: 12rpx;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.checkout-schedule-hint {
|
||
display: block;
|
||
font-size: 22rpx;
|
||
color: $checkout-text-tertiary;
|
||
margin-top: 12rpx;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.checkout-schedule-addon {
|
||
display: block;
|
||
margin-top: 20rpx;
|
||
text-align: right;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: $checkout-text;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.checkout-thumb-scroll {
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.checkout-thumb-strip {
|
||
background: #f5f5f5;
|
||
padding: 24rpx 28rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-deliver-hint {
|
||
padding: 16rpx 28rpx 28rpx;
|
||
font-size: 24rpx;
|
||
color: $checkout-text-secondary;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.checkout-order-line {
|
||
width: 100%;
|
||
min-height: 120rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 28rpx;
|
||
box-sizing: border-box;
|
||
border-top: 1rpx solid $checkout-border;
|
||
background: #fff;
|
||
}
|
||
|
||
.checkout-qty-badge {
|
||
position: absolute;
|
||
right: 8rpx;
|
||
bottom: 8rpx;
|
||
min-width: 36rpx;
|
||
padding: 4rpx 10rpx;
|
||
font-size: 22rpx;
|
||
font-weight: 600;
|
||
color: #fff;
|
||
background: rgba(0, 0, 0, 0.82);
|
||
border-radius: 8rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.checkout-batch-pill-card {
|
||
padding: 28rpx 28rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-merchant-head {
|
||
padding: 28rpx 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
background: #f3f3f3;
|
||
border-bottom: 1rpx solid $checkout-border;
|
||
}
|
||
|
||
.checkout-merchant-body {
|
||
padding: 8rpx 24rpx 16rpx;
|
||
}
|
||
|
||
.checkout-merchant-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
padding: 24rpx 0;
|
||
}
|
||
|
||
.checkout-merchant-item.is-divider {
|
||
border-bottom: 1rpx solid $checkout-border;
|
||
}
|
||
|
||
.checkout-merchant-footer {
|
||
padding: 8rpx 24rpx 28rpx;
|
||
border-top: 1rpx solid $checkout-border;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.checkout-merchant-tip-block {
|
||
border-top: 1rpx solid $checkout-border;
|
||
padding-top: 24rpx;
|
||
}
|
||
|
||
.checkout-page-bottom-spacer {
|
||
/* 浮窗底栏 + 左右下边距 + 可选省钱条 */
|
||
height: 300rpx;
|
||
}
|
||
|
||
.checkout-driver-tip-desc {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: $checkout-text-secondary;
|
||
line-height: 1.55;
|
||
margin-bottom: 28rpx;
|
||
}
|
||
|
||
.checkout-tip-card {
|
||
padding: 28rpx 28rpx 32rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-tip-card .checkout-driver-tip-desc {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.checkout-tip-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 16rpx;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-tip-grid--merchant {
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.checkout-tip-cell {
|
||
min-width: 0;
|
||
min-height: 72rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-tip-cell--merchant {
|
||
min-height: 64rpx;
|
||
}
|
||
|
||
.checkout-tip-cell--input {
|
||
padding: 0 4rpx;
|
||
}
|
||
|
||
.checkout-tip-grid .checkout-tip-pill {
|
||
width: 100%;
|
||
}
|
||
|
||
.checkout-tip-pill {
|
||
border-radius: 20rpx;
|
||
border: 2rpx solid #000;
|
||
background: #fff;
|
||
color: $checkout-text;
|
||
font-weight: 500;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-tip-pill--on {
|
||
background: #000;
|
||
color: #fff;
|
||
border-color: #000;
|
||
}
|
||
|
||
.checkout-breakdown {
|
||
padding: 32rpx 28rpx 36rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-member-line {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 20rpx;
|
||
margin-bottom: 28rpx;
|
||
}
|
||
|
||
.checkout-member-icon {
|
||
color: #e8954a;
|
||
font-size: 32rpx;
|
||
line-height: 1;
|
||
flex-shrink: 0;
|
||
margin-top: 2rpx;
|
||
}
|
||
|
||
.checkout-member-text {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
color: #ce7138;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.checkout-breakdown-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 28rpx;
|
||
font-size: 28rpx;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.checkout-breakdown-row:last-of-type {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.checkout-breakdown-row--service .checkout-breakdown-label-wrap {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.checkout-breakdown-strike-fee {
|
||
font-size: 26rpx;
|
||
margin-left: 4rpx;
|
||
}
|
||
|
||
.checkout-breakdown-value-wrap {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
flex-wrap: wrap;
|
||
text-align: right;
|
||
}
|
||
|
||
.checkout-breakdown-value-placeholder {
|
||
min-width: 20rpx;
|
||
}
|
||
|
||
/* 服务费 $0.00 + 免费标签同一行(勿用 text 包 view,小程序易换行) */
|
||
.checkout-breakdown-service-fee-free {
|
||
display: inline-flex;
|
||
flex-direction: row;
|
||
flex-wrap: nowrap;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
flex-shrink: 0;
|
||
max-width: 100%;
|
||
gap: 8rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.checkout-breakdown-service-fee-free .checkout-breakdown-strike-fee {
|
||
margin-left: 0;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.checkout-breakdown-service-fee-free .checkout-tag-free {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.checkout-breakdown-label {
|
||
color: $checkout-text;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.checkout-breakdown-value {
|
||
color: $checkout-text;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.checkout-breakdown-label-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.checkout-breakdown-info-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
opacity: 0.55;
|
||
}
|
||
|
||
.checkout-tag-free {
|
||
font-size: 20rpx;
|
||
font-weight: 600;
|
||
color: #b8860b;
|
||
background: #fff4d4;
|
||
padding: 6rpx 14rpx;
|
||
border-radius: 8rpx;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.checkout-breakdown-row--emphasis {
|
||
margin-top: 8rpx;
|
||
margin-bottom: 28rpx;
|
||
// font-weight: 600;
|
||
}
|
||
|
||
|
||
.checkout-breakdown-divider {
|
||
height: 1rpx;
|
||
background: $checkout-border;
|
||
margin: 28rpx 0;
|
||
}
|
||
|
||
.checkout-breakdown-row--total {
|
||
margin-bottom: 0;
|
||
align-items: baseline;
|
||
}
|
||
|
||
.checkout-breakdown-total-label {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: $checkout-text;
|
||
}
|
||
|
||
.checkout-breakdown-total-value {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: $checkout-text;
|
||
}
|
||
|
||
.checkout-bottom-dock {
|
||
position: fixed;
|
||
z-index: 9;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
padding: 0 24rpx 20rpx;
|
||
box-sizing: border-box;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.checkout-bottom-dock > * {
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.checkout-savings-banner {
|
||
min-height: 76rpx;
|
||
padding: 0 40rpx 0 36rpx;
|
||
background: #ce7138;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.checkout-savings-banner--float {
|
||
margin-bottom: 16rpx;
|
||
border-radius: 28rpx;
|
||
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.14);
|
||
}
|
||
|
||
.checkout-savings-banner-text {
|
||
color: #fff;
|
||
font-size: 24rpx;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.checkout-bottom-bar {
|
||
position: relative;
|
||
border-radius: 78rpx;
|
||
overflow: hidden;
|
||
box-shadow:
|
||
0 16rpx 48rpx rgba(0, 0, 0, 0.12),
|
||
0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
/* 毛玻璃层:半透明白 + 背景模糊 */
|
||
.checkout-bottom-bar-glass {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 0;
|
||
border-radius: inherit;
|
||
background: rgba(255, 255, 255, 0.62);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.92);
|
||
box-shadow: inset 0 1rpx 0 rgba(255, 255, 255, 0.75);
|
||
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
||
backdrop-filter: saturate(180%) blur(20px);
|
||
}
|
||
|
||
.checkout-bottom-bar-inner {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
// gap: 28rpx;
|
||
border-radius: 48rpx;
|
||
padding: 24rpx 28rpx 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.checkout-bottom-bar > view:last-child {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.checkout-bottom-bar-price {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.checkout-bottom-bar-price-label {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: $checkout-text-secondary;
|
||
margin-bottom: 8rpx;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.checkout-bottom-bar-price-num {
|
||
font-size: 40rpx;
|
||
font-weight: 700;
|
||
color: $checkout-text;
|
||
line-height: 1.15;
|
||
letter-spacing: -0.02em;
|
||
}
|
||
|
||
.checkout-bottom-bar-one-line {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: $checkout-text;
|
||
line-height: 1.35;
|
||
letter-spacing: 0.01em;
|
||
}
|
||
|
||
:deep(.checkout-pay-btn) {
|
||
flex-shrink: 0;
|
||
min-width: 260rpx !important;
|
||
height: 96rpx !important;
|
||
padding: 0 48rpx !important;
|
||
font-size: 32rpx !important;
|
||
font-weight: 600 !important;
|
||
color: #fff !important;
|
||
background: #000 !important;
|
||
border: none !important;
|
||
border-radius: 48rpx !important;
|
||
line-height: 96rpx !important;
|
||
}
|
||
|
||
:deep(.checkout-page-navbar) {
|
||
background: $checkout-bg !important;
|
||
}
|
||
|
||
:deep(.checkout-page-navbar .wd-navbar__title) {
|
||
font-size: 34rpx !important;
|
||
font-weight: 700 !important;
|
||
color: $checkout-text !important;
|
||
}
|
||
|
||
:deep(.checkout-order-collapse .wd-collapse-item__header) {
|
||
padding: 0 !important;
|
||
background: transparent !important;
|
||
}
|
||
|
||
:deep(.wd-collapse-item) {
|
||
&::after {
|
||
display: none;
|
||
background-color: transparent !important;
|
||
}
|
||
}
|
||
|
||
:deep(.wd-collapse-item__header.is-expanded) {
|
||
&::after {
|
||
background-color: transparent !important;
|
||
}
|
||
}
|
||
|
||
:deep(.wd-collapse-item__body) {
|
||
padding: 0 !important;
|
||
}
|
||
</style>
|