- 将创建订单界面的所有静态文本替换为国际化标签 - 实现在移动端和桌面端的响应式表单布局 - 添加多语言国家名称显示功能 - 集成Vue I18n国际化框架到应用主入口 - 优化订单确认页面的国际化文本显示 - 移除导航菜单中的创建订单项 - 添加针对不同国家的地址字段验证规则 - 实现越南地址层级映射逻辑 - 添加货币代码自动设置国家功能
1315 lines
30 KiB
Vue
1315 lines
30 KiB
Vue
<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>
|