new_app/pages/me/almost-lottery copy/components/almost-lottery/almost-lottery.vue

1088 lines
35 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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',
{ 'almost-lottery__canvas-img-other': !selfRotaty },
{ 'almost-lottery__canvas-img-self': selfRotated }
]"
mode="widthFix"
:src="lotteryImg"
:style="{
width: canvasImgPxSize + 'px',
height: canvasImgPxSize + 'px',
left: canvasImgToLeftPx + 'px',
top: canvasImgToLeftPx + 'px',
transform: `rotate(${canvasAngle + targetAngle}deg)`,
transitionDuration: `${transitionDuration}s`
}"
></image>
<image
class="almost-lottery__action-bg"
mode="widthFix"
:src="actionBg"
:style="{
width: actionPxSize + 'px',
height: actionPxSize + 'px',
left: actionBgToLeftPx + 'px',
top: actionBgToLeftPx + 'px',
transform: `rotate(${actionAngle + targetActionAngle}deg)`,
transitionDuration: `${transitionDuration}s`
}"
@click="handleActionStart"
></image>
</template>
</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>
import { getStore, setStore, clearStore, circleImg, clacTextLen, downloadFile, pathToBase64, base64ToPath } 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'
},
// 渲染延迟
renderDelay: {
type: Number,
default: 0
},
// 抽奖转盘的整体尺寸
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'
},
// 是否开启自转
selfRotaty: {
type: Boolean,
default: false
},
// 自转时,最少转多少毫秒
selfTime: {
type: Number,
default: 1000
},
// 旋转动画时间 单位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: 60
},
// 奖品文字多行情况下的行高
strLineHeight: {
type: Number,
default: 1.2
},
// 奖品文字总长度限制
strMaxLen: {
type: Number,
default: 12
},
// 奖品文字多行情况下第一行文字长度
strLineLen: {
type: Number,
default: 6
},
// 奖品图片的宽
imgWidth: {
type: Number,
default: 50
},
// 奖品图片的高
imgHeight: {
type: Number,
default: 50
},
// 是否绘制奖品图片
imgDrawed: {
type: Boolean,
default: true
},
// 奖品图片是否裁切为圆形
imgCircled: {
type: Boolean,
default: false
},
// 转盘绘制成功的提示
successMsg: {
type: String,
default: '奖品准备就绪,快来参与抽奖吧'
},
// 转盘绘制失败的提示
failMsg: {
type: String,
default: '奖品仍在准备中,请稍后再来...'
},
// 是否开启画板的缓存
canvasCached: {
type: Boolean,
default: false
}
},
data() {
return {
// 画板className
className: 'almost-lottery__canvas',
// 高清固定 2 倍,不再从 system 中动态获取,因为 h5、app-vue 中单个尺寸过大时存在 iOS/Safari 无法绘制的问题,且 2 倍基本也可以解决模糊的问题
systemPixelRatio: 2,
// 抽奖转盘的整体px尺寸
lotteryPxSize: 0,
// 画板的px尺寸
canvasImgPxSize: 0,
// 抽奖按钮的px尺寸
actionPxSize: 0,
// 奖品文字距离转盘边缘的距离
strMarginPxOutside: 0,
// 奖品图片相对奖品文字的距离
imgMarginPxStr: 0,
// 奖品图片的宽、高
imgPxWidth: 0,
imgPxHeight: 0,
// 画板导出的图片
lotteryImg: '',
// 旋转到奖品目标需要的角度
targetAngle: 0,
targetActionAngle: 0,
// 配合自转使用
selfRotated: false,
selfRotatyStartTime: null,
// 是否正在旋转
isRotate: false,
// 当前停留在那个奖品的序号
stayIndex: 0,
// 当前中奖奖品的序号
targetIndex: 0,
// 是否存在可用的缓存转盘图
isCacheImg: false,
oldLotteryImg: '',
// 解决 app 不支持 measureText 的问题
// app 已在 2.9.3 的版本中提供了对 measureText 的支持,将在后续版本逐渐稳定后移除相关兼容代码
measureText: ''
}
},
computed: {
// 高清尺寸
higtCanvasSize() {
return this.canvasImgPxSize * this.systemPixelRatio
},
// 高清字体
higtFontSize() {
return Math.round(this.strFontSize / this.pixelRatio) * this.systemPixelRatio
},
// 高清行高
higtHeightMultiple() {
return Math.round(this.strFontSize / this.pixelRatio) * this.strLineHeight * this.systemPixelRatio
},
canvasImgToLeftPx () {
return (this.lotteryPxSize - this.canvasImgPxSize) / 2
},
actionBgToLeftPx () {
return (this.lotteryPxSize - this.actionPxSize) / 2
},
// 根据奖品列表计算 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 * this.systemPixelRatio
},
// 文字距离边缘的距离
textRadius() {
return this.strMarginPxOutside * this.systemPixelRatio || (this.higtFontSize / 2)
},
// 根据画板的宽度计算奖品文字与中心点的距离
textDistance() {
const textZeroY = Math.round(this.outsideRadius - (this.insideRadius / 2))
return textZeroY - this.textRadius
},
// 旋转动画时间 单位 s
transitionDuration () {
return this.selfRotaty ? 2 : this.duration
}
},
watch: {
// 监听获奖序号的变动
prizeIndex(newVal, oldVal) {
if (newVal > -1) {
if (this.selfRotaty) {
const diffTime = Date.now() - this.selfRotatyStartTime
const timeDelay = diffTime < this.selfTime ? this.selfTime : 0
setTimeout(() => {
this.selfRotated = false
this.targetIndex = newVal
this.onRotateStart()
}, timeDelay)
} else {
setTimeout(() => {
this.targetIndex = newVal
this.onRotateStart()
}, 0)
}
} else {
console.info('旋转结束prizeIndex 已重置')
}
}
},
methods: {
// 开始旋转
onRotateStart() {
// 奖品总数
if (!this.selfRotaty) {
if (this.isRotate) return
this.isRotate = true
}
let prizeCount = this.prizeList.length
let baseAngle = 360 / prizeCount
let angles = 0
let ringCount = this.selfRotaty ? 1 : this.ringCount
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 * 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 * ringCount
}
// 计算转盘结束的时间,预加一些延迟确保转盘停止后触发结束事件
let endTime = this.selfRotaty ? 0 : (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-before', (shouldContinue) => {
console.log('shouldContinue', shouldContinue)
if (!shouldContinue) return
const ringDuration = (this.duration / this.ringCount).toFixed(1)
if (ringDuration >= 2.5) {
console.warn('当前每一圈的旋转可能过慢,请检查 duration 和 ringCount 这 2 个参数是否设置合理')
} else if (ringDuration < 1) {
console.warn('当前每一圈的旋转可能过快,请检查 duration 和 ringCount 这 2 个参数是否设置合理')
}
if (this.selfRotaty) {
this.isRotate = true
this.selfRotated = true
this.selfRotatyStartTime = Date.now()
}
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.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)
// 设置文字颜色
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])
}
// 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 textCount = 0
let tempTxt = ''
let rewardNames = []
for (let j = 0; j < rewardName.length; j++) {
textCount += clacTextLen(rewardName[j]).byteLen
tempTxt += rewardName[j]
if (textCount >= (this.strLineLen * 2)) {
rewardNames.push(tempTxt)
textCount = 0
tempTxt = ''
} else {
if ((rewardName.length - 1) === j) {
rewardNames.push(tempTxt)
textCount = 0
tempTxt = ''
}
}
}
// 循环文本数组,计算每一行的文本宽度
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 {
// #ifndef MP
// 不是小程序环境,把本地图片处理成 base64
if (prizeItem.prizeImage.indexOf(';base64,') === -1) {
console.log('开始处理本地图片', prizeItem.prizeImage)
prizeItem.prizeImage = await pathToBase64(prizeItem.prizeImage)
console.log('处理本地图片结束', prizeItem.prizeImage)
}
// #endif
// #ifdef MP-WEIXIN
// 小程序环境,把 base64 处理成小程序的本地临时路径
if (prizeItem.prizeImage.indexOf(';base64,') !== -1) {
console.log('开始处理BASE64图片', prizeItem.prizeImage)
prizeItem.prizeImage = await base64ToPath(prizeItem.prizeImage)
console.log('处理BASE64图片完成', prizeItem.prizeImage)
}
// #endif
}
let prizeImageX = -(this.imgPxWidth * this.systemPixelRatio / 2)
let prizeImageY = this.imgMarginPxStr * this.systemPixelRatio
let prizeImageW = this.imgPxWidth * this.systemPixelRatio
let prizeImageH = this.imgPxHeight * this.systemPixelRatio
if (this.imgCircled) {
// 重新设置每个圆形的背景色
if (this.colors.length === 2) {
ctx.setFillStyle(this.colors[i % 2])
} else {
ctx.setFillStyle(this.colors[i])
}
circleImg(ctx, prizeItem.prizeImage, prizeImageX, prizeImageY, prizeImageW, prizeImageH)
} else {
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,
destWidth: this.higtCanvasSize,
destHeight: this.higtCanvasSize,
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.msg, res)
// #ifdef H5
console.error('###当前为 H5 端,下载网络图片需要后端配置允许跨域###')
// #endif
// #ifdef MP
console.error('###当前为小程序端,下载网络图片需要配置域名白名单###')
// #endif
}
},
// 处理图片完成
handlePrizeImgSuc (res) {
this.$emit('finish', {
ok: res.ok,
data: res.data,
msg: res.ok ? this.successMsg : this.failMsg
})
},
// 兼容 app 端不支持 ctx.measureText
// 已知问题:初始绘制时,低端安卓机 平均耗时 2s
// hbx 2.8.12+ 已在 app 端支持
getTextWidth() {
console.warn('正在采用兼容方式获取文本的 size 信息')
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) {
console.log(`经查,本地缓存中存在转盘图 => ${this.oldLotteryImg},继续判断这张缓存图是否可用`)
if (oldPrizeList === newPrizeList) {
this.isCacheImg = true
console.log('缓存图可用')
this.handlePrizeImg({
ok: true,
data: this.oldLotteryImg,
msg: '画布导出生成图片成功'
})
return
}
}
this.initCanvasDraw()
},
// 初始化绘制
initCanvasDraw () {
console.log('开始初始化转盘绘制')
this.isCacheImg = false
this.lotteryImg = ''
clearStore(`${this.canvasId}LotteryImg`)
setStore(`${this.canvasId}PrizeList`, this.prizeList)
this.onCreateCanvas()
},
// 预处理初始化
async beforeInit () {
let query = uni.createSelectorQuery().in(this)
// 处理 rpx 自适应尺寸
let lotterySize = await new Promise((resolve) => {
query.select('.almost-lottery__wrap').boundingClientRect((rects) => {
resolve(rects)
// console.log('处理 lottery rpx 的自适应', rects)
}).exec()
})
let actionSize = await new Promise((resolve) => {
query.select('.lottery-action').boundingClientRect((rects) => {
resolve(rects)
// console.log('处理 action rpx 的自适应', rects)
}).exec()
})
let strMarginSize = await new Promise((resolve) => {
query.select('.str-margin-outside').boundingClientRect((rects) => {
resolve(rects)
// console.log('处理 str-margin-outside rpx 的自适应', rects)
}).exec()
})
let imgMarginStr = await new Promise((resolve) => {
query.select('.img-margin-str').boundingClientRect((rects) => {
resolve(rects)
// console.log('处理 img-margin-str rpx 的自适应', rects)
}).exec()
})
let imgSize = await new Promise((resolve) => {
query.select('.img-size').boundingClientRect((rects) => {
resolve(rects)
// console.log('处理 img-size rpx 的自适应', rects)
}).exec()
})
this.lotteryPxSize = Math.floor(lotterySize.width)
this.canvasImgPxSize = this.lotteryPxSize - Math.floor(actionSize.left) + Math.floor(lotterySize.left)
this.actionPxSize = Math.floor(actionSize.width)
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)
// console.log(this.lotteryPxSize, this.canvasImgPxSize, this.actionPxSize)
let stoTimer = setTimeout(() => {
clearTimeout(stoTimer)
stoTimer = null
// 判断画板是否设置缓存
if (this.canvasCached) {
this.checkCacheImg()
} else {
this.initCanvasDraw()
}
}, 50)
}
},
mounted() {
this.$nextTick(() => {
let delay = 50 + this.renderDelay
let stoTimer = setTimeout(() => {
clearTimeout(stoTimer)
stoTimer = null
this.beforeInit()
}, delay)
})
}
}
</script>
<style lang="scss" scoped>
.almost-lottery {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
// 以下元素不可见,是 canvas 的实例
.almost-lottery__canvas {
position: absolute;
left: -9999px;
opacity: 0;
display: flex;
justify-content: center;
align-items: center;
}
// 以下元素不可见,用于获得自适应的值
.lottery-action,
.str-margin-outside,
.img-margin-str,
.img-size {
position: absolute;
left: 0;
top: 0;
z-index: -1;
// background-color: blue;
}
// 以下元素不可见,用于计算文本的宽度
.almost-lottery__measureText {
position: absolute;
left: 0;
top: 0;
white-space: nowrap;
font-size: 12px;
opacity: 0;
}
// 以下为可见内容的样式
.almost-lottery__wrap {
position: relative;
// display: flex;
// justify-content: center;
// align-items: center;
// background-color: #FFFFFF;
}
.almost-lottery__bg,
.almost-lottery__canvas-img,
.almost-lottery__action-bg {
position: absolute;
left: 0;
top: 0;
}
.almost-lottery__canvas-img-other {
transition: transform cubic-bezier(.34, .12, .05, .95);
}
@keyframes selfRotate {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}
.almost-lottery__canvas-img-self {
transition: transform ease-in;
animation: selfRotate .6s linear infinite;
}
</style>