You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
956 lines
28 KiB
956 lines
28 KiB
<template>
|
|
<view class="almost-lottery">
|
|
<view class="almost-lottery__wrap" :style="{ width: lotterySize + 'rpx', height: lotterySize + 'rpx' }">
|
|
<view class="lottery-action"
|
|
:style="{ width: actionSize + 'rpx', height: actionSize + 'rpx', left: canvasMarginOutside + 'rpx' }">
|
|
</view>
|
|
<view class="str-margin-outside" :style="{ left: strMarginOutside + 'rpx' }"></view>
|
|
<view class="img-margin-str" :style="{ left: imgMarginStr + 'rpx' }"></view>
|
|
<view class="img-size" :style="{ width: imgWidth + 'rpx', height: imgHeight + 'rpx' }"></view>
|
|
<template v-if="lotteryImg">
|
|
<image class="almost-lottery__bg" mode="widthFix" :src="lotteryBg" :style="{
|
|
width: lotteryPxSize + 'px',
|
|
height: lotteryPxSize + 'px'
|
|
}"></image>
|
|
<image class="almost-lottery__canvas-img" mode="widthFix" :src="lotteryImg" :style="{
|
|
width: canvasPxSize + 'px',
|
|
height: canvasPxSize + 'px',
|
|
transform: `rotate(${canvasAngle + targetAngle}deg)`,
|
|
transitionDuration: `${transitionDuration}s`
|
|
}"></image>
|
|
<image class="almost-lottery__action almost-lottery__action-bg" mode="widthFix" :src="actionBg" :style="{
|
|
width: actionPxSize + 'px',
|
|
height: actionPxSize + 'px',
|
|
transform: `rotate(${actionAngle + targetActionAngle}deg)`,
|
|
transitionDuration: `${transitionDuration}s`
|
|
}" @click="handleActionStart"></image>
|
|
</template>
|
|
|
|
<!-- 正在绘制转盘时的提示文本 -->
|
|
<text class="almost-lottery__tip" v-else>{{ almostLotteryTip }}</text>
|
|
</view>
|
|
|
|
<!-- 为了兼容 app 端 ctx.measureText 所需的标签 -->
|
|
<text class="almost-lottery__measureText" :style="{ fontSize: higtFontSize + 'px' }">{{ measureText }}</text>
|
|
|
|
<!-- #ifdef MP-ALIPAY -->
|
|
<canvas :class="className" :id="canvasId" :width="higtCanvasSize" :height="higtCanvasSize" :style="{
|
|
width: higtCanvasSize + 'px',
|
|
height: higtCanvasSize + 'px'
|
|
}" />
|
|
<!-- #endif -->
|
|
<!-- #ifndef MP-ALIPAY -->
|
|
<canvas :class="className" :canvas-id="canvasId" :width="higtCanvasSize" :height="higtCanvasSize" :style="{
|
|
width: higtCanvasSize + 'px',
|
|
height: higtCanvasSize + 'px'
|
|
}" />
|
|
<!-- #endif -->
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
const systemInfo = uni.getSystemInfoSync()
|
|
import {
|
|
getStore,
|
|
setStore,
|
|
clearStore,
|
|
clacTextLen,
|
|
downloadFile,
|
|
pathToBase64
|
|
} from '@/uni_modules/almost-lottery/utils/almost-utils.js'
|
|
export default {
|
|
name: 'AlmostLottery',
|
|
props: {
|
|
// 设计稿的像素比基准值
|
|
pixelRatio: {
|
|
type: Number,
|
|
default: 2
|
|
},
|
|
// canvas 标识
|
|
canvasId: {
|
|
type: String,
|
|
default: 'almostLottery'
|
|
},
|
|
// 抽奖转盘的整体尺寸
|
|
lotterySize: {
|
|
type: Number,
|
|
default: 600
|
|
},
|
|
// 抽奖按钮的尺寸
|
|
actionSize: {
|
|
type: Number,
|
|
default: 200
|
|
},
|
|
// canvas边缘距离转盘边缘的距离
|
|
canvasMarginOutside: {
|
|
type: Number,
|
|
default: 90
|
|
},
|
|
// 奖品列表
|
|
prizeList: {
|
|
type: Array,
|
|
required: true,
|
|
validator: (value) => {
|
|
return value.length > 1
|
|
}
|
|
},
|
|
// 中奖奖品在列表中的下标
|
|
prizeIndex: {
|
|
type: Number,
|
|
required: true
|
|
},
|
|
// 奖品区块对应背景颜色
|
|
colors: {
|
|
type: Array,
|
|
default: () => [
|
|
'#FFFFFF',
|
|
'#FFBF05'
|
|
]
|
|
},
|
|
// 转盘外环背景图
|
|
lotteryBg: {
|
|
type: String,
|
|
default: '/uni_modules/almost-lottery/static/almost-lottery/almost-lottery__bg2x.png'
|
|
},
|
|
// 抽奖按钮背景图
|
|
actionBg: {
|
|
type: String,
|
|
default: '/uni_modules/almost-lottery/static/almost-lottery/almost-lottery__action2x.png'
|
|
},
|
|
// 是否绘制奖品名称
|
|
prizeNameDrawed: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
// 是否开启奖品区块描边
|
|
stroked: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 描边颜色
|
|
strokeColor: {
|
|
type: String,
|
|
default: '#FFBF05'
|
|
},
|
|
// 旋转的类型
|
|
rotateType: {
|
|
type: String,
|
|
default: 'roulette'
|
|
},
|
|
// 旋转动画时间 单位s
|
|
duration: {
|
|
type: Number,
|
|
default: 8
|
|
},
|
|
// 旋转的圈数
|
|
ringCount: {
|
|
type: Number,
|
|
default: 8
|
|
},
|
|
// 指针位置
|
|
pointerPosition: {
|
|
type: String,
|
|
default: 'edge',
|
|
validator: (value) => {
|
|
return value === 'edge' || value === 'middle'
|
|
}
|
|
},
|
|
// 文字方向
|
|
strDirection: {
|
|
type: String,
|
|
default: 'horizontal',
|
|
validator: (value) => {
|
|
return value === 'horizontal' || value === 'vertical'
|
|
}
|
|
},
|
|
// 字体颜色
|
|
strFontColors: {
|
|
type: Array,
|
|
default: () => [
|
|
'#FFBF05',
|
|
'#FFFFFF'
|
|
]
|
|
},
|
|
// 文字的大小
|
|
strFontSize: {
|
|
type: Number,
|
|
default: 24
|
|
},
|
|
// 奖品文字距离边缘的距离
|
|
strMarginOutside: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
// 奖品图片距离奖品文字的距离
|
|
imgMarginStr: {
|
|
type: Number,
|
|
default: 40
|
|
},
|
|
// 奖品文字多行情况下的行高
|
|
strLineHeight: {
|
|
type: Number,
|
|
default: 1.2
|
|
},
|
|
// 奖品文字总长度限制
|
|
strMaxLen: {
|
|
type: Number,
|
|
default: 12
|
|
},
|
|
// 奖品文字多行情况下第一行文字长度
|
|
strLineLen: {
|
|
type: Number,
|
|
default: 6
|
|
},
|
|
// 奖品图片的宽
|
|
imgWidth: {
|
|
type: Number,
|
|
default: 80
|
|
},
|
|
// 奖品图片的高
|
|
imgHeight: {
|
|
type: Number,
|
|
default: 80
|
|
},
|
|
// 是否绘制奖品图片
|
|
imgDrawed: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
// 转盘绘制成功的提示
|
|
successMsg: {
|
|
type: String,
|
|
default: '奖品准备就绪,快来参与抽奖吧'
|
|
},
|
|
// 转盘绘制失败的提示
|
|
failMsg: {
|
|
type: String,
|
|
default: '奖品仍在准备中,请稍后再来...'
|
|
},
|
|
// 是否开启画板的缓存
|
|
canvasCached: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
// 画板className
|
|
className: 'almost-lottery__canvas',
|
|
// 抽奖转盘的整体px尺寸
|
|
lotteryPxSize: 0,
|
|
// 画板的px尺寸
|
|
canvasPxSize: 0,
|
|
// 抽奖按钮的px尺寸
|
|
actionPxSize: 0,
|
|
// 奖品文字距离转盘边缘的距离
|
|
strMarginPxOutside: 0,
|
|
// 奖品图片相对奖品文字的距离
|
|
imgMarginPxStr: 0,
|
|
// 奖品图片的宽、高
|
|
imgPxWidth: 0,
|
|
imgPxHeight: 0,
|
|
// 画板导出的图片
|
|
lotteryImg: '',
|
|
// 旋转到奖品目标需要的角度
|
|
targetAngle: 0,
|
|
targetActionAngle: 0,
|
|
// 旋转动画时间 单位 s
|
|
transitionDuration: 0,
|
|
// 是否正在旋转
|
|
isRotate: false,
|
|
// 当前停留在那个奖品的序号
|
|
stayIndex: 0,
|
|
// 当前中奖奖品的序号
|
|
targetIndex: 0,
|
|
// 是否存在可用的缓存转盘图
|
|
isCacheImg: false,
|
|
oldLotteryImg: '',
|
|
// 转盘绘制时的提示
|
|
almostLotteryTip: '奖品准备中...',
|
|
// 解决 app 不支持 measureText 的问题
|
|
// app 已在 2.9.3 的版本中提供了对 measureText 的支持,将在后续版本逐渐稳定后移除相关兼容代码
|
|
measureText: ''
|
|
}
|
|
},
|
|
computed: {
|
|
// 高清尺寸
|
|
higtCanvasSize() {
|
|
return this.canvasPxSize * systemInfo.pixelRatio
|
|
},
|
|
// 高清字体
|
|
higtFontSize() {
|
|
return (this.strFontSize / this.pixelRatio) * systemInfo.pixelRatio
|
|
},
|
|
// 高清行高
|
|
higtHeightMultiple() {
|
|
return (this.strFontSize / this.pixelRatio) * this.strLineHeight * systemInfo.pixelRatio
|
|
},
|
|
// 根据奖品列表计算 canvas 旋转角度
|
|
canvasAngle() {
|
|
let result = 0
|
|
|
|
let prizeCount = this.prizeList.length
|
|
let prizeClip = 360 / prizeCount
|
|
let diffNum = 90 / prizeClip
|
|
if (this.pointerPosition === 'edge' || this.rotateType === 'pointer') {
|
|
result = -(prizeClip * diffNum)
|
|
} else {
|
|
result = -(prizeClip * diffNum + prizeClip / 2)
|
|
}
|
|
return result
|
|
},
|
|
actionAngle() {
|
|
return 0
|
|
},
|
|
// 外圆的半径
|
|
outsideRadius() {
|
|
return this.higtCanvasSize / 2
|
|
},
|
|
// 内圆的半径
|
|
insideRadius() {
|
|
return 20 * systemInfo.pixelRatio
|
|
},
|
|
// 文字距离边缘的距离
|
|
textRadius() {
|
|
return this.strMarginPxOutside * systemInfo.pixelRatio || (this.higtFontSize / 2)
|
|
},
|
|
// 根据画板的宽度计算奖品文字与中心点的距离
|
|
textDistance() {
|
|
const textZeroY = Math.round(this.outsideRadius - (this.insideRadius / 2))
|
|
return textZeroY - this.textRadius
|
|
}
|
|
},
|
|
watch: {
|
|
// 监听获奖序号的变动
|
|
prizeIndex(newVal, oldVal) {
|
|
if (newVal > -1) {
|
|
this.targetIndex = newVal
|
|
this.onRotateStart()
|
|
} else {
|
|
console.info('旋转结束,prizeIndex 已重置')
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
// 开始旋转
|
|
onRotateStart() {
|
|
if (this.isRotate) return
|
|
this.isRotate = true
|
|
// 奖品总数
|
|
let prizeCount = this.prizeList.length
|
|
let baseAngle = 360 / prizeCount
|
|
let angles = 0
|
|
|
|
if (this.rotateType === 'pointer') {
|
|
if (this.targetActionAngle === 0) {
|
|
// 第一次旋转
|
|
angles = (this.targetIndex - this.stayIndex) * baseAngle + baseAngle / 2 - this.actionAngle
|
|
} else {
|
|
// 后续旋转
|
|
// 后续继续旋转 就只需要计算停留的位置与目标位置的角度
|
|
angles = (this.targetIndex - this.stayIndex) * baseAngle
|
|
}
|
|
|
|
// 更新目前序号
|
|
this.stayIndex = this.targetIndex
|
|
// 转 8 圈,圈数越多,转的越快
|
|
this.targetActionAngle += angles + 360 * this.ringCount
|
|
console.log('targetActionAngle', this.targetActionAngle)
|
|
} else {
|
|
if (this.targetAngle === 0) {
|
|
// 第一次旋转
|
|
// 因为第一个奖品是从0°开始的,即水平向右方向
|
|
// 第一次旋转角度 = 270度 - (停留的序号-目标序号) * 每个奖品区间角度 - 每个奖品区间角度的一半 - canvas自身旋转的度数
|
|
angles = (270 - (this.targetIndex - this.stayIndex) * baseAngle - baseAngle / 2) - this.canvasAngle
|
|
} else {
|
|
// 后续旋转
|
|
// 后续继续旋转 就只需要计算停留的位置与目标位置的角度
|
|
angles = -(this.targetIndex - this.stayIndex) * baseAngle
|
|
}
|
|
|
|
// 更新目前序号
|
|
this.stayIndex = this.targetIndex
|
|
// 转 8 圈,圈数越多,转的越快
|
|
this.targetAngle += angles + 360 * this.ringCount
|
|
}
|
|
|
|
// 计算转盘结束的时间,预加一些延迟确保转盘停止后触发结束事件
|
|
let endTime = this.transitionDuration * 1000 + 100
|
|
let endTimer = setTimeout(() => {
|
|
clearTimeout(endTimer)
|
|
endTimer = null
|
|
|
|
this.isRotate = false
|
|
this.$emit('draw-end')
|
|
}, endTime)
|
|
|
|
let resetPrizeTimer = setTimeout(() => {
|
|
clearTimeout(resetPrizeTimer)
|
|
resetPrizeTimer = null
|
|
|
|
// 每次抽奖结束后都要重置父级组件的 prizeIndex
|
|
this.$emit('reset-index')
|
|
}, endTime + 50)
|
|
},
|
|
// 点击 开始抽奖 按钮
|
|
handleActionStart() {
|
|
if (!this.lotteryImg) return
|
|
if (this.isRotate) return
|
|
this.$emit('draw-start')
|
|
},
|
|
// 渲染转盘
|
|
async onCreateCanvas() {
|
|
// 获取 canvas 画布
|
|
const canvasId = this.canvasId
|
|
const ctx = uni.createCanvasContext(canvasId, this)
|
|
|
|
// canvas 的宽高
|
|
let canvasW = this.higtCanvasSize
|
|
let canvasH = this.higtCanvasSize
|
|
|
|
// 根据奖品个数计算 角度
|
|
let prizeCount = this.prizeList.length
|
|
let baseAngle = Math.PI * 2 / prizeCount
|
|
|
|
// 设置字体
|
|
ctx.setFontSize(this.higtFontSize)
|
|
|
|
// 注意,开始画的位置是从0°角的位置开始画的。也就是水平向右的方向。
|
|
// 画具体内容
|
|
for (let i = 0; i < prizeCount; i++) {
|
|
let prizeItem = this.prizeList[i]
|
|
// 当前角度
|
|
let angle = i * baseAngle
|
|
|
|
// 保存当前画布的状态
|
|
ctx.save()
|
|
|
|
// x => 圆弧对应的圆心横坐标 x
|
|
// y => 圆弧对应的圆心横坐标 y
|
|
// radius => 圆弧的半径大小
|
|
// startAngle => 圆弧开始的角度,单位是弧度
|
|
// endAngle => 圆弧结束的角度,单位是弧度
|
|
// anticlockwise(可选) => 绘制方向,true 为逆时针,false 为顺时针
|
|
|
|
ctx.beginPath()
|
|
// 外圆
|
|
ctx.arc(canvasW * 0.5, canvasH * 0.5, this.outsideRadius, angle, angle + baseAngle, false)
|
|
// 内圆
|
|
ctx.arc(canvasW * 0.5, canvasH * 0.5, this.insideRadius, angle + baseAngle, angle, true)
|
|
|
|
// 每个奖品区块背景填充颜色
|
|
if (this.colors.length === 2) {
|
|
ctx.setFillStyle(this.colors[i % 2])
|
|
} else {
|
|
ctx.setFillStyle(this.colors[i])
|
|
}
|
|
// 填充颜色
|
|
ctx.fill()
|
|
|
|
// 设置文字颜色
|
|
if (this.strFontColors.length === 1) {
|
|
ctx.setFillStyle(this.strFontColors[0])
|
|
} else if (this.strFontColors.length === 2) {
|
|
ctx.setFillStyle(this.strFontColors[i % 2])
|
|
} else {
|
|
ctx.setFillStyle(this.strFontColors[i])
|
|
}
|
|
|
|
// 开启描边
|
|
if (this.stroked) {
|
|
// 设置描边颜色
|
|
ctx.setStrokeStyle(`${this.strokeColor}`)
|
|
// 描边
|
|
ctx.stroke()
|
|
}
|
|
|
|
// 开始绘制奖品内容
|
|
// 重新映射画布上的 (0,0) 位置
|
|
let translateX = canvasW * 0.5 + Math.cos(angle + baseAngle / 2) * this.textDistance
|
|
let translateY = canvasH * 0.5 + Math.sin(angle + baseAngle / 2) * this.textDistance
|
|
ctx.translate(translateX, translateY)
|
|
|
|
// 绘制奖品名称
|
|
let rewardName = this.strLimit(prizeItem.prizeName)
|
|
|
|
// rotate方法旋转当前的绘图,因为文字是和当前扇形中心线垂直的
|
|
ctx.rotate(angle + (baseAngle / 2) + (Math.PI / 2))
|
|
|
|
// 设置文本位置并处理换行
|
|
if (this.strDirection === 'horizontal') {
|
|
// 是否需要换行
|
|
if (rewardName && this.prizeNameDrawed) {
|
|
let realLen = clacTextLen(rewardName).realLen
|
|
let isLineBreak = realLen > this.strLineLen
|
|
if (isLineBreak) {
|
|
// 获得多行文本数组
|
|
let firstText = ''
|
|
let lastText = ''
|
|
let firstCount = 0
|
|
for (let j = 0; j < rewardName.length; j++) {
|
|
firstCount += clacTextLen(rewardName[j]).byteLen
|
|
if (firstCount <= (this.strLineLen * 2)) {
|
|
firstText += rewardName[j]
|
|
} else {
|
|
lastText += rewardName[j]
|
|
}
|
|
}
|
|
rewardName = firstText + ',' + lastText
|
|
let rewardNames = rewardName.split(',')
|
|
// 循环文本数组,计算每一行的文本宽度
|
|
for (let j = 0; j < rewardNames.length; j++) {
|
|
if (ctx.measureText && ctx.measureText(rewardNames[j]).width > 0) {
|
|
// 文本的宽度信息
|
|
let tempStrSize = ctx.measureText(rewardNames[j])
|
|
let tempStrWidth = -(tempStrSize.width / 2).toFixed(2)
|
|
ctx.fillText(rewardNames[j], tempStrWidth, j * this.higtHeightMultiple)
|
|
} else {
|
|
this.measureText = rewardNames[j]
|
|
|
|
// 等待页面重新渲染
|
|
await this.$nextTick()
|
|
|
|
let textWidth = await this.getTextWidth()
|
|
let tempStrWidth = -(textWidth / 2).toFixed(2)
|
|
ctx.fillText(rewardNames[j], tempStrWidth, j * this.higtHeightMultiple)
|
|
// console.log(rewardNames[j], textWidth, j)
|
|
}
|
|
}
|
|
} else {
|
|
if (ctx.measureText && ctx.measureText(rewardName).width > 0) {
|
|
// 文本的宽度信息
|
|
let tempStrSize = ctx.measureText(rewardName)
|
|
let tempStrWidth = -(tempStrSize.width / 2).toFixed(2)
|
|
ctx.fillText(rewardName, tempStrWidth, 0)
|
|
} else {
|
|
this.measureText = rewardName
|
|
|
|
// 等待页面重新渲染
|
|
await this.$nextTick()
|
|
|
|
let textWidth = await this.getTextWidth()
|
|
let tempStrWidth = -(textWidth / 2).toFixed(2)
|
|
ctx.fillText(rewardName, tempStrWidth, 0)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
let rewardNames = rewardName.split('')
|
|
for (let j = 0; j < rewardNames.length; j++) {
|
|
if (ctx.measureText && ctx.measureText(rewardNames[j]).width > 0) {
|
|
// 文本的宽度信息
|
|
let tempStrSize = ctx.measureText(rewardNames[j])
|
|
let tempStrWidth = -(tempStrSize.width / 2).toFixed(2)
|
|
ctx.fillText(rewardNames[j], tempStrWidth, j * this.higtHeightMultiple)
|
|
} else {
|
|
this.measureText = rewardNames[j]
|
|
|
|
// 等待页面重新渲染
|
|
await this.$nextTick()
|
|
|
|
let textWidth = await this.getTextWidth()
|
|
let tempStrWidth = -(textWidth / 2).toFixed(2)
|
|
ctx.fillText(rewardNames[j], tempStrWidth, j * this.higtHeightMultiple)
|
|
// console.log(rewardNames[j], textWidth, i)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 绘制奖品图片,文字竖向展示时,不支持图片展示
|
|
if (this.imgDrawed && prizeItem.prizeImage && this.strDirection !== 'vertical') {
|
|
// App-Android平台 系统 webview 更新到 Chrome84+ 后 canvas 组件绘制本地图像 uni.canvasToTempFilePath 会报错
|
|
// 统一将图片处理成 base64
|
|
// https://ask.dcloud.net.cn/question/103303
|
|
let reg = /^(https|http)/g
|
|
// 处理远程图片
|
|
if (reg.test(prizeItem.prizeImage)) {
|
|
let platformTips = ''
|
|
// #ifdef APP-PLUS
|
|
platformTips = ''
|
|
// #endif
|
|
// #ifdef MP
|
|
platformTips = '需要处理好下载域名的白名单问题,'
|
|
// #endif
|
|
// #ifdef H5
|
|
platformTips = '需要处理好跨域问题,'
|
|
// #endif
|
|
console.warn(`###当前数据列表中的奖品图片为网络图片,${platformTips}开始尝试下载图片...###`)
|
|
let res = await downloadFile(prizeItem.prizeImage)
|
|
console.log('处理远程图片', res)
|
|
if (res.ok) {
|
|
let tempFilePath = res.tempFilePath
|
|
// #ifndef MP
|
|
prizeItem.prizeImage = await pathToBase64(tempFilePath)
|
|
// #endif
|
|
// #ifdef MP
|
|
prizeItem.prizeImage = tempFilePath
|
|
// #endif
|
|
} else {
|
|
this.handlePrizeImgSuc({
|
|
ok: false,
|
|
data: res.data,
|
|
msg: res.msg
|
|
})
|
|
}
|
|
} else {
|
|
console.log('处理非远程图片', prizeItem.prizeImage)
|
|
// #ifndef MP
|
|
if (prizeItem.prizeImage.indexOf(';base64,') === -1) {
|
|
prizeItem.prizeImage = await pathToBase64(prizeItem.prizeImage)
|
|
}
|
|
// #endif
|
|
}
|
|
|
|
let prizeImageX = -(this.imgPxWidth * systemInfo.pixelRatio / 2)
|
|
let prizeImageY = this.imgMarginPxStr * systemInfo.pixelRatio
|
|
let prizeImageW = this.imgPxWidth * systemInfo.pixelRatio
|
|
let prizeImageH = this.imgPxHeight * systemInfo.pixelRatio
|
|
ctx.drawImage(prizeItem.prizeImage, prizeImageX, prizeImageY, prizeImageW, prizeImageH)
|
|
}
|
|
|
|
ctx.restore()
|
|
}
|
|
|
|
// 保存绘图并导出图片
|
|
ctx.draw(true, () => {
|
|
let drawTimer = setTimeout(() => {
|
|
clearTimeout(drawTimer)
|
|
drawTimer = null
|
|
|
|
// #ifdef MP-ALIPAY
|
|
ctx.toTempFilePath({
|
|
destWidth: this.higtCanvasSize,
|
|
destHeight: this.higtCanvasSize,
|
|
success: (res) => {
|
|
// console.log(res.apFilePath)
|
|
this.handlePrizeImg({
|
|
ok: true,
|
|
data: res.apFilePath,
|
|
msg: '画布导出生成图片成功'
|
|
})
|
|
},
|
|
fail: (err) => {
|
|
this.handlePrizeImg({
|
|
ok: false,
|
|
data: err,
|
|
msg: '画布导出生成图片失败'
|
|
})
|
|
}
|
|
})
|
|
// #endif
|
|
|
|
// #ifndef MP-ALIPAY
|
|
uni.canvasToTempFilePath({
|
|
canvasId: this.canvasId,
|
|
success: (res) => {
|
|
// 在 H5 平台下,tempFilePath 为 base64
|
|
// console.log(res.tempFilePath)
|
|
this.handlePrizeImg({
|
|
ok: true,
|
|
data: res.tempFilePath,
|
|
msg: '画布导出生成图片成功'
|
|
})
|
|
},
|
|
fail: (err) => {
|
|
this.handlePrizeImg({
|
|
ok: false,
|
|
data: err,
|
|
msg: '画布导出生成图片失败'
|
|
})
|
|
}
|
|
}, this)
|
|
// #endif
|
|
}, 500)
|
|
})
|
|
},
|
|
// 处理导出的图片
|
|
handlePrizeImg(res) {
|
|
if (res.ok) {
|
|
let data = res.data
|
|
|
|
if (!this.canvasCached) {
|
|
this.lotteryImg = data
|
|
this.handlePrizeImgSuc(res)
|
|
return
|
|
}
|
|
|
|
// #ifndef H5
|
|
if (this.isCacheImg) {
|
|
uni.getSavedFileList({
|
|
success: (sucRes) => {
|
|
let fileList = sucRes.fileList
|
|
// console.log('getSavedFileList Cached', fileList)
|
|
|
|
let cached = false
|
|
|
|
if (fileList.length) {
|
|
for (let i = 0; i < fileList.length; i++) {
|
|
let item = fileList[i]
|
|
if (item.filePath === data) {
|
|
cached = true
|
|
this.lotteryImg = data
|
|
|
|
console.info('经查,本地缓存中存在的转盘图可用,本次将不再绘制转盘')
|
|
this.handlePrizeImgSuc(res)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cached) {
|
|
console.info('经查,本地缓存中存在的转盘图不可用,需要重新初始化转盘绘制')
|
|
this.initCanvasDraw()
|
|
}
|
|
},
|
|
fail: (err) => {
|
|
this.initCanvasDraw()
|
|
}
|
|
})
|
|
} else {
|
|
uni.saveFile({
|
|
tempFilePath: data,
|
|
success: (sucRes) => {
|
|
let filePath = sucRes.savedFilePath
|
|
// console.log('saveFile', filePath)
|
|
setStore(`${this.canvasId}LotteryImg`, filePath)
|
|
this.lotteryImg = filePath
|
|
this.handlePrizeImgSuc({
|
|
ok: true,
|
|
data: filePath,
|
|
msg: '画布导出生成图片成功'
|
|
})
|
|
},
|
|
fail: (err) => {
|
|
this.handlePrizeImg({
|
|
ok: false,
|
|
data: err,
|
|
msg: '画布导出生成图片失败'
|
|
})
|
|
}
|
|
})
|
|
}
|
|
// #endif
|
|
// #ifdef H5
|
|
setStore(`${this.canvasId}LotteryImg`, data)
|
|
this.lotteryImg = data
|
|
this.handlePrizeImgSuc(res)
|
|
|
|
// console info
|
|
let consoleText = this.isCacheImg ? '缓存' : '导出'
|
|
console.info(`当前为 H5 端,使用${consoleText}中的 base64 图`)
|
|
// #endif
|
|
} else {
|
|
console.error('处理导出的图片失败', res)
|
|
uni.hideLoading()
|
|
// #ifdef H5
|
|
console.error('###当前为 H5 端,下载网络图片需要后端配置允许跨域###')
|
|
// #endif
|
|
// #ifdef MP
|
|
console.error('###当前为小程序端,下载网络图片需要配置域名白名单###')
|
|
// #endif
|
|
}
|
|
},
|
|
// 处理图片完成
|
|
handlePrizeImgSuc(res) {
|
|
uni.hideLoading()
|
|
this.$emit('finish', {
|
|
ok: res.ok,
|
|
data: res.data,
|
|
msg: res.ok ? this.successMsg : this.failMsg
|
|
})
|
|
this.almostLotteryTip = res.ok ? this.successMsg : this.failMsg
|
|
},
|
|
// 兼容 app 端不支持 ctx.measureText
|
|
// 已知问题:初始绘制时,低端安卓机 平均耗时 2s
|
|
// hbx 2.8.12+ 已在 app 端支持
|
|
getTextWidth() {
|
|
console.warn('正在采用兼容方式获取文本的 size 信息,虽然没有任何问题,如果可以,请将此 systemInfo 及 hbx 版本号 反馈给作者', systemInfo)
|
|
let query = uni.createSelectorQuery().in(this)
|
|
let nodesRef = query.select('.almost-lottery__measureText')
|
|
return new Promise((resolve, reject) => {
|
|
nodesRef.fields({
|
|
size: true,
|
|
}, (res) => {
|
|
resolve(res.width)
|
|
}).exec()
|
|
})
|
|
},
|
|
// 处理文字溢出
|
|
strLimit(value) {
|
|
let maxLength = this.strMaxLen
|
|
if (!value || !maxLength) return value
|
|
return clacTextLen(value).realLen > maxLength ? value.slice(0, maxLength - 1) + '..' : value
|
|
},
|
|
// 检查本地缓存中是否存在转盘图
|
|
checkCacheImg() {
|
|
console.log('检查本地缓存中是否存在转盘图')
|
|
// 检查是否已有缓存的转盘图
|
|
// 检查是否与本次奖品数据相同
|
|
this.oldLotteryImg = getStore(`${this.canvasId}LotteryImg`)
|
|
let oldPrizeList = getStore(`${this.canvasId}PrizeList`)
|
|
let newPrizeList = JSON.stringify(this.prizeList)
|
|
if (this.oldLotteryImg) {
|
|
if (oldPrizeList === newPrizeList) {
|
|
console.log(`经查,本地缓存中存在转盘图 => ${this.oldLotteryImg}`)
|
|
this.isCacheImg = true
|
|
|
|
console.log('需要继续判断这张缓存图是否可用')
|
|
this.handlePrizeImg({
|
|
ok: true,
|
|
data: this.oldLotteryImg,
|
|
msg: '画布导出生成图片成功'
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
console.log('经查,本地缓存中不存在转盘图')
|
|
this.initCanvasDraw()
|
|
},
|
|
// 初始化绘制
|
|
initCanvasDraw() {
|
|
console.log('开始初始化转盘绘制')
|
|
this.isCacheImg = false
|
|
this.lotteryImg = ''
|
|
clearStore(`${this.canvasId}LotteryImg`)
|
|
setStore(`${this.canvasId}PrizeList`, this.prizeList)
|
|
this.onCreateCanvas()
|
|
},
|
|
// 预处理初始化
|
|
async beforeInit() {
|
|
// 处理 rpx 自适应尺寸
|
|
let lotterySize = await new Promise((resolve) => {
|
|
uni.createSelectorQuery().in(this).select('.almost-lottery__wrap').boundingClientRect((
|
|
rects) => {
|
|
resolve(rects)
|
|
// console.log('处理 lottery rpx 的自适应', rects)
|
|
}).exec()
|
|
})
|
|
let actionSize = await new Promise((resolve) => {
|
|
uni.createSelectorQuery().in(this).select('.lottery-action').boundingClientRect((
|
|
rects) => {
|
|
resolve(rects)
|
|
// console.log('处理 action rpx 的自适应', rects)
|
|
}).exec()
|
|
})
|
|
let strMarginSize = await new Promise((resolve) => {
|
|
uni.createSelectorQuery().in(this).select('.str-margin-outside').boundingClientRect((
|
|
rects) => {
|
|
resolve(rects)
|
|
// console.log('处理 str-margin-outside rpx 的自适应', rects)
|
|
}).exec()
|
|
})
|
|
let imgMarginStr = await new Promise((resolve) => {
|
|
uni.createSelectorQuery().in(this).select('.img-margin-str').boundingClientRect((
|
|
rects) => {
|
|
resolve(rects)
|
|
// console.log('处理 img-margin-str rpx 的自适应', rects)
|
|
}).exec()
|
|
})
|
|
let imgSize = await new Promise((resolve) => {
|
|
uni.createSelectorQuery().in(this).select('.img-size').boundingClientRect((rects) => {
|
|
resolve(rects)
|
|
// console.log('处理 img-size rpx 的自适应', rects)
|
|
}).exec()
|
|
})
|
|
this.lotteryPxSize = Math.floor(lotterySize.width)
|
|
this.actionPxSize = Math.floor(actionSize.width)
|
|
this.canvasPxSize = this.lotteryPxSize - Math.floor(actionSize.left) + Math.floor(lotterySize.left)
|
|
this.strMarginPxOutside = Math.floor(strMarginSize.left) - Math.floor(lotterySize.left)
|
|
this.imgMarginPxStr = Math.floor(imgMarginStr.left) - Math.floor(lotterySize.left)
|
|
this.imgPxWidth = Math.floor(imgSize.width)
|
|
this.imgPxHeight = Math.floor(imgSize.height)
|
|
|
|
let stoTimer = setTimeout(() => {
|
|
clearTimeout(stoTimer)
|
|
stoTimer = null
|
|
|
|
// 判断画板是否设置缓存
|
|
if (this.canvasCached) {
|
|
this.checkCacheImg()
|
|
} else {
|
|
this.initCanvasDraw()
|
|
}
|
|
this.transitionDuration = this.duration
|
|
}, 50)
|
|
}
|
|
},
|
|
mounted() {
|
|
this.$nextTick(() => {
|
|
let stoTimer = setTimeout(() => {
|
|
clearTimeout(stoTimer)
|
|
stoTimer = null
|
|
|
|
this.beforeInit()
|
|
}, 50)
|
|
})
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.almost-lottery {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.almost-lottery__wrap {
|
|
position: relative;
|
|
// background-color: red;
|
|
}
|
|
|
|
.lottery-action,
|
|
.str-margin-outside,
|
|
.img-margin-str,
|
|
.img-size {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
z-index: -1;
|
|
// background-color: blue;
|
|
}
|
|
|
|
.almost-lottery__wrap {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.almost-lottery__action,
|
|
.almost-lottery__bg,
|
|
.almost-lottery__canvas {
|
|
position: absolute;
|
|
}
|
|
|
|
.almost-lottery__canvas {
|
|
left: -9999px;
|
|
opacity: 0;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.almost-lottery__canvas-img,
|
|
.almost-lottery__action-bg {
|
|
display: block;
|
|
transition: transform cubic-bezier(.34, .12, .05, .95);
|
|
}
|
|
|
|
.almost-lottery__tip {
|
|
color: #FFFFFF;
|
|
font-size: 24rpx;
|
|
text-align: center;
|
|
}
|
|
|
|
.almost-lottery__measureText {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
white-space: nowrap;
|
|
font-size: 12px;
|
|
opacity: 0;
|
|
}
|
|
</style>
|
|
|