收藏
回答

商家转账requestMerchantTransfer:fail, the permission ?


代码:

<template>

    <view class="lottery-page">

        <!-- 背景装饰 -->

        <view class="bg-decoration">

            <view class="star" v-for="i in 20" :key="i" :style="getStarStyle(i)"></view>

        </view>


        <!-- 用户登录状态 -->

        <view class="user-status" @click="handleUserClick">

            <!-- 已登录:显示头像 -->

            <view class="user-avatar" v-if="isLoggedIn">

                <image v-if="userInfo.avatar" class="avatar-img" :src="$api.loadFile(userInfo.avatar)" mode="aspectFill"></image>

                <view v-else class="avatar-default">

                    <text class="cuIcon-peoplefill"></text>

                </view>

                <view class="online-dot"></view>

            </view>

            <!-- 未登录:显示登录按钮 -->

            <view class="login-btn" v-else>

                <text class="login-icon">🔐</text>

                <text class="login-text">登录</text>

            </view>

        </view>


        <!-- 顶部信息栏 -->

        <view class="header">

            <view class="title">{{ activity.title || '幸运大转盘' }}</view>

            <view class="subtitle">{{ activity.description || '转动转盘,赢取惊喜大奖!' }}</view>

            <view class="remaining" v-if="activity.remainingTimes !== undefined">

                剩余抽奖次数:<text class="count">{{ activity.remainingTimes }}</text> 次

            </view>

        </view>


        <!-- 中奖滚动 -->

        <view class="winner-marquee" v-if="winners.length > 0">

            <view class="marquee-icon">🎉</view>

            <swiper class="marquee-swiper" vertical autoplay circular :interval="2500" :duration="500">

                <swiper-item v-for="(item, index) in winners" :key="index">

                    <view class="winner-item">

                        <text class="winner-name">{{ maskName(item.userName) }}</text>

                        <text class="winner-text">获得了</text>

                        <text class="winner-prize">{{ item.prizeName }}</text>

                    </view>

                </swiper-item>

            </swiper>

        </view>


        <!-- 大转盘 -->

        <view class="wheel-container">

            <!-- 外圈装饰灯 -->

            <view class="wheel-lights">

                <view class="light" v-for="i in 16" :key="i" :class="{ active: lightActive === i % 2 }"

                    :style="getLightStyle(i)">

                </view>

            </view>


            <!-- 转盘主体 -->

            <view class="wheel-wrapper">

                <view class="wheel" :style="{ transform: `rotate(${wheelRotation}deg)` }">

                    <!-- 奖品扇形区域 -->

                    <view class="prize-sector" v-for="(prize, index) in prizes" :key="prize.id"

                        :style="getSectorStyle(index)">

                        <view class="prize-content" :style="getPrizeContentStyle(index)">

                            <image v-if="prize.image" class="prize-image" :src="$api.loadFile(prize.image)"

                                mode="aspectFit"></image>

                            <view v-else class="prize-icon">{{ getPrizeIcon(prize.prizeType) }}</view>

                            <text class="prize-name">{{ prize.name }}</text>

                        </view>

                    </view>

                </view>


                <!-- 中心抽奖按钮 -->

                <view class="wheel-pointer" @click="handleDraw" :class="{ disabled: isSpinning }">

                    <view class="pointer-arrow"></view>

                    <view class="pointer-btn">

                        <text>{{ isSpinning ? '抽奖中' : '抽奖' }}</text>

                    </view>

                </view>

            </view>

        </view>


        <!-- 活动规则 -->

        <view class="rules-section">

            <view class="rules-title" @click="showRules = !showRules">

                <text>📋 活动规则</text>

                <text class="arrow">{{ showRules ? '▲' : '▼' }}</text>

            </view>

            <view class="rules-content" v-show="showRules">

                <rich-text :nodes="activity.ruleText || defaultRules"></rich-text>

            </view>

        </view>


        <!-- 我的奖品按钮 -->

        <view class="my-prizes-btn" @click="showMyPrizes = true">

            🎁 我的奖品

        </view>


        <!-- ========== 弹窗区域 ========== -->


        <!-- 抽奖结果弹窗 -->

        <view class="popup-mask" v-if="showResult" @click="closeResult">

            <view class="result-popup" :class="resultData.isWin ? 'win' : 'lose'" @click.stop>

                <view class="result-icon">{{ resultData.isWin ? '🎊' : '😢' }}</view>

                <view class="result-title">{{ resultData.isWin ? '恭喜中奖!' : '很遗憾' }}</view>


                <!-- 中奖内容展示 -->

                <view class="result-prize" v-if="resultData.isWin">

                    <image v-if="resultData.prizeImage" class="prize-img"

                        :src="$api.loadFile(resultData.prizeImage)" mode="aspectFit"></image>


                    <!-- 现金红包特殊显示 -->

                    <view v-if="resultData.prizeType === 'Cash'" class="cash-prize">

                        <text class="cash-icon">🧧</text>

                        <text class="cash-amount">¥{{ resultData.cashAmount.toFixed(2) }}</text>

                        <text class="cash-label">现金红包</text>

                    </view>

                    <!-- 普通奖品 -->

                    <text v-else class="prize-text">{{ resultData.prizeName }}</text>

                </view>


                <!-- 现金红包提示消息 -->

                <view class="cash-message" v-if="resultData.prizeType === 'Cash' && resultData.cashMessage">

                    {{ resultData.cashMessage }}

                </view>


                <view class="result-tip" v-if="!resultData.isWin">谢谢参与,下次加油!</view>


                <!-- 实物奖品需要填地址 -->

                <view class="address-form" v-if="resultData.isWin && isPhysicalPrize(resultData.prizeType)">

                    <view class="form-title">请填写收货信息</view>

                    <input class="form-input" v-model="addressForm.receiverName" placeholder="收货人姓名" />

                    <input class="form-input" v-model="addressForm.receiverPhone" placeholder="手机号码" type="tel" />

                    <textarea class="form-textarea" v-model="addressForm.receiverAddress"

                        placeholder="详细收货地址"></textarea>

                    <button class="submit-btn" @click="handleSubmitAddress">提交地址</button>

                </view>


                <!-- 现金红包:立即领取按钮 -->

                <button class="receive-cash-btn" v-else-if="isCashPrizeNeedReceive" @click="handleReceiveCash">

                    💰 立即领取 ¥{{ resultData.cashAmount.toFixed(2) }}

                </button>


                <!-- 普通关闭按钮 -->

                <button class="close-btn" @click="closeResult" v-else>

                    {{ resultData.isWin ? '太棒了' : '再试一次' }}

                </button>

            </view>

        </view>


        <!-- 我的奖品弹窗 -->

        <view class="popup-mask" v-if="showMyPrizes" @click="showMyPrizes = false">

            <view class="prizes-popup" @click.stop>

                <view class="popup-header">

                    <text class="popup-title">🎁 我的奖品</text>

                    <text class="popup-close" @click="showMyPrizes = false">✕</text>

                </view>

                <scroll-view class="prizes-list" scroll-y>

                    <view class="prize-item" v-for="item in myPrizes" :key="item.id">

                        <image class="item-image" :src="$api.loadFile(item.prizeImage)" mode="aspectFit"></image>

                        <view class="item-info">

                            <text class="item-name">{{ item.prizeName }}</text>

                            <text class="item-time">{{ formatTime(item.addTime) }}</text>

                            <text class="item-status" :class="'status-' + item.deliveryStatus">

                                {{ item.deliveryStatusName || '待发货' }}

                            </text>

                        </view>

                    </view>

                    <view class="empty-tip" v-if="myPrizes.length === 0">

                        暂无中奖记录,快去抽奖吧!

                    </view>

                </scroll-view>

            </view>

        </view>


        <!-- 加载中 -->

        <view class="loading-mask" v-if="loading">

            <view class="loading-spinner"></view>

            <text class="loading-text">加载中...</text>

        </view>

    </view>

