feat(payment): 集成 PayPal 支付并添加货币转换功能
- 集成 PayPal 支付流程,包括订单创建、支付确认和取消页面 - 实现货币转换功能,支持不支持的货币自动转换为 PayPal 支持的货币 - 在订单确认页面显示货币转换信息,包括实际支付金额和汇率 - 添加 PayPal 支付成功和取消的路由处理 - 优化商品详情页图片预览,支持鼠标悬停显示主图 - 添加货币转换相关的 API 接口和工具函数
This commit is contained in:
@@ -31,3 +31,14 @@ export function getOrderById(id) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算并更新订单的货币转换信息
|
||||||
|
*/
|
||||||
|
export function calculateCurrencyConversion(data) {
|
||||||
|
return request({
|
||||||
|
url: '/order/calculate-currency-conversion',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,18 @@ const routes = [
|
|||||||
name: 'OrderConfirm',
|
name: 'OrderConfirm',
|
||||||
component: () => import('../views/OrderConfirm.vue')
|
component: () => import('../views/OrderConfirm.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/paypal/success',
|
||||||
|
name: 'PayPalSuccess',
|
||||||
|
component: () => import('../views/PayPalSuccess.vue'),
|
||||||
|
meta: { isCustomerPage: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/paypal/cancel',
|
||||||
|
name: 'PayPalCancel',
|
||||||
|
component: () => import('../views/PayPalCancel.vue'),
|
||||||
|
meta: { isCustomerPage: true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/checkout',
|
path: '/checkout',
|
||||||
name: 'Checkout',
|
name: 'Checkout',
|
||||||
|
|||||||
@@ -21,7 +21,44 @@
|
|||||||
<el-descriptions :column="2" border>
|
<el-descriptions :column="2" border>
|
||||||
<el-descriptions-item label="订单号">{{ order.orderNo }}</el-descriptions-item>
|
<el-descriptions-item label="订单号">{{ order.orderNo }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="订单金额">
|
<el-descriptions-item label="订单金额">
|
||||||
|
<div class="order-amount-section">
|
||||||
|
<div class="original-amount">
|
||||||
<span class="order-amount">{{ order.currency }} {{ formatPrice(order.totalAmount) }}</span>
|
<span class="order-amount">{{ order.currency }} {{ formatPrice(order.totalAmount) }}</span>
|
||||||
|
</div>
|
||||||
|
<!-- 货币转换信息 - 更醒目的提示 -->
|
||||||
|
<div v-if="order.paymentCurrency && order.paymentCurrency !== order.currency" class="currency-conversion-info">
|
||||||
|
<el-alert
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
:effect="'dark'"
|
||||||
|
class="conversion-alert-box"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="conversion-alert-content">
|
||||||
|
<div class="conversion-title">
|
||||||
|
<el-icon class="warning-icon"><Warning /></el-icon>
|
||||||
|
<strong class="title-text">您将以{{ getCurrencyName(order.paymentCurrency) }}支付</strong>
|
||||||
|
</div>
|
||||||
|
<div class="conversion-main-info">
|
||||||
|
<div class="payment-amount-highlight">
|
||||||
|
<span class="label">实际费用:</span>
|
||||||
|
<span class="payment-amount-large">{{ order.paymentCurrency }} {{ formatPrice(order.paymentAmount) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="order.exchangeRate" class="exchange-rate-info">
|
||||||
|
<span class="equivalent-amount">(约 {{ order.currency }} {{ formatPrice(order.totalAmount) }})</span>
|
||||||
|
<span class="rate-value">汇率:{{ formatRate(order.exchangeRate) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="order.rateLockedAt" class="rate-locked-info">
|
||||||
|
<el-icon><Clock /></el-icon>
|
||||||
|
<span>汇率锁定时间:{{ formatDateTime(order.rateLockedAt) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="商品名称" :span="2">{{ order.productName }}</el-descriptions-item>
|
<el-descriptions-item label="商品名称" :span="2">{{ order.productName }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="SKU名称" :span="2">{{ order.skuName }}</el-descriptions-item>
|
<el-descriptions-item label="SKU名称" :span="2">{{ order.skuName }}</el-descriptions-item>
|
||||||
@@ -91,11 +128,10 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { Money } from '@element-plus/icons-vue'
|
import { Money, Clock, Warning } from '@element-plus/icons-vue'
|
||||||
import { getOrderByOrderNo } from '../api/order'
|
import { getOrderByOrderNo, calculateCurrencyConversion } from '../api/order'
|
||||||
import { createPaymentOrder } from '../api/payment'
|
import { createPayPalOrder, getPayPalOrder, capturePayPalOrder } from '../api/paypal'
|
||||||
import { formatAmount } from '../utils/helpers'
|
import { formatAmount } from '../utils/helpers'
|
||||||
import { generateOrderId } from '../utils/helpers'
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -132,6 +168,30 @@ const formatPrice = (price) => {
|
|||||||
return formatAmount(price)
|
return formatAmount(price)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取货币名称
|
||||||
|
const getCurrencyName = (currencyCode) => {
|
||||||
|
const currencyNames = {
|
||||||
|
'USD': '美元',
|
||||||
|
'EUR': '欧元',
|
||||||
|
'GBP': '英镑',
|
||||||
|
'CNY': '人民币',
|
||||||
|
'MYR': '马来西亚林吉特',
|
||||||
|
'VND': '越南盾',
|
||||||
|
'JPY': '日元',
|
||||||
|
'KRW': '韩元',
|
||||||
|
'THB': '泰铢',
|
||||||
|
'SGD': '新加坡元',
|
||||||
|
'HKD': '港币'
|
||||||
|
}
|
||||||
|
return currencyNames[currencyCode] || currencyCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化汇率
|
||||||
|
const formatRate = (rate) => {
|
||||||
|
if (!rate) return ''
|
||||||
|
return rate.toFixed(6)
|
||||||
|
}
|
||||||
|
|
||||||
// 格式化日期时间
|
// 格式化日期时间
|
||||||
const formatDateTime = (dateTime) => {
|
const formatDateTime = (dateTime) => {
|
||||||
if (!dateTime) return '-'
|
if (!dateTime) return '-'
|
||||||
@@ -169,6 +229,44 @@ const loadOrder = async () => {
|
|||||||
const response = await getOrderByOrderNo(orderNo)
|
const response = await getOrderByOrderNo(orderNo)
|
||||||
if (response.code === '0000' && response.data) {
|
if (response.code === '0000' && response.data) {
|
||||||
order.value = response.data
|
order.value = response.data
|
||||||
|
|
||||||
|
// 如果订单未支付且需要货币转换,提前计算货币转换信息
|
||||||
|
if (order.value.paymentStatus === 'UNPAID' && order.value.currency) {
|
||||||
|
// 检查是否需要货币转换(PayPal支持的货币列表)
|
||||||
|
const supportedCurrencies = ['USD', 'EUR', 'GBP', 'AUD', 'CAD', 'JPY', 'CNY', 'HKD', 'SGD', 'NZD',
|
||||||
|
'CHF', 'SEK', 'NOK', 'DKK', 'PLN', 'MXN', 'BRL', 'INR', 'KRW', 'THB']
|
||||||
|
|
||||||
|
const needsConversion = !supportedCurrencies.includes(order.value.currency)
|
||||||
|
|
||||||
|
// 如果订单还没有货币转换信息,或者需要更新,则计算
|
||||||
|
if (needsConversion && (!order.value.paymentCurrency || order.value.paymentCurrency === order.value.currency)) {
|
||||||
|
try {
|
||||||
|
const conversionResponse = await calculateCurrencyConversion({
|
||||||
|
orderNo: order.value.orderNo,
|
||||||
|
originalCurrency: order.value.currency,
|
||||||
|
originalAmount: order.value.totalAmount
|
||||||
|
})
|
||||||
|
|
||||||
|
if (conversionResponse.code === '0000' && conversionResponse.data) {
|
||||||
|
const conversion = conversionResponse.data
|
||||||
|
// 更新订单显示信息
|
||||||
|
order.value = {
|
||||||
|
...order.value,
|
||||||
|
originalCurrency: conversion.originalCurrency,
|
||||||
|
originalAmount: conversion.originalAmount,
|
||||||
|
paymentCurrency: conversion.paymentCurrency,
|
||||||
|
paymentAmount: conversion.paymentAmount,
|
||||||
|
exchangeRate: conversion.exchangeRate,
|
||||||
|
rateLockedAt: conversion.rateLockedAt
|
||||||
|
}
|
||||||
|
console.log('货币转换信息已计算并更新')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('计算货币转换信息失败:', error)
|
||||||
|
// 不显示错误,因为不影响订单显示
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(response.message || '获取订单信息失败')
|
ElMessage.error(response.message || '获取订单信息失败')
|
||||||
order.value = null
|
order.value = null
|
||||||
@@ -182,7 +280,7 @@ const loadOrder = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理支付
|
// 处理PayPal支付(步骤1-4)
|
||||||
const handlePay = async () => {
|
const handlePay = async () => {
|
||||||
if (!order.value) {
|
if (!order.value) {
|
||||||
ElMessage.error('订单信息不存在')
|
ElMessage.error('订单信息不存在')
|
||||||
@@ -197,77 +295,73 @@ const handlePay = async () => {
|
|||||||
payLoading.value = true
|
payLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 构建支付请求数据
|
// 步骤2:构建PayPal订单创建请求
|
||||||
const paymentData = {
|
const paypalOrderData = {
|
||||||
merchantTransactionId: generateOrderId(),
|
intent: 'CAPTURE', // 立即捕获
|
||||||
amount: order.value.totalAmount.toFixed(2),
|
referenceId: order.value.orderNo, // ERP订单号
|
||||||
currency: order.value.currency,
|
amount: order.value.totalAmount,
|
||||||
paymentType: 'SALE',
|
currencyCode: order.value.currency,
|
||||||
merchantUserId: '',
|
itemName: order.value.productName,
|
||||||
shopperResultUrl: `${window.location.origin}/result`,
|
itemDescription: order.value.skuName,
|
||||||
shopperCancelUrl: `${window.location.origin}/result`,
|
itemSku: order.value.skuName,
|
||||||
signType: 'MD5',
|
itemQuantity: order.value.quantity,
|
||||||
language: 'zh',
|
itemUnitAmount: order.value.unitPrice,
|
||||||
threeDSecure: 'N',
|
returnUrl: `${window.location.origin}/paypal/success?orderNo=${order.value.orderNo}`,
|
||||||
riskInfo: {
|
cancelUrl: `${window.location.origin}/paypal/cancel?orderNo=${order.value.orderNo}`,
|
||||||
customer: {
|
shippingName: order.value.shippingName,
|
||||||
firstName: order.value.customerName.split(' ')[0] || order.value.customerName,
|
shippingAddressLine1: order.value.shippingStreet,
|
||||||
lastName: order.value.customerName.split(' ').slice(1).join(' ') || '',
|
shippingCity: order.value.shippingCity,
|
||||||
email: order.value.customerEmail || '',
|
shippingState: order.value.shippingState || null,
|
||||||
phone: order.value.customerPhone,
|
shippingPostalCode: order.value.shippingPostcode || null,
|
||||||
registerTime: new Date().toISOString().replace(/[-:]/g, '').split('.')[0],
|
shippingCountryCode: order.value.shippingCountry,
|
||||||
registerIp: '',
|
emailAddress: order.value.customerEmail || null
|
||||||
registerTerminal: 'PC',
|
|
||||||
registerRange: '1',
|
|
||||||
orderTime: new Date(order.value.createTime).toISOString().replace(/[-:]/g, '').split('.')[0],
|
|
||||||
orderIp: '',
|
|
||||||
orderCountry: order.value.shippingCountry || 'US'
|
|
||||||
},
|
|
||||||
goods: [{
|
|
||||||
name: order.value.productName,
|
|
||||||
description: order.value.skuName,
|
|
||||||
sku: order.value.skuName,
|
|
||||||
averageUnitPrice: order.value.unitPrice.toFixed(2),
|
|
||||||
number: order.value.quantity.toString(),
|
|
||||||
virtualProduct: 'N'
|
|
||||||
}],
|
|
||||||
shipping: {
|
|
||||||
firstName: order.value.shippingName.split(' ')[0] || order.value.shippingName,
|
|
||||||
lastName: order.value.shippingName.split(' ').slice(1).join(' ') || '',
|
|
||||||
street: order.value.shippingStreet,
|
|
||||||
city: order.value.shippingCity,
|
|
||||||
state: order.value.shippingState || '',
|
|
||||||
postcode: order.value.shippingPostcode || '',
|
|
||||||
country: order.value.shippingCountry
|
|
||||||
},
|
|
||||||
billing: {
|
|
||||||
firstName: order.value.shippingName.split(' ')[0] || order.value.shippingName,
|
|
||||||
lastName: order.value.shippingName.split(' ').slice(1).join(' ') || '',
|
|
||||||
street: order.value.shippingStreet,
|
|
||||||
city: order.value.shippingCity,
|
|
||||||
state: order.value.shippingState || '',
|
|
||||||
postcode: order.value.shippingPostcode || '',
|
|
||||||
country: order.value.shippingCountry
|
|
||||||
}
|
|
||||||
},
|
|
||||||
notificationUrl: `${window.location.origin}/api/callback/pingpong`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await createPaymentOrder(paymentData)
|
// 调用后端创建PayPal订单
|
||||||
|
const response = await createPayPalOrder(paypalOrderData)
|
||||||
|
|
||||||
if (response.code === '0000' && response.data && response.data.token) {
|
if (response.code === '0000' && response.data) {
|
||||||
ElMessage.success('正在跳转到支付页面...')
|
const responseData = response.data
|
||||||
// 跳转到收银台页面
|
const paypalOrder = responseData.paypalOrder || responseData // 兼容新旧格式
|
||||||
router.push({
|
const currencyConversion = responseData.currencyConversion
|
||||||
path: '/checkout',
|
|
||||||
query: { token: response.data.token }
|
// 如果有货币转换信息,更新订单显示
|
||||||
|
if (currencyConversion) {
|
||||||
|
order.value = {
|
||||||
|
...order.value,
|
||||||
|
originalCurrency: currencyConversion.originalCurrency,
|
||||||
|
originalAmount: currencyConversion.originalAmount,
|
||||||
|
paymentCurrency: currencyConversion.paymentCurrency,
|
||||||
|
paymentAmount: currencyConversion.paymentAmount,
|
||||||
|
exchangeRate: currencyConversion.exchangeRate,
|
||||||
|
rateLockedAt: currencyConversion.rateLockedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示货币转换提示
|
||||||
|
if (currencyConversion.conversionRequired) {
|
||||||
|
ElMessage.info({
|
||||||
|
message: `您将以${getCurrencyName(currencyConversion.paymentCurrency)}支付,实际费用为:${currencyConversion.paymentCurrency} ${formatPrice(currencyConversion.paymentAmount)}`,
|
||||||
|
duration: 5000
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤3-4:获取approval_url并跳转到PayPal登录页
|
||||||
|
const approvalLink = paypalOrder.links?.find(link => link.rel === 'payer-action')
|
||||||
|
|
||||||
|
if (approvalLink && approvalLink.href) {
|
||||||
|
ElMessage.success('正在跳转到PayPal支付页面...')
|
||||||
|
// 步骤4:跳转到PayPal登录页
|
||||||
|
window.location.href = approvalLink.href
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(response.message || '创建支付订单失败')
|
ElMessage.error('获取PayPal支付链接失败')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error(response.message || '创建PayPal订单失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建支付订单失败:', error)
|
console.error('创建PayPal订单失败:', error)
|
||||||
ElMessage.error(error.response?.data?.message || '创建支付订单失败,请稍后重试')
|
ElMessage.error(error.response?.data?.message || '创建PayPal订单失败,请稍后重试')
|
||||||
} finally {
|
} finally {
|
||||||
payLoading.value = false
|
payLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -326,5 +420,115 @@ onMounted(() => {
|
|||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
border-top: 1px solid #e4e7ed;
|
border-top: 1px solid #e4e7ed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-amount-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency-conversion-info {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversion-alert-box {
|
||||||
|
border: 2px solid #e6a23c;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(135deg, #fff7e6 0%, #fff3d9 100%);
|
||||||
|
box-shadow: 0 4px 12px rgba(230, 162, 60, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversion-alert-content {
|
||||||
|
line-height: 1.8;
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversion-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #e6a23c;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
color: #e6a23c;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversion-main-info {
|
||||||
|
background: #fff;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-left: 4px solid #e6a23c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-amount-highlight {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-amount-highlight .label {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #606266;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-amount-large {
|
||||||
|
font-weight: 800;
|
||||||
|
color: #e6a23c;
|
||||||
|
font-size: 28px;
|
||||||
|
text-shadow: 0 2px 4px rgba(230, 162, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exchange-rate-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px dashed #dcdfe6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equivalent-amount {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-value {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-locked-info {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@
|
|||||||
class="thumbnail-item"
|
class="thumbnail-item"
|
||||||
:class="{ active: currentImageIndex === index }"
|
:class="{ active: currentImageIndex === index }"
|
||||||
@click="selectImage(index)"
|
@click="selectImage(index)"
|
||||||
|
@mouseenter="hoverImage(img)"
|
||||||
|
@mouseleave="hoverImage(null)"
|
||||||
>
|
>
|
||||||
<el-image :src="img" fit="cover" class="thumbnail-img" />
|
<el-image :src="img" fit="cover" class="thumbnail-img" />
|
||||||
</div>
|
</div>
|
||||||
@@ -263,6 +265,8 @@ const router = useRouter()
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const productLoading = ref(true)
|
const productLoading = ref(true)
|
||||||
const currentImageIndex = ref(0)
|
const currentImageIndex = ref(0)
|
||||||
|
// 鼠标悬停的主图(优先展示)
|
||||||
|
const hoveredImage = ref(null)
|
||||||
const quantity = ref(1)
|
const quantity = ref(1)
|
||||||
const activeTab = ref('detail')
|
const activeTab = ref('detail')
|
||||||
const selectedSku = ref(null)
|
const selectedSku = ref(null)
|
||||||
@@ -324,12 +328,19 @@ const currentCurrencySkus = computed(() => {
|
|||||||
|
|
||||||
// 当前显示的图片
|
// 当前显示的图片
|
||||||
const currentImage = computed(() => {
|
const currentImage = computed(() => {
|
||||||
|
// 1) 若有悬停主图,优先展示
|
||||||
|
if (hoveredImage.value) {
|
||||||
|
return hoveredImage.value
|
||||||
|
}
|
||||||
|
// 2) 已选SKU且有SKU图
|
||||||
if (selectedSku.value && selectedSku.value.skuImage) {
|
if (selectedSku.value && selectedSku.value.skuImage) {
|
||||||
return selectedSku.value.skuImage
|
return selectedSku.value.skuImage
|
||||||
}
|
}
|
||||||
|
// 3) 主图列表
|
||||||
if (product.value.mainImages && product.value.mainImages.length > 0) {
|
if (product.value.mainImages && product.value.mainImages.length > 0) {
|
||||||
return product.value.mainImages[currentImageIndex.value] || product.value.mainImages[0]
|
return product.value.mainImages[currentImageIndex.value] || product.value.mainImages[0]
|
||||||
}
|
}
|
||||||
|
// 4) 兜底单张主图
|
||||||
return product.value.mainImage || ''
|
return product.value.mainImage || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -378,6 +389,10 @@ const formatSize = (sizeJson) => {
|
|||||||
const selectImage = (index) => {
|
const selectImage = (index) => {
|
||||||
currentImageIndex.value = index
|
currentImageIndex.value = index
|
||||||
}
|
}
|
||||||
|
// 悬停主图时优先展示该主图
|
||||||
|
const hoverImage = (img) => {
|
||||||
|
hoveredImage.value = img
|
||||||
|
}
|
||||||
|
|
||||||
// 立即购买
|
// 立即购买
|
||||||
const handleBuyNow = () => {
|
const handleBuyNow = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user