组件预览

image-20250415174320687.png

创建commponents/lottery/index.vue 文件

输入如下代码

轮盘抽检组件

<template>
    <view class="main">
        <view v-if="options.length > 0" class="canvas-container">
            <canvas canvas-id="canvas" id="canvas" :style="canvasStyle" />

            <canvas canvas-id="canvasBtn" id="canvasBtn" class="canvasBtn" :style="canvasStyle" />

            <cover-view @tap="playReward" :class="['canvas-btn', { 'isLottery': isLottery }]"
                :style="{ backgroundColor: centerBtnColor }">
                {{ centerBtnText }}
            </cover-view>
        </view>
        <view v-else class="empty-tip">
            <text>暂无抽奖选项</text>
        </view>
        <view v-if="showBtn" class="btn-container">
            <button type="primary" :disabled="isLottery" @click="playReward">{{ btnTitle }}</button>
        </view>
    </view>
</template>
<script>
var ctx = null;
export default {

    props: {
        // 弹窗内容 ,当 turnModalContent=【】时,默认弹窗内容为抽奖结果,并且不展示标题
        turnModalContent: {
            type: Array,
            default: () => []
        },
        // 减速,值越小,减速效果越明显 turnReduceSpeed
        turnReduceSpeed: {
            type: Number,
            default: 50
        },
        // 表示转速:表示再转多少圈再进行抽奖
        turnCircle: {
            type: Number,
            default: 0
        },
        // 画布宽度
        width: {
            type: Number,
            default: 100,
        },
        // 画布高度
        height: {
            type: Number,
            default: 100
        },
        // 画布内字体大小
        fontSize: {
            type: Number,
            default: 18
        },
        // 钩子函数,抽奖开始前执行,返回值为选项列表下标,若返回值为-1,则进行随机抽奖
        setWinnerFn: {
            type: Function,
            default: null,
        },
        // // 钩子函数,抽奖开始前执行
        beforePlay: {
            type: Function,
            default: null,
        },
        // // 钩子函数,抽奖结束后执行
        afterPlay: {
            type: Function,
            default: null,
        },
        // 如果 true,则 使用组件默认的选项数据
        isUseDefaultOptions: {
            type: Boolean,
            default: false
        },
        // 选项列表
        data: {
            type: Array,
            default: () => []
        },
        // 是否要展示开始按钮
        showBtn: {
            type: Boolean,
            default: false,
        },
        // 按钮文本
        btnTitle: {
            type: String,
            default: "开始"
        },
        // 是否显示结果弹窗
        showResultModal: {
            type: Boolean,
            default: true
        },
        // 中心按钮文本
        centerBtnText: {
            type: String,
            default: "GO"
        },
        // 中心按钮颜色
        centerBtnColor: {
            type: String,
            default: "#ffffff"
        }
    },
    data() {
        return {
            options: [{
                id: 1, // 唯一id
                name: '水饺', // 名称
                // "weight": 50, //  中奖权重,0-100
                img: '', // 展示图片
                color: "#f6e174" // 轮盘区域底色
            },
            {
                id: 2,
                name: '火锅',
                img: '',
                color: "#94494d"
            },
            {
                id: 3,
                name: '川菜',
                img: '',
                color: "#ffaa7f"
            },
            {
                id: 4,
                name: '麻辣烫',
                img: '',
                color: "#a48342"
            },
            {
                id: 5,
                name: '炸鸡汉堡',
                img: '',
                color: "#a25f81"
            },
            {
                id: 6,
                name: '重庆小面',
                img: '',
                color: "#98e2e2"
            },
            {
                id: 7,
                name: '酸辣粉',
                img: '',
                color: "#aaaa00"
            },
            {
                id: 8,
                name: '面条',
                img: '',
                color: "#648f76"
            },
            {
                id: 9,
                name: '汤河粉',
                img: '',
                color: "#40b3ff"
            },
            {
                id: 10,
                name: '猪脚饭',
                img: '',
                color: "#ffaaff"
            },
            {
                id: 11,
                name: '烧鸭饭',
                img: '',
                color: "#5555ff"
            },
            {
                id: 12,
                name: '白切鸡',
                img: '',
                color: "#ff90cf"
            },
            {
                id: 13,
                name: '烧鸡饭',
                img: '',
                color: "#55aa7f"
            },
            {
                id: 14,
                name: '肥肠面',
                img: '',
                color: "#4f95ff"
            },
            {
                id: 15,
                name: '炒饭',
                img: '',
                color: "#a509aa"
            },
            {
                id: 16,
                name: '黄焖鸡米饭',
                img: '',
                color: "#aaaa00"
            },
            {
                id: 17,
                name: '皮蛋瘦肉粥',
                img: '',
                color: "#ff52d1"
            },
            {
                id: 18,
                name: '沙县小吃',
                img: '',
                color: "#944ade"
            },
            {
                id: 19,
                name: '焖锅',
                img: '',
                color: "#43c900"
            },
            {
                id: 20,
                name: '披萨',
                img: '',
                color: "#7cbaba"
            }
            ],
            isLottery: false, // 是否正在抽奖
            winnerIndex: -1, // 中奖索引
            isInitialized: false // 是否已初始化
        };
    },
    computed: {
        canvasStyle() {
            return "width:" + this.width + "px;; height:" + this.height + "px;";
        },
    },
    watch: {
        // 监听数据变化,重新初始化
        data: {
            handler(newVal) {
                if (newVal && newVal.length > 0 && !this.isUseDefaultOptions) {
                    this.init();
                }
            },
            deep: true
        }
    },
    methods: {
        // 处理文本,根据长度进行截断或调整
        processText(text, maxLength) {
            if (!text) return '';

            // 如果文本长度超过最大长度,截断并添加省略号
            if (text.length > maxLength) {
                return text.substring(0, maxLength - 2) + '..';
            }
            return text;
        },
        // 获取文本显示的字体大小
        getTextFontSize(text, baseSize) {
            // 根据文本长度动态调整字体大小
            if (!text) return baseSize;

            if (text.length > 6) {
                // 文本较长时,逐步减小字体
                const reduction = Math.min(Math.floor(text.length / 2), 8);
                return Math.max(baseSize - reduction, 10); // 最小字体为10
            }
            return baseSize;
        },


        async playReward() {
            if (this.isLottery) {
                return
            }
            let len = this.options.length
            if (len == 0) {
                console.error("抽奖选项为空,无法开始抽奖");
                return;
            }
            this.isLottery = true
            console.log("开始抽奖")
            // 触发beforePlay事件
            if (this.beforePlay) {
                this.beforePlay();
            }
            this.$emit("beforePlay");
            // 获取中奖索引
            let num = -1;
            // 使用setWinnerFn获取中奖索引
            if (this.setWinnerFn && typeof this.setWinnerFn === 'function') {
                try {
                    // 修复调用方式
                    num = this.setWinnerFn(this.options);
                    if (num === undefined || num < 0 || num >= len) {
                        console.warn("setWinnerFn返回的索引无效,将使用随机抽奖");
                        num = -1;
                    }
                } catch (error) {
                    console.error("调用setWinnerFn出错:", error);
                    num = -1;
                }
            }
            if (num < 0) {
                // 进行权重抽奖
                let optionIndex = this.lotteryWeight(this.options)
                if (optionIndex !== undefined && optionIndex >= 0) {
                    num = optionIndex
                } else {
                    // 没有自动抽奖结果 && 没有定义选项权重,则进行随机数抽奖
                    num = Math.floor(Math.random() * len)
                }
            }
            this.winnerIndex = num;
            try {
                const result = await this.roateCanvas(num)
                console.log("抽奖结果:", result.name)
                this.isLottery = false
                // 是否显示结果弹窗
                if (this.showResultModal) {
                    let title = ""
                    let content = result.name
                    if (this.turnModalContent.length > 0) {
                        // 若设置了 content ,则随机取一个 content 内容,并且 title 将展示为抽奖结果 
                        title = result.name
                        content = this.turnModalContent[Math.floor(Math.random() * this.turnModalContent.length)]
                    }
                    uni.showModal({
                        confirmText: "确定",
                        showCancel: false,
                        title: title,
                        content: content,
                        success: (res) => {
                            if (res.confirm) {
                                console.log('用户点击确定');
                                // 调用钩子函数
                                if (this.afterPlay) {
                                    this.afterPlay(result);
                                }
                                this.$emit("afterPlay", result)
                            }
                        }
                    })
                } else {
                    // 直接触发afterPlay事件
                    if (this.afterPlay) {
                        this.afterPlay(result);
                    }
                    this.$emit("afterPlay", result)
                }
            } catch (error) {
                console.error("抽奖过程出错:", error);
                this.isLottery = false;
                uni.showToast({
                    title: '抽奖失败,请重试',
                    icon: 'none'
                });
            }

        },

        initBtnCanvas() {
            try {
                let angleTo = 0
                let ctx = uni.createCanvasContext("canvasBtn", this);
                // 圆中心点的坐标 x: 宽度的一半
                let center_x = this.width / 2;
                // 圆中心点的坐标 y: 高度的一半
                let center_y = this.height / 2;

                // 先清除画布上在该矩形区域内的内容
                ctx.clearRect(0, 0, this.width, this.height);

                ctx.translate(center_x, center_y);

                // 画中心点圆
                ctx.beginPath();
                ctx.moveTo(0, -50); // 三角形顶点坐标
                ctx.lineTo(-20, 10); // 左下角坐标
                ctx.lineTo(20, 10); // 右下角坐标
                ctx.setFillStyle("#ff0000");
                ctx.fill();
                ctx.draw();
            } catch (error) {
                console.error("初始化按钮画布失败:", error);
            }
        },
        initCanvas: function (ctx, angleTo) {
            try {
                const len = this.options.length; //数组长度
                if (len == 0) {
                    console.warn("options len == 0");
                    return;
                }

                if (!angleTo) {
                    angleTo = 0
                }

                // 圆中心点的坐标 x: 宽度的一半
                let center_x = this.width / 2;
                // 圆中心点的坐标 y: 高度的一半
                let center_y = this.height / 2;
                // 圆的弧度的总度数,2π表示画圆
                let totalAngle = 2 * Math.PI;
                // 平均一个选项占用的孤度数
                let avgAngle = totalAngle / len;

                let radius = center_x - 14;
                let baseFontSize = this.getFontSize();

                // 先清除画布上在该矩形区域内的内容
                ctx.clearRect(0, 0, this.width, this.height);

                ctx.translate(center_x, center_y);
                ctx.setLineWidth(14);
                ctx.save();

                // 画外圆
                ctx.rotate(angleTo * Math.PI / 180);
                var beginAngle = 2 * Math.PI / 360 * (-90);
                ctx.setStrokeStyle("#ffffff");
                ctx.arc(0, 0, radius - 3, 0, Math.PI * 2);
                ctx.stroke();

                // 划分区域,并且填充颜色
                ctx.setLineWidth(0.1);
                beginAngle = 2 * Math.PI / 360 * (-90);
                //绘制填充形状
                for (var i = 0; i < len; i++) {
                    ctx.save();
                    ctx.beginPath();
                    ctx.moveTo(0, 0);
                    ctx.setStrokeStyle(this.options[i].color);
                    ctx.setFillStyle(this.options[i].color);

                    ctx.arc(0, 0, radius, beginAngle, beginAngle + avgAngle, false);
                    ctx.fill();
                    ctx.save();
                    beginAngle = beginAngle + avgAngle;
                }

                // 绘制选项文字
                beginAngle = 0;
                for (var i = 0; i < len; i++) {
                    // 根据奖项数量和文本长度调整文本位置和大小
                    let textDistance = -(center_x / 2) - 25;

                    // 奖项过多时,文本更靠近中心
                    if (len > 10) {
                        textDistance = -(center_x / 2) - 15;
                    }
                    if (len > 15) {
                        textDistance = -(center_x / 2) - 5;
                    }

                    // 处理文本,根据奖项数量决定最大长度
                    let maxTextLength = len > 12 ? 4 : (len > 8 ? 6 : 8);
                    let processedText = this.processText(this.options[i].name, maxTextLength);

                    // 根据文本长度调整字体大小
                    let fontSize = this.getTextFontSize(processedText, baseFontSize);

                    //绘制旋转文字
                    ctx.rotate((beginAngle + (avgAngle * 0.5))); //顺时针旋转
                    ctx.setTextAlign("center");
                    ctx.setFillStyle("#FFFFFF");
                    ctx.setFontSize(fontSize);
                    ctx.fillText(processedText, 0, textDistance);

                    ctx.restore();
                    beginAngle = beginAngle + avgAngle;
                }
                ctx.save();
                // 画中心点圆
                ctx.beginPath();
                ctx.arc(0, 0, 15, 0, Math.PI * 2); // 15 为中心点圆的半径
                ctx.setFillStyle("#FFFFFF");
                ctx.fill();
                ctx.draw();

                this.isInitialized = true;
            } catch (error) {
                console.error("初始化画布失败:", error);
                this.isInitialized = false;
            }
        },
        // 根据设置的权重进行抽奖
        lotteryWeight(prizes) {
            console.log("进行权重抽奖")
            if (!prizes || prizes.length === 0) {
                console.log("奖品列表为空")
                return -1
            }

            // 计算总权重和有效奖品
            let totalWeight = 0
            const validPrizes = []

            // 筛选有效奖品并计算总权重
            for (let index = 0; index < prizes.length; index++) {
                const prize = prizes[index]
                const weight = Number(prize.weight)

                // 只有权重大于0的奖品才参与抽奖
                if (weight && weight > 0) {
                    totalWeight += weight
                    validPrizes.push({
                        index,
                        weight,
                        name: prize.name
                    })
                }
            }

            // 如果没有有效奖品,返回-1表示随机抽奖
            if (validPrizes.length === 0 || totalWeight === 0) {
                console.log("没有设置有效权重的奖品,将进行随机抽奖")
                return -1
            }

            // 生成0到总权重之间的随机数
            const random = Math.random() * totalWeight
            console.log("权重抽奖随机数:", random, "总权重:", totalWeight)

            // 根据权重区间确定中奖奖品
            let accumulatedWeight = 0
            for (const prize of validPrizes) {
                accumulatedWeight += prize.weight
                if (random < accumulatedWeight) {
                    console.log("权重抽奖奖品 ", prizes[prize.index].name, " 中奖,权重:", prize.weight)
                    return prize.index
                }
            }
            // 保底返回最后一个有效奖品
            console.log("未找到匹配奖品,进行随机抽奖")
            return -1
        },
        // 旋转画布,num 表示选项 options 的下标
        roateCanvas(num) {
            let len = this.options.length
            let angle = 360 / len;
            angle = num * angle + angle / 2;
            angle = angle || 0;
            angle = 360 - angle;
            angle += 360 * 5;
            if (this.turnCircle > 0) {
                let turnCircle = this.turnCircle
                // 最多只能转 10 圈
                if (turnCircle > 10) {
                    turnCircle = 10
                }
                angle += 360 * turnCircle;
            }
            let that = this;
            let count = 1;
            // 减速,值越小,减速效果越明显 
            let turnReduceSpeed = this.turnReduceSpeed
            if (turnReduceSpeed == 0) {
                turnReduceSpeed = 1
            }
            let baseStep = turnReduceSpeed;
            // 起始滚动速度
            let baseSpeed = 1;
            let result = {}
            return new Promise((resolve, reject) => {
                let timer = setInterval(function () {
                    // console.log("count = ", count);
                    that.initCanvas(that.ctx, count);
                    if (count == angle) {
                        clearInterval(timer);
                        result = that.options[num];
                        resolve(result)
                    }
                    count = count + baseStep * (((angle - count) / angle) > baseSpeed ? baseSpeed :
                        ((angle -
                            count) / angle)) + 0.1;

                    if (angle - count < 0.5) {
                        count = angle;
                    }
                }, 25);
            });
        },
        getFontSize() {
            let fontSize = this.fontSize
            if (this.options.length > 10) {
                if (this.fontSize >= 18) {
                    fontSize = this.fontSize - (this.options.length - 10)
                }
            }

            return fontSize
        },
        // 生成随机颜色
        genRandColor() {
            // 生成随机的 RGB 值
            var r = Math.floor(Math.random() * 256); // 0 到 255 之间的随机数
            var g = Math.floor(Math.random() * 256);
            var b = Math.floor(Math.random() * 256);

            // 将 RGB 值转换为 Hex 颜色表示
            var hexColor = "#" + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b);

            return hexColor;
        },
        componentToHex(c) {
            var hex = c.toString(16);
            return hex.length === 1 ? "0" + hex : hex;
        },
        initOptions: function () {
            try {
                let defaultOptions = JSON.parse(JSON.stringify(this.options)); // 深拷贝默认选项

                if (this.isUseDefaultOptions) {
                    // 使用默认数据
                    console.log("使用默认选项数据");
                } else if (this.data && this.data.length > 0) {
                    console.log("使用传入的选项数据, 长度:", this.data.length);
                    this.options = JSON.parse(JSON.stringify(this.data)); // 深拷贝传入数据
                } else {
                    console.warn("未提供有效的选项数据,将使用默认选项");
                }

                // 所有默认的颜色
                let allDefColorArr = defaultOptions.map(item => item.color);

                // 找到最大的 id
                let maxId = 0;
                this.options.forEach(item => {
                    if (item.id && item.id > maxId) {
                        maxId = item.id;
                    }
                });

                // 填充 id,确保 id 唯一
                var existIds = []
                for (var i = 0; i < this.options.length; i++) {
                    let item = this.options[i]
                    if (item['id'] == undefined || item.id == 0 || existIds.includes(item.id)) {
                        // id 不存在,或者 id 重复了
                        maxId++;
                        this.options[i]['id'] = maxId;
                    }
                    existIds.push(this.options[i].id)
                }

                // 填充颜色,确保颜色唯一
                let availableColor = JSON.parse(JSON.stringify(allDefColorArr)) // 可用颜色
                let existColor = [] // 存在颜色

                // 先收集已有颜色
                for (var i = 0; i < this.options.length; i++) {
                    let item = this.options[i]
                    if (item['color'] && item.color !== "") {
                        let color = item.color
                        existColor.push(color)
                        // 过滤掉已经用了的 color
                        availableColor = availableColor.filter(c => c !== color);
                    }
                }

                // 为没有颜色的选项分配颜色
                for (var i = 0; i < this.options.length; i++) {
                    let item = this.options[i]
                    if (!item['color'] || item.color === "") {
                        if (availableColor.length == 0) {
                            // 没有可用颜色了,则随机生成一个
                            let color = '';
                            for (var j = 0; j < 100; j++) {
                                if (color !== '') break;
                                let genColor = this.genRandColor()
                                if (!existColor.includes(genColor)) {
                                    existColor.push(genColor)
                                    color = genColor
                                }
                            }
                            this.options[i]['color'] = color
                        } else {
                            const color = availableColor.shift();
                            existColor.push(color)
                            this.options[i].color = color
                        }
                    }
                }

                console.log("选项初始化完成,数量:", this.options.length);
            } catch (error) {
                console.error("初始化选项失败:", error);
                // 确保至少有默认选项
                if (!this.options || this.options.length === 0) {
                    this.options = [{
                        id: 1,
                        name: '默认选项',
                        color: "#f6e174"
                    }];
                }
            }
        },
        init() {
            try {
                this.initOptions();
                this.ctx = uni.createCanvasContext("canvas", this);
                this.initCanvas(this.ctx, 0);
                this.initBtnCanvas();
            } catch (error) {
                console.error("初始化组件失败:", error);
                uni.showToast({
                    title: '初始化抽奖组件失败',
                    icon: 'none'
                });
            }
        }
    },
    // 初始化画布
    mounted: function () {
        console.log("lottery mounted init......")
        this.init()
    }
}
</script>
<style scoped>
.main {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.title-container {
    font-size: 25px;
    margin-top: 120rpx;
    margin-bottom: 40rpx;
    z-index: 100;
}

.canvas-container {
    position: relative;
    width: fit-content;
    height: fit-content;
}

.canvasBtn {
    position: absolute;
    top: 0%;
}

.canvas-btn {
    position: absolute;
    top: 50%;
    left: 50%;
    background-color: #ffffff;
    transform: translate(-50%, -50%);
    width: 110rpx;
    height: 110rpx;
    border-radius: 50%;
    line-height: 110rpx;
    text-align: center;
    font-size: 45rpx;
    text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.6);
    box-shadow: 0 3px 5px rgba(0, 0, 0, 0.6);
    text-decoration: none;
}

