123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704 |
- <template>
- <view class="main" @click="end" >
- <!-- <view class="title-container">
- {{name}}
- </view> -->
- <view v-if="options.length>0" class="canvas-container">
- <canvas canvas-id="canvas" id="canvas" :style="canvasStyle" />
-
- </view>
- <!-- <image src="./national-2024-1.png"
- style="width: 560rpx; height: 560rpx;z-index: 999; margin-top: -560rpx;"
- ></image> -->
- <image src="./national-2024-7.png"
- style="width: 660rpx; height: 640rpx;z-index: 999; margin-top: -600rpx;"
- ></image>
- <image src="./national-2024-6-2.png"
- style="width: 660rpx; height: 500rpx; margin-top: -160rpx;"
- ></image>
- <view class="show1" style=" position: relative;
- top: -280rpx;
- color: rgba(157, 58, 15, 1);font-weight: bold;
- font-size: 56rpx;" >
- {{name}}
- </view>
- <view class="show1" @click="clickBtn"
-
- style=" position: relative;
- top: -222rpx;font-weight: bold;
- color: #FFF;
- font-size: 56rpx;" >
- 点击抽奖
- </view>
-
- </view>
- </template>
- <script>
- //var img2=require('./national-2024-9.png')
- var img3=require('./national-2024-8.png')
- var ctx = null;
- export default {
- props: {
- // 弹窗内容 ,当 turnModalContent=【】时,默认弹窗内容为抽奖结果,并且不展示标题
- turnModalContent: {
- type: Array,
-
- default () {
- return []
- }
- },
- // 减速,值越小,减速效果越明显 turnReduceSpeed
- turnReduceSpeed: {
- type: Number,
- default: 50
- },
- // 表示转速:表示再转多少圈再进行抽奖
- turnCircle: {
- type: Number,
- default: 0
- },
- // 转盘名称
- name: {
- type: String,
- default: "点击下方抽奖按钮"
- },
- // 画布宽度
- 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: "开始"
- }
- },
- data() {
- return {
- //img2:img2,
- img3:img3,
- options: [{
- id: 0, // 唯一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"
- }
-
- ],
- isLottery: false, // 是否正在抽奖
- };
- },
- computed: {
- canvasStyle() {
- return "width:" + this.width + "rpx; height:" + this.height + "rpx;";
- },
- },
- methods: {
- clickBtn(){
- this.$emit("clickBtn")
- },
- end(){
- if (this.isLottery) {
-
- uni.showToast({
- title: "摇奖中...",
- icon: "none"
- })
- }else{
- this.$emit("end")
- }
- },
- async playReward() {
- if (this.isLottery) {
- return
- }
- this.isLottery = true
- let len = this.options.length
- if (len == 0) {
- return;
- }
- //console.log("playReward")
- // if (this.beforePlay) {
- // () => this.beforePlay()
- // this.beforePlay()
- this.$emit("beforePlay")
- // }
- let num = -1;
- if (this.setWinnerFn) {
- // 自定义抽奖结果
- let res = () => this.setWinnerFn(this.options)
- if (res != undefined && res >= 0) {
- num = res
- }
-
- }
- if (num < 0) {
- // 进行权重抽奖
- let optionIndex = this.lotteryWeight(this.options)
- if (optionIndex != undefined || optionIndex >= 0) {
- num = optionIndex
- }
- }
- if (num < 0) {
- // 没有自动抽奖结果 && 没有定义选项权重,则进行随机数抽奖
- num = Math.floor(Math.random() * len)
- }
- const result = await this.roateCanvas(num)
- //console.log("抽奖结果:", result.name)
- this.name=result.name
- this.isLottery = false
- 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)]
- }
- this.$emit("playRewardEnd",result.name)
- },
- initBtnCanvas() {
- let angleTo = 0
- let ctx = uni.createCanvasContext("canvasBtn", this);
- // 6. 画中心点圆
- // 圆中心点的坐标 x: 宽度的一半
- let center_x = uni.upx2px(this.width) / 2;
- // 圆中心点的坐标 y: 高度的一半
- let center_y = uni.upx2px(this.height) / 2;
- // 1. 先清除画布上在该矩形区域内的内容
- ctx.clearRect(0, 0, uni.upx2px(this.width), uni.upx2px(this.height));
- ctx.translate(center_x, center_y);
- // 6. 画中心点圆
- ctx.beginPath();
- // ctx.arc(0, 0, 15, 0, Math.PI * 2); // 15 为中心点圆的半径
- ctx.moveTo(0, -50); // 三角形顶点坐标
- ctx.lineTo(-20, 10); // 左下角坐标
- ctx.lineTo(20, 10); // 右下角坐标
- ctx.setFillStyle("#ff0000");
- ctx.fill();
- ctx.draw();
- },
- initCanvas: function(ctx, angleTo) {
- const len = this.options.length; //数组长度
- if (len == 0) {
- //console.log("options len == 0")
- return;
- }
- if (!angleTo) {
- angleTo = 0
- }
- // 圆中心点的坐标 x: 宽度的一半
- let center_x = uni.upx2px(this.width) / 2;
- // 圆中心点的坐标 y: 高度的一半
- let center_y = uni.upx2px(this.height) / 2;
- // 圆的弧度的总度数,2π表示画圆
- let totalAngle = 2 * Math.PI;
- // 平均一个选项占用的孤度数
- let avgAngle = totalAngle / len;
- let radius = center_x - 14;
- let fontSize = this.getFontSize()
- // 1. 先清除画布上在该矩形区域内的内容
- ctx.clearRect(0, 0,uni.upx2px( this.width),uni.upx2px( this.height));
- ctx.translate(center_x, center_y);
- // 2. 设置画布内字体大小
- ctx.setFontSize(fontSize);
- ctx.setLineWidth(14);
- ctx.save();
- // 3. 画外圆
- ctx.rotate(angleTo * Math.PI / 180);
- var beginAngle = 2 * Math.PI / 360 * (-90);
- // ctx.setStrokeStyle("#ffaa00");
- ctx.setStrokeStyle("#ffffff");
- ctx.arc(0, 0, radius - 3, 0, Math.PI * 2);
- ctx.stroke();
- // 4. 划分区域,并且填充颜色
- 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.stroke();
-
- beginAngle = beginAngle + avgAngle;
- ctx.fill();
- ctx.save();
-
-
-
- }
-
- // 5. 绘制选项文字
- beginAngle = 0; //avgAngle / 2;
- for (var i = 0; i < len; i++) {
- var sz=this.options[i].nameText
- var key=ctx.font
- for (var j = 0; j < sz.length; j++) {
- var ry = -(center_x / 2) - 25;
- //绘制旋转文字
- ctx.rotate((beginAngle + (avgAngle * 0.5))); //顺时针旋转
- ctx.setTextAlign("center");
- ctx.setFillStyle("#9D3A0F");
- ctx.font=uni.upx2px(32)+"px sans-serif"
- ctx.fillText(sz[j], 0, ry+(20*j+4));
- ctx.restore();
- }
-
- ctx.rotate((beginAngle + (avgAngle * 0.5))); //顺时针旋转
- if(this.options[i].img==1){
-
- ctx.setTextAlign("center");
- ctx.setFillStyle("red");
- ctx.font='bold '+uni.upx2px(36)+"px sans-serif"
- ctx.fillText("1张", -2, ry+(22*2));
- ctx.restore();
- }else{
- ctx.drawImage(this.img3,uni.upx2px(-17*2) ,uni.upx2px( -90*2), uni.upx2px(64), uni.upx2px(64));
- ctx.restore();
- }
- ctx.font = key;
- ctx.save();
-
- beginAngle = beginAngle + avgAngle;
- }
-
- var img = new Image();
- // img.onload = function() {
-
- //beginAngle = 0; //avgAngle / 2;
- for (var i = 0; i < len; i++) {
-
-
- var ry = -(center_x / 2) - 25;
- //绘制旋转文字
- ctx.rotate(beginAngle ); //顺时针旋转
-
-
- if(this.options[i].img==1){
- //ctx.drawImage(this.img2,uni.upx2px(-17*2) ,uni.upx2px( -90*2), uni.upx2px(64), uni.upx2px(64));
- }else{
- }
-
-
- //ctx.restore();
- beginAngle = beginAngle + avgAngle;
- }
- //
- //ctx.draw();
-
- // }
-
- // img.src=this.img3
-
-
-
-
- ctx.save();
- // 6. 画中心点圆
- ctx.beginPath();
- ctx.arc(0, 0, 15, 0, Math.PI * 2); // 15 为中心点圆的半径
- ctx.setFillStyle("#FFFFFF");
- ctx.fill();
- ctx.draw();
- },
- // 根据设置的权重进行抽奖
- lotteryWeight(prizes) {
- if (!prizes || prizes.length == 0) {
- //console.log("奖品列表为空")
- return -1
- }
- let winPrizesIndex = [] // 抽中的奖品下标,多个是因为如果存在多个奖品的权重一致的情况,则再进行随机抽奖
- let winPrizeWeight = 0; // 抽中的奖品的权重
- let round = Math.random() * 100; // 生成一个 0-100的随机数
- //console.log("lotteryWeight 生成的 round:", round)
- for (let index = 0; index < prizes.length; index++) {
- let prize = prizes[index];
- let weight = prize['weight']
- if (!weight) {
- // 没有设置权重,则跳过
- //console.log("奖品 ", prize.name, " 未设置权重,则不参与抽奖")
- continue
- }
- if (weight <= 0) {
- // 如果奖品的权重设置<0,则表示不参与抽奖
- //console.log("奖品 ", prize.name, " 不参与抽奖")
- continue
- }
- if (round > weight) {
- // 随机数超过了权重,则未抽中
- continue
- }
- if (weight < winPrizeWeight) {
- // 权重比之前抽中的还小,则跳过
- //console.log("奖品 ", prize.name, "小于已经抽中的奖品", winPrizeWeight, " 不参与抽奖")
- continue
- }
- if (weight == winPrizeWeight) {
- // 本次抽中的奖品和已经抽中的奖品权重一致,则加入抽中列表
- //console.log("再抽中奖品 ", prize.name, " 中奖")
- winPrizesIndex.push(index)
- continue
- }
- if (weight > winPrizeWeight) {
- // 权重比之前抽中的还大,则重置抽中奖品
- winPrizesIndex = [index]
- winPrizeWeight = weight
- //console.log("奖品 ", prize.name, " 中奖")
- continue
- }
- }
- if (winPrizesIndex.length <= 0) {
- // 本次没有抽中奖品
- //console.log("本次没有抽中奖品")
- return -1
- }
- if (winPrizesIndex.length == 1) {
- // 只抽中了一个奖品,这直接返回
- let index = winPrizesIndex[0];
- //console.log("奖品 ", prizes[index], " 抽中");
- return index
- }
- if (winPrizesIndex.length > 1) {
- // 抽中多个,则再进行随机抽奖
- //console.log(" 抽中多个,则再进行随机抽奖");
- let index = round % winPrizesIndex.length
- index = Math.floor(index)
- //console.log("再次抽中结果:", winPrizesIndex[index])
- return winPrizesIndex[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() {
-
- 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 uni.upx2px(fontSize*2)
- },
- // 生成随机颜色
- 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() {
- let defaultOptions = this.options
- if (this.isUseDefaultOptions) {
- // 使用默认数据
- } else {
- this.options = this.data
- }
- // 所有默认的颜色
- let allDefColorArr = defaultOptions.map(item => item.color);
- // 找到最大的 id
- let maxId = -Infinity;
- this.options.forEach(item => {
- if (!item.id) {
- return
- }
- if (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 重复了
- this.options[i]['id'] = maxId + 1
- }
- existIds.push(this.options[i].id)
- if (item['color'] == undefined || item.color == "") {
- }
- }
- // 填充颜色,确保颜色唯一
- 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'] == undefined || item.color == "") {
- continue
- }
- let color = item.color
- existColor.push(color)
- // 过滤掉已经用了的 color
- availableColor = availableColor.filter(item => item !== color);
- }
- // 剩下的 allDefColorArr 都是可用的颜色
- for (var i = 0; i < this.options.length; i++) {
- let item = this.options[i]
- if (item['color'] == undefined || item.color == "") {
- if (availableColor.length == 0) {
- // 没有可用颜色了,则随机生成一个
- let color = ''
- for (var j = 0; j < 100; j++) {
- if (color != '') {
- continue
- }
- let genColor = this.genRandColor()
- if (!existColor.includes(genColor)) {
- existColor.push(color)
- color = genColor
- }
- }
- this.options[i]['color'] = color
- continue
- }
- const color = availableColor.shift();
- existColor.push(color)
- this.options[i].color = color
- }
- }
- },
- init() {
- this.initOptions();
- this.ctx = uni.createCanvasContext("canvas", this);
- this.initCanvas(this.ctx, 0);
- this.initBtnCanvas();
- //this.playReward()
- }
- },
- // 初始化画布
- 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: 50rpx;
- margin-top: 120rpx;
- margin-bottom: 40rpx;
- z-index: 100;
- }
- .canvas-container {
- position: relative;
- width: fit-content;
- height: fit-content;
- z-index: 99;
- }
- .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::before {
- content: "";
- position: absolute;
- top: -15px;
- left: 50%;
- transform: translateX(-50%);
- width: 0;
- height: 0;
- border-left: 10px solid transparent;
- border-right: 10px solid transparent;
- border-bottom: 20px solid #ffffff;
- } */
- .canvas-btn.isLottery {
- background-color: #CCCCCC;
- }
- /* .canvas-btn.isLottery {
- pointer-events: none;
- background: #CCCCCC;
- color: #ccc;
- } */
- /* .canvas-btn.isLottery::before {
- border-bottom-color: #CCCCCC;
- }
- .canvas-btn.isLottery::after {
- border-bottom-color: #CCCCCC;
- } */
- </style>
|