1171 lines
26 KiB
Vue
1171 lines
26 KiB
Vue
<template>
|
||
<view class="purchase-page">
|
||
<!-- Tab 切换 -->
|
||
<view class="tab-container">
|
||
<view class="tab-item" :class="{ active: currentTab === 'card' }" @click="switchTab('card')">
|
||
<text class="tab-text">{{ $t('purchase.memberCard') }}</text>
|
||
</view>
|
||
<view class="tab-item" :class="{ active: currentTab === 'coupon' }" @click="switchTab('coupon')">
|
||
<text class="tab-text">{{ $t('purchase.coupon') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 内容区域 -->
|
||
<scroll-view class="content-area" scroll-y>
|
||
|
||
<!-- 会员卡列表 -->
|
||
<view v-if="currentTab === 'card'" class="product-list">
|
||
<view v-for="item in memberCards" :key="item.id" class="product-card"
|
||
:class="{ selected: selectedProduct?.id === item.id }" @click="selectProduct(item)">
|
||
<view class="card-content">
|
||
<view class="card-left">
|
||
<view class="card-title-row">
|
||
<text class="card-name">{{ item.name }}</text>
|
||
<view class="card-type-tag" :class="{ 'type-time': item.type === 'TIME', 'type-count': item.type === 'COUNT' }">
|
||
<text class="card-type-text">{{ item.type === 'TIME' ? $t('myCard.durationCard') : $t('myCard.timesCard') }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="card-desc">{{ item.description }}</text>
|
||
|
||
<!-- 使用限制信息 -->
|
||
<view class="card-limits">
|
||
<!-- 时长卡:展示每日限用次数和单次限时 -->
|
||
<view v-if="item.type === 'TIME'" class="limit-tags">
|
||
<view class="limit-tag" v-if="item.dailyLimitCount > 0">
|
||
<text class="limit-label">{{ $t('myCard.dailyLimit') }}:</text>
|
||
<text class="limit-value">{{ item.dailyLimitCount }}{{ $t('myCard.times') }}</text>
|
||
</view>
|
||
<view class="limit-tag">
|
||
<text class="limit-label">{{ $t('myCard.singleTimeLimit') }}:</text>
|
||
<text class="limit-value" :class="{ unlimited: item.singleLimitMinutes === 0 }">
|
||
{{ item.singleLimitMinutes > 0 ? item.singleLimitMinutes + $t('myCard.minutes') : $t('myCard.unlimited') }}
|
||
</text>
|
||
</view>
|
||
<view class="limit-tag validity-tag" v-if="item.cycleDays">
|
||
<text class="limit-value">{{ item.cycleDays }}{{ $t('myCard.daysValid') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 次卡:展示总次数和单次限时 -->
|
||
<view v-if="item.type === 'COUNT'" class="limit-tags">
|
||
<view class="limit-tag count-tag" v-if="item.totalCount > 0">
|
||
<text class="limit-label">{{ $t('myCard.totalCount') }}:</text>
|
||
<text class="limit-value">{{ item.totalCount }}{{ $t('myCard.times') }}</text>
|
||
</view>
|
||
<view class="limit-tag">
|
||
<text class="limit-label">{{ $t('myCard.singleTimeLimit') }}:</text>
|
||
<text class="limit-value" :class="{ unlimited: item.singleLimitMinutesForCount === 0 }">
|
||
{{ item.singleLimitMinutesForCount > 0 ? item.singleLimitMinutesForCount + $t('myCard.minutes') : $t('myCard.unlimited') }}
|
||
</text>
|
||
</view>
|
||
<view class="limit-tag validity-tag" v-if="item.validDays">
|
||
<text class="limit-value">{{ item.validDays }}{{ $t('myCard.daysValid') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="card-right">
|
||
<view class="card-price">
|
||
<text class="price-currency">¥</text>
|
||
<text class="price-value">{{ item.price }}</text>
|
||
</view>
|
||
<view class="card-original-price" v-if="item.originalPrice">
|
||
<text class="original-currency">¥</text>
|
||
<text class="original-value">{{ item.originalPrice }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空数据提示 -->
|
||
<uv-empty v-if="memberCards.length === 0" mode="car" :text="$t('purchase.noCards')"></uv-empty>
|
||
</view>
|
||
|
||
<!-- 优惠券列表 -->
|
||
<view v-if="currentTab === 'coupon'" class="product-list">
|
||
<view v-for="item in coupons" :key="item.id" class="coupon-card-wrapper">
|
||
<view class="coupon-card" :class="{ selected: selectedProduct?.id === item.id }"
|
||
@click="selectProduct(item)">
|
||
<!-- 左侧圆形缺口 -->
|
||
<view class="coupon-circle-left"></view>
|
||
<!-- 右侧圆形缺口 -->
|
||
<view class="coupon-circle-right"></view>
|
||
|
||
<view class="coupon-left">
|
||
<view class="coupon-value-wrapper">
|
||
<text class="coupon-value-num">{{ item.type === 'discount' ? item.discount : item.value
|
||
}}</text>
|
||
<text class="coupon-value-unit">{{ item.type === 'discount' ? '折' : '¥' }}</text>
|
||
</view>
|
||
<view style="display: flex;flex-direction: column;">
|
||
<text class="coupon-condition">{{ item.condition }}</text>
|
||
<text class="coupon-validity">{{ item.validity }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="coupon-divider"></view>
|
||
<view class="coupon-right">
|
||
<view class="coupon-price">
|
||
<text class="price-label">¥</text>
|
||
<text class="price-value">{{ item.price }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空数据提示 -->
|
||
<uv-empty v-if="coupons.length === 0" mode="coupon" :text="$t('purchase.noCoupons')"></uv-empty>
|
||
</view>
|
||
|
||
<!-- 说明部分 -->
|
||
<view class="description-section">
|
||
<text class="description-title">{{ currentTab === 'card' ? $t('purchase.cardDescription') :
|
||
$t('purchase.couponDescription') }}</text>
|
||
<view class="description-content">
|
||
<view v-for="(desc, index) in descriptions" :key="index" class="description-item">
|
||
<text class="description-subtitle">{{ desc.title }}</text>
|
||
<text class="description-text">{{ desc.content }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 底部操作栏 -->
|
||
<view class="bottom-bar">
|
||
<view class="my-products-wrapper" @click="goToMyProducts">
|
||
<view class="" style="display: flex;">
|
||
<view class="my-products">
|
||
<image class="my-products-icon" src="/static/couponList.png" mode="aspectFit">
|
||
</image>
|
||
<text class="my-products-text">{{ currentTab === 'card' ? $t('purchase.myCards') :
|
||
$t('purchase.myCoupons') }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="" style="width: 40rpx;height: 40rpx;">
|
||
<image src="/static/gotoBuy.png" mode="aspectFill" style="width: 40rpx;height: 40rpx;"></image>
|
||
</view>
|
||
</view>
|
||
<view class="action-wrapper">
|
||
<view class="price-info">
|
||
<text class="current-price">¥{{ selectedProduct?.price || '0.00' }}</text>
|
||
<text class="original-price" v-if="selectedProduct?.originalPrice">¥{{
|
||
selectedProduct?.originalPrice
|
||
}}</text>
|
||
</view>
|
||
<view class="buy-button" :class="{ disabled: !selectedProduct }" @click="handleBuy">
|
||
<text class="buy-button-text">{{ $t('purchase.buyNow') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {
|
||
ref,
|
||
computed,
|
||
onMounted
|
||
} from 'vue'
|
||
import {
|
||
onLoad
|
||
} from '@dcloudio/uni-app'
|
||
import {
|
||
useI18n
|
||
} from '@/utils/i18n.js'
|
||
import {
|
||
getCouponsByPosition,
|
||
createCouponPayment,
|
||
cancelCouponPayment
|
||
} from '@/config/api/coupon.js'
|
||
import {
|
||
createMemberCardPayment,
|
||
getMemberCardsByPosition,
|
||
cancelMemberCardPayment
|
||
} from '@/config/api/member.js'
|
||
|
||
const {
|
||
t
|
||
} = useI18n()
|
||
|
||
// 当前选中的 Tab
|
||
const currentTab = ref('card') // 'card' 或 'coupon'
|
||
|
||
// 选中的商品
|
||
const selectedProduct = ref(null)
|
||
|
||
// 当前场地ID(从路由参数获取)
|
||
const positionId = ref(null)
|
||
|
||
// 生命周期 onLoad 钩子 - 获取路由参数
|
||
onLoad((options) => {
|
||
if (options.positionId) {
|
||
positionId.value = options.positionId
|
||
console.log('获取到场地ID:', positionId.value)
|
||
}
|
||
})
|
||
|
||
// 会员卡列表(从后端加载)
|
||
const memberCards = ref([])
|
||
|
||
// 优惠券列表(从后端加载)
|
||
const coupons = ref([])
|
||
|
||
// 说明内容
|
||
const descriptions = computed(() => {
|
||
if (currentTab.value === 'card') {
|
||
return [
|
||
{
|
||
title: t('purchase.cardUseInstruction'),
|
||
content: t('purchase.cardUseDescription')
|
||
},
|
||
{
|
||
title: t('purchase.cardValidityPeriod'),
|
||
content: t('purchase.cardValidityDescription')
|
||
},
|
||
{
|
||
title: t('purchase.cardRefundPolicy'),
|
||
content: t('purchase.cardRefundDescription')
|
||
}
|
||
]
|
||
} else {
|
||
return [
|
||
{
|
||
title: t('purchase.couponUseInstruction'),
|
||
content: t('purchase.couponUseDescription')
|
||
},
|
||
{
|
||
title: t('purchase.couponValidityPeriod'),
|
||
content: t('purchase.couponValidityDescription')
|
||
},
|
||
{
|
||
title: t('purchase.couponUsageScope'),
|
||
content: t('purchase.couponUsageDescription')
|
||
}
|
||
]
|
||
}
|
||
})
|
||
|
||
// 加载会员卡列表
|
||
const loadMemberCards = async () => {
|
||
try {
|
||
uni.showLoading({
|
||
title: '加载中...'
|
||
})
|
||
|
||
const res = await getMemberCardsByPosition(positionId.value)
|
||
uni.hideLoading()
|
||
|
||
if (res.code === 200 && res.data) {
|
||
// 转换为页面需要的格式
|
||
memberCards.value = res.data.map(item => ({
|
||
id: item.memberCardId,
|
||
name: item.cardName,
|
||
type: item.cardType,
|
||
description: item.positionName || '无描述',
|
||
price: item.purchasePrice ? item.purchasePrice.toString() : '0.00',
|
||
originalPrice: null,
|
||
availablePositions: item.positionName,
|
||
cycleDays: item.cycleDays,
|
||
dailyLimitCount: item.dailyLimitCount || 0,
|
||
singleLimitMinutes: item.singleLimitMinutes || 0,
|
||
singleLimitMinutesForCount: item.singleLimitMinutesForCount || 0,
|
||
validDays: item.validDays,
|
||
totalCount: item.totalCount,
|
||
purchaseQuantity: item.purchaseQuantity
|
||
}))
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('加载会员卡失败:', error)
|
||
uni.showToast({
|
||
title: '加载会员卡失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
|
||
// 加载优惠券列表
|
||
const loadCoupons = async () => {
|
||
try {
|
||
uni.showLoading({
|
||
title: '加载中...'
|
||
})
|
||
|
||
const res = await getCouponsByPosition(positionId.value)
|
||
uni.hideLoading()
|
||
|
||
if (res.code === 200 && res.data) {
|
||
// 转换为页面需要的格式
|
||
coupons.value = res.data.map(item => ({
|
||
id: item.couponId,
|
||
couponId: item.couponId,
|
||
name: item.couponName,
|
||
type: item.couponType === 'discount_coupon' ? 'discount' : 'cash',
|
||
discount: item.discountRate ? (item.discountRate * 10).toFixed(0) : null,
|
||
value: item.deductAmount ? item.deductAmount.toString() : null,
|
||
condition: item.usableCondition > 0 ? `满${item.usableCondition}元可用` : '无门槛',
|
||
validity: item.validDays > 0 ? `从购买日起 有效期${item.validDays}天` : '永久有效',
|
||
price: item.purchasePrice ? item.purchasePrice.toString() : '0.00',
|
||
originalPrice: null,
|
||
usablePositions: item.usablePositionNameMap,
|
||
remark: item.remark
|
||
}))
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('加载优惠券失败:', error)
|
||
uni.showToast({
|
||
title: '加载优惠券失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 初始化
|
||
onMounted(async () => {
|
||
// 加载会员卡列表
|
||
if (positionId.value) {
|
||
await loadMemberCards()
|
||
}
|
||
|
||
// 加载优惠券列表
|
||
if (positionId.value) {
|
||
await loadCoupons()
|
||
}
|
||
|
||
// 默认选中第一个会员卡(如果有的话)
|
||
if (memberCards.value.length > 0) {
|
||
selectedProduct.value = memberCards.value[0]
|
||
}
|
||
})
|
||
const switchTab = (tab) => {
|
||
currentTab.value = tab
|
||
selectedProduct.value = null
|
||
}
|
||
|
||
// 选择商品
|
||
const selectProduct = (product) => {
|
||
selectedProduct.value = product
|
||
}
|
||
|
||
const orderNo = ref('')
|
||
|
||
// 获取当前支付平台(前端维度)
|
||
const getPaymentPlatform = () => {
|
||
// 小程序环境
|
||
// #ifdef MP-WEIXIN
|
||
return 'WECHAT'
|
||
// #endif
|
||
// #ifdef MP-ALIPAY
|
||
return 'ALIPAY'
|
||
// #endif
|
||
// H5 默认使用 ANTOM
|
||
// #ifdef H5
|
||
return 'ANTOM'
|
||
// #endif
|
||
|
||
return 'WECHAT'
|
||
}
|
||
|
||
// 支付宝 tradePay 兼容:优先 tradeNO,其次 orderStr
|
||
const alipayTradePay = ({ tradeNo, orderStr, onSuccess, onFail }) => {
|
||
// #ifdef MP-ALIPAY
|
||
const tradeNO = tradeNo
|
||
if (tradeNO) {
|
||
my.tradePay({
|
||
tradeNO,
|
||
success: onSuccess,
|
||
fail: onFail
|
||
})
|
||
return
|
||
}
|
||
if (orderStr) {
|
||
my.tradePay({
|
||
orderStr,
|
||
success: onSuccess,
|
||
fail: onFail
|
||
})
|
||
return
|
||
}
|
||
// #endif
|
||
onFail && onFail(new Error('未获取到支付宝支付参数'))
|
||
}
|
||
|
||
// 处理购买(会员卡 / 优惠券),内部使用商品多支付平台方案下的统一思路
|
||
const handleBuy = async () => {
|
||
if (!selectedProduct.value) {
|
||
uni.showToast({
|
||
title: t('purchase.pleaseSelect'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
const paymentPlatform = getPaymentPlatform()
|
||
|
||
// 会员卡购买(按接口文档:POST /app/member/pay)
|
||
if (currentTab.value === 'card') {
|
||
try {
|
||
uni.showLoading({
|
||
title: '正在创建订单...'
|
||
})
|
||
|
||
const res = await createMemberCardPayment(selectedProduct.value.id, paymentPlatform)
|
||
uni.hideLoading()
|
||
|
||
if (res.code === 200 && res.data) {
|
||
// 不同平台分别发起支付(字段按文档)
|
||
// 微信小程序
|
||
// #ifdef MP-WEIXIN
|
||
if (paymentPlatform === 'WECHAT') {
|
||
// 会员卡订单号:OutOrderNo(文档)
|
||
orderNo.value = res.data.OutOrderNo
|
||
uni.requestPayment({
|
||
timeStamp: res.data.timeStamp,
|
||
nonceStr: res.data.nonceStr,
|
||
package: res.data.package,
|
||
signType: res.data.signType || 'MD5',
|
||
paySign: res.data.paySign,
|
||
success: () => {
|
||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||
setTimeout(() => {
|
||
uni.navigateTo({ url: '/subPackages/business/my-card' })
|
||
}, 1500)
|
||
},
|
||
fail: (err) => {
|
||
console.error('支付失败:', err)
|
||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||
// 取消支付(本项目取消接口走 device 侧 cancel)
|
||
orderNo.value && cancelMemberCardPayment(orderNo.value).catch(() => {})
|
||
uni.showToast({ title: '已取消支付', icon: 'none' })
|
||
} else {
|
||
uni.showToast({ title: '支付失败', icon: 'none' })
|
||
}
|
||
}
|
||
})
|
||
return
|
||
}
|
||
// #endif
|
||
|
||
// 支付宝小程序
|
||
// #ifdef MP-ALIPAY
|
||
if (paymentPlatform === 'ALIPAY') {
|
||
// 文档返回:tradeNo / outTradeNo;也兼容 orderStr
|
||
const tradeNo = res.data.tradeNo || res.data.tradeNO
|
||
const payForm = res.data.payForm || res.data.orderStr
|
||
alipayTradePay({
|
||
tradeNo,
|
||
orderStr: payForm,
|
||
onSuccess: (payRes) => {
|
||
if (payRes.resultCode === '9000') {
|
||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||
setTimeout(() => {
|
||
uni.navigateTo({ url: '/subPackages/business/my-card' })
|
||
}, 1500)
|
||
} else {
|
||
uni.showToast({ title: '支付失败', icon: 'none' })
|
||
}
|
||
},
|
||
onFail: () => {
|
||
uni.showToast({ title: '支付失败', icon: 'none' })
|
||
}
|
||
})
|
||
return
|
||
}
|
||
// #endif
|
||
|
||
// H5 + Antom(文档里 Antom 预留,当前后端可能返回 cashierUrl / h5Url 之一)
|
||
// #ifdef H5
|
||
if (paymentPlatform === 'ANTOM') {
|
||
const cashierUrl = res.data.cashierUrl || res.data.h5Url
|
||
if (cashierUrl) {
|
||
window.location.href = cashierUrl
|
||
return
|
||
}
|
||
}
|
||
// #endif
|
||
} else {
|
||
uni.showToast({
|
||
title: res.msg || '创建订单失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('购买失败:', error)
|
||
uni.showToast({
|
||
title: '购买失败,请稍后重试',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
return
|
||
}
|
||
|
||
// 优惠券购买(按接口文档:POST /app/coupon/pay)
|
||
if (currentTab.value === 'coupon') {
|
||
try {
|
||
uni.showLoading({
|
||
title: '正在创建订单...'
|
||
})
|
||
|
||
const res = await createCouponPayment(selectedProduct.value.couponId, paymentPlatform)
|
||
uni.hideLoading()
|
||
|
||
if (res.code === 200 && res.data) {
|
||
// 微信小程序
|
||
// #ifdef MP-WEIXIN
|
||
if (paymentPlatform === 'WECHAT') {
|
||
uni.requestPayment({
|
||
timeStamp: res.data.timeStamp,
|
||
nonceStr: res.data.nonceStr,
|
||
package: res.data.package,
|
||
signType: res.data.signType || 'MD5',
|
||
paySign: res.data.paySign,
|
||
success: () => {
|
||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||
setTimeout(() => {
|
||
uni.navigateTo({ url: '/subPackages/business/my-coupon' })
|
||
}, 1500)
|
||
},
|
||
fail: (err) => {
|
||
console.error('支付失败:', err)
|
||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||
const outOrderNo = res.data.OutOrderNo || res.data.outOrderNo
|
||
outOrderNo && cancelCouponPayment(outOrderNo).catch(() => {})
|
||
uni.showToast({ title: '已取消支付', icon: 'none' })
|
||
} else {
|
||
uni.showToast({ title: '支付失败', icon: 'none' })
|
||
}
|
||
}
|
||
})
|
||
return
|
||
}
|
||
// #endif
|
||
|
||
// 支付宝小程序
|
||
// #ifdef MP-ALIPAY
|
||
if (paymentPlatform === 'ALIPAY') {
|
||
const tradeNo = res.data.tradeNo || res.data.tradeNO
|
||
const payForm = res.data.payForm || res.data.orderStr
|
||
alipayTradePay({
|
||
tradeNo,
|
||
orderStr: payForm,
|
||
onSuccess: (payRes) => {
|
||
if (payRes.resultCode === '9000') {
|
||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||
setTimeout(() => {
|
||
uni.navigateTo({ url: '/subPackages/business/my-coupon' })
|
||
}, 1500)
|
||
} else {
|
||
uni.showToast({ title: '支付失败', icon: 'none' })
|
||
}
|
||
},
|
||
onFail: () => {
|
||
uni.showToast({ title: '支付失败', icon: 'none' })
|
||
}
|
||
})
|
||
return
|
||
}
|
||
// #endif
|
||
|
||
// H5 + Antom
|
||
// #ifdef H5
|
||
if (paymentPlatform === 'ANTOM') {
|
||
const cashierUrl = res.data.cashierUrl || res.data.h5Url
|
||
if (cashierUrl) {
|
||
window.location.href = cashierUrl
|
||
return
|
||
}
|
||
}
|
||
// #endif
|
||
} else {
|
||
uni.showToast({
|
||
title: res.msg || '创建订单失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('购买失败:', error)
|
||
uni.showToast({
|
||
title: '购买失败,请稍后重试',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 查看我的商品
|
||
const goToMyProducts = () => {
|
||
if (currentTab.value === 'card') {
|
||
uni.navigateTo({
|
||
url: '/subPackages/business/my-card'
|
||
})
|
||
} else if (currentTab.value === 'coupon') {
|
||
uni.navigateTo({
|
||
url: '/subPackages/business/my-coupon'
|
||
})
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.purchase-page {
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
padding-bottom: 180rpx;
|
||
}
|
||
|
||
/* Tab 切换 */
|
||
.tab-container {
|
||
top: 20rpx;
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
display: flex;
|
||
background-color: #ffffff;
|
||
width: 500rpx;
|
||
z-index: 999;
|
||
padding: 5rpx 0;
|
||
margin: 0 auto;
|
||
border-radius: 26rpx;
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 10rpx 30rpx;
|
||
margin: 0 10rpx;
|
||
width: 120rpx;
|
||
position: relative;
|
||
|
||
.tab-text {
|
||
font-size: 28rpx;
|
||
color: #A6A6A6;
|
||
font-weight: 500;
|
||
}
|
||
|
||
&.active {
|
||
background:#FFE2B8;
|
||
border-radius: 26rpx;
|
||
|
||
|
||
.tab-text {
|
||
color: #A16300;
|
||
font-weight: 600;
|
||
}
|
||
|
||
// &::after {
|
||
// content: '';
|
||
// position: absolute;
|
||
// bottom: 0;
|
||
// left: 50%;
|
||
// transform: translateX(-50%);
|
||
// width: 60rpx;
|
||
// height: 6rpx;
|
||
// background-color: #FFA928;
|
||
// border-radius: 3rpx;
|
||
// }
|
||
}
|
||
}
|
||
|
||
/* 内容区域 */
|
||
.content-area {
|
||
height: calc(100vh - 180rpx);
|
||
padding: 20rpx;
|
||
padding-top: 80rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 商品列表 */
|
||
.product-list {
|
||
margin-bottom: 20rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
/* 会员卡卡片 */
|
||
.product-card {
|
||
background-color: #ffffff;
|
||
border-radius: 20rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
border: 2rpx solid transparent;
|
||
transition: all 0.3s;
|
||
box-sizing: border-box;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
&.selected {
|
||
border-color: #FFA928;
|
||
box-shadow: 0 4rpx 20rpx rgba(255, 169, 40, 0.2);
|
||
}
|
||
|
||
.card-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.card-left {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.card-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.card-name {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.card-type-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
flex-shrink: 0;
|
||
|
||
&.type-time {
|
||
background-color: #E6F7FF;
|
||
border: 1rpx solid #91D5FF;
|
||
}
|
||
|
||
&.type-count {
|
||
background-color: #FFF1F0;
|
||
border: 1rpx solid #FFCCC7;
|
||
}
|
||
|
||
.card-type-text {
|
||
font-size: 20rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.type-time .card-type-text {
|
||
color: #1890FF;
|
||
}
|
||
|
||
.type-count .card-type-text {
|
||
color: #FF4D4F;
|
||
}
|
||
|
||
.card-desc {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* 会员卡限制信息样式 */
|
||
.card-limits {
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.limit-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8rpx;
|
||
align-items: center;
|
||
}
|
||
|
||
.limit-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4rpx;
|
||
padding: 6rpx 12rpx;
|
||
background-color: #FFF4E6;
|
||
border-radius: 8rpx;
|
||
border: 1rpx solid #FFE8CC;
|
||
|
||
.limit-label {
|
||
font-size: 22rpx;
|
||
color: #B8741A;
|
||
// font-weight: 500;
|
||
}
|
||
|
||
.limit-value {
|
||
font-size: 22rpx;
|
||
color: #FF6B00;
|
||
// font-weight: 600;
|
||
|
||
&.unlimited {
|
||
color: #07c160;
|
||
}
|
||
}
|
||
|
||
/* 有效期标签特殊样式 */
|
||
&.validity-tag {
|
||
background-color: #F0F5FF;
|
||
border: 1rpx solid #D6E4FF;
|
||
|
||
.limit-value {
|
||
color: #1890FF;
|
||
}
|
||
}
|
||
|
||
/* 总次数标签特殊样式 */
|
||
&.count-tag {
|
||
background-color: #FFF1F0;
|
||
border: 1rpx solid #FFCCC7;
|
||
|
||
.limit-label {
|
||
color: #CF1322;
|
||
}
|
||
|
||
.limit-value {
|
||
color: #FF4D4F;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
.card-right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.card-price {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 2rpx;
|
||
|
||
.price-currency {
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
color: #FF6B00;
|
||
}
|
||
|
||
.price-value {
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
color: #FF6B00;
|
||
}
|
||
}
|
||
|
||
.card-original-price {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 2rpx;
|
||
text-decoration: line-through;
|
||
|
||
.original-currency {
|
||
font-size: 20rpx;
|
||
font-weight: 400;
|
||
color: #999;
|
||
}
|
||
|
||
.original-value {
|
||
font-size: 24rpx;
|
||
font-weight: 400;
|
||
color: #999;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 优惠券卡片 */
|
||
.coupon-card-wrapper {
|
||
position: relative;
|
||
margin-bottom: 20rpx;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.coupon-card {
|
||
background: linear-gradient(135deg, #FFF4E6 0%, #FFE8CC 100%);
|
||
border-radius: 20rpx;
|
||
padding: 40rpx 30rpx;
|
||
display: flex;
|
||
align-items: stretch;
|
||
position: relative;
|
||
box-sizing: border-box;
|
||
border: 2rpx solid transparent;
|
||
transition: all 0.3s;
|
||
overflow: visible;
|
||
min-height: 180rpx;
|
||
|
||
&.selected {
|
||
border-color: #B8741A;
|
||
box-shadow: 0 4rpx 20rpx rgba(184, 116, 26, 0.2);
|
||
|
||
.coupon-circle-left,
|
||
.coupon-circle-right {
|
||
background-color: #FFF4E6;
|
||
border: 2rpx solid #B8741A;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 左侧圆形缺口 */
|
||
.coupon-circle-left {
|
||
position: absolute;
|
||
left: -16rpx;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border-radius: 50%;
|
||
background-color: #f5f5f5;
|
||
z-index: 10;
|
||
transition: all 0.3s;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 右侧圆形缺口 */
|
||
.coupon-circle-right {
|
||
position: absolute;
|
||
right: -16rpx;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border-radius: 50%;
|
||
background-color: #f5f5f5;
|
||
z-index: 10;
|
||
transition: all 0.3s;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.coupon-left {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 8rpx;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 优惠券金额显示包裹器 */
|
||
.coupon-value-wrapper {
|
||
width: 180rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
/* 优惠券实际值(数字) */
|
||
.coupon-value-num {
|
||
font-size: 52rpx;
|
||
font-weight: 700;
|
||
color: #B8741A;
|
||
}
|
||
|
||
/* 优惠券单位(折/¥) */
|
||
.coupon-value-unit {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #B8741A;
|
||
}
|
||
|
||
.coupon-value {
|
||
font-size: 48rpx;
|
||
font-weight: 700;
|
||
color: #B8741A;
|
||
}
|
||
|
||
.coupon-condition {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.coupon-validity {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.coupon-divider {
|
||
width: 2rpx;
|
||
height: 100%;
|
||
position: absolute;
|
||
left: 65%;
|
||
transform: translateX(-50%);
|
||
top: 0;
|
||
bottom: 0;
|
||
// min-height: 160rpx;
|
||
align-self: stretch;
|
||
background: repeating-linear-gradient(to bottom,
|
||
#B8741A 0rpx,
|
||
#B8741A 8rpx,
|
||
transparent 8rpx,
|
||
transparent 16rpx);
|
||
margin: 0 30rpx;
|
||
}
|
||
|
||
.coupon-right {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.coupon-price {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 4rpx;
|
||
|
||
.price-label {
|
||
font-size: 24rpx;
|
||
color: #B8741A;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.price-value {
|
||
font-size: 36rpx;
|
||
color: #B8741A;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 说明部分 */
|
||
.description-section {
|
||
background-color: #ffffff;
|
||
border-radius: 20rpx;
|
||
padding: 30rpx;
|
||
margin-top: 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.description-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 20rpx;
|
||
display: block;
|
||
}
|
||
|
||
.description-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.description-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.description-subtitle {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.description-text {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
/* 底部操作栏 */
|
||
.bottom-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: #ffffff;
|
||
padding-bottom: 20rpx;
|
||
border-radius: 40rpx 40rpx 0 0;
|
||
// padding: 20rpx 30rpx;
|
||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||
z-index: 1000;
|
||
}
|
||
|
||
.my-products-wrapper {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
flex-direction: row;
|
||
background: #FFF4E3;
|
||
border-radius: 40rpx;
|
||
padding: 16rpx 32rpx;
|
||
border-radius: 40rpx 40rpx 0 0;
|
||
}
|
||
|
||
.action-wrapper {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex: 1;
|
||
margin-left: 20rpx;
|
||
margin-right: 20rpx;
|
||
padding-top: 20rpx;
|
||
}
|
||
|
||
.my-products {
|
||
display: flex;
|
||
// flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4rpx;
|
||
min-width: 120rpx;
|
||
cursor: pointer;
|
||
|
||
&.full {
|
||
flex-direction: row;
|
||
gap: 8rpx;
|
||
padding: 16rpx 32rpx;
|
||
background-color: #f5f5f5;
|
||
border-radius: 40rpx;
|
||
}
|
||
|
||
.my-products-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.my-products-text {
|
||
margin-left: 10rpx;
|
||
font-size: 22rpx;
|
||
color: #A16300;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.price-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
// align-items: flex-end;
|
||
gap: 4rpx;
|
||
flex: 1;
|
||
margin: 0 20rpx;
|
||
}
|
||
|
||
.current-price {
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
color: #FF6B00;
|
||
}
|
||
|
||
.original-price {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
text-decoration: line-through;
|
||
}
|
||
|
||
.buy-button {
|
||
padding: 24rpx 60rpx;
|
||
background-color: #B8741A;
|
||
border-radius: 48rpx;
|
||
transition: all 0.3s;
|
||
|
||
&.disabled {
|
||
background-color: #ccc;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.buy-button-text {
|
||
font-size: 32rpx;
|
||
color: #ffffff;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
</style> |