Files
taimed-international-app/pages/order/confirmOrder.vue

1420 lines
34 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>
<view class="confirm-order-page">
<!-- 自定义导航栏 -->
<nav-bar :title="$t('order.confirmTitle')" />
<!-- 商品列表区域 -->
<view class="goods-section common-section">
<view class="section-title">{{ $t('order.goodsInfo') }}</view>
<view
v-for="(item, index) in goodsList"
:key="index"
class="goods-item"
>
<!-- VIP优惠标签 -->
<wd-tag v-if="item.isVipPrice === 1 && item.vipPrice" type="danger" mark custom-class="vip-badge">{{ $t('order.vipLabel') }}</wd-tag>
<!-- 商品图片 -->
<view class="goods-image">
<image
:src="item.productImages || '/static/nobg.jpg'"
mode="aspectFit"
/>
</view>
<!-- 商品信息 -->
<view class="goods-info">
<text class="goods-name">{{ item.productName }}</text>
<!-- 价格信息 -->
<view class="price-info">
<!-- VIP优惠价 -->
<!-- <view v-if="item.isVipPrice === 1 && item.vipPrice" class="price-row">
<text class="vip-price">{{ item.vipPrice.toFixed(2) }}</text>
<text class="vip-label">{{ $t('order.vipPriceLabel') }}</text>
<text class="original-price">{{ item.price.toFixed(2) }}</text>
</view> -->
<!-- 活动价 -->
<!-- <view v-else-if="item.activityPrice && item.activityPrice > 0" class="price-row">
<text class="activity-price">{{ item.activityPrice.toFixed(2) }}</text>
<text class="activity-label">{{ $t('order.activityLabel') }}</text>
<text class="original-price">{{ item.price.toFixed(2) }}</text>
</view> -->
<!-- 普通价格 -->
<view class="price-row">
<text class="normal-price">{{ item.price.toFixed(2) }} 天医币</text>
</view>
</view>
<!-- 数量 -->
<!-- <view class="quantity-row">
<text class="quantity-label">{{ $t('order.quantity') }}</text>
<view v-if="showNumber" class="quantity-control">
<wd-number-box
v-model="item.productAmount"
:min="1"
@change="handleQuantityChange(item, $event)"
/>
</view>
<text v-else>X {{ item.productAmount }}</text>
</view> -->
</view>
</view>
</view>
<!-- 订单备注区域 -->
<view class="remark-section common-section" @click="showRemarkPopup = true">
<view class="remark-row">
<text class="remark-label">{{ $t('order.remark') }}</text>
<view class="remark-value">
<text :class="remark ? 'remark-text' : 'remark-placeholder'">
{{ remark || $t('order.remarkPlaceholder') }}
</text>
<image src="/static/icon/icon_right.png" class="arrow-icon" />
</view>
</view>
</view>
<!-- 价格明细区域 -->
<view class="price-section common-section">
<view class="section-title">{{ $t('order.priceDetail') }}</view>
<!-- 商品总价 -->
<view class="price-item">
<text class="label">{{ $t('order.totalPrice') }}</text>
<text class="value">{{ totalPrice.toFixed(2) }} 天医币</text>
</view>
<!-- 优惠券 -->
<!-- <view class="price-item" @click="showCouponPopup = true">
<view class="label-row">
<text class="label">{{ $t('order.coupon') }}</text>
</view>
<view class="value-row">
<template v-if="!selectedCoupon">
<view v-if="availableCouponCount > 0" class="coupon-badge">
<text>{{ $t('order.couponCount', { count: availableCouponCount }) }}</text>
</view>
<text v-else-if="couponList.length === 0" class="unavailable-text">
{{ $t('order.noCoupon') }}
</text>
<text v-else class="unavailable-text">
{{ $t('order.unavailable') }}
</text>
</template>
<template v-else>
<text class="discount-value">-{{ selectedCoupon.couponEntity.couponAmount }}</text>
<text class="reselect-btn">{{ $t('order.reselect') }}</text>
</template>
<image
v-if="availableCouponCount > 0 || selectedCoupon"
src="/static/icon/icon_right.png"
class="arrow-icon"
/>
</view>
</view> -->
<!-- 活动立减 -->
<view v-if="hasActivityDiscount" class="price-item">
<text class="label">{{ $t('order.activityDiscount') }}</text>
<text class="discount-value">-{{ activityDiscountAmount.toFixed(2) }}</text>
</view>
<!-- VIP专享立减 -->
<view v-if="vipPrice > 0" class="price-item">
<view class="label-row">
<text class="vip-icon">VIP</text>
<text class="label">{{ $t('order.vipDiscount') }}</text>
</view>
<text class="discount-value">-{{ vipPrice.toFixed(2) }}</text>
</view>
<!-- 地区优惠 -->
<view v-if="districtAmount > 0" class="price-item">
<text class="label">{{ $t('order.districtDiscount') }}</text>
<text class="discount-value">-{{ districtAmount.toFixed(2) }}</text>
</view>
<!-- 积分 -->
<view v-if="userInfo && userInfo.jf > 0" class="price-item">
<view class="label-row">
<image src="/static/icon/jifen.png" class="icon-img" />
<text class="label">{{ $t('order.points') }}</text>
<text class="points-total">
({{ $t('order.allPoints') }}{{ userInfo.jf }})
</text>
</view>
<text class="discount-value">-{{ jfNumber.toFixed(2) }}</text>
</view>
<!-- 积分输入 -->
<view v-if="userInfo && userInfo.jf > 0" class="points-input-section">
<text class="points-label">
{{ $t('order.maxPoints', { max: jfNumberMax }) }}
</text>
<view class="points-input-box">
<input
v-model="jfNumber"
type="number"
clearable
:placeholder="$t('order.pointsPlaceholder')"
class="text-right"
@input="handlePointsInput"
@clear="handlePointsClear"
/>
</view>
</view>
</view>
<!-- 页面内容 -->
<view class="page-content">
<!-- 支付方式区域 -->
<view class="payment-section common-section">
<view class="section-title">{{ $t('order.paymentMethod') }}</view>
<!-- 天医币支付 -->
<view class="payment-item">
<view class="payment-left">
<image src="/static/icon/pay_3.png" class="payment-icon" />
<text class="">{{ $t('order.virtualCoin') }}</text>
<text class="text-[#7dc1f0]">
({{ $t('order.balance') }}{{ userInfo?.peanutCoin || 0 }})
</text>
</view>
<!-- <wd-radio :model-value="true" disabled /> -->
</view>
<!-- 支付说明 -->
<view class="payment-tips">
<view class="tips-header">
<wd-icon name="error-circle" color="#7dc1f0" size="20" />
<text>{{ $t('order.ensureBalance') }}</text>
<text class="recharge-btn" @click="goToRecharge">
{{ $t('order.recharge') }}
</text>
</view>
<view class="tips-content">
<view class="tip-title">{{ $t('order.paymentTipTitle') }}</view>
<view class="tip-item">{{ $t('order.paymentTip1') }}</view>
<view class="tip-item">
{{ $t('order.paymentTip2') }}
<text class="link-text" @click="makePhoneCall('022-24142321')">022-24142321</text>
</view>
<view class="tip-item">
{{ $t('order.paymentTip3') }}
<text class="link-text" @click="copyToClipboard('publisher@tmrjournals.com')">
publisher@tmrjournals.com
</text>
{{ $t('order.paymentTip3_1') }}
<text class="link-text" @click="copyToClipboard('yilujiankangkefu')">
yilujiankangkefu
</text>
{{ $t('order.paymentTip3_2') }}
</view>
</view>
</view>
</view>
</view>
<!-- 底部汇总栏 -->
<view class="bottom-bar">
<view class="total-info">
<text class="label">{{ $t('order.total') }}</text>
<text class="amount">{{ actualPayment.toFixed(2) }} 天医币</text>
</view>
<wd-button type="primary" :loading="submitting" @click="handleSubmit">
{{ $t('order.submit') }}
</wd-button>
</view>
<!-- 订单备注弹窗 -->
<wd-popup
v-model="showRemarkPopup"
position="bottom"
>
<view class="remark-popup">
<view class="popup-header">{{ $t('order.remarkTitle') }}</view>
<view class="remark-content">
<wd-textarea
v-model="remark"
:placeholder="$t('order.remarkPlaceholder')"
:maxlength="200"
show-word-limit
class="pb-0!"
/>
</view>
<view class="popup-footer">
<wd-button type="primary" block @click="handleRemarkConfirm">{{ $t('global.ok') }}</wd-button>
</view>
</view>
</wd-popup>
<!-- 优惠券选择弹窗 -->
<wd-popup
v-model="showCouponPopup"
position="bottom"
:close-on-click-modal="false"
>
<view class="coupon-popup">
<view class="popup-header">
<text class="popup-title">{{ $t('order.selectCoupon') }}</text>
<wd-icon name="close" @click="showCouponPopup = false" />
</view>
<view class="coupon-list">
<view
v-for="(coupon, index) in couponList"
:key="index"
:class="['coupon-item', coupon.canUse === 0 ? 'disabled' : '', selectedCoupon?.couponId === coupon.couponId ? 'selected' : '']"
@click="handleCouponSelect(coupon)"
>
<view class="coupon-type-badge">
{{ getCouponTypeText(coupon.couponEntity.couponRange) }}
</view>
<view class="coupon-amount">
<text class="currency"></text>
<text class="amount">{{ coupon.couponEntity.couponAmount }}</text>
</view>
<view class="coupon-info">
<text class="coupon-name">{{ coupon.couponEntity.couponName }}</text>
<text class="coupon-condition">
{{ $t('order.couponUseLevel', { level: coupon.couponEntity.useLevel }) }}
</text>
<text class="coupon-expiry">
{{ $t('order.couponExpiry') }}
{{ coupon.effectType === 0 ? $t('order.couponForever') : coupon.endTime }}
</text>
<text v-if="coupon.canUse === 0" class="unavailable-reason">
{{ $t('order.couponReason') }}{{ coupon.canUseReason }}
</text>
</view>
<view class="coupon-select">
<wd-icon
v-if="coupon.canUse === 1"
:name="selectedCoupon?.couponId === coupon.couponId ? 'checkmark-circle-fill' : 'circle'"
:color="selectedCoupon?.couponId === coupon.couponId ? '#fd6004' : '#d9d9d9'"
size="20"
/>
</view>
</view>
<view v-if="couponList.length === 0" class="empty-coupon">
<text>{{ $t('order.noCoupon') }}</text>
</view>
</view>
<view class="popup-footer">
<wd-button
v-if="availableCouponCount > 0"
type="default"
@click="handleCouponConfirm(null)"
>
{{ $t('order.notUseCoupon') }}
</wd-button>
<wd-button
v-if="availableCouponCount > 0"
type="primary"
@click="handleCouponConfirm(selectedCoupon)"
>
{{ $t('order.selected') }}
</wd-button>
<wd-button
v-else
type="default"
@click="showCouponPopup = false"
>
{{ $t('order.confirm') }}
</wd-button>
</view>
</view>
</wd-popup>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useUserStore } from '@/stores/user'
import { orderApi } from '@/api/modules/order'
import { makePhoneCall, copyToClipboard } from '@/utils/index'
import { t } from '@/utils/i18n'
import type {
IOrderRouteParams,
IOrderGoods,
IOrderInitData,
ICoupon
} from '@/types/order'
// Stores
const userStore = useUserStore()
// 页面数据
const orderData = ref<IOrderRouteParams>({
goods: [],
typeId: 0
})
const goodsIds = ref<string>('')
const goodsList = ref<IOrderGoods[]>([])
const initData = ref<IOrderInitData | null>(null)
const userInfo = ref(null)
// 价格相关
const totalPrice = ref(0)
const vipPrice = ref(0)
const districtAmount = ref(0)
const actualPayment = ref(0)
// 积分相关
const jfNumber = ref(0)
const jfNumberMax = ref(0)
// 优惠券相关
const couponList = ref<ICoupon[]>([])
const selectedCoupon = ref<ICoupon | null>(null)
const showCouponPopup = ref(false)
// 订单备注
const remark = ref('')
const showRemarkPopup = ref(false)
// 支付相关
const payType = ref(4) // 4=天医币
// UI状态
const loading = ref(false)
const submitting = ref(false)
const buyingFlag = ref(false)
const showNumber = ref(true) // 是否显示数量调整
/**
* 可用优惠券数量
*/
const availableCouponCount = computed(() => {
return couponList.value.filter(c => c.canUse === 1).length
})
/**
* 是否有活动优惠
*/
const hasActivityDiscount = computed(() => {
return goodsList.value.some(item => item.activityPrice && item.activityPrice > 0)
})
/**
* 活动优惠金额
*/
const activityDiscountAmount = computed(() => {
return goodsList.value.reduce((sum, item) => {
if (item.activityPrice && item.activityPrice > 0) {
return sum + (item.price - item.activityPrice) * item.productAmount
}
return sum
}, 0)
})
/**
* 获取用户信息
*/
const getUserInfo = async () => {
try {
const res = await orderApi.getUserInfo()
if (res.code === 0) {
userInfo.value = res.result || {}
}
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
/**
* 页面加载
*/
onLoad(async (options: any) => {
if (options.goods) {
try {
// 获取用户信息
await getUserInfo()
// 根据商品ID获取商品详细信息
goodsIds.value = options.goods || ''
await getGoodsList()
} catch (error) {
console.error('解析商品数据失败:', error)
uni.showToast({
title: '商品数据错误',
icon: 'none'
})
}
}
})
/**
* 初始化订单
*/
const initOrder = async () => {
try {
} catch (error) {
console.error('初始化订单失败:', error)
uni.showToast({
title: '加载失败',
icon: 'none'
})
}
}
/**
* 处理商品数量变化
*/
const handleQuantityChange = async (item: IOrderGoods, value: number) => {
try {
// 更新购物车
await orderApi.updateCart({
productId: item.productId,
productAmount: value,
price: item.price
})
// 重新获取商品列表和计算价格
await getGoodsList()
} catch (error) {
console.error('更新数量失败:', error)
uni.showToast({
title: '更新失败',
icon: 'none'
})
}
}
/**
* 获取商品列表并重新计算价格
*/
const getGoodsList = async () => {
try {
// 获取商品详情
const res = await orderApi.getShopProductListByIds(goodsIds.value)
if (res.code === 0 && res.shopProductList?.length > 0) {
// 更新商品列表,保持数量
// res.data.shopProductList.forEach((product: IOrderGoods) => {
// const existingItem = goodsList.value.find(item => item.productId === product.productId)
// if (existingItem) {
// product.productAmount = existingItem.productAmount
// }
// })
goodsList.value = res.shopProductList
// 计算价格
await calculateAllPrices()
}
} catch (error) {
console.error('获取商品列表失败:', error)
}
}
/**
* 计算所有价格
*/
const calculateAllPrices = async () => {
try {
// 计算商品总价
calculateTotalPrice()
// 获取VIP优惠
// await calculateVipDiscount()
// 获取地区优惠
// await calculateDistrictDiscount()
// 获取优惠券列表
// await getAvailableCoupons()
// 计算最终价格
calculateFinalPrice()
} catch (error) {
console.error('计算价格失败:', error)
}
}
/**
* 计算商品总价
*/
const calculateTotalPrice = () => {
console.log('商品列表:', goodsList.value)
totalPrice.value = goodsList.value.reduce((sum, item) => {
// return sum + (item.price * item.productAmount)
return sum + (item.price * 1)
}, 0)
}
/**
* 计算VIP优惠
*/
const calculateVipDiscount = async () => {
try {
const productList = goodsList.value.map(item => ({
productId: item.productId,
quantity: item.productAmount
}))
const res = await orderApi.getVipDiscountAmount(productList)
if (res.code === 0 && res.data) {
vipPrice.value = res.data.discountAmount
}
} catch (error) {
console.error('获取VIP优惠失败:', error)
}
}
/**
* 计算活动优惠
*/
const calculateDistrictDiscount = async () => {
try {
const productList = goodsList.value.map(item => ({
productId: item.productId,
quantity: item.productAmount
}))
const res = await orderApi.getDistrictAmount(productList)
if (res.code === 0 && res.data) {
districtAmount.value = res.data.districtAmount
}
} catch (error) {
console.error('获取地区优惠失败:', error)
}
}
/**
* 获取可用优惠券
*/
const getAvailableCoupons = async () => {
try {
const shopProductInfos = goodsList.value.map(item => {
const price = item.activityPrice && item.activityPrice > 0 ? item.activityPrice : item.price
return `${item.productId}:${price}:${item.productAmount}`
}).join(',')
const res = await orderApi.getCouponListPayment(shopProductInfos)
if (res.code === 0 && res.data) {
couponList.value = res.data.couponHistoryList || []
// 如果当前选中的优惠券不可用,清除选择
if (selectedCoupon.value) {
const stillAvailable = couponList.value.find(
c => c.couponId === selectedCoupon.value?.couponId && c.canUse === 1
)
if (!stillAvailable) {
selectedCoupon.value = null
}
}
}
} catch (error) {
console.error('获取优惠券失败:', error)
}
}
/**
* 计算最终价格
*/
const calculateFinalPrice = () => {
const couponAmount = selectedCoupon.value?.couponEntity.couponAmount || 0
// 计算最大可用积分
const orderAmountAfterDiscount = totalPrice.value - districtAmount.value - vipPrice.value
jfNumberMax.value = Math.min(
userInfo.value.jf || 0,
Math.floor(orderAmountAfterDiscount - couponAmount)
)
// 限制当前积分不超过最大值
if (jfNumber.value > jfNumberMax.value) {
jfNumber.value = jfNumberMax.value
}
// 计算实付款
actualPayment.value = Math.max(
0,
totalPrice.value - couponAmount - jfNumber.value - districtAmount.value - vipPrice.value
)
}
/**
* 处理积分输入
*/
const handlePointsInput = (value: any) => {
let val = String(value.detail.value)
// 只允许数字字符,去掉小数点
val = val.replace(/[^0-9]/g, '')
if (val === '0' || val === '') {
jfNumber.value = 0
} else {
let numericValue = parseInt(val, 10)
if (numericValue < 0 || isNaN(numericValue)) {
numericValue = 0
}
// 确保不超过最大值
if (numericValue >= jfNumberMax.value) {
numericValue = jfNumberMax.value
}
jfNumber.value = numericValue
}
// 重新计算实付款
calculateFinalPrice()
}
/**
* 清除积分输入
*/
const handlePointsClear = () => {
jfNumber.value = 0
calculateFinalPrice()
}
/**
* 获取优惠券类型文本
*/
const getCouponTypeText = (type: number) => {
const typeMap: Record<number, string> = {
0: 'couponType0',
1: 'couponType1',
2: 'couponType2'
}
return typeMap[type] || 'couponType0'
}
/**
* 选择优惠券
*/
const handleCouponSelect = (coupon: ICoupon) => {
if (coupon.canUse === 0) {
return
}
if (selectedCoupon.value?.couponId === coupon.couponId) {
selectedCoupon.value = null
} else {
selectedCoupon.value = coupon
}
}
/**
* 确认优惠券选择
*/
const handleCouponConfirm = (coupon: ICoupon | null) => {
selectedCoupon.value = coupon
showCouponPopup.value = false
calculateFinalPrice()
}
/**
* 跳转到充值页面
*/
const goToRecharge = () => {
uni.navigateTo({
url: '/pages/user/wallet/recharge/index?source=order'
})
}
/**
* 拨打客服电话
*/
const handleCall = () => {
uni.makePhoneCall({
phoneNumber: '022-24142321',
fail: (err) => {
console.error('拨打电话失败:', err)
}
})
}
/**
* 复制文本
*/
const handleCopy = (text: string, title: string) => {
uni.setClipboardData({
data: text,
success: () => {
uni.showToast({
title: `${title}${t('order.copySuccess')}`,
icon: 'none'
})
},
fail: (err) => {
console.error('复制失败:', err)
}
})
}
/**
* 确认备注
*/
const handleRemarkConfirm = () => {
showRemarkPopup.value = false
}
/**
* 验证订单
*/
const validateOrder = (): boolean => {
// 验证天医币余额
if (actualPayment.value > 0 && actualPayment.value > (userInfo.value.peanutCoin || 0)) {
uni.showToast({
title: t('order.insufficientBalance'),
icon: 'none'
})
return false
}
return true
}
/**
* 检查重复订单
*/
const checkDuplicateOrder = (orderParams: any): Promise<boolean> => {
return new Promise((resolve) => {
// 简化版:这里可以添加更复杂的重复订单检测逻辑
// 暂时直接返回true继续
resolve(true)
})
}
/**
* 提交订单
*/
const handleSubmit = async () => {
if (submitting.value || buyingFlag.value) {
uni.showToast({
title: t('order.tooFrequent'),
icon: 'none'
})
return
}
// 验证订单
if (!validateOrder()) {
return
}
try {
submitting.value = true
buyingFlag.value = true
// 创建订单 此app用天医币支付创建订单成功即支付成功
await createOrder()
} catch (error) {
console.error('提交订单失败:', error)
uni.showToast({
title: t('order.orderFailed'),
icon: 'none'
})
} finally {
submitting.value = false
}
}
/**
* 创建订单
*/
const createOrder = async (): Promise<string | null> => {
try {
const orderParams = {
userId: userInfo.value.id,
paymentMethod: 4, // 天医币
orderMoney: totalPrice.value,
realMoney: actualPayment.value,
jfDeduction: jfNumber.value,
// couponId: selectedCoupon.value?.id,
// couponName: selectedCoupon.value?.couponEntity.couponName,
// vipDiscountAmount: vipPrice.value,
// districtMoney: districtAmount.value,
remark: remark.value,
productList: goodsList.value.map(item => ({
productId: item.productId,
quantity: item.productAmount || 1
})),
orderType: 'order',
come: 10
}
const res = await orderApi.placeCourseOrder(orderParams)
if (res.code === 0 && res.orderSn) {
return res.orderSn
}
return null
} catch (error) {
console.error('创建订单失败:', error)
throw error
}
}
/**
* 完成支付
*/
const completePayment = async (orderSn: string) => {
try {
// 天医币支付直接完成
uni.showToast({
title: t('order.orderSuccess'),
icon: 'success'
})
// 1秒后跳转到课程列表
// setTimeout(() => {
// uni.navigateTo({
// url: '/pages/course/details/course?id=' + orderParams.productList[0].productId
// })
// }, 1000)
} catch (error) {
console.error('支付失败:', error)
throw error
}
}
</script>
<style lang="scss" scoped>
.confirm-order-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20rpx;
padding-bottom: 60px;
}
.common-section {
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.price-section {
.price-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15rpx 0;
font-size: 28rpx;
.label-row {
display: flex;
align-items: center;
gap: 10rpx;
.label {
color: #666;
}
.vip-icon {
padding: 2rpx 8rpx;
background-color: #f94f04;
color: #fff;
font-size: 20rpx;
border-radius: 4rpx;
font-weight: bold;
}
.icon-img {
width: 40rpx;
height: 40rpx;
}
.points-total {
font-size: 24rpx;
color: #aaa;
}
}
.label {
color: #666;
}
.value {
color: #333;
font-weight: 500;
}
.value-row {
display: flex;
align-items: center;
gap: 10rpx;
.coupon-badge {
padding: 4rpx 20rpx;
background-color: #fceeeb;
color: #ec4729;
font-size: 24rpx;
border-radius: 10rpx;
}
.unavailable-text {
font-size: 24rpx;
color: #999;
}
.reselect-btn {
padding: 4rpx 12rpx;
background-color: #fe6035;
color: #fff;
font-size: 24rpx;
border-radius: 30rpx;
}
.arrow-icon {
width: 24rpx;
height: 24rpx;
}
}
.discount-value {
color: #fe6035;
font-weight: 500;
}
}
.points-input-section {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15rpx 0;
margin-top: 10rpx;
background-color: #f5f5f5;
border-radius: 20rpx;
padding: 20rpx;
.points-label {
font-size: 28rpx;
color: #7dc1f0;
font-weight: 600;
}
.points-input-box {
width: 320rpx;
}
}
}
.goods-section {
.goods-item {
position: relative;
display: flex;
padding-bottom: 20rpx;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
padding-bottom: 0;
}
.vip-badge {
position: absolute;
top: 20rpx;
left: 0;
z-index: 1;
}
.goods-image {
width: 140rpx;
height: 140rpx;
flex-shrink: 0;
margin-right: 20rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.goods-info {
flex: 1;
display: flex;
flex-direction: column;
.goods-name {
font-size: 28rpx;
color: #333;
line-height: 1.4;
margin-bottom: 10rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.price-info {
.price-row {
display: flex;
align-items: baseline;
gap: 10rpx;
.vip-price,
.activity-price {
font-size: 32rpx;
font-weight: bold;
color: #e97512;
}
.normal-price {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.vip-label {
font-size: 22rpx;
color: #fa2d12;
}
.activity-label {
font-size: 22rpx;
color: #613804;
}
.original-price {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
}
}
.quantity-row {
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
.quantity-label {
margin-right: 10rpx;
}
.quantity-control {
display: flex;
align-items: center;
}
}
}
}
}
.remark-section {
.remark-row {
display: flex;
justify-content: space-between;
align-items: center;
.remark-label {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.remark-value {
display: flex;
align-items: center;
gap: 10rpx;
max-width: 500rpx;
.remark-text {
font-size: 24rpx;
color: #333;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.remark-placeholder {
font-size: 24rpx;
color: #b0b0b0;
}
.arrow-icon {
width: 24rpx;
height: 24rpx;
}
}
}
}
.remark-popup {
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1px solid #f0f0f0;
.popup-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
}
.remark-content {
min-height: 300rpx;
}
.popup-footer {
padding: 20rpx;
border-top: 1px solid #f0f0f0;
}
}
.payment-section {
.payment-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
.payment-left {
display: flex;
align-items: center;
gap: 10rpx;
font-size: 28rpx;
font-weight: bold;
.payment-icon {
width: 40rpx;
height: 40rpx;
}
}
}
.payment-tips {
margin-top: 20rpx;
padding: 20rpx;
background-color: #f7f8f9;
border-radius: 10rpx;
.tips-header {
display: flex;
align-items: center;
gap: 10rpx;
margin-bottom: 12rpx;
font-size: 28rpx;
color: #333;
.recharge-btn {
margin-left: auto;
padding: 4rpx 14rpx;
background-color: #7dc1f0;
color: #fff;
font-size: 24rpx;
border-radius: 10rpx;
}
}
.tips-content {
font-size: 28rpx;
color: #5a5a5a;
line-height: 1.6;
.tip-title {
font-weight: 500;
margin-bottom: 10rpx;
}
.tip-item {
margin-bottom: 10rpx;
word-wrap: break-word;
word-break: break-all;
}
.link-text {
color: #7dc1f0;
text-decoration: underline;
}
}
}
}
.coupon-popup {
max-height: 80vh;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1px solid #f0f0f0;
.popup-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
}
.coupon-list {
max-height: 60vh;
overflow-y: auto;
padding: 20rpx;
.coupon-item {
position: relative;
display: flex;
align-items: center;
padding: 30rpx 20rpx;
margin-bottom: 20rpx;
background: linear-gradient(to top right, #fff, #fef2f4);
border: 1px solid #d9d9d9;
border-radius: 10rpx;
&.selected {
border-color: #fd6004;
}
&.disabled {
background: linear-gradient(to top right, #fafafa, #fafafa);
color: #979797;
.coupon-amount {
color: #979797;
}
}
.coupon-type-badge {
position: absolute;
top: 10rpx;
right: 10rpx;
padding: 6rpx;
background-color: #ffe3e9;
color: #c81346;
font-size: 20rpx;
border-radius: 10rpx;
}
.coupon-amount {
width: 25%;
text-align: center;
color: #ff0043;
.currency {
font-size: 24rpx;
}
.amount {
font-size: 45rpx;
font-weight: bold;
}
}
.coupon-info {
flex: 1;
padding-left: 5%;
display: flex;
flex-direction: column;
gap: 10rpx;
.coupon-name {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.coupon-condition {
font-size: 24rpx;
color: #666;
}
.coupon-expiry {
font-size: 22rpx;
color: #999;
}
.unavailable-reason {
font-size: 20rpx;
color: #333;
}
}
.coupon-select {
width: 7%;
display: flex;
justify-content: center;
}
}
.empty-coupon {
padding: 60rpx 0;
text-align: center;
color: #999;
font-size: 28rpx;
}
}
.popup-footer {
display: flex;
gap: 20rpx;
padding: 20rpx;
border-top: 1px solid #f0f0f0;
}
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
background-color: #fff;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1);
z-index: 999;
.total-info {
flex: 1;
.label {
font-size: 28rpx;
color: #666;
}
.amount {
font-size: 36rpx;
color: #ff4444;
font-weight: bold;
margin-left: 10rpx;
}
}
}
</style>