.canvas-btn.isLottery {
    background-color: #CCCCCC;
    pointer-events: none;
}

.empty-tip {
    padding: 30rpx;
    text-align: center;
    color: #999;
    font-size: 28rpx;
}

.btn-container {
    margin-top: 30rpx;
}
</style>

测试Demo

<template>
    <view class="container">
        <view class="title">转盘抽奖组件演示</view>

        <!-- 转盘抽奖组件 -->
        <lottery ref="lotteryRef" :width="300" :height="300" :fontSize="16" :data="lotteryOptions"
            :isUseDefaultOptions="useDefaultOptions" :turnReduceSpeed="reduceSpeed" :turnCircle="circleCount"
            :showBtn="true" :btnTitle="'开始抽奖'" :centerBtnText="'GO'" :centerBtnColor="'#ff5252'"
            :showResultModal="showModal" :turnModalContent="modalContents" :setWinnerFn="setWinner"
            :beforePlay="onBeforePlay" :afterPlay="onAfterPlay" />

        <!-- 配置面板 -->
        <view class="config-panel">
            <view class="panel-title">配置选项</view>

            <view class="config-item">
                <text>使用默认选项:</text>
                <switch :checked="useDefaultOptions" @change="(e) => useDefaultOptions = e.detail.value" />
            </view>

            <view class="config-item">
                <text>减速效果({{ reduceSpeed }}):</text>
                <slider :value="reduceSpeed" :min="1" :max="100" :step="1" show-value
                    @change="(e) => reduceSpeed = e.detail.value" />
            </view>

            <view class="config-item">
                <text>额外旋转圈数({{ circleCount }}):</text>
                <slider :value="circleCount" :min="0" :max="10" :step="1" show-value
                    @change="(e) => circleCount = e.detail.value" />
            </view>

            <view class="config-item">
                <text>显示结果弹窗:</text>
                <switch :checked="showModal" @change="(e) => showModal = e.detail.value" />
            </view>

            <view class="config-item">
                <text>指定中奖项:</text>
                <picker :value="winnerIndex" :range="winnerOptions" range-key="name" @change="onWinnerChange">
                    <view class="picker-value">{{ winnerOptions[winnerIndex].name || '随机' }}</view>
                </picker>
            </view>

            <button class="reset-btn" type="default" @click="resetLottery">重置转盘</button>
        </view>

        <!-- 事件日志 -->
        <view class="event-log">
            <view class="log-title">事件日志</view>
            <scroll-view scroll-y class="log-content">
                <view v-for="(log, index) in eventLogs" :key="index" class="log-item">
                    {{ log }}
                </view>
            </scroll-view>
            <button size="mini" type="default" @click="clearLogs">清空日志</button>
        </view>
    </view>
