| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 | /** * ease: * 'linear'  动画从头到尾的速度是相同的 * 'ease'  动画以低速开始,然后加快,在结束前变慢 * 'ease-in'  动画以低速开始 *  * 'ease-in-out'  动画以低速开始和结束 * 'ease-out'  动画以低速结束 * 'step-start'  动画第一帧就跳至结束状态直到结束 * 'step-end'  动画一直保持开始状态,最后一帧跳到结束状态 */let config = {    size: {        width: '560rpx',        height: '560rpx'    }, // 转盘宽高    bgColors: ['#FFC53F', '#FFED97'], // 转盘间隔背景色 支持多种颜色交替    fontSize: 12, // 文字大小    fontColor: '#C31A34', // 文字颜色    nameMarginTop: 12, // 最外文字边距    nameLength: 6, // 最外文字个数    iconWidth: 32, // 图标宽度    iconHeight: 32, // 图标高度    iconAndTextPadding: 4, // 最内文字与图标的边距    duration: 3000, // 转盘转动动画时长    rate: 1.5, // 由时长s / 圈数得到    border: 'border: 10rpx solid #FEFAE4;', // 转盘边框    ease: 'ease-out' // 转盘动画};let preAngle = 0; // 上一次选择角度let preAngle360 = 0; // 上一次选择角度和360度之间的差let retryCount = 10; // 报错重试次数let retryTimer; // 重试setTimeoutlet drawTimer; // 绘制setTimeoutComponent({    properties: {        // 是否可用        enable: {            type: Boolean,            value: true        },        // 数据        gifts: {            type: Array,            value: []        },        //  中奖id        prizeId: {            type: String,            value: ''        },        // 配置项 传入后和默认的配置进行合并        config: {            type: Object,            value: {}        },        // 抽奖次数        count: {            type: Number,            default: ""        },    },    data: {        lotteryCount: null,        cost: null,        turnCanvasInfo: { width: 0, height: 0 },        size: config.size,        giftModule: [],        disable: false,        canvasImgUrl: '',        border: config.border,        infos: []    },    methods: {        async getCanvasContainerInfo(id) {            return new Promise((resolve) => {                const query = wx.createSelectorQuery().in(this);                query.select(id).boundingClientRect(function (res) {                    const { width, height } = res;                    resolve({ width, height });                }).exec();            });        },        async init() {            try {                const info = await this.getCanvasContainerInfo('#turn');                if (info.width && info.height) {                    this.setData({                        turnCanvasInfo: info                    });                    this.drawTurn();                } else {                    wx.showToast({                        icon: 'nont',                        title: '获取转盘宽高失败'                    })                }            } catch (e) {                if (retryCount <= 0) {                    return;                }                retryCount--;                if (retryTimer) {                    clearTimeout(retryTimer);                }                retryTimer = setTimeout(async () => {                    await this.init();                }, 100);            }        },        drawTurn() {            const turnCanvasInfo = this.data.turnCanvasInfo;            const giftModule = this.properties.gifts;            const ctx = wx.createCanvasContext('turn', this);            // 计算没个扇区弧度            const radian = Number((2 * Math.PI / giftModule.length).toFixed(2));            // 绘制扇区并记录每个扇区信息            const infos = this.drawSector(radian, giftModule, ctx, turnCanvasInfo);            // 记录旋转角度            this.recordTheRotationAngle(infos);            // 绘制扇区文本及图片            this.drawTextAndImage(giftModule, ctx, turnCanvasInfo, radian);            ctx.draw(false, () => {                this.saveToTempPath(turnCanvasInfo);            });        },        saveToTempPath(turnCanvasInfo) {            if (drawTimer) {                clearTimeout(drawTimer);            }            drawTimer = setTimeout(() => {                wx.canvasToTempFilePath({                    canvasId: 'turn',                    quality: 1,                    x: 0,                    y: 0,                    width: turnCanvasInfo.width,                    height: turnCanvasInfo.height,                    success: (res) => {                        this.setData({                            canvasImgUrl: res.tempFilePath                        });                    },                    fail: (error) => {                        console.log(error);                    }                }, this);            }, 500);        },        drawSector(radian, giftModule, ctx, turnCanvasInfo) {            const halfRadian = Number((radian / 2).toFixed(2));            let startRadian = -Math.PI / 2 - halfRadian;            const angle = 360 / giftModule.length;            const halfAngle = angle / 2;            let startAngle = -90 - halfAngle;            const infos = [];            // 绘制扇形            for (let i = 0; i < giftModule.length; i++) {                // 保存当前状态                ctx.save();                // 开始一条新路径                ctx.beginPath();                ctx.moveTo(turnCanvasInfo.width / 2, turnCanvasInfo.height / 2);                ctx.arc(turnCanvasInfo.width / 2, turnCanvasInfo.height / 2, turnCanvasInfo.width / 2, startRadian, startRadian + radian);                if (giftModule[i].bgColor) {                    ctx.setFillStyle(giftModule[i].bgColor);                } else {                    ctx.setFillStyle(config.bgColors[i % config.bgColors.length]);                }                ctx.fill();                ctx.closePath();                ctx.restore();                infos.push({                    id: giftModule[i].objectId,                    angle: (startAngle + startAngle + angle) / 2                });                startRadian += radian;                startAngle += angle;            }            return infos;        },        drawTextAndImage(giftModule, ctx, turnCanvasInfo, radian) {            let startRadian = 0;            // 绘制扇形文字和logo            for (let i = 0; i < giftModule.length; i++) {                // 保存当前状态                ctx.save();                // 开始一条新路径                ctx.beginPath();                ctx.translate(turnCanvasInfo.width / 2, turnCanvasInfo.height / 2);                ctx.rotate(startRadian);                ctx.translate(-turnCanvasInfo.width / 2, -turnCanvasInfo.height / 2);                if (giftModule[i].fontSize) {                    ctx.setFontSize(giftModule[i].fontSize);                } else {                    ctx.setFontSize(config.fontSize);                }                ctx.setTextAlign('center');                if (giftModule[i].fontColor) {                    ctx.setFillStyle(giftModule[i].fontColor);                } else {                    ctx.setFillStyle(config.fontColor);                }                ctx.setTextBaseline('top');                if (giftModule[i].name) {                    ctx.fillText(giftModule[i].name, turnCanvasInfo.width / 2, config.nameMarginTop);                }                if (giftModule[i].subname) {                    ctx.fillText(giftModule[i].subname ? giftModule[i].subname : '', turnCanvasInfo.width / 2, config.nameMarginTop + config.fontSize + 2);                }                if (giftModule[i].imgUrl) {                    ctx.drawImage(giftModule[i].imgUrl,                        turnCanvasInfo.width / 2 - config.iconWidth / 2,                        config.nameMarginTop + config.fontSize * 2 + 2 + config.iconAndTextPadding,                        config.iconWidth, config.iconHeight);                }                ctx.closePath();                ctx.restore();                startRadian += radian;            }        },        recordTheRotationAngle(infos) {            for (let i = infos.length - 1; i >= 0; i--) {                infos[i].angle -= infos[0].angle;                infos[i].angle = 360 - infos[i].angle;            }            // 记录id及滚动的角度            this.setData({                infos: infos            });        },        luckDrawHandle() {            if (this.data.disable || !this.data.canvasImgUrl) {                return;            }            this.setData({                disable: true            });            console.log('开始抽奖')            this.triggerEvent('LuckDraw');        },        startAnimation(angle) {            if (this.data.lotteryCount - this.data.cost < 0) {                this.setData({                    disable: false                });                this.triggerEvent('NotEnough', '积分不足!');                return;            }            // 抽奖次数减一            this.setData({                lotteryCount: this.data.lotteryCount - this.data.cost            });            const currentAngle = preAngle;            preAngle += Math.floor((config.duration / 1000) / config.rate) * 360 + angle + preAngle360;            this.animate('#canvas-img', [                { rotate: currentAngle, ease: 'linear' },                { rotate: preAngle, ease: config.ease },            ], config.duration, () => {                this.setData({                    disable: false                });                preAngle360 = 360 - angle;                this.triggerEvent('LuckDrawFinish');            });        },        downloadHandle(url) {            return new Promise((resolve, reject) => {                wx.downloadFile({                    url: url, // 仅为示例,并非真实的资源                    success: (res) => {                        // 只要服务器有响应数据,就会把响应内容写入文件并进入 success 回调,业务需要自行判断是否下载到了想要的内容                        if (res.statusCode === 200) {                            resolve(res.tempFilePath);                        } else {                            reject();                        }                    },                    fail: () => {                        reject();                    }                });            });        },        async downloadImg(imgs) {            let result;            try {                const downloadHandles = [];                for (const url of imgs) {                    if (this.isAbsoluteUrl(url)) { // 是网络地址                        downloadHandles.push(this.downloadHandle(url));                    } else {                        downloadHandles.push(Promise.resolve(url));                    }                }                result = await Promise.all(downloadHandles);            } catch (e) {                console.log(e);                result = [];            }            return result;        },        clearTimeout() {            if (retryTimer) {                clearTimeout(retryTimer);            }            if (drawTimer) {                clearTimeout(drawTimer);            }        },        isAbsoluteUrl(url) {            return /(^[a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);        },        async initData(data) {            let name;            let subname;            let imgUrls = [];            if (this.properties.config) {                config = Object.assign(config, this.properties.config);            }            for (const d of data) {                name = d.name;                imgUrls.push(d.imgUrl);                d.imgUrl = '';                if (name.length > config.nameLength) {                    d.name = name.slice(0, config.nameLength);                    subname = name.slice(config.nameLength);                    if (subname.length > config.nameLength - 2) {                        d['subname'] = subname.slice(0, config.nameLength - 2) + '...';                    } else {                        d['subname'] = subname;                        /*   console.log('是否开启了概率???', that.data.probability);                          //开启概率 probability这属性必须要传个ture                          if (that.data.probability) {                              r = that._openProbability();                          } */                    }                }            }            imgUrls = await this.downloadImg(imgUrls);            for (let i = 0; i < imgUrls.length; i++) {                data[i].imgUrl = imgUrls[i];            }            this.setData({                giftModule: data            });            await this.init();        }    },    observers: {        'gifts': async function (gifts) {            if (!gifts || !gifts.length) {                return;            }            await this.initData(gifts);        },        'enable': function (enable) {            this.setData({                disable: !enable            });        },        'prizeId': function (id) {            if (!id) {                this.setData({                    disable: false                });                return;            }            try {                const infos = this.data.infos;                console.log(infos, id)                const info = infos.find((item) => item.id == id);                console.log(info)                this.startAnimation(info.angle);            } catch (e) {                this.setData({                    disable: false                });            }        },        'count': function (lotteryCount) {            console.log(lotteryCount)            this.setData({                lotteryCount            });        },        // 'cost': function(cost) {        //     console.log(cost)        //     this.setData({        //         cost        //     });        // },    },    lifetimes: {        detached() {            this.clearTimeout();        }    },    pageLifetimes: {        hide() {            this.clearTimeout();        }    }});
 |