Files
MTKJPAY-FRONT/src/views/ProductDetail.vue

1315 lines
30 KiB
Vue
Raw Normal View History

<template>
<div class="product-detail">
<!-- 加载状态 -->
<div v-if="productLoading" class="loading-container">
<el-skeleton :rows="10" animated />
</div>
<!-- 无商品数据提示 -->
<div v-else-if="!product.id" class="empty-container">
<el-empty :description="t('product.productNotExist')">
<p class="empty-tip">{{ t('product.linkExpired') }}</p>
</el-empty>
</div>
<!-- 商品详情内容 -->
<template v-else>
<!-- 商品基本信息区域 -->
<div class="product-main">
<!-- 左侧商品图片 -->
<div class="product-images">
<div class="main-image">
<el-image
:src="currentImage"
:preview-src-list="product.mainImages || []"
fit="contain"
class="main-img"
:lazy="true"
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
<div>{{ t('product.imageLoadFailed') }}</div>
</div>
</template>
</el-image>
</div>
<div class="thumbnail-list" v-if="product.mainImages && product.mainImages.length > 1">
<div
v-for="(img, index) in product.mainImages"
:key="index"
class="thumbnail-item"
:class="{ active: currentImageIndex === index }"
@click="selectImage(index)"
@mouseenter="hoverImage(img)"
@mouseleave="hoverImage(null)"
>
<el-image :src="img" fit="cover" class="thumbnail-img" />
</div>
</div>
</div>
<!-- 右侧商品信息 -->
<div class="product-info">
<h1 class="product-title">{{ product.name }}</h1>
<!-- 货币和语言选择如果有多个货币- 突出显示 -->
<div class="currency-selector-card" v-if="Object.keys(skusByCurrency).length > 1">
<div class="currency-selector-header">
<el-icon class="currency-icon"><Location /></el-icon>
<span class="selector-label">{{ t('product.selectCurrency') }}</span>
</div>
<div class="currency-selector-content">
<el-radio-group v-model="currentCurrency" size="default" class="currency-radio-group">
<el-radio-button
v-for="(skus, currency) in skusByCurrency"
:key="currency"
:label="currency"
class="currency-radio-button"
>
{{ getCurrencyName(currency) }}
</el-radio-button>
</el-radio-group>
</div>
</div>
<!-- SKU选择区域 -->
<div class="sku-selection-section" v-if="currentCurrencySkus.length > 0">
<div class="sku-section-title">{{ t('product.selectSku') }}</div>
<div class="sku-list">
<div
v-for="(sku, index) in currentCurrencySkus"
:key="sku.id || index"
:class="['sku-item', {
active: selectedSku && selectedSku.id === sku.id,
disabled: !sku.stock || sku.stock === 0
}]"
@click="selectSku(sku)"
>
<div class="sku-info">
<div class="sku-name">{{ sku.sku }}</div>
<div class="sku-price-stock">
<span class="sku-price">{{ sku.currency }} {{ formatPrice(sku.price) }}</span>
<span class="sku-stock" :class="{ 'stock-zero': !sku.stock || sku.stock === 0 }">
{{ t('product.stock') }}{{ sku.stock || 0 }}
</span>
</div>
</div>
<div class="sku-check" v-if="selectedSku && selectedSku.id === sku.id">
<el-icon><Check /></el-icon>
</div>
<div class="sku-disabled-mask" v-if="!sku.stock || sku.stock === 0">
{{ t('product.outOfStock') }}
</div>
</div>
</div>
</div>
<div v-else class="no-sku-tip">
<el-alert type="warning" :closable="false">
{{ t('product.noSkuAvailable') }}
</el-alert>
</div>
<!-- 价格区域显示选中SKU的价格 -->
<div class="price-section" v-if="selectedSku">
<div class="price-label">{{ t('product.currentPrice') }}</div>
<div class="price-main">
<span class="currency">{{ selectedSku.currency }}</span>
<span class="price">{{ formatPrice(selectedSku.price) }}</span>
</div>
</div>
<div class="price-section" v-else>
<div class="price-label">{{ t('product.selectSkuForPrice') }}</div>
</div>
<!-- 数量选择 -->
<div class="quantity-section">
<div class="quantity-label">{{ t('product.quantity') }}</div>
<el-input-number
v-model="quantity"
:min="1"
:max="selectedSku ? (selectedSku.stock || 999) : 999"
:disabled="!selectedSku || !selectedSku.stock || selectedSku.stock === 0"
class="quantity-input"
/>
<span class="stock-info">{{ t('product.stock') }}{{ selectedSku ? (selectedSku.stock || 0) : t('product.selectSku') }}</span>
</div>
<!-- 服务保障 -->
<div class="service-section">
<div class="service-item">
<el-icon><Goods /></el-icon>
<span>{{ t('product.sevenDayReturn') }}</span>
</div>
<div class="service-item">
<el-icon><Lock /></el-icon>
<span>{{ t('product.authenticGuarantee') }}</span>
</div>
<div class="service-item">
<el-icon><Clock /></el-icon>
<span>{{ t('product.expeditedShipping') }}</span>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<el-button
type="primary"
size="large"
class="buy-now-btn"
@click="handleBuyNow"
:loading="loading"
:disabled="!selectedSku || !selectedSku.stock || selectedSku.stock === 0"
>
<el-icon><ShoppingCart /></el-icon>
{{ t('product.buyNow') }}
</el-button>
</div>
</div>
</div>
<!-- 商品详情区域 -->
<div class="product-tabs">
<el-tabs v-model="activeTab" class="detail-tabs">
<el-tab-pane :label="t('product.productDetails')" name="detail">
<div class="tab-content">
<div v-if="product.description" v-html="product.description" class="product-description"></div>
<div v-else class="empty-description">
<el-empty :description="t('product.noProductDetails')" />
</div>
</div>
</el-tab-pane>
<el-tab-pane :label="t('product.specifications')" name="params">
<div class="tab-content">
<el-descriptions :column="2" border v-if="selectedSku">
<el-descriptions-item :label="t('product.skuCode')">{{ selectedSku.sku }}</el-descriptions-item>
<el-descriptions-item :label="t('product.price')">{{ selectedSku.currency }} {{ formatPrice(selectedSku.price) }}</el-descriptions-item>
<el-descriptions-item :label="t('product.stock')">{{ selectedSku.stock || 0 }}</el-descriptions-item>
<el-descriptions-item :label="t('product.weight')" v-if="selectedSku.weight">
{{ selectedSku.weight }}g
</el-descriptions-item>
<el-descriptions-item :label="t('product.size')" v-if="selectedSku.size" :span="2">
{{ formatSize(selectedSku.size) }}
</el-descriptions-item>
</el-descriptions>
<el-empty v-else :description="t('product.selectSkuForSpecs')" />
</div>
</el-tab-pane>
</el-tabs>
</div>
<!-- 购买确认对话框 -->
<el-dialog
v-model="showConfirmDialog"
:title="t('product.confirmPurchaseInfo')"
:width="isMobile ? '95%' : '600px'"
:close-on-click-modal="false"
class="confirm-dialog"
>
<div class="confirm-dialog-content">
<!-- 商品信息 -->
<div class="confirm-product-info">
<el-image
:src="currentImage"
fit="cover"
class="confirm-product-image"
/>
<div class="confirm-product-details">
<div class="confirm-product-name">{{ product.name }}</div>
<div class="confirm-sku-info" v-if="selectedSku">
<span class="confirm-sku-label">{{ t('product.sku') }}</span>
<span class="confirm-sku-value">{{ selectedSku.sku }}</span>
</div>
</div>
</div>
<!-- 价格和数量 -->
<el-divider />
<div class="confirm-price-section">
<div class="confirm-price-item">
<span class="confirm-price-label">{{ t('product.unitPrice') }}</span>
<span class="confirm-price-value">
{{ selectedSku ? selectedSku.currency : '' }} {{ formatPrice(selectedSku ? selectedSku.price : 0) }}
</span>
</div>
<div class="confirm-price-item">
<span class="confirm-price-label">{{ t('product.quantity') }}</span>
<span class="confirm-price-value">{{ quantity }}</span>
</div>
<div class="confirm-price-item confirm-total">
<span class="confirm-price-label">{{ t('product.total') }}</span>
<span class="confirm-price-value confirm-total-price">
{{ selectedSku ? selectedSku.currency : '' }} {{ formatPrice(selectedSku ? (selectedSku.price * quantity) : 0) }}
</span>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="showConfirmDialog = false">{{ t('product.cancel') }}</el-button>
<el-button type="primary" @click="confirmBuy" :loading="loading">
{{ t('product.confirmPurchase') }}
</el-button>
</div>
</template>
</el-dialog>
</template>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
// 图标已全局注册在 main.js 中,直接使用组件名称即可
import { getProduct, getProductByLinkCode } from '../api/product'
import { formatAmount } from '../utils/helpers'
import { loadTranslationByCurrency } from '../i18n'
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const productLoading = ref(true)
const currentImageIndex = ref(0)
// 鼠标悬停的主图(优先展示)
const hoveredImage = ref(null)
const quantity = ref(1)
const activeTab = ref('detail')
const selectedSku = ref(null)
const showConfirmDialog = ref(false)
// 移动端检测
const isMobile = computed(() => {
if (typeof window !== 'undefined') {
return window.innerWidth <= 768
}
return false
})
// 商品数据
const product = ref({
id: null,
name: '',
price: null,
mainImage: '',
mainImages: [],
status: '',
shopId: null,
skus: []
})
// 国家名称映射(使用对应国家的本地语言,固定不变)
const countryNamesByCurrency = {
'USD': 'United States', // 美国 - 英语
'CNY': '中国', // 中国 - 中文
'MYR': 'Malaysia', // 马来西亚 - 马来语(英语也是官方语言)
'PHP': 'Pilipinas', // 菲律宾 - 菲律宾语
'THB': 'ประเทศไทย', // 泰国 - 泰语
'VND': 'Việt Nam', // 越南 - 越南语
'SGD': 'Singapore', // 新加坡 - 英语
'EUR': 'Europe', // 欧洲 - 英语
'GBP': 'United Kingdom' // 英国 - 英语
}
// 按货币分组SKU
const skusByCurrency = computed(() => {
if (!product.value.skus || product.value.skus.length === 0) {
return {}
}
const grouped = {}
product.value.skus.forEach(sku => {
const currency = sku.currency || 'USD'
if (!grouped[currency]) {
grouped[currency] = []
}
grouped[currency].push(sku)
})
return grouped
})
// 当前货币(默认使用第一个货币)
const currentCurrency = ref('')
// 当前货币的SKU列表
const currentCurrencySkus = computed(() => {
if (!currentCurrency.value || !skusByCurrency.value[currentCurrency.value]) {
return []
}
return skusByCurrency.value[currentCurrency.value]
})
// 当前显示的图片
const currentImage = computed(() => {
// 1) 若有悬停主图,优先展示
if (hoveredImage.value) {
return hoveredImage.value
}
// 2) 已选SKU且有SKU图
if (selectedSku.value && selectedSku.value.skuImage) {
return selectedSku.value.skuImage
}
// 3) 主图列表
if (product.value.mainImages && product.value.mainImages.length > 0) {
return product.value.mainImages[currentImageIndex.value] || product.value.mainImages[0]
}
// 4) 兜底单张主图
return product.value.mainImage || ''
})
// 获取国家名称(使用对应国家的本地语言,固定不变)
const getCurrencyName = (currency) => {
return countryNamesByCurrency[currency] || currency
}
// 选择SKU
const selectSku = (sku) => {
if (!sku.stock || sku.stock === 0) {
ElMessage.warning('该SKU已缺货')
return
}
selectedSku.value = sku
// 如果SKU有图片更新当前图片
if (sku.skuImage) {
const imageIndex = product.value.mainImages?.findIndex(img => img === sku.skuImage)
if (imageIndex >= 0) {
currentImageIndex.value = imageIndex
}
}
}
// 格式化价格
const formatPrice = (price) => {
if (!price) return '0.00'
return formatAmount(price)
}
// 格式化尺寸
const formatSize = (sizeJson) => {
if (!sizeJson) return '-'
try {
const size = typeof sizeJson === 'string' ? JSON.parse(sizeJson) : sizeJson
if (size.length && size.width && size.height) {
return `${size.length} × ${size.width} × ${size.height} ${size.unit || 'cm'}`
}
return sizeJson
} catch (e) {
return sizeJson
}
}
// 选择图片
const selectImage = (index) => {
currentImageIndex.value = index
}
// 悬停主图时优先展示该主图
const hoverImage = (img) => {
hoveredImage.value = img
}
// 立即购买
const handleBuyNow = () => {
if (!selectedSku.value) {
ElMessage.warning('请先选择商品SKU')
return
}
if (selectedSku.value.stock && quantity.value > selectedSku.value.stock) {
ElMessage.warning('库存不足')
return
}
showConfirmDialog.value = true
}
// 确认购买
const confirmBuy = () => {
if (!selectedSku.value) {
ElMessage.warning('请先选择商品SKU')
return
}
showConfirmDialog.value = false
// 构建订单数据
const orderData = {
product: {
id: product.value.id,
name: product.value.name,
price: selectedSku.value.price,
currency: selectedSku.value.currency,
sku: selectedSku.value.sku,
skuId: selectedSku.value.id,
quantity: quantity.value,
stock: selectedSku.value.stock || 0,
image: selectedSku.value.skuImage || currentImage.value
}
}
// 跳转到创建订单页面(填写收货信息)
router.push({
path: '/create-order',
query: {
data: encodeURIComponent(JSON.stringify(orderData))
}
})
}
// 判断是否为链接码32位字符串
const isLinkCode = (id) => {
if (!id) return false
const idStr = String(id)
// 链接码是32位字符串UUID去掉横线
return idStr.length === 32 && /^[a-f0-9]+$/i.test(idStr)
}
// 加载商品详情
const loadProductDetail = async (id) => {
productLoading.value = true
try {
let response
// 判断是商品ID还是链接码
if (isLinkCode(id)) {
// 使用链接码获取商品
response = await getProductByLinkCode(id)
} else {
// 使用商品ID获取商品
response = await getProduct(id)
}
if (response.code === '0000' && response.data) {
product.value = response.data
// 初始化货币选择(使用第一个货币)
const currencies = Object.keys(skusByCurrency.value)
if (currencies.length > 0) {
currentCurrency.value = currencies[0]
// 自动选择第一个有库存的SKU
const firstAvailableSku = currentCurrencySkus.value.find(sku => sku.stock && sku.stock > 0)
if (firstAvailableSku) {
selectedSku.value = firstAvailableSku
}
}
} else {
ElMessage.error(response.message || '获取商品详情失败')
// 如果获取失败设置product.id为null以显示空状态
product.value.id = null
}
} catch (error) {
console.error('加载商品详情失败:', error)
ElMessage.error(error.response?.data?.message || '加载商品详情失败,请稍后重试')
} finally {
productLoading.value = false
}
}
// 监听货币变化重置选中的SKU并加载翻译
watch(currentCurrency, async (newCurrency) => {
// 加载对应货币的翻译
if (newCurrency) {
await loadTranslationByCurrency(newCurrency)
}
selectedSku.value = null
const firstAvailableSku = currentCurrencySkus.value.find(sku => sku.stock && sku.stock > 0)
if (firstAvailableSku) {
selectedSku.value = firstAvailableSku
}
}, { immediate: false })
onMounted(() => {
const id = route.params.id
if (id) {
loadProductDetail(id)
} else {
// 如果没有ID显示提示并停止加载
ElMessage.error('商品ID或链接码不能为空')
productLoading.value = false
// 客户访问时不应该跳转到管理页面,只显示错误提示
product.value.id = null
}
})
</script>
<style scoped>
.product-detail {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f7fa;
min-height: calc(100vh - 60px);
}
.loading-container {
background: white;
padding: 40px;
border-radius: 12px;
margin-bottom: 20px;
}
/* 商品主区域 */
.product-main {
display: flex;
gap: 30px;
background: white;
padding: 40px;
border-radius: 12px;
margin-bottom: 20px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
/* 左侧图片区域 */
.product-images {
flex: 0 0 450px;
}
.main-image {
width: 100%;
height: 450px;
border: 1px solid #e8e8e8;
border-radius: 8px;
margin-bottom: 15px;
overflow: hidden;
background: #fafafa;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.main-image:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.main-img {
width: 100%;
height: 100%;
}
.image-slot {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f7fa;
color: #909399;
}
.thumbnail-list {
display: flex;
gap: 10px;
justify-content: center;
}
.thumbnail-item {
width: 80px;
height: 80px;
border: 2px solid transparent;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s;
}
.thumbnail-item:hover {
border-color: #409eff;
}
.thumbnail-item.active {
border-color: #409eff;
}
.thumbnail-img {
width: 100%;
height: 100%;
}
/* 右侧商品信息 */
.product-info {
flex: 1;
padding-left: 20px;
}
.product-title {
font-size: 26px;
font-weight: 700;
color: #333;
margin: 0 0 20px 0;
line-height: 1.5;
letter-spacing: 0.5px;
}
/* 货币选择器卡片 - 紧凑但醒目,适合移动端 */
.currency-selector-card {
margin-top: 15px;
margin-bottom: 20px;
padding: 12px 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);
border: 2px solid rgba(255, 255, 255, 0.3);
position: relative;
overflow: hidden;
}
.currency-selector-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #ffd700, #ffed4e, #ffd700);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.currency-selector-header {
display: flex;
align-items: center;
margin-bottom: 10px;
color: white;
}
.currency-icon {
font-size: 18px;
margin-right: 6px;
color: #ffd700;
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2));
}
.currency-selector-header .selector-label {
font-size: 14px;
font-weight: 600;
color: white;
margin: 0;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.currency-selector-content {
background: white;
padding: 10px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.currency-radio-group {
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.currency-radio-button {
flex: 1;
min-width: 0;
}
.currency-radio-button :deep(.el-radio-button__inner) {
font-size: 13px;
font-weight: 500;
padding: 8px 12px;
border-radius: 6px;
transition: all 0.3s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 移动端:紧凑布局,全部展示在一行 */
@media (max-width: 768px) {
.currency-selector-content {
padding: 8px;
}
.currency-radio-group {
display: flex;
flex-wrap: nowrap;
gap: 4px;
width: 100%;
}
.currency-radio-button {
flex: 1;
min-width: 0;
}
.currency-radio-button :deep(.el-radio-button__inner) {
font-size: 11px;
padding: 6px 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.currency-radio-button :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-color: #667eea;
color: white;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
}
/* 旧样式保留(兼容性) */
.currency-selector {
margin-bottom: 25px;
padding: 15px;
background: #f5f7fa;
border-radius: 8px;
}
.selector-label {
font-size: 14px;
color: #606266;
margin-right: 15px;
font-weight: 500;
}
/* SKU选择区域 */
.sku-selection-section {
margin-bottom: 25px;
}
.sku-section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 15px;
}
.sku-list {
display: flex;
flex-direction: column;
gap: 12px;
max-height: 400px;
overflow-y: auto;
}
.sku-item {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border: 2px solid #e4e7ed;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
background: #fff;
}
.sku-item:hover:not(.disabled) {
border-color: #409eff;
background: #f0f9ff;
}
.sku-item.active:not(.disabled) {
border-color: #409eff;
background: #ecf5ff;
}
.sku-item.disabled {
cursor: not-allowed;
opacity: 0.6;
background: #f5f7fa;
}
.sku-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.sku-name {
font-size: 14px;
color: #303133;
font-weight: 500;
line-height: 1.5;
}
.sku-price-stock {
display: flex;
align-items: center;
gap: 15px;
}
.sku-price {
font-size: 18px;
color: #f56c6c;
font-weight: 600;
}
.sku-stock {
font-size: 14px;
color: #909399;
}
.sku-stock.stock-zero {
color: #f56c6c;
font-weight: 500;
}
.sku-check {
color: #409eff;
font-size: 20px;
}
.sku-disabled-mask {
position: absolute;
top: 50%;
right: 50px;
transform: translateY(-50%);
background: rgba(0, 0, 0, 0.6);
color: white;
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
}
.no-sku-tip {
margin-bottom: 25px;
}
/* 价格区域 */
.price-section {
background: linear-gradient(135deg, #fff5f5 0%, #ffeef0 100%);
padding: 25px;
border-radius: 8px;
margin-bottom: 25px;
border: 1px solid #ffe0e0;
}
.price-label {
font-size: 14px;
color: #999;
margin-bottom: 10px;
}
.price-main {
display: flex;
align-items: baseline;
margin-bottom: 10px;
}
.currency {
font-size: 20px;
color: #f56c6c;
font-weight: 700;
margin-right: 4px;
}
.price {
font-size: 42px;
color: #f56c6c;
font-weight: 700;
letter-spacing: -1px;
}
/* 数量选择 */
.quantity-section {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 25px;
padding: 15px 0;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
}
.quantity-label {
font-size: 14px;
color: #666;
font-weight: 500;
}
.quantity-input {
width: 120px;
}
.stock-info {
font-size: 14px;
color: #999;
}
/* 服务保障 */
.service-section {
display: flex;
gap: 40px;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #f9f9f9 0%, #f5f5f5 100%);
border-radius: 8px;
border: 1px solid #eee;
}
.service-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #666;
}
.service-item .el-icon {
color: #409eff;
font-size: 18px;
}
/* 操作按钮 */
.action-buttons {
display: flex;
gap: 15px;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.buy-now-btn {
flex: 1;
height: 52px;
font-size: 16px;
font-weight: 600;
background: linear-gradient(135deg, #ff9500 0%, #ff6b00 100%);
border: none;
box-shadow: 0 4px 12px rgba(255, 107, 0, 0.3);
transition: all 0.3s;
}
.buy-now-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #ff6b00 0%, #ff9500 100%);
box-shadow: 0 6px 16px rgba(255, 107, 0, 0.4);
transform: translateY(-2px);
}
.pay-now-btn {
flex: 1;
height: 52px;
font-size: 16px;
font-weight: 600;
background: linear-gradient(135deg, #f56c6c 0%, #ff4757 100%);
border: none;
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
transition: all 0.3s;
}
.pay-now-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #ff4757 0%, #f56c6c 100%);
box-shadow: 0 6px 16px rgba(245, 108, 108, 0.4);
transform: translateY(-2px);
}
/* 确认对话框样式 */
.confirm-dialog-content {
padding: 10px 0;
}
.confirm-product-info {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.confirm-product-image {
width: 120px;
height: 120px;
border-radius: 8px;
border: 1px solid #e4e7ed;
flex-shrink: 0;
}
.confirm-product-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
}
.confirm-product-name {
font-size: 16px;
font-weight: 600;
color: #303133;
line-height: 1.5;
}
/* 移动端:弹窗优化 */
@media (max-width: 768px) {
.confirm-dialog :deep(.el-dialog) {
margin: 5vh auto !important;
max-height: 90vh;
display: flex;
flex-direction: column;
}
.confirm-dialog :deep(.el-dialog__body) {
flex: 1;
overflow-y: auto;
padding: 15px;
}
.confirm-dialog-content {
padding: 0;
}
.confirm-product-info {
flex-direction: column;
gap: 12px;
margin-bottom: 15px;
}
.confirm-product-image {
width: 100%;
height: 200px;
align-self: center;
}
.confirm-product-name {
font-size: 14px;
}
.confirm-price-section {
gap: 10px;
}
.confirm-price-item {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.dialog-footer {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px 0;
}
.dialog-footer .el-button {
width: 100%;
margin: 0;
}
}
.confirm-sku-info {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #f5f7fa;
border-radius: 4px;
border-left: 3px solid #409eff;
}
.confirm-sku-label {
font-size: 14px;
color: #606266;
font-weight: 500;
}
.confirm-sku-value {
font-size: 14px;
color: #303133;
font-weight: 600;
font-family: 'Courier New', monospace;
background: #fff;
padding: 2px 8px;
border-radius: 4px;
border: 1px solid #dcdfe6;
}
.confirm-price-section {
display: flex;
flex-direction: column;
gap: 12px;
}
.confirm-price-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
}
.confirm-price-label {
font-size: 14px;
color: #606266;
}
.confirm-price-value {
font-size: 14px;
color: #303133;
font-weight: 500;
}
.confirm-total {
padding-top: 12px;
border-top: 1px solid #e4e7ed;
margin-top: 8px;
}
.confirm-total-price {
font-size: 20px;
color: #f56c6c;
font-weight: 700;
}
/* 商品详情标签页 */
.product-tabs {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
margin-top: 20px;
}
.detail-tabs {
margin-top: 20px;
}
.tab-content {
padding: 20px 0;
min-height: 300px;
}
.product-description {
line-height: 1.8;
color: #666;
}
.empty-description {
text-align: center;
padding: 60px 0;
}
/* 空状态容器 */
.empty-container {
background: white;
padding: 60px 20px;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
margin-top: 20px;
text-align: center;
}
.empty-tip {
margin-top: 20px;
color: #909399;
font-size: 14px;
line-height: 1.6;
}
/* 响应式设计 */
@media (max-width: 768px) {
.product-detail {
padding: 10px;
}
.product-main {
flex-direction: column;
padding: 15px;
gap: 20px;
}
.product-images {
flex: 1;
width: 100%;
}
.main-image {
height: 300px;
}
.product-info {
padding-left: 0;
}
.product-title {
font-size: 20px;
}
.sku-list {
max-height: 300px;
}
.action-buttons {
flex-direction: column;
}
.buy-now-btn,
.pay-now-btn {
width: 100%;
}
.service-section {
flex-direction: column;
gap: 15px;
}
.currency-selector-card {
padding: 10px 12px;
margin-top: 10px;
margin-bottom: 15px;
}
.currency-selector-header {
margin-bottom: 8px;
}
.currency-icon {
font-size: 16px;
margin-right: 5px;
}
.currency-selector-header .selector-label {
font-size: 13px;
}
.currency-selector-content {
padding: 8px;
}
.currency-radio-group {
display: flex;
flex-wrap: nowrap;
gap: 4px;
width: 100%;
}
.currency-radio-button {
flex: 1;
min-width: 0;
}
.currency-radio-button :deep(.el-radio-button__inner) {
font-size: 11px;
padding: 6px 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
/* 旧样式兼容 */
.currency-selector {
flex-direction: column;
gap: 10px;
}
.selector-label {
margin-right: 0;
margin-bottom: 10px;
}
}
</style>