</template>

<script>
import Lottery from '@/components/lottery/index.vue'

export default {
    // 注册组件
    components: {
        Lottery
    },
    data() {
        return {
            // 转盘选项数据
            lotteryOptions: [
                { id: 1, name: '一等奖', weight: 5, color: '#ff4444' },
                { id: 2, name: '二等奖', weight: 10, color: '#ff8800' },
                { id: 3, name: '三等奖', weight: 15, color: '#ffcc00' },
                { id: 4, name: '四等奖', weight: 20, color: '#33cc33' },
                { id: 5, name: '五等奖', weight: 25, color: '#3399ff' },
                { id: 6, name: '谢谢参与', weight: 25, color: '#9966ff' }
            ],

            // 配置选项
            useDefaultOptions: false, // 是否使用组件默认选项
            reduceSpeed: 50, // 减速效果,值越小减速效果越明显
            circleCount: 3, // 额外旋转圈数
            showModal: true, // 是否显示结果弹窗

            // 弹窗内容配置
            modalContents: [
                '恭喜您获得了奖品!',
                '太棒了!您中奖了!',
                '好运连连!'
            ],

            // 指定中奖项配置
            forceWinner: false, // 是否强制指定中奖项
            winnerIndex: 0, // 指定的中奖项索引

            // 事件日志
            eventLogs: [],

            // 中奖选项列表(用于选择器)
            winnerOptions: [
                { index: -1, name: '随机抽取' }
            ]
        };
    },

    created() {
        // 初始化中奖选项列表
        this.initWinnerOptions();
    },

    methods: {
        /**
         * 初始化中奖选项列表
         */
        initWinnerOptions() {
            // 先添加随机选项
            this.winnerOptions = [{ index: -1, name: '随机抽取' }];

            // 添加所有奖品选项
            this.lotteryOptions.forEach((item, index) => {
                this.winnerOptions.push({
                    index: index,
                    name: item.name
                });
            });
        },

        /**
         * 设置中奖者的钩子函数
         * @param {Array} options - 当前抽奖选项列表
         * @returns {Number} - 返回中奖索引,-1表示随机抽取
         */
        setWinner(options) {
            // 记录日志
            this.addLog('调用 setWinnerFn 钩子函数');

            // 如果强制指定中奖项且索引有效,则返回指定索引
            if (this.forceWinner && this.winnerIndex > 0) {
                const selectedIndex = this.winnerOptions[this.winnerIndex].index;
                this.addLog(`指定中奖项: ${options[selectedIndex].name}`);
                return selectedIndex;
            }

            // 返回-1表示随机抽取
            this.addLog('未指定中奖项,将随机抽取');
            return -1;
        },

        /**
         * 抽奖开始前的钩子函数
         */
        onBeforePlay() {
            this.addLog('抽奖开始');
        },

        /**
         * 抽奖结束后的钩子函数
         * @param {Object} result - 抽奖结果
         */
        onAfterPlay(result) {
            this.addLog(`抽奖结束,结果: ${result.name}`);
        },

        /**
         * 重置转盘
         */
        resetLottery() {
            // 重新初始化转盘
            this.$refs.lotteryRef.init();
            this.addLog('转盘已重置');
        },

        /**
         * 添加日志
         * @param {String} message - 日志消息
         */
        addLog(message) {
            const now = new Date();
            const timeStr = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`;
            this.eventLogs.unshift(`[${timeStr}] ${message}`);
        },

        /**
         * 清空日志
         */
        clearLogs() {
            this.eventLogs = [];
        },

        /**
         * 中奖项选择变更
         */
        onWinnerChange(e) {
            this.winnerIndex = e.detail.value;
            this.forceWinner = this.winnerIndex > 0;

            if (this.forceWinner) {
                const selectedIndex = this.winnerOptions[this.winnerIndex].index;
                this.addLog(`已设置指定中奖项: ${this.lotteryOptions[selectedIndex].name}`);
            } else {
                this.addLog('已设置为随机抽取');
            }
        }
    }
}
</script>

<style>
.container {
    padding: 20rpx;
}

.title {
    font-size: 36rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
}

.config-panel {
    margin-top: 40rpx;
    padding: 20rpx;
    border-radius: 10rpx;
    background-color: #f8f8f8;
}

.panel-title,
.log-title {
    font-size: 30rpx;
    font-weight: bold;
    margin-bottom: 20rpx;
    border-bottom: 1px solid #eee;
    padding-bottom: 10rpx;
}

.config-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20rpx;
}

.picker-value {
    padding: 10rpx 20rpx;
    background-color: #fff;
    border-radius: 6rpx;
    min-width: 200rpx;
    text-align: center;
}

.reset-btn {
    margin: 20rpx 0;
}

.event-log {
    margin-top: 40rpx;
    padding: 20rpx;
    border-radius: 10rpx;
    background-color: #f8f8f8;
}

.log-content {
    height: 300rpx;
    background-color: #fff;
    padding: 10rpx;
    border-radius: 6rpx;
    margin-bottom: 20rpx;
}

.log-item {
    font-size: 24rpx;
    padding: 6rpx 0;
    border-bottom: 1px dashed #eee;
}
</style>