Files
jurong_circle_frontdesk/src/views/Pay.vue

1422 lines
37 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="pay-page">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-left">
<el-button
type="text"
@click="handleGoBack"
class="back-btn"
>
<el-icon><ArrowLeft /></el-icon>
</el-button>
</div>
<div class="nav-center">
<h1 class="nav-title">确认订单</h1>
</div>
<div class="nav-right">
<!-- 占位元素保持标题居中 -->
</div>
</nav>
<div v-loading="loading" class="page-content">
<!-- 收货地址 -->
<div class="address-section">
<div class="address-header">
<el-icon><Location /></el-icon>
<span class="address-label">收货地址</span>
<el-button
type="text"
@click="goToAddressManage"
class="manage-address-btn"
>
管理地址
</el-button>
</div>
<div class="address-content">
<el-select
v-model="selectedAddressId"
placeholder="请选择收货地址"
class="address-select"
@change="handleAddressChange"
>
<el-option
v-for="address in addresses"
:key="address.id"
:label="`${address.recipientName} ${address.recipientPhone} ${address.province}${address.city}${address.district}${address.detailAddress}`"
:value="address.id"
>
<div class="address-option">
<div class="address-info">
<span class="recipient-info">{{ address.recipientName }} {{ address.recipientPhone }}</span>
<el-tag v-if="address.isDefault" type="danger" size="small" class="default-tag">默认</el-tag>
</div>
<div class="address-detail">{{ address.province }}{{ address.city }}{{ address.district }}{{ address.detailAddress }}</div>
</div>
</el-option>
</el-select>
<div v-if="addresses.length === 0" class="no-address">
<span class="no-address-text">暂无收货地址</span>
<el-button type="text" @click="goToAddressManage" class="add-address-btn">
立即添加
</el-button>
</div>
</div>
</div>
<!-- 支付金额 -->
<div class="amount-section">
<h3 class="section-title">支付</h3>
<div class="amount-display">
<div class="total-amount-large">
<!-- 根据选择的支付方式动态显示支付格式 -->
<template v-if="selectedPaymentMethod === 'mixed' && paymentData.pointsAmount > 0 && paymentData.beansAmount > 0">
<!-- 积分+融豆混合支付 -->
<div class="payment-price-container">
<div class="payment-main-price">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="payment-rongdou-icon" />
<span class="payment-rongdou-price">{{ totalRongdouPrice }}</span>
</div>
<div class="payment-sub-price">
<el-icon class="payment-points-icon"><Coin /></el-icon>
<span class="payment-points-price">{{ totalPointsPrice }}</span>
</div>
</div>
</template>
<template v-else-if="selectedPaymentMethod === 'beans' && paymentData.beansAmount > 0">
<!-- 仅融豆支付 -->
<div class="payment-price-container">
<div class="payment-main-price">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="payment-rongdou-icon" />
<span class="payment-rongdou-price">{{ totalRongdouPrice }}</span>
</div>
<div class="payment-sub-price">
<el-icon class="payment-points-icon"><Coin /></el-icon>
<span class="payment-points-price">{{ totalPointsPrice }}</span>
</div>
</div>
</template>
<template v-else-if="selectedPaymentMethod === 'points' && paymentData.pointsAmount > 0">
<!-- 仅积分支付 -->
<div class="payment-price-container">
<div class="payment-main-price">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="payment-rongdou-icon" />
<span class="payment-rongdou-price">{{ totalRongdouPrice }}</span>
</div>
<div class="payment-sub-price">
<el-icon class="payment-points-icon"><Coin /></el-icon>
<span class="payment-points-price">{{ totalPointsPrice }}</span>
</div>
</div>
</template>
<template v-else>
<!-- 未选择支付方式时的默认显示 -->
<div class="payment-price-container">
<div class="payment-main-price">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="payment-rongdou-icon" />
<span class="payment-rongdou-price">{{ totalRongdouPrice }}</span>
</div>
<div class="payment-sub-price">
<el-icon class="payment-points-icon"><Coin /></el-icon>
<span class="payment-points-price">{{ totalPointsPrice }}</span>
</div>
</div>
</template>
</div>
<div class="amount-breakdown" v-if="selectedPaymentMethod && (paymentData.pointsAmount > 0 || paymentData.beansAmount > 0)">
<div v-if="paymentData.pointsAmount > 0" class="breakdown-item">
<el-icon><Coin /></el-icon>
<span>积分{{ paymentData.pointsAmount }}</span>
</div>
<div v-if="paymentData.beansAmount > 0" class="breakdown-item">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="breakdown-icon" />
<span>融豆{{ paymentData.beansAmount }}</span>
</div>
</div>
</div>
</div>
<!-- 商品列表 -->
<div class="items-section" v-if="paymentData.items && paymentData.items.length > 0">
<h3 class="section-title">商品清单 ({{ paymentData.items.length }})</h3>
<div class="items-list">
<div
v-for="item in paymentData.items"
:key="item.id || item.productId"
class="item-card"
>
<div class="item-image">
<img :src="item.image || '/imgs/productdetail/商品主图.png'" :alt="item.name" />
</div>
<div class="item-info">
<div class="item-name">{{ item.name }}</div>
<div class="item-details">
<span v-if="item.category" class="item-category">{{ item.category }}</span>
<span v-if="item.size" class="item-size">{{ item.size }}</span>
</div>
<div class="item-price">
<div class="item-price-container">
<div class="item-main-price">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="item-rongdou-icon" />
<span class="item-rongdou-price">{{ item.rongdouPrice }}</span>
</div>
<div class="item-sub-price">
<el-icon class="item-points-icon"><Coin /></el-icon>
<span class="item-points-price">{{ item.points }}</span>
</div>
</div>
</div>
</div>
<div class="item-quantity">
<span class="quantity-label">x{{ item.quantity }}</span>
</div>
</div>
</div>
</div>
<!-- 支付方式选择 -->
<div class="payment-method-section">
<h3 class="section-title">支付方式</h3>
<div class="payment-options">
<!-- 融豆支付选项 -->
<div
v-if="shouldShowPaymentMethod('beans')"
class="payment-option"
:class="{
active: selectedPaymentMethod === 'beans',
disabled: !isPaymentMethodAvailable('beans')
}"
@click="selectPaymentMethod('beans')"
>
<img src='/imgs/profile/rongdou.png' alt="融豆" class="payment-icon-img" />
<div class="payment-info">
<div class="payment-name">融豆支付</div>
<div class="payment-desc">
<span v-if="isPaymentMethodAvailable('beans')">使用账户融豆进行支付</span>
<span v-else class="insufficient-balance">余额不足当前{{ userBalance.beans }}</span>
</div>
</div>
<el-icon class="check-icon" v-if="selectedPaymentMethod === 'beans'"><Check /></el-icon>
</div>
<!-- 积分支付选项 -->
<div
v-if="shouldShowPaymentMethod('points')"
class="payment-option"
:class="{
active: selectedPaymentMethod === 'points',
disabled: !isPaymentMethodAvailable('points')
}"
@click="selectPaymentMethod('points')"
>
<el-icon class="payment-icon"><Coin /></el-icon>
<div class="payment-info">
<div class="payment-name">积分支付</div>
<div class="payment-desc">
<span v-if="isPaymentMethodAvailable('points')">使用账户积分进行支付</span>
<span v-else class="insufficient-balance">余额不足当前{{ userBalance.points }}</span>
</div>
</div>
<el-icon class="check-icon" v-if="selectedPaymentMethod === 'points'"><Check /></el-icon>
</div>
<!-- 积分+融豆混合支付选项 -->
<div
v-if="shouldShowPaymentMethod('mixed')"
class="payment-option"
:class="{
active: selectedPaymentMethod === 'mixed',
disabled: !isPaymentMethodAvailable('mixed')
}"
@click="selectPaymentMethod('mixed')"
>
<div class="payment-icon-group">
<el-icon class="payment-icon"><Coin /></el-icon>
<span class="plus-sign">+</span>
<img src='/imgs/profile/rongdou.png' alt="融豆" class="payment-icon-img" />
</div>
<div class="payment-info">
<div class="payment-name">积分+融豆</div>
<div class="payment-desc">
<span v-if="isPaymentMethodAvailable('mixed')">使用积分和融豆组合支付</span>
<span v-else class="insufficient-balance">余额不足当前融豆{{ userBalance.beans }} + 积分{{ userBalance.points }}</span>
</div>
</div>
<el-icon class="check-icon" v-if="selectedPaymentMethod === 'mixed'"><Check /></el-icon>
</div>
</div>
</div>
</div>
<!-- 底部支付按钮 -->
<div class="bottom-actions">
<div class="payment-summary">
<div class="total-amount">
<span>实付</span>
<!-- 根据选择的支付方式动态显示实付格式 -->
<template v-if="selectedPaymentMethod === 'mixed' && paymentData.pointsAmount > 0 && paymentData.beansAmount > 0">
<!-- 积分+融豆混合支付 -->
<div class="bottom-price-container">
<div class="bottom-main-price">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="bottom-rongdou-icon" />
<span class="bottom-rongdou-price">{{ paymentData.beansAmount }}</span>
</div>
<span class="bottom-plus-sign">+</span>
<div class="bottom-sub-price">
<el-icon class="bottom-points-icon"><Coin /></el-icon>
<span class="bottom-points-price">{{ paymentData.pointsAmount }}</span>
</div>
</div>
</template>
<template v-else-if="selectedPaymentMethod === 'beans' && paymentData.beansAmount > 0">
<!-- 仅融豆支付 -->
<div class="bottom-price-container">
<div class="bottom-main-price">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="bottom-rongdou-icon" />
<span class="bottom-rongdou-price">{{ paymentData.beansAmount }}</span>
</div>
<span class="bottom-plus-sign" v-if="paymentData.pointsAmount > 0">+</span>
<div class="bottom-sub-price" v-if="paymentData.pointsAmount > 0">
<el-icon class="bottom-points-icon"><Coin /></el-icon>
<span class="bottom-points-price">{{ paymentData.pointsAmount || 0 }}</span>
</div>
</div>
</template>
<template v-else-if="selectedPaymentMethod === 'points' && paymentData.pointsAmount > 0">
<!-- 仅积分支付 -->
<div class="bottom-price-container">
<div class="bottom-main-price" v-if="paymentData.beansAmount > 0">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="bottom-rongdou-icon" />
<span class="bottom-rongdou-price">{{ paymentData.beansAmount || 0 }}</span>
</div>
<span class="bottom-plus-sign" v-if="paymentData.beansAmount > 0 && paymentData.pointsAmount > 0">+</span>
<div class="bottom-sub-price">
<el-icon class="bottom-points-icon"><Coin /></el-icon>
<span class="bottom-points-price">{{ paymentData.pointsAmount }}</span>
</div>
</div>
</template>
<template v-else>
<!-- 未选择支付方式时的默认显示 -->
<div class="bottom-price-container">
<div class="bottom-main-price" v-if="paymentData.beansAmount > 0">
<img src='/imgs/profile/rongdou.png' alt="融豆" class="bottom-rongdou-icon" />
<span class="bottom-rongdou-price">{{ paymentData.beansAmount || 0 }}</span>
</div>
<span class="bottom-plus-sign" v-if="paymentData.beansAmount > 0 && paymentData.pointsAmount > 0">+</span>
<div class="bottom-sub-price" v-if="paymentData.pointsAmount > 0">
<el-icon class="bottom-points-icon"><Coin /></el-icon>
<span class="bottom-points-price">{{ paymentData.pointsAmount || 0 }}</span>
</div>
</div>
</template>
</div>
</div>
<el-button
type="primary"
size="large"
class="pay-button"
@click="confirmPayment"
:disabled="paying || !selectedPaymentMethod"
:loading="paying"
>
{{ paying ? '支付中...' : '确认支付' }}
</el-button>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
ArrowLeft,
Location,
Coin,
Orange,
Check
} from '@element-plus/icons-vue'
import api from '@/utils/api'
import { getImageUrl } from '@/config'
const route = useRoute()
const router = useRouter()
// 响应式数据
const loading = ref(false)
const paying = ref(false)
const selectedPaymentMethod = ref('') // 当前选择的支付方式
const paymentData = ref({
totalAmount: 0,
pointsAmount: 0,
beansAmount: 0,
cartId: null,
items: [] // 添加商品列表
})
// 计算商品总价
const totalRongdouPrice = computed(() => {
return paymentData.value.items.reduce((sum, item) => {
const rongdouPrice = item.rongdouPrice || (item.rongdou_price || 0)
return sum + (rongdouPrice * item.quantity)
}, 0)
})
const totalPointsPrice = computed(() => {
return paymentData.value.items.reduce((sum, item) => {
const pointsPrice = item.points || (item.points_price || 0)
return sum + (pointsPrice * item.quantity)
}, 0)
})
// 用户余额数据
const userBalance = ref({
points: 0, // 用户积分
beans: 0 // 用户融豆
})
// 地址相关数据
const addresses = ref([])
const selectedAddressId = ref('')
const selectedAddress = ref(null)
// 方法
const selectPaymentMethod = async (method) => {
// 仅基于商品的points_price价值总和计算支付总额
const totalPointsPrice = paymentData.value.items.reduce((sum, item) => sum + (item.points_price * item.quantity), 0)
const EXCHANGE_RATE = 10000 // 1融豆 = 10000积分
// 检查支付方式是否可用
if (!isPaymentMethodAvailable(method)) {
let message = ''
if (method === 'beans') {
const requiredBeans = Math.ceil(totalPointsPrice / EXCHANGE_RATE)
message = `融豆余额不足,当前余额:${userBalance.value.beans},需要:${requiredBeans}`
} else if (method === 'points') {
message = `积分余额不足,当前余额:${userBalance.value.points},需要:${totalPointsPrice}`
} else if (method === 'mixed') {
message = `余额不足,无法完成支付`
}
ElMessage.warning(message)
return
}
selectedPaymentMethod.value = method
// 根据选择的支付方式更新显示的金额
if (method === 'beans') {
// 融豆支付:按积分/10000计算所需融豆
const requiredBeans = Math.ceil(totalPointsPrice / EXCHANGE_RATE)
paymentData.value.totalAmount = totalPointsPrice
paymentData.value.pointsAmount = 0
paymentData.value.beansAmount = requiredBeans
} else if (method === 'points') {
// 积分支付:直接使用积分
paymentData.value.totalAmount = totalPointsPrice
paymentData.value.pointsAmount = totalPointsPrice
paymentData.value.beansAmount = 0
} else if (method === 'mixed') {
// 积分+融豆支付积分必须是10000的倍数总积分-当前积分)/10000计算所需融豆
let availablePoints = Math.min(userBalance.value.points, totalPointsPrice)
// 确保积分是10000的倍数
availablePoints = Math.floor(availablePoints / 10000) * 10000
const remainingPoints = totalPointsPrice - availablePoints
const requiredBeans = Math.ceil(remainingPoints / EXCHANGE_RATE)
paymentData.value.totalAmount = totalPointsPrice
paymentData.value.pointsAmount = availablePoints
paymentData.value.beansAmount = requiredBeans
}
}
// 获取用户地址列表
const getAddressList = async () => {
try {
const response = await api.get('/addresses')
console.log('获取地址列表响应:', response)
if (response.data.success) {
// 根据接口文档转换数据格式与Address.vue保持一致
const addressList = response.data.data || []
addresses.value = addressList.map(addr => ({
id: addr.id,
recipientName: addr.receiver_name,
recipientPhone: addr.receiver_phone,
province: addr.province_name,
city: addr.city_name,
district: addr.district_name,
detailAddress: addr.detailed_address,
isDefault: addr.is_default,
labelName: addr.label_name,
labelColor: addr.label_color
}))
// 如果有默认地址,自动选中
const defaultAddress = addresses.value.find(addr => addr.isDefault)
if (defaultAddress) {
selectedAddressId.value = defaultAddress.id
selectedAddress.value = defaultAddress
}
} else {
throw new Error(response.data.message || '获取地址列表失败')
}
} catch (error) {
console.error('获取地址列表失败:', error)
ElMessage.error(error.message || '获取地址列表失败')
}
}
// 处理地址选择变化
const handleAddressChange = (addressId) => {
selectedAddress.value = addresses.value.find(addr => addr.id === addressId)
}
// 跳转到地址管理页面
const goToAddressManage = () => {
router.push('/address')
}
// 获取用户余额信息
const fetchUserBalance = async () => {
try {
// 获取用户积分
const pointsResponse = await api.get('/user/points')
userBalance.value.points = pointsResponse.data?.currentPoints ?? pointsResponse.data?.points ?? 0
// 获取用户融豆(从用户资料接口获取,需要取反)
const profileResponse = await api.get('/user/profile')
if (profileResponse.data.success && profileResponse.data.user) {
userBalance.value.beans = -(parseFloat(profileResponse.data.user.balance) || 0)
}
} catch (error) {
console.error('获取用户余额失败:', error)
ElMessage.error('获取用户余额失败')
userBalance.value = { points: 0, beans: 0 }
}
}
// 检查支付方式是否可用
const isPaymentMethodAvailable = (method) => {
// 仅基于商品的points_price价值总和计算
const totalPointsPrice = paymentData.value.items.reduce((sum, item) => sum + (item.points_price * item.quantity), 0)
const EXCHANGE_RATE = 10000 // 1融豆 = 10000积分
switch (method) {
case 'beans':
// 融豆支付检查总积分是否大于等于10000且融豆是否足够支付总积分价值
if (totalPointsPrice < 10000) {
return false
}
const requiredBeans = Math.ceil(totalPointsPrice / EXCHANGE_RATE)
return userBalance.value.beans >= requiredBeans
case 'points':
// 积分支付:检查积分是否足够
return userBalance.value.points >= totalPointsPrice
case 'mixed':
// 混合支付:检查积分+融豆换算后是否足够
const totalUserBalanceInPoints = userBalance.value.points + (userBalance.value.beans * EXCHANGE_RATE)
return totalUserBalanceInPoints >= totalPointsPrice
default:
return true
}
}
// 检查支付方式是否应该显示
const shouldShowPaymentMethod = (method) => {
// 仅基于商品的points_price价值总和计算
const totalPointsPrice = paymentData.value.items.reduce((sum, item) => sum + (item.points_price * item.quantity), 0)
const EXCHANGE_RATE = 10000 // 1融豆 = 10000积分
switch (method) {
case 'beans':
// 当总积分大于等于10000且用户融豆足够支付总积分价值时显示融豆支付选项
if (totalPointsPrice < 10000) {
return false
}
const requiredBeans = Math.ceil(totalPointsPrice / EXCHANGE_RATE)
return totalPointsPrice > 0 && userBalance.value.beans >= requiredBeans
case 'points':
// 当用户积分足够支付时显示积分支付选项
return totalPointsPrice > 0 && userBalance.value.points >= totalPointsPrice
case 'mixed':
// 当用户积分不够但积分+融豆换算足够时显示混合支付选项且用户积分必须大于等于10000
const pointsNotEnough = totalPointsPrice > 0 && userBalance.value.points < totalPointsPrice
const hasEnoughPoints = userBalance.value.points >= 10000
return pointsNotEnough && hasEnoughPoints && isPaymentMethodAvailable('mixed')
default:
return false
}
}
const fetchPaymentData = async () => {
try {
loading.value = true
// 从路由参数获取订单ID
const orderId = route.params.orderId
if (!orderId) {
throw new Error('订单ID不存在')
}
// 先获取用户余额信息
await fetchUserBalance()
// 获取地址列表
await getAddressList()
// 从后端获取当前用户的待支付订单信息
const response = await api.get(`/orders/pending-payment/${orderId}`)
if (response.data.success) {
const data = response.data.data
const items = (data.items || []).map(item => ({
id: item.id,
productId: item.product_id,
name: item.product_name,
image: item.image_url,
points: item.points_price,
points_price: item.points_price, // 添加points_price字段
price: item.price,
rongdouPrice: item.rongdou_price,
quantity: item.quantity,
description: item.description,
specInfo: item.spec_info
}))
// 计算总积分价格和总融豆价格
const totalPointsPrice = items.reduce((sum, item) => sum + (item.points * item.quantity), 0)
const totalBeansPrice = items.reduce((sum, item) => sum + (item.rongdouPrice * item.quantity), 0)
paymentData.value = {
totalAmount: 0, // 初始不设置总金额,等用户选择支付方式后再设置
pointsAmount: totalPointsPrice, // 积分金额
beansAmount: totalBeansPrice, // 融豆金额
orderId: data.id || null, // 订单ID
orderNo: data.order_no || '', // 订单号
items: items
}
// 根据用户余额自动选择支付方式
if (shouldShowPaymentMethod('beans')) {
await selectPaymentMethod('beans')
} else if (shouldShowPaymentMethod('points')) {
await selectPaymentMethod('points')
} else if (shouldShowPaymentMethod('mixed')) {
await selectPaymentMethod('mixed')
}
// 如果没有可用的支付方式,不自动选择,让用户看到所有选项都不可用
} else {
throw new Error(response.data.message || '获取订单信息失败')
}
} catch (error) {
ElMessage.error(error.message || '获取订单信息失败')
} finally {
loading.value = false
}
}
const handleGoBack = async () => {
try {
await ElMessageBox.confirm(
'确认要取消订单吗?取消后可以在个人中心-我的订单里查看。',
'取消订单',
{
confirmButtonText: '确认取消',
cancelButtonText: '继续支付',
type: 'warning'
}
)
// 用户确认取消订单
router.go(-1)
} catch {
// 用户取消,什么都不做,留在当前页面
}
}
const confirmPayment = async () => {
if (!selectedPaymentMethod.value) {
ElMessage.error('请选择支付方式')
return
}
if (!selectedAddress.value) {
ElMessage.error('请选择收货地址')
return
}
try {
// 根据商品价格显示确认信息
let confirmMessage = ''
const totalPoints = paymentData.value.pointsAmount
const totalRongdou = paymentData.value.beansAmount
if (totalPoints > 0 && totalRongdou > 0) {
confirmMessage = `确认支付 ${totalPoints} 积分 + ${totalRongdou} 融豆?`
} else if (totalPoints === 0 && totalRongdou > 0) {
confirmMessage = `确认支付 ${totalRongdou} 融豆?`
} else if (totalRongdou === 0 && totalPoints > 0) {
confirmMessage = `确认支付 ${totalPoints} 积分?`
}
await ElMessageBox.confirm(
confirmMessage,
'确认支付',
{
confirmButtonText: '确定支付',
cancelButtonText: '取消',
type: 'warning'
}
)
paying.value = true
// 创建订单数据
const orderData = {
orderId: paymentData.value.orderId,
addressId: selectedAddress.value.id,
paymentMethod: selectedPaymentMethod.value,
pointsAmount: paymentData.value.pointsAmount,
beansAmount: paymentData.value.beansAmount
}
// 向后端发送订单支付请求
const response = await api.post('/orders/confirm-payment', orderData)
console.log(orderData)
if (response.data.success) {
console.log(orderData)
ElMessage.success('订单创建成功!')
// 跳转到PayLoading页面传递订单ID
router.push({
path: '/payloading',
query: {
orderId: paymentData.value.orderId
}
})
} else {
console.log(orderData)
throw new Error(response.data.message || '创建订单失败')
}
} catch (error) {
console.log(orderData)
if (error !== 'cancel') {
ElMessage.error(error.message || '创建订单失败,请重试')
}
} finally {
paying.value = false
}
}
// 页面初始化
onMounted(() => {
fetchPaymentData()
})
</script>
<style scoped>
.pay-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: white;
border-bottom: 1px solid #eee;
position: relative;
}
.nav-left,
.nav-right {
width: 48px;
display: flex;
justify-content: center;
}
.nav-center {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.nav-title {
font-size: 18px;
font-weight: 500;
margin: 0;
}
.back-btn {
color: #333;
padding: 0;
}
.page-content {
flex: 1;
padding: 0;
}
.countdown-section {
background: white;
padding: 24px 16px;
margin-bottom: 8px;
text-align: center;
}
.countdown-header {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 16px;
}
.clock-icon {
color: #ff4757;
font-size: 18px;
}
.countdown-label {
font-size: 16px;
font-weight: 500;
color: #333;
}
.countdown-display {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 12px;
}
.time-block {
display: flex;
flex-direction: column;
align-items: center;
background: #ff4757;
color: white;
padding: 12px 16px;
border-radius: 8px;
min-width: 60px;
}
.time-number {
font-size: 24px;
font-weight: bold;
line-height: 1;
}
.time-label {
font-size: 12px;
margin-top: 4px;
}
.time-separator {
font-size: 24px;
font-weight: bold;
color: #ff4757;
}
.countdown-tip {
font-size: 14px;
color: #666;
}
.timeout-text {
color: #ff4757;
font-weight: 500;
}
.amount-section,
.items-section,
.payment-method-section {
background: white;
padding: 16px;
margin-bottom: 8px;
}
.section-title {
font-size: 16px;
font-weight: 500;
margin: 0 0 12px 0;
color: #333;
}
.items-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.item-card {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border: 1px solid #eee;
border-radius: 8px;
background: #fafafa;
}
.item-image {
width: 60px;
height: 60px;
border-radius: 6px;
overflow: hidden;
flex-shrink: 0;
}
.item-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.item-info {
flex: 1;
min-width: 0;
}
.item-name {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-details {
display: flex;
gap: 8px;
margin-bottom: 6px;
}
.item-category,
.item-size {
font-size: 12px;
color: #666;
background: #f0f0f0;
padding: 2px 6px;
border-radius: 4px;
}
.item-price {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
color: #ffae00;
font-weight: 500;
}
.item-quantity {
flex-shrink: 0;
}
.quantity-label {
font-size: 14px;
color: #666;
font-weight: 500;
}
.amount-display {
text-align: center;
padding: 20px 0;
}
.total-amount-large {
display: flex;
align-items: baseline;
justify-content: center;
gap: 4px;
margin-bottom: 16px;
}
.currency-icon {
width: 24px;
height: 24px;
margin-right: 4px;
vertical-align: middle;
}
.currency-icon-el {
font-size: 24px;
color: #ffae00;
margin-right: 4px;
}
.currency-icon-group {
display: flex;
align-items: center;
gap: 2px;
margin-right: 4px;
}
.plus-sign-small {
font-size: 14px;
color: #666;
font-weight: bold;
margin: 0 2px;
}
.mixed-payment-item {
display: flex;
align-items: center;
gap: 2px;
}
.mixed-amount {
font-size: 20px;
font-weight: bold;
color: #ff4757;
}
.mixed-payment-item-small {
display: flex;
align-items: center;
gap: 2px;
}
.mixed-amount-small {
font-size: 14px;
font-weight: bold;
color: #ff4757;
}
.amount-number {
font-size: 36px;
font-weight: bold;
color: #ff4757;
}
.amount-breakdown {
display: flex;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
}
.breakdown-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
color: #666;
background: #f8f9fa;
padding: 6px 12px;
border-radius: 12px;
}
.breakdown-icon {
width: 16px;
height: 16px;
}
.payment-options {
display: flex;
flex-direction: column;
gap: 12px;
}
.payment-option {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.payment-option:hover:not(.disabled) {
border-color: #ffae00;
background: #fff7e6;
}
.payment-option.active {
border-color: #ffae00;
background: #fff7e6;
}
.payment-option.disabled {
opacity: 0.5;
cursor: not-allowed;
background: #f5f5f5;
border-color: #ddd;
}
.payment-option.disabled:hover {
border-color: #ddd;
background: #f5f5f5;
}
.insufficient-balance {
color: #ff4757;
font-size: 12px;
}
/* 地址选择样式 */
.address-section {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.address-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.address-label {
font-size: 16px;
font-weight: 500;
color: #333;
flex: 1;
}
.manage-address-btn {
color: #409eff;
font-size: 14px;
padding: 0;
}
.address-select {
width: 100%;
}
.address-option {
padding: 8px 0;
}
.address-info {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 4px;
}
.recipient-info {
font-weight: 500;
color: #333;
}
.default-tag {
background: #ff4757;
color: white;
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
}
.address-detail {
color: #666;
font-size: 14px;
line-height: 1.4;
}
.no-address {
text-align: center;
padding: 40px 20px;
color: #999;
}
.no-address-text {
display: block;
margin-bottom: 12px;
}
.add-address-btn {
color: #409eff;
font-size: 14px;
padding: 0;
}
/* 订单商品信息样式 */
.order-items-section {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.section-header {
margin-bottom: 16px;
}
.section-header .title {
font-size: 16px;
font-weight: 500;
color: #333;
}
.order-items-section .items-list {
margin-top: 16px;
}
.payment-icon {
color: #ffae00;
font-size: 20px;
}
.payment-icon-img {
width: 24px;
height: 24px;
margin-right: 12px;
}
.payment-icon-group {
display: flex;
align-items: center;
gap: 4px;
}
.plus-sign {
font-size: 14px;
color: #666;
font-weight: bold;
}
.payment-info {
flex: 1;
}
.payment-name {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.payment-desc {
font-size: 12px;
color: #666;
}
.check-icon {
color: #ffae00;
font-size: 18px;
}
.bottom-actions {
padding: 16px;
background: white;
border-top: 1px solid #eee;
display: flex;
align-items: center;
gap: 16px;
}
.payment-info {
flex: 1;
}
.payment-summary {
flex: 1;
}
.total-amount {
display: flex;
align-items: center;
gap: 4px;
font-size: 16px;
}
.amount {
color: #ff4757;
font-size: 18px;
font-weight: bold;
}
.amount-icon {
width: 18px;
height: 18px;
margin-right: 4px;
vertical-align: middle;
}
.amount-icon-el {
font-size: 18px;
color: #ffae00;
margin-right: 4px;
}
.amount-icon-group {
display: flex;
align-items: center;
gap: 2px;
margin-right: 4px;
}
.pay-button {
min-width: 120px;
height: 48px;
background: #ffae00;
border: none;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
}
.pay-button:hover {
background: #e69900;
}
.pay-button:disabled {
background: #ccc;
cursor: not-allowed;
}
/* 商品单价双价格显示样式 */
.item-price-container {
flex-direction: column;
align-items: flex-end;
gap: 2px;
}
.item-main-price {
display: flex;
align-items: center;
gap: 2px;
}
.item-rongdou-icon {
width: 10px;
height: 10px;
}
.item-rongdou-price {
font-size: 16px;
font-weight: 500;
color: #333;
}
.item-sub-price {
display: flex;
align-items: center;
gap: 2px;
}
.item-points-icon {
font-size: 12px;
color: #ffae00;
}
.item-points-price {
font-size: 12px;
color: #666;
}
/* 支付金额双价格显示样式 */
.payment-price-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.payment-main-price {
display: flex;
align-items: center;
gap: 4px;
}
.payment-rongdou-icon {
width: 24px;
height: 24px;
}
.payment-rongdou-price {
font-size: 24px;
font-weight: bold;
color: #333;
}
.payment-sub-price {
display: flex;
align-items: center;
gap: 2px;
}
.payment-points-icon {
font-size: 16px;
color: #ffae00;
}
.payment-points-price {
font-size: 16px;
color: #666;
}
/* 底部实付双价格显示样式 */
.bottom-price-container {
display: flex;
align-items: center;
gap: 8px;
}
.bottom-main-price {
display: flex;
align-items: center;
gap: 2px;
}
.bottom-rongdou-icon {
width: 18px;
height: 18px;
}
.bottom-rongdou-price {
font-size: 18px;
font-weight: bold;
color: #ff4757;
}
.bottom-plus-sign {
font-size: 16px;
font-weight: bold;
color: #666;
margin: 0 2px;
}
.bottom-sub-price {
display: flex;
align-items: center;
gap: 2px;
margin-left: 0px;
padding-left: 0px;
}
.bottom-points-icon {
font-size: 14px;
color: #ffae00;
}
.bottom-points-price {
font-size: 14px;
color: #666;
}
.bottom-points-price {
font-size: 14px;
color: #666;
}
</style>