未推送到的文件
This commit is contained in:
parent
b79cd37932
commit
5298ed1acf
271
components/PaymentPopup.vue
Normal file
271
components/PaymentPopup.vue
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="visible" class="payment-popup-mask" @tap="handleMaskClick">
|
||||||
|
<view class="payment-popup-content" @tap.stop>
|
||||||
|
<!-- 顶部提示 -->
|
||||||
|
<view class="risk-warning">
|
||||||
|
<text>盲盒具有随机性,请理性消费,购买即表示同意</text>
|
||||||
|
<text class="agreement-link" @tap="openAgreement">《购买协议》</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">确认支付</text>
|
||||||
|
<view class="close-icon" @tap="handleClose">×</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-body">
|
||||||
|
<view class="amount-section" v-if="amount !== undefined && amount !== null">
|
||||||
|
<text class="label">支付金额</text>
|
||||||
|
<text class="amount">¥{{ amount }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">优惠券</text>
|
||||||
|
<picker
|
||||||
|
mode="selector"
|
||||||
|
:range="coupons"
|
||||||
|
range-key="name"
|
||||||
|
@change="onCouponChange"
|
||||||
|
:value="couponIndex"
|
||||||
|
:disabled="!coupons || coupons.length === 0"
|
||||||
|
>
|
||||||
|
<view class="picker-display">
|
||||||
|
<text v-if="selectedCoupon" class="selected-text">{{ selectedCoupon.name }} (-¥{{ selectedCoupon.amount }})</text>
|
||||||
|
<text v-else-if="!coupons || coupons.length === 0" class="placeholder">暂无优惠券可用</text>
|
||||||
|
<text v-else class="placeholder">请选择优惠券</text>
|
||||||
|
<text class="arrow"></text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item" v-if="showCards">
|
||||||
|
<text class="label">道具卡</text>
|
||||||
|
<picker
|
||||||
|
mode="selector"
|
||||||
|
:range="propCards"
|
||||||
|
range-key="name"
|
||||||
|
@change="onCardChange"
|
||||||
|
:value="cardIndex"
|
||||||
|
:disabled="!propCards || propCards.length === 0"
|
||||||
|
>
|
||||||
|
<view class="picker-display">
|
||||||
|
<text v-if="selectedCard" class="selected-text">{{ selectedCard.name }}</text>
|
||||||
|
<text v-else-if="!propCards || propCards.length === 0" class="placeholder">暂无道具卡可用</text>
|
||||||
|
<text v-else class="placeholder">请选择道具卡</text>
|
||||||
|
<text class="arrow"></text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-footer">
|
||||||
|
<button class="btn-cancel" @tap="handleClose">取消</button>
|
||||||
|
<button class="btn-confirm" @tap="handleConfirm">确认支付</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: { type: Boolean, default: false },
|
||||||
|
amount: { type: [Number, String], default: 0 },
|
||||||
|
coupons: { type: Array, default: () => [] },
|
||||||
|
propCards: { type: Array, default: () => [] },
|
||||||
|
showCards: { type: Boolean, default: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:visible', 'confirm', 'cancel'])
|
||||||
|
|
||||||
|
const couponIndex = ref(-1)
|
||||||
|
const cardIndex = ref(-1)
|
||||||
|
|
||||||
|
const selectedCoupon = computed(() => {
|
||||||
|
if (couponIndex.value >= 0 && props.coupons[couponIndex.value]) {
|
||||||
|
return props.coupons[couponIndex.value]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedCard = computed(() => {
|
||||||
|
if (cardIndex.value >= 0 && props.propCards[cardIndex.value]) {
|
||||||
|
return props.propCards[cardIndex.value]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.visible, (val) => {
|
||||||
|
if (val) {
|
||||||
|
couponIndex.value = -1
|
||||||
|
cardIndex.value = -1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function onCouponChange(e) {
|
||||||
|
couponIndex.value = e.detail.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCardChange(e) {
|
||||||
|
cardIndex.value = e.detail.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAgreement() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/agreement/purchase' // 假设协议页面路径,如果没有请替换为实际路径
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMaskClick() {
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
emit('update:visible', false)
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfirm() {
|
||||||
|
emit('confirm', {
|
||||||
|
coupon: selectedCoupon.value,
|
||||||
|
card: props.showCards ? selectedCard.value : null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.payment-popup-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 999;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-popup-content {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
padding: 30rpx;
|
||||||
|
padding-bottom: calc(30rpx + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-warning {
|
||||||
|
background-color: #fffbe6;
|
||||||
|
color: #ed6a0c;
|
||||||
|
font-size: 24rpx;
|
||||||
|
padding: 16rpx 24rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-link {
|
||||||
|
color: #1890ff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
.popup-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.close-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 30rpx;
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
.popup-body {
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
.amount-section {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
.amount-section .label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
.amount-section .amount {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
.form-item .label {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
.picker-display {
|
||||||
|
border: 1rpx solid #ddd;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
.selected-text {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.arrow {
|
||||||
|
color: #ccc;
|
||||||
|
width: 16rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
border-right: 2rpx solid #ccc;
|
||||||
|
border-bottom: 2rpx solid #ccc;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
|
.popup-footer {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
.btn-cancel, .btn-confirm {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 30rpx;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.btn-cancel::after, .btn-confirm::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.btn-cancel {
|
||||||
|
color: #666;
|
||||||
|
border-right: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
.btn-confirm {
|
||||||
|
color: #007AFF;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
411
components/YifanSelector.vue
Normal file
411
components/YifanSelector.vue
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
<template>
|
||||||
|
<view class="choice-grid-container">
|
||||||
|
<view v-if="loading" class="loading-state">加载中...</view>
|
||||||
|
<view v-else-if="!choices || choices.length === 0" class="empty-state">暂无可选位置</view>
|
||||||
|
|
||||||
|
<view v-else class="grid-wrapper">
|
||||||
|
<view class="choices-grid">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in choices"
|
||||||
|
:key="item.id || index"
|
||||||
|
class="choice-item"
|
||||||
|
:class="{
|
||||||
|
'is-sold': item.status === 'sold' || item.is_sold,
|
||||||
|
'is-selected': isSelected(item),
|
||||||
|
'is-available': !item.status || item.status === 'available'
|
||||||
|
}"
|
||||||
|
@tap="handleSelect(item)"
|
||||||
|
>
|
||||||
|
<text class="choice-number">{{ item.number || item.position || index + 1 }}</text>
|
||||||
|
<view class="choice-status">
|
||||||
|
<text v-if="item.status === 'sold' || item.is_sold">已售</text>
|
||||||
|
<text v-else-if="isSelected(item)">已选</text>
|
||||||
|
<text v-else>可选</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="action-bar">
|
||||||
|
<view class="selection-info" v-if="selectedItems.length > 0">
|
||||||
|
已选 <text class="highlight">{{ selectedItems.length }}</text> 个位置
|
||||||
|
</view>
|
||||||
|
<view class="selection-info" v-else>
|
||||||
|
请选择位置
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="action-buttons">
|
||||||
|
<button v-if="selectedItems.length === 0" class="btn-random" @tap="handleRandomOne">随机一发</button>
|
||||||
|
<button v-else class="btn-buy" @tap="handleBuy">去支付</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 支付弹窗 -->
|
||||||
|
<PaymentPopup
|
||||||
|
v-model:visible="paymentVisible"
|
||||||
|
:amount="totalAmount"
|
||||||
|
:coupons="coupons"
|
||||||
|
:showCards="false"
|
||||||
|
@confirm="onPaymentConfirm"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
|
import { getIssueChoices, getUserCoupons, joinLottery, createWechatOrder, getLotteryResult } from '@/api/appUser'
|
||||||
|
import PaymentPopup from '@/components/PaymentPopup.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
activityId: { type: [String, Number], required: true },
|
||||||
|
issueId: { type: [String, Number], required: true },
|
||||||
|
pricePerDraw: { type: Number, default: 0 } // 单抽价格,用于计算总价
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['payment-success'])
|
||||||
|
|
||||||
|
const choices = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const selectedItems = ref([])
|
||||||
|
const paymentVisible = ref(false)
|
||||||
|
|
||||||
|
// 模拟优惠券和道具卡数据,实际项目中可能需要从接口获取
|
||||||
|
const coupons = ref([])
|
||||||
|
|
||||||
|
const totalAmount = computed(() => {
|
||||||
|
return (selectedItems.value.length * props.pricePerDraw).toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.issueId, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
loadChoices()
|
||||||
|
selectedItems.value = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.issueId) {
|
||||||
|
loadChoices()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadChoices() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getIssueChoices(props.activityId, props.issueId)
|
||||||
|
|
||||||
|
// 处理 { total_slots: 1, available: [1], claimed: [] } 这种格式
|
||||||
|
if (res && typeof res.total_slots === 'number' && Array.isArray(res.available)) {
|
||||||
|
const total = res.total_slots
|
||||||
|
const list = []
|
||||||
|
// 转换为 Set 提高查找效率,确保类型一致
|
||||||
|
const availableSet = new Set(res.available.map(v => Number(v)))
|
||||||
|
|
||||||
|
for (let i = 1; i <= total; i++) {
|
||||||
|
const isAvailable = availableSet.has(i)
|
||||||
|
list.push({
|
||||||
|
id: i,
|
||||||
|
number: i,
|
||||||
|
position: i,
|
||||||
|
status: isAvailable ? 'available' : 'sold',
|
||||||
|
is_sold: !isAvailable
|
||||||
|
})
|
||||||
|
}
|
||||||
|
choices.value = list
|
||||||
|
}
|
||||||
|
// 兼容旧的/其他的返回结构
|
||||||
|
else if (Array.isArray(res)) {
|
||||||
|
choices.value = res
|
||||||
|
} else if (res && Array.isArray(res.data)) {
|
||||||
|
choices.value = res.data
|
||||||
|
} else if (res && Array.isArray(res.choices)) {
|
||||||
|
choices.value = res.choices
|
||||||
|
} else {
|
||||||
|
choices.value = []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load choices:', error)
|
||||||
|
uni.showToast({ title: '加载位置失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelected(item) {
|
||||||
|
return selectedItems.value.some(i => i.id === item.id || (i.position && i.position === item.position))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelect(item) {
|
||||||
|
if (item.status === 'sold' || item.is_sold) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = selectedItems.value.findIndex(i => i.id === item.id || (i.position && i.position === item.position))
|
||||||
|
if (index > -1) {
|
||||||
|
selectedItems.value.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
selectedItems.value.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBuy() {
|
||||||
|
if (selectedItems.value.length === 0) return
|
||||||
|
paymentVisible.value = true
|
||||||
|
fetchCoupons()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRandomOne() {
|
||||||
|
const available = choices.value.filter(item =>
|
||||||
|
!item.is_sold && item.status !== 'sold' && !isSelected(item)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (available.length === 0) {
|
||||||
|
uni.showToast({ title: '没有可选位置了', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomIndex = Math.floor(Math.random() * available.length)
|
||||||
|
const randomItem = available[randomIndex]
|
||||||
|
|
||||||
|
// 选中该位置
|
||||||
|
selectedItems.value.push(randomItem)
|
||||||
|
|
||||||
|
// 立即弹出支付
|
||||||
|
paymentVisible.value = true
|
||||||
|
fetchCoupons()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchCoupons() {
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
if (!user_id) return
|
||||||
|
try {
|
||||||
|
const res = await getUserCoupons(user_id, 0, 1, 100)
|
||||||
|
let list = []
|
||||||
|
if (Array.isArray(res)) list = res
|
||||||
|
else if (res && Array.isArray(res.list)) list = res.list
|
||||||
|
else if (res && Array.isArray(res.data)) list = res.data
|
||||||
|
coupons.value = list.map((i, idx) => {
|
||||||
|
const cents = (i.remaining !== undefined && i.remaining !== null) ? Number(i.remaining) : Number(i.amount ?? i.value ?? 0)
|
||||||
|
const yuan = isNaN(cents) ? 0 : (cents / 100)
|
||||||
|
return {
|
||||||
|
id: i.id ?? i.coupon_id ?? String(idx),
|
||||||
|
name: i.name ?? i.title ?? '优惠券',
|
||||||
|
amount: Number(yuan).toFixed(2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error('fetchCoupons error', e)
|
||||||
|
coupons.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onPaymentConfirm(paymentData) {
|
||||||
|
paymentVisible.value = false
|
||||||
|
|
||||||
|
const selectedSlots = selectedItems.value.map(item => item.id || item.position)
|
||||||
|
|
||||||
|
if (selectedSlots.length === 0) {
|
||||||
|
uni.showToast({ title: '未选择位置', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const openid = uni.getStorageSync('openid')
|
||||||
|
if (!openid) {
|
||||||
|
uni.showToast({ title: '未获取到OpenID,请重新登录', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showLoading({ title: '处理中...' })
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 先调用抽奖接口 (joinLottery),服务器会返回订单号等信息
|
||||||
|
const payload = {
|
||||||
|
activity_id: Number(props.activityId),
|
||||||
|
issue_id: Number(props.issueId),
|
||||||
|
channel: 'miniapp',
|
||||||
|
count: selectedSlots.length,
|
||||||
|
coupon_id: paymentData.coupon ? Number(paymentData.coupon.id) : 0,
|
||||||
|
slot_index: selectedSlots.map(Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinRes = await joinLottery(payload)
|
||||||
|
// 假设 joinRes 包含 order_no,如果结构不同请调整
|
||||||
|
const orderNo = joinRes.order_no || joinRes.data?.order_no || joinRes.result?.order_no
|
||||||
|
|
||||||
|
if (!orderNo) {
|
||||||
|
throw new Error('未获取到订单号')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 使用返回的订单号去发起支付
|
||||||
|
const payRes = await createWechatOrder({
|
||||||
|
openid: openid,
|
||||||
|
order_no: orderNo
|
||||||
|
})
|
||||||
|
|
||||||
|
// 调起微信支付
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
uni.requestPayment({
|
||||||
|
provider: 'wxpay',
|
||||||
|
timeStamp: payRes.timeStamp || payRes.timestamp,
|
||||||
|
nonceStr: payRes.nonceStr || payRes.noncestr,
|
||||||
|
package: payRes.package,
|
||||||
|
signType: payRes.signType || 'MD5',
|
||||||
|
paySign: payRes.paySign,
|
||||||
|
success: resolve,
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showLoading({ title: '查询结果...' })
|
||||||
|
|
||||||
|
// 3. 支付成功,查询抽奖结果
|
||||||
|
const resultRes = await getLotteryResult(orderNo)
|
||||||
|
console.log('Lottery Result:', resultRes) // 打印结果供查看
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||||
|
|
||||||
|
// 触发支付成功事件
|
||||||
|
emit('payment-success', {
|
||||||
|
result: resultRes,
|
||||||
|
items: selectedItems.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清空选择并刷新
|
||||||
|
selectedItems.value = []
|
||||||
|
loadChoices()
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
uni.hideLoading()
|
||||||
|
console.error('Flow failed:', e)
|
||||||
|
|
||||||
|
if (e.errMsg && e.errMsg.indexOf('cancel') !== -1) {
|
||||||
|
uni.showToast({ title: '支付已取消', icon: 'none' })
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: e.message || '操作失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.choice-grid-container {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-state, .empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choices-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr); /* 一行5个 */
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-bottom: 120rpx; /* 留出底部操作栏空间 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-item {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
background: #fff;
|
||||||
|
border: 2rpx solid #e0e0e0;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-number {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-status {
|
||||||
|
font-size: 20rpx;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态样式 */
|
||||||
|
.is-available {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-sold {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-color: #eee;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.is-sold .choice-number {
|
||||||
|
color: #ccc;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-selected {
|
||||||
|
background: #e6f7ff;
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
.is-selected .choice-number {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
.is-selected .choice-status {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部操作栏 */
|
||||||
|
.action-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 100;
|
||||||
|
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-info {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-buy {
|
||||||
|
background: #ff4d4f;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 0 60rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-random {
|
||||||
|
background: #007AFF;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 0 60rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user