</template>


<script setup>

    import {

        ref,

        reactive,

        computed,

        getCurrentInstance,

        onMounted,

        onUnmounted

    } from 'vue'

    import {

        onLoad,

        onShow

    } from '@dcloudio/uni-app'

    import {

        getActivityDetail,

        getWinners,

        doDraw,

        getLotteryLogs,

        submitAddress,

        PrizeType

    } from '/common/api/lottery.js'


    // ========== 配置 ==========

    const ACTIVITY_ID = 1 // 活动ID


    // ========== 获取全局属性 ==========

    const {

        proxy

    } = getCurrentInstance()


    // ========== 用户登录状态 ==========

    const isLoggedIn = ref(false) // 是否已登录

    const userInfo = ref({}) // 用户信息


    /**

     * 检查登录状态并获取用户信息

     */

    const checkLoginStatus = () => {

        const token = uni.getStorageSync('dtcms_client_token')

        isLoggedIn.value = !!(token && token.accessToken)

       

        // 已登录时获取用户信息

        if (isLoggedIn.value) {

            fetchUserInfo()

        }

    }


    /**

     * 获取用户信息(头像等)

     */

    const fetchUserInfo = () => {

        proxy.$api.request({

            url: '/account/member/info',

            success(res) {

                userInfo.value = res.data || {}

            }

        })

    }


    /**

     * 点击用户头像/登录按钮

     */

    const handleUserClick = () => {

        if (isLoggedIn.value) {

            // 已登录:跳转会员中心

            uni.navigateTo({

                url: '/pages/account/index'

            })

        } else {

            // 未登录:跳转登录页

            uni.navigateTo({

                url: '/pages/account/login'

            })

        }

    }


    // ========== 响应式数据 ==========

    const loading = ref(true) // 加载状态

    const activity = ref({}) // 活动详情

    const prizes = ref([]) // 奖品列表

    const winners = ref([]) // 中奖记录

    const myPrizes = ref([]) // 我的奖品


    const isSpinning = ref(false) // 是否正在抽奖

    const wheelRotation = ref(0) // 转盘旋转角度

    const lightActive = ref(0) // 灯光闪烁状态


    const showResult = ref(false) // 显示结果弹窗

    const showRules = ref(false) // 显示规则

    const showMyPrizes = ref(false) // 显示我的奖品


    // 抽奖结果数据

    const resultData = ref({

        isWin: false,

        prizeName: '',

        prizeImage: '',

        prizeType: 0,

        drawLogId: 0,

        // ===== 现金红包相关 =====

        cashAmount: 0,        // 红包金额

        cashStatus: 0,        // 发放状态(1=已发放待领取)

        cashMessage: '',      // 提示消息

        packageInfo: ''       // 微信package_info(用于调起确认收款)

    })


    // 地址表单

    const addressForm = reactive({

        receiverName: '',

        receiverPhone: '',

        receiverAddress: ''

    })


    // 默认规则

    const defaultRules = `

    <p>1. 每位用户每天可抽奖3次</p>

    <p>2. 中奖后请在7日内填写收货信息</p>

    <p>3. 奖品以实际发放为准</p>

    <p>4. 活动最终解释权归主办方所有</p>

  `


    // 灯光闪烁定时器

    let lightTimer = null


    // ========== 计算属性 ==========

    // 每个奖品的角度

    const anglePerPrize = computed(() => {

        return prizes.value.length > 0 ? 360 / prizes.value.length : 45

    })


    // ========== 方法 ==========


    /**

     * 初始化数据

     */

    const initData = async () => {

        loading.value = true

        try {

            // 并行请求活动详情和中奖记录

            const [activityData, winnersData] = await Promise.all([

                getActivityDetail(proxy, ACTIVITY_ID),

                getWinners(proxy, ACTIVITY_ID, 10)

            ])


            activity.value = activityData || {}

            prizes.value = activityData?.prizes || []

            winners.value = winnersData || []


            // 按position排序奖品

            prizes.value.sort((a, b) => a.position - b.position)


            console.log('活动详情:', activityData)

            console.log('奖品列表:', prizes.value)

        } catch (err) {

            console.error('加载活动失败:', err)

            uni.showToast({

                title: '加载活动失败',

                icon: 'none'

            })

        } finally {

            loading.value = false

        }

    }


    /**

     * 加载我的奖品

     */

    const loadMyPrizes = async () => {

        try {

            const data = await getLotteryLogs(proxy, ACTIVITY_ID, 1, 20)

            // 只显示中奖记录

            myPrizes.value = (data || []).filter(item => item.isWin)

        } catch (err) {

            console.error('加载奖品失败:', err)

        }

    }


    /**

     * 点击抽奖

     */

    const handleDraw = async () => {

        // 防止重复点击

        if (isSpinning.value) return


        // 检查剩余次数

        if (activity.value.remainingTimes !== undefined && activity.value.remainingTimes <= 0) {

            uni.showToast({

                title: '抽奖次数不足',

                icon: 'none'

            })

            return

        }


        isSpinning.value = true


        try {

            // 调用抽奖API

            const result = await doDraw(proxy, ACTIVITY_ID)

            console.log('抽奖结果:', result)


            // 找到中奖奖品的位置

            let targetIndex = 0

            if (result && result.prizeId) {

                targetIndex = prizes.value.findIndex(p => p.id === result.prizeId)

                if (targetIndex < 0) targetIndex = 0

            }


            // 计算目标角度(转5圈 + 目标位置)

            // 注意:转盘顺时针转,奖品逆时针排列

            const baseRotation = 360 * 5 // 转5圈

            const targetAngle = (prizes.value.length - targetIndex - 1) * anglePerPrize.value + anglePerPrize.value / 2

            const finalRotation = wheelRotation.value + baseRotation + targetAngle - (wheelRotation.value % 360)


            // 播放转盘动画

            wheelRotation.value = finalRotation


            // 等待动画结束(4秒)

            setTimeout(() => {

                isSpinning.value = false


                // 设置结果数据

                resultData.value = {

                    isWin: result?.isWin || false,

                    prizeName: result?.prizeName || '谢谢参与',

                    prizeImage: result?.prizeImage || '',

                    prizeType: result?.prizeType || 0,

                    drawLogId: result?.drawLogId || result?.id || 0,

                    // ===== 现金红包相关 =====

                    cashAmount: result?.cashAmount || 0,

                    cashStatus: result?.cashStatus || 0,

                    cashMessage: result?.cashMessage || '',

                    packageInfo: result?.packageInfo || ''

                }


                // 显示结果弹窗

                showResult.value = true


                // 更新剩余次数

                if (activity.value.remainingTimes !== undefined) {

                    activity.value.remainingTimes = Math.max(0, activity.value.remainingTimes - 1)

                }

            }, 4000)


        } catch (err) {

            console.error('抽奖失败:', err)

            isSpinning.value = false

            uni.showToast({

                title: err.message || '抽奖失败,请重试',

                icon: 'none'

            })

        }

    }


    /**

     * 提交收货地址

     */

    const handleSubmitAddress = async () => {

        // 表单验证

        if (!addressForm.receiverName) {

            uni.showToast({

                title: '请输入收货人姓名',

                icon: 'none'

            })

            return

        }

        if (!/^1[3-9]\d{9}$/.test(addressForm.receiverPhone)) {

            uni.showToast({

                title: '请输入正确的手机号',

                icon: 'none'

            })

            return

        }

        if (!addressForm.receiverAddress) {

            uni.showToast({

                title: '请输入收货地址',

                icon: 'none'

            })

            return

        }


        try {

            await submitAddress(proxy, {

                drawLogId: resultData.value.drawLogId,

                receiverName: addressForm.receiverName,

                receiverPhone: addressForm.receiverPhone,

                receiverAddress: addressForm.receiverAddress

            })


            uni.showToast({

                title: '地址提交成功',

                icon: 'success'

            })

            showResult.value = false


            // 清空表单

            addressForm.receiverName = ''

            addressForm.receiverPhone = ''

            addressForm.receiverAddress = ''

        } catch (err) {

            uni.showToast({

                title: '提交失败,请重试',

                icon: 'none'

            })

        }

    }


    /**

     * 关闭结果弹窗

     */

    const closeResult = () => {

        showResult.value = false

    }


    // ========== 现金红包领取功能 ==========


    /**

     * 判断是否为现金红包且需要领取

     * 条件:中奖 + 现金红包类型 + 有packageInfo

     */

    const isCashPrizeNeedReceive = computed(() => {

        return resultData.value.isWin &&

            resultData.value.prizeType === 'Cash' &&

            resultData.value.packageInfo

    })


    /**

     * 立即领取现金红包

     * 微信公众号H5环境 - 严格按照官方示例

     * 参考:https://pay.weixin.qq.com/doc/v3/merchant/4012716430

     */

    const handleReceiveCash = () => {

        const { packageInfo, cashAmount } = resultData.value


        // 检查packageInfo是否存在

        if (!packageInfo) {

            uni.showToast({

                title: '红包信息异常',

                icon: 'none'

            })

            return

        }


        // 检查微信环境

        if (typeof WeixinJSBridge === 'undefined') {

            uni.showToast({

                title: '请在微信中打开',

                icon: 'none'

            })

            return

        }


        // 实际调用转账的函数

        const doInvokeTransfer = () => {

            console.log('调用 WeixinJSBridge.invoke requestMerchantTransfer')

            WeixinJSBridge.invoke('requestMerchantTransfer', {

                mchId: '131865',                  // 商户号

                appId: 'wx0ererrf886b32b4349',        // 公众号AppID

                package: packageInfo               // 后端返回的package信息

            }, function(res) {

                console.log('WeixinJSBridge 响应:', res)

               

                // 官方示例使用 err_msg (下划线)

                if (res.err_msg === 'requestMerchantTransfer:ok') {

                    uni.showToast({

                        title: `¥${cashAmount.toFixed(2)} 领取成功!`,

                        icon: 'success'

                    })

                    showResult.value = false

                } else {

                    // 显示具体错误信息便于调试

                    const errMsg = res.err_msg || ''

                    console.error('领取失败:', errMsg)

                   

                    let tip = '领取失败,请稍后重试'

                    if (errMsg.includes('offline verifying')) {

                        tip = '微信侧正在核验权限,请稍后再试'

                    } else if (errMsg.includes('cancel')) {

                        tip = '您取消了领取'

                    }

                   

                    uni.showToast({

                        title: tip,

                        icon: 'none',

                        duration: 3000

                    })

                }

            })

        }


        // 官方标准流程:先检查API可用性

        if (typeof wx !== 'undefined' && typeof wx.checkJsApi === 'function') {

            wx.checkJsApi({

                jsApiList: ['requestMerchantTransfer'],

                success: function(res) {

                    console.log('checkJsApi 结果:', res)

                    if (res.checkResult && res.checkResult.requestMerchantTransfer) {

                        // API可用,执行调用

                        doInvokeTransfer()

                    } else {

                        uni.showToast({

                            title: '您的微信版本过低,请更新至最新版本',

                            icon: 'none',

                            duration: 3000

                        })

                    }

                },

                fail: function(err) {

                    console.warn('checkJsApi 失败,降级直接调用:', err)

                    // 降级:直接尝试调用

                    doInvokeTransfer()

                }

            })

        } else {

            // wx对象不存在或无checkJsApi方法,直接调用

            console.log('wx.checkJsApi 不可用,直接调用')

            doInvokeTransfer()

        }

    }


    /**

     * 获取扇形区域样式

     */

    const getSectorStyle = (index) => {

        const angle = anglePerPrize.value

        const rotation = index * angle

        const skewAngle = 90 - angle


        return {

            transform: `rotate(${rotation}deg) skewY(${skewAngle}deg)`,

            backgroundColor: index % 2 === 0 ? '#FFE4B5' : '#FFF8DC'

        }

    }


    /**

     * 获取奖品内容样式

     */

    const getPrizeContentStyle = (index) => {

        const angle = anglePerPrize.value

        const skewAngle = 90 - angle


        return {

            transform: `skewY(${-skewAngle}deg) rotate(${angle / 2}deg)`

        }

    }


    /**

     * 获取装饰灯样式

     */

    const getLightStyle = (index) => {

        const angle = (index - 1) * (360 / 16)

        return {

            transform: `rotate(${angle}deg) translateY(-140px)`

        }

    }


    /**

     * 获取星星装饰样式

     */

    const getStarStyle = (index) => {

        return {

            left: `${Math.random() * 100}%`,

            top: `${Math.random() * 100}%`,

            animationDelay: `${Math.random() * 2}s`,

            fontSize: `${12 + Math.random() * 10}rpx`

        }

    }


    /**

     * 获取奖品图标(支持字符串和数字类型)

     */

    const getPrizeIcon = (prizeType) => {

        // 字符串类型映射

        const stringIcons = {

            'Consolation': '😢',  // 谢谢参与

            'Physical': '🎁',     // 实物

            'Points': '💰',       // 积分

            'Coupon': '🎫',       // 优惠券

            'Cash': '🧧',         // 现金红包

            'Virtual': '🎮'       // 虚拟物品

        }

        // 数字类型映射(兼容旧版)

        const numberIcons = {

            0: '😢', 1: '🎁', 2: '💰', 3: '🎫', 4: '🧧', 5: '🎮'

        }

        return stringIcons[prizeType] || numberIcons[prizeType] || '🎁'

    }


    /**

     * 判断是否为实物奖品(需要填写地址)

     */

    const isPhysicalPrize = (prizeType) => {

        return prizeType === 'Physical' || prizeType === 1

    }


    /**

     * 脱敏用户名

     */

    const maskName = (name) => {

        if (!name) return '匿名用户'

        if (name.length <= 2) return name[0] + '*'

        return name[0] + '**' + name[name.length - 1]

    }


    /**

     * 格式化时间

     */

    const formatTime = (timeStr) => {

        if (!timeStr) return ''

        const date = new Date(timeStr)

        return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`

    }


    /**

     * 开启灯光闪烁

     */

    const startLightFlash = () => {

        lightTimer = setInterval(() => {

            lightActive.value = lightActive.value === 0 ? 1 : 0

        }, 500)

    }


    // ========== 生命周期 ==========

    onLoad(() => {

        checkLoginStatus() // 检查登录状态

        initData()

        startLightFlash()

    })


    onShow(() => {

        // 页面显示时刷新登录状态(登录后返回时更新)

        checkLoginStatus()

    })


    onMounted(() => {

        // 打开我的奖品弹窗时加载数据

    })


    onUnmounted(() => {

        if (lightTimer) {

            clearInterval(lightTimer)

        }

    })


    // 监听打开我的奖品

    const openMyPrizes = () => {

        loadMyPrizes()

        showMyPrizes.value = true

    }

</script>


<style lang="scss">

    .lottery-page {

        min-height: 100vh;

        background: linear-gradient(180deg, #FF6B6B 0%, #C41E3A 50%, #8B0000 100%);

        padding: 30rpx;

        position: relative;

        overflow: hidden;

    }


    /* 用户登录状态 */

    .user-status {

        position: absolute;

        top: 40rpx;

        left: 30rpx;

        z-index: 100;


        .user-avatar {

            position: relative;

            width: 80rpx;

            height: 80rpx;

            border-radius: 50%;

            background: #FFF;

            box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.25);

            overflow: visible;


            .avatar-img {

                width: 100%;

                height: 100%;

                border-radius: 50%;

            }


            .avatar-default {

                width: 100%;

                height: 100%;

                border-radius: 50%;

                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

                display: flex;

                align-items: center;

                justify-content: center;


                text {

                    font-size: 44rpx;

                    color: #FFF;

                }

            }


            // 在线小绿点

            .online-dot {

                position: absolute;

                bottom: 2rpx;

                right: 2rpx;

                width: 20rpx;

                height: 20rpx;

                background: #52C41A;

                border-radius: 50%;

                border: 3rpx solid #FFF;

            }

        }


        .login-btn {

            background: rgba(255, 255, 255, 0.95);

            border-radius: 40rpx;

            padding: 14rpx 28rpx;

            display: flex;

            align-items: center;

            box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.2);


            .login-icon {

                font-size: 28rpx;

                margin-right: 10rpx;

            }


            .login-text {

                font-size: 26rpx;

                color: #C41E3A;

                font-weight: bold;

            }

        }

    }


    /* 背景装饰 */

    .bg-decoration {

        position: fixed;

        top: 0;

        left: 0;

        right: 0;

        bottom: 0;

        pointer-events: none;

        z-index: 0;


        .star {

            position: absolute;

            color: rgba(255, 255, 255, 0.6);

            animation: twinkle 2s ease-in-out infinite;


            &::before {

                content: '✦';

            }

        }

    }


    @keyframes twinkle {


        0%,

        100% {

            opacity: 0.3;

        }


        50% {

            opacity: 1;

        }

    }


    /* 顶部信息 */

    .header {

        text-align: center;

        padding: 40rpx 0;

        position: relative;

        z-index: 1;


        .title {

            font-size: 48rpx;

            font-weight: bold;

            color: #FFD700;

            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);

        }


        .subtitle {

            font-size: 28rpx;

            color: #FFF;

            margin-top: 16rpx;

            opacity: 0.9;

        }


        .remaining {

            margin-top: 24rpx;

            font-size: 28rpx;

            color: #FFF;


            .count {

                font-size: 40rpx;

                font-weight: bold;

                color: #FFD700;

            }

        }

    }


    /* 中奖滚动 */

    .winner-marquee {

        background: rgba(255, 255, 255, 0.15);

        border-radius: 40rpx;

        padding: 16rpx 30rpx;

        margin: 20rpx 0;

        display: flex;

        align-items: center;

        position: relative;

        z-index: 1;


        .marquee-icon {

            font-size: 32rpx;

            margin-right: 16rpx;

        }


        .marquee-swiper {

            flex: 1;

            height: 40rpx;

        }


        .winner-item {

            font-size: 26rpx;

            color: #FFF;

            line-height: 40rpx;


            .winner-name {

                color: #FFD700;

            }


            .winner-prize {

                color: #FFD700;

                font-weight: bold;

            }

        }

    }


    /* 转盘容器 */

    .wheel-container {

        position: relative;

        width: 600rpx;

        height: 600rpx;

        margin: 40rpx auto;

        z-index: 1;

    }


    /* 装饰灯 */

    .wheel-lights {

        position: absolute;

        width: 100%;

        height: 100%;

        top: 0;

        left: 0;


        .light {

            position: absolute;

            width: 20rpx;

            height: 20rpx;

            border-radius: 50%;

            background: #FFF;

            left: 50%;

            top: 50%;

            margin-left: -10rpx;

            margin-top: -10rpx;

            transition: all 0.3s;


            &.active {

                background: #FFD700;

                box-shadow: 0 0 10px #FFD700;

            }

        }

    }


    /* 转盘主体 */

    .wheel-wrapper {

        position: absolute;

        width: 540rpx;

        height: 540rpx;

        top: 30rpx;

        left: 30rpx;

        border-radius: 50%;

        background: #FFF;

        box-shadow: 0 0 30rpx rgba(0, 0, 0, 0.3);

        overflow: hidden;

    }


    .wheel {

        width: 100%;

        height: 100%;

        border-radius: 50%;

        position: relative;

        transition: transform 4s cubic-bezier(0.17, 0.67, 0.12, 0.99);

    }


    /* 奖品扇形 */

    .prize-sector {

        position: absolute;

        width: 50%;

        height: 50%;

        top: 0;

        right: 0;

        transform-origin: bottom left;

        overflow: hidden;

    }


    .prize-content {

        position: absolute;

        left: -100%;

        width: 200%;

        height: 200%;

        text-align: center;

        padding-top: 30rpx;


        .prize-image {

            width: 60rpx;

            height: 60rpx;

        }


        .prize-icon {

            font-size: 48rpx;

        }


        .prize-name {

            display: block;

            font-size: 20rpx;

            color: #333;

            margin-top: 8rpx;

            max-width: 100rpx;

            overflow: hidden;

            text-overflow: ellipsis;

            white-space: nowrap;

        }

    }


    /* 中心按钮 */

    .wheel-pointer {

        position: absolute;

        top: 50%;

        left: 50%;

        transform: translate(-50%, -50%);

        z-index: 10;


        &.disabled {

            pointer-events: none;

            opacity: 0.7;

        }


        .pointer-arrow {

            width: 0;

            height: 0;

            border-left: 30rpx solid transparent;

            border-right: 30rpx solid transparent;

            border-bottom: 50rpx solid #C41E3A;

            position: absolute;

            top: -45rpx;

            left: 50%;

            transform: translateX(-50%);

        }


        .pointer-btn {

            width: 120rpx;

            height: 120rpx;

            border-radius: 50%;

            background: linear-gradient(180deg, #FFD700 0%, #FFA500 100%);

            display: flex;

            align-items: center;

            justify-content: center;

            box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);

            border: 6rpx solid #C41E3A;


            text {

                font-size: 28rpx;

                font-weight: bold;

                color: #C41E3A;

            }

        }

    }


    /* 活动规则 */

    .rules-section {

        background: rgba(255, 255, 255, 0.15);

        border-radius: 20rpx;

        margin-top: 40rpx;

        overflow: hidden;

        position: relative;

        z-index: 1;


        .rules-title {

            padding: 24rpx 30rpx;

            display: flex;

            justify-content: space-between;

            align-items: center;

            color: #FFF;

            font-size: 28rpx;


            .arrow {

                font-size: 24rpx;

            }

        }


        .rules-content {

            padding: 0 30rpx 24rpx;

            color: rgba(255, 255, 255, 0.9);

            font-size: 26rpx;

            line-height: 1.8;

        }

    }


    /* 我的奖品按钮 */

    .my-prizes-btn {

        position: fixed;

        right: 0;

        top: 50%;

        transform: translateY(-50%);

        background: linear-gradient(90deg, #FFD700, #FFA500);

        color: #8B0000;

        font-size: 26rpx;

        font-weight: bold;

        padding: 20rpx 24rpx;

        border-radius: 40rpx 0 0 40rpx;

        box-shadow: -4rpx 0 20rpx rgba(0, 0, 0, 0.2);

        z-index: 100;

    }


    /* 弹窗蒙层 */

    .popup-mask {

        position: fixed;

        top: 0;

        left: 0;

        right: 0;

        bottom: 0;

        background: rgba(0, 0, 0, 0.6);

        display: flex;

        align-items: center;

        justify-content: center;

        z-index: 1000;

    }


    /* 结果弹窗 */

    .result-popup {

        width: 600rpx;

        background: #FFF;

        border-radius: 30rpx;

        padding: 50rpx 40rpx;

        text-align: center;


        &.win {

            background: linear-gradient(180deg, #FFF9E6 0%, #FFF 100%);

        }


        .result-icon {

            font-size: 100rpx;

        }


        .result-title {

            font-size: 40rpx;

            font-weight: bold;

            color: #333;

            margin: 20rpx 0;

        }


        .result-prize {

            .prize-img {

                width: 160rpx;

                height: 160rpx;

                border-radius: 20rpx;

            }


            .prize-text {

                display: block;

                font-size: 32rpx;

                color: #C41E3A;

                font-weight: bold;

                margin-top: 16rpx;

            }

        }


        .result-tip {

            font-size: 28rpx;

            color: #666;

            margin: 20rpx 0;

        }


        .close-btn {

            margin-top: 30rpx;

            background: linear-gradient(90deg, #FF6B6B, #C41E3A);

            color: #FFF;

            border: none;

            border-radius: 40rpx;

            font-size: 30rpx;

            height: 80rpx;

            line-height: 80rpx;

        }

    }


    /* 现金红包样式 */

    .cash-prize {

        display: flex;

        flex-direction: column;

        align-items: center;

        padding: 30rpx 0;


        .cash-icon {

            font-size: 80rpx;

            margin-bottom: 16rpx;

        }


        .cash-amount {

            font-size: 56rpx;

            font-weight: bold;

            color: #C41E3A;

        }


        .cash-label {

            font-size: 28rpx;

            color: #666;

            margin-top: 8rpx;

        }

    }


    .cash-message {

        font-size: 26rpx;

        color: #FF6B6B;

        text-align: center;

        padding: 20rpx;

        background: #FFF5F5;

        border-radius: 12rpx;

        margin: 20rpx 0;

    }


    /* 立即领取按钮 */

    .receive-cash-btn {

        margin-top: 30rpx;

        background: linear-gradient(90deg, #FF4D4F, #C41E3A);

        color: #FFF;

        border: none;

        border-radius: 40rpx;

        font-size: 32rpx;

        font-weight: bold;

        height: 88rpx;

        line-height: 88rpx;

        box-shadow: 0 8rpx 20rpx rgba(196, 30, 58, 0.4);

        animation: pulse 1.5s ease-in-out infinite;

    }


    @keyframes pulse {


        0%,

        100% {

            transform: scale(1);

        }


        50% {

            transform: scale(1.03);

        }

    }


    /* 地址表单 */

    .address-form {

        margin-top: 30rpx;

        text-align: left;


        .form-title {

            font-size: 28rpx;

            color: #333;

            font-weight: bold;

            margin-bottom: 20rpx;

            text-align: center;

        }


        .form-input {

            width: 100%;

            height: 80rpx;

            border: 2rpx solid #DDD;

            border-radius: 12rpx;

            padding: 0 24rpx;

            margin-bottom: 20rpx;

            font-size: 28rpx;

            box-sizing: border-box;

        }


        .form-textarea {

            width: 100%;

            height: 160rpx;

            border: 2rpx solid #DDD;

            border-radius: 12rpx;

            padding: 20rpx 24rpx;

            margin-bottom: 20rpx;

            font-size: 28rpx;

            box-sizing: border-box;

        }


        .submit-btn {

            background: linear-gradient(90deg, #FFD700, #FFA500);

            color: #8B0000;

            border: none;

            border-radius: 40rpx;

            font-size: 30rpx;

            font-weight: bold;

            height: 80rpx;

            line-height: 80rpx;

        }

    }


    /* 我的奖品弹窗 */

    .prizes-popup {

        width: 650rpx;

        max-height: 80vh;

        background: #FFF;

        border-radius: 30rpx;

        overflow: hidden;


        .popup-header {

            padding: 30rpx;

            display: flex;

            justify-content: space-between;

            align-items: center;

            border-bottom: 2rpx solid #EEE;


            .popup-title {

                font-size: 32rpx;

                font-weight: bold;

                color: #333;

            }


            .popup-close {

                font-size: 36rpx;

                color: #999;

            }

        }


        .prizes-list {

            max-height: 60vh;

            padding: 20rpx;

        }


        .prize-item {

            display: flex;

            padding: 20rpx;

            border-bottom: 2rpx solid #F5F5F5;


            .item-image {

                width: 120rpx;

                height: 120rpx;

                border-radius: 12rpx;

                background: #F5F5F5;

            }


            .item-info {

                flex: 1;

                margin-left: 20rpx;

                display: flex;

                flex-direction: column;

                justify-content: center;


                .item-name {

                    font-size: 28rpx;

                    color: #333;

                    font-weight: bold;

                }


                .item-time {

                    font-size: 24rpx;

                    color: #999;

                    margin-top: 8rpx;

                }


                .item-status {

                    font-size: 24rpx;

                    margin-top: 8rpx;


                    &.status-0 {

                        color: #FFA500;

                    }


                    &.status-1 {

                        color: #1890FF;

                    }


                    &.status-2 {

                        color: #52C41A;

                    }

                }

            }

        }


        .empty-tip {

            text-align: center;

            padding: 60rpx 0;

            color: #999;

            font-size: 28rpx;

        }

    }


    /* 加载中 */

    .loading-mask {

        position: fixed;

        top: 0;

        left: 0;

        right: 0;

        bottom: 0;

        background: rgba(0, 0, 0, 0.5);

        display: flex;

        flex-direction: column;

        align-items: center;

        justify-content: center;

        z-index: 2000;


        .loading-spinner {

            width: 60rpx;

            height: 60rpx;

            border: 6rpx solid rgba(255, 255, 255, 0.3);

            border-top-color: #FFF;

            border-radius: 50%;

            animation: spin 1s linear infinite;

        }


        .loading-text {

            color: #FFF;

            font-size: 28rpx;

            margin-top: 20rpx;

        }

    }


    @keyframes spin {

        to {

            transform: rotate(360deg);

        }

    }

</style>



回答关注问题邀请回答
收藏
登录 后发表内容