第一次提交
This commit is contained in:
109
uni_modules/almost-lottery/changelog.md
Normal file
109
uni_modules/almost-lottery/changelog.md
Normal file
@ -0,0 +1,109 @@
|
||||
## 1.7.18(2021-09-05)
|
||||
本次更新:
|
||||
- 修复一个已知问题
|
||||
## 1.7.17(2021-08-23)
|
||||
本次更新:
|
||||
- 更新示例项目
|
||||
## 1.7.16(2021-08-14)
|
||||
本次更新:
|
||||
- 更新示例项目
|
||||
## 1.7.15(2021-08-03)
|
||||
本次更新:
|
||||
- 新增文字竖向展示的功能,详见文档说明
|
||||
## 1.7.13(2021-08-02)
|
||||
本次更新:
|
||||
- 更新文档
|
||||
## 1.7.12(2021-07-30)
|
||||
本次更新:
|
||||
- 修复示例项目的已知问题
|
||||
- 现已提供Almost-Lottery抽奖转盘的uniCloud云端一体页面模板
|
||||
- 现已提供Almost-Lottery抽奖转盘云端一体页面配套的Admin配置中心
|
||||
## 1.7.11(2021-07-22)
|
||||
本次更新:
|
||||
- 修复部分安卓手机文字大小异常的问题
|
||||
- 字段 `strHeightMultiple` 更换为 `strLineHeight`
|
||||
## 1.7.10(2021-07-12)
|
||||
本次更新:
|
||||
- 修复奖品名称 `name` 为空字符串时无法成功绘制转盘的问题
|
||||
- 新增 `prizeNameDrawed` 是否绘制奖品名称的配置项,现在可以仅展示奖品图片了
|
||||
## 1.7.9(2021-07-09)
|
||||
本次更新:
|
||||
- 优化组件内部代码
|
||||
- 修复奖品图片已然是 `base64` 格式时导致转盘绘制失败的问题
|
||||
- 文档新增QQ群号,让沟通更便捷
|
||||
## 1.7.8(2021-07-08)
|
||||
本次更新:
|
||||
- 调整 `Canvas` 默认宽高为 `280`
|
||||
## 1.7.7(2021-07-08)
|
||||
本次更新:
|
||||
- 新增多个配置项,满足更多自定义需求
|
||||
- 优化多行文本情况下非中文字符的字节处理
|
||||
- 修复偶发的第一条数据文本不居中显示的问题
|
||||
## 1.7.6(2021-07-02)
|
||||
本次更新:
|
||||
- 调整 `imageWidth` 、 `imageHeight` 字段为 `imgWidth` 、 `imgHeight`
|
||||
- 更新示例项目
|
||||
## 1.7.5(2021-07-01)
|
||||
本次更新:
|
||||
- 新增配置项 `imgMarginStr` 奖品图片距离奖品文字的距离
|
||||
## 1.7.4(2021-06-28)
|
||||
本次更新:
|
||||
- 新增轮盘旋转或指针旋转配置项
|
||||
- 转盘内置的外环图片以及按钮图片统一调整为 `image` 展示
|
||||
- 更新相关文档说明
|
||||
## 1.7.3(2021-06-16)
|
||||
本次更新:
|
||||
- 优化错误提示
|
||||
- 优化示例项目
|
||||
- 优化文档说明
|
||||
## 1.7.2(2021-06-11)
|
||||
本次更新:
|
||||
- 新增 `canvasId` 参数配置项,多画板情况下需要配置不同的 `canvasId`
|
||||
- 优化多画板情况下的缓存功能
|
||||
- 优化示例项目
|
||||
- 修改文档说明
|
||||
## 1.7.1(2021-06-10)
|
||||
本次更新:
|
||||
- 优化示例项目中的注释
|
||||
## 1.7.0(2021-06-04)
|
||||
本次更新:
|
||||
- 修复 `1.6.1` 引起的多行奖品文字行高异常的问题
|
||||
- 新增配置转盘外环和抽奖按钮图片的功能,详见文档说明
|
||||
- 更新示例项目,新增抽奖次数等业务有关的逻辑供参考
|
||||
## 1.6.1(2021-05-28)
|
||||
本次更新:
|
||||
- 修复小程序平台画板模糊的问题
|
||||
## 1.6.0(2021-05-28)
|
||||
本次更新:
|
||||
- 新增奖品区块是否开启描边的配置项,默认不开启
|
||||
- 调整画板缓存为默认不开启
|
||||
- 优化代码
|
||||
- 优化文档说明
|
||||
- 更新示例项目并修改部分注释
|
||||
## 1.5.13(2021-05-22)
|
||||
本次更新:
|
||||
- 优化文档说明
|
||||
- 更新示例项目
|
||||
## 1.5.12(2021-05-22)
|
||||
本次更新:
|
||||
- 新增配置项 `strokeColor` 奖品区块边框颜色
|
||||
- 更新文档说明
|
||||
## 1.5.11(2021-05-19)
|
||||
本次更新:
|
||||
- 新增`strMarginOutside`参数,用于设置奖品文字距离边缘的距离
|
||||
- 修复奖品文字在某些情况下不是居中显示的问题
|
||||
## 1.5.10(2021-05-19)
|
||||
本次更新:
|
||||
- 修复示例项目中权重值相同时的取值逻辑
|
||||
## 1.5.9(2021-05-14)
|
||||
本次更新:
|
||||
- 调整代码,优化小程序端的展示
|
||||
## 1.5.8(2021-05-12)
|
||||
本次更新:
|
||||
- 文档增加预警提示:不再维护非 `uni_modules` 模式下的版本
|
||||
## 1.5.7(2021-05-12)
|
||||
本次更新:
|
||||
- 修复小程序平台奖品名称不清晰的问题
|
||||
## 1.5.6(2021-03-18)
|
||||
本次更新:
|
||||
- 适配 uni_modules 插件模式
|
||||
@ -0,0 +1,899 @@
|
||||
<template>
|
||||
<view class="almost-lottery">
|
||||
<view class="almost-lottery__wrap"
|
||||
:style="{
|
||||
width: outerWidth + higtCanvasMargin + 'px',
|
||||
height: outerWidth + higtCanvasMargin + 'px'
|
||||
}"
|
||||
v-if="lotteryImg"
|
||||
>
|
||||
<image
|
||||
class="almost-lottery__bg"
|
||||
mode="widthFix"
|
||||
:src="lotteryBg"
|
||||
:style="{
|
||||
width: outerWidth + higtCanvasMargin + 'px',
|
||||
height: outerWidth + higtCanvasMargin + 'px'
|
||||
}"
|
||||
></image>
|
||||
<image
|
||||
class="almost-lottery__canvas-img"
|
||||
mode="widthFix"
|
||||
:src="lotteryImg"
|
||||
:style="{
|
||||
width: canvasWidth + 'px',
|
||||
height: canvasWidth + '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: actionWidth + 'px',
|
||||
height: actionHeight + 'px',
|
||||
transform: `rotate(${actionAngle + targetActionAngle}deg)`,
|
||||
transitionDuration: `${transitionDuration}s`
|
||||
}"
|
||||
@click="handleActionStart"
|
||||
></image>
|
||||
</view>
|
||||
|
||||
<!-- 正在绘制转盘时的提示文本 -->
|
||||
<text class="almost-lottery__tip" v-else>{{ almostLotteryTip }}</text>
|
||||
|
||||
<!-- 为了兼容 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: {
|
||||
// canvas 宽度
|
||||
canvasId: {
|
||||
type: String,
|
||||
default: 'almostLotteryCanvas'
|
||||
},
|
||||
// canvas 宽度
|
||||
canvasWidth: {
|
||||
type: Number,
|
||||
default: 242
|
||||
},
|
||||
// canvas 高度
|
||||
canvasHeight: {
|
||||
type: Number,
|
||||
default: 242,
|
||||
},
|
||||
// 转盘外圈的宽度
|
||||
outerWidth: {
|
||||
type: Number,
|
||||
default: 267
|
||||
},
|
||||
// 转盘外圈的高度
|
||||
outerHeight: {
|
||||
type: Number,
|
||||
default: 267
|
||||
},
|
||||
// 内圈与外圈的间距
|
||||
canvasMargin: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 抽奖按钮的宽度
|
||||
actionWidth: {
|
||||
type: Number,
|
||||
default: 120
|
||||
},
|
||||
// 抽奖按钮的高度
|
||||
actionHeight: {
|
||||
type: Number,
|
||||
default: 120
|
||||
},
|
||||
// 奖品列表
|
||||
prizeList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator: (value) => {
|
||||
return value.length > 1
|
||||
}
|
||||
},
|
||||
// 中奖奖品在列表中的下标
|
||||
prizeIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
// 奖品区块对应背景颜色
|
||||
colors: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
'#fbe7bb',
|
||||
'#fcd68c'
|
||||
]
|
||||
},
|
||||
// 转盘外环背景图
|
||||
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: '#FFE9AA'
|
||||
},
|
||||
// 旋转的类型
|
||||
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'
|
||||
},
|
||||
// 字体颜色
|
||||
strFontColor: {
|
||||
type: String,
|
||||
default: '#A62A2A'
|
||||
},
|
||||
// 文字的大小
|
||||
strFontSize: {
|
||||
type: Number,
|
||||
default: 9
|
||||
},
|
||||
// 奖品文字距离边缘的距离
|
||||
strMarginOutside: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 奖品图片距离奖品文字的距离
|
||||
imgMarginStr: {
|
||||
type: Number,
|
||||
default: 25
|
||||
},
|
||||
// 奖品文字多行情况下的行高
|
||||
strLineHeight: {
|
||||
type: Number,
|
||||
default: 1.2
|
||||
},
|
||||
// 奖品名称所对应的 key 值
|
||||
strKey: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
// 奖品文字总长度限制
|
||||
strMaxLen: {
|
||||
type: Number,
|
||||
default: 12
|
||||
},
|
||||
// 奖品文字多行情况下第一行文字长度
|
||||
strLineLen: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
// 奖品图片的宽
|
||||
imgWidth: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 奖品图片的高
|
||||
imgHeight: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 转盘绘制成功的提示
|
||||
successMsg: {
|
||||
type: String,
|
||||
default: '奖品准备就绪,快来参与抽奖吧'
|
||||
},
|
||||
// 转盘绘制失败的提示
|
||||
failMsg: {
|
||||
type: String,
|
||||
default: '奖品仍在准备中,请稍后再来...'
|
||||
},
|
||||
// 是否开启画板的缓存
|
||||
canvasCached: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 画板className
|
||||
className: 'almost-lottery__canvas',
|
||||
// 画板导出的图片
|
||||
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.canvasWidth * systemInfo.pixelRatio
|
||||
},
|
||||
// 高清字体
|
||||
higtFontSize() {
|
||||
return this.strFontSize * systemInfo.pixelRatio
|
||||
},
|
||||
// 高清行高
|
||||
higtHeightMultiple() {
|
||||
return this.strFontSize * this.strLineHeight * systemInfo.pixelRatio
|
||||
},
|
||||
// 高清内外圈间距
|
||||
higtCanvasMargin() {
|
||||
return this.canvasMargin * 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.strMarginOutside * 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.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)
|
||||
|
||||
// 绘制奖品名称
|
||||
ctx.setFillStyle(this.strFontColor)
|
||||
let rewardName = this.strLimit(prizeItem[this.strKey])
|
||||
|
||||
// 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, i)
|
||||
}
|
||||
}
|
||||
} 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 (prizeItem.image && 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.image)) {
|
||||
let platformTips = ''
|
||||
// #ifdef APP-PLUS
|
||||
platformTips = ''
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
platformTips = '需要处理好下载域名的白名单问题,'
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
platformTips = '需要处理好跨域问题,'
|
||||
// #endif
|
||||
console.warn(`###当前数据列表中的奖品图片为网络图片,${platformTips}开始尝试下载图片...###`)
|
||||
let res = await downloadFile(prizeItem.image)
|
||||
console.log('处理远程图片', res)
|
||||
if (res.ok) {
|
||||
let tempFilePath = res.tempFilePath
|
||||
// #ifndef MP
|
||||
prizeItem.image = await pathToBase64(tempFilePath)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
prizeItem.image = tempFilePath
|
||||
// #endif
|
||||
} else {
|
||||
this.handlePrizeImgSuc({
|
||||
ok: false,
|
||||
data: res.data,
|
||||
msg: res.msg
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.log('处理非远程图片', prizeItem.image)
|
||||
// #ifndef MP
|
||||
if (prizeItem.image.indexOf(';base64,') === -1) {
|
||||
prizeItem.image = await pathToBase64(prizeItem.image)
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
let prizeImageX = -(this.imgWidth * systemInfo.pixelRatio / 2)
|
||||
let prizeImageY = this.imgMarginStr * systemInfo.pixelRatio
|
||||
let prizeImageW = this.imgWidth * systemInfo.pixelRatio
|
||||
let prizeImageH = this.imgHeight * systemInfo.pixelRatio
|
||||
ctx.drawImage(prizeItem.image, prizeImageX, prizeImageY, prizeImageW, prizeImageH)
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
// 保存绘图并导出图片
|
||||
ctx.draw(true, () => {
|
||||
let drawTimer = setTimeout(() => {
|
||||
clearTimeout(drawTimer)
|
||||
drawTimer = null
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
ctx.toTempFilePath({
|
||||
destWidth: this.canvasWidth * systemInfo.pixelRatio,
|
||||
destHeight: this.canvasHeight * systemInfo.pixelRatio,
|
||||
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()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
let stoTimer = setTimeout(() => {
|
||||
clearTimeout(stoTimer)
|
||||
stoTimer = null
|
||||
|
||||
if (this.canvasCached) {
|
||||
this.checkCacheImg()
|
||||
} else {
|
||||
this.onCreateCanvas()
|
||||
}
|
||||
this.transitionDuration = this.duration
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.almost-lottery {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.almost-lottery__wrap {
|
||||
position: relative;
|
||||
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>
|
||||
78
uni_modules/almost-lottery/package.json
Normal file
78
uni_modules/almost-lottery/package.json
Normal file
@ -0,0 +1,78 @@
|
||||
{
|
||||
"id": "almost-lottery",
|
||||
"displayName": "Almost-Lottery抽奖转盘",
|
||||
"version": "1.7.18",
|
||||
"description": "【荣获2021插件大赛三等奖】提供奇数、缓存等众多配置项,更有抽奖概率、抽奖次数、付费抽奖等功能内置于示例项目中,完美支持APP、各平台小程序、H5、PC,同时提供 uniCloud 云端版本",
|
||||
"keywords": [
|
||||
"转盘",
|
||||
"抽奖",
|
||||
"大转盘抽奖"
|
||||
],
|
||||
"repository": "https://github.com/ialmost/almost-components_uniapp",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.22"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "n"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
162
uni_modules/almost-lottery/readme.md
Normal file
162
uni_modules/almost-lottery/readme.md
Normal file
@ -0,0 +1,162 @@
|
||||
# almost-lottery
|
||||
*使用 Canvas 绘制的抽奖转盘,提供奇数、缓存等众多配置项,更有抽奖概率、抽奖次数、付费抽奖等功能内置于示例项目中*
|
||||
|
||||
|
||||
> <br />
|
||||
>
|
||||
> 如果用着还行,请支持一下
|
||||
> - 前往 [GitHub](https://github.com/ialmost/almost-components_uniapp) 给个 Star
|
||||
> - 前往 [UniApp](https://ext.dcloud.net.cn/plugin?id=1030) 给个五星
|
||||
> - 使用中遇到问题时,可以添加 **QQ群 20441313**
|
||||
>
|
||||
> <br />
|
||||
|
||||
|
||||
## 基于 uniCloud 开发的云端 Almost-Lottery 抽奖转盘,欢迎尝试体验
|
||||
- [Almost-Lottery抽奖转盘的云端一体页面](https://ext.dcloud.net.cn/plugin?id=5763)
|
||||
- [Almost-Lottery抽奖转盘的配置中心](https://ext.dcloud.net.cn/plugin?id=5762)
|
||||
|
||||
|
||||
## 高能预警
|
||||
- 本插件仅支持 `uni_modules` 模式,强烈推荐使用该模式,**非 `uni_modules` 模式不再维护**
|
||||
- 在使用本插件之前,强烈建议使用 `HBuilderX` 导入示例项目验证可用性并参照修改
|
||||
|
||||
## 功能概要
|
||||
- [x] 可配置奖品文字 **支持横向/竖向展示**
|
||||
- [x] 可配置每个奖品区块的背景颜色
|
||||
- [x] 可配置奖品区块是否开启描边以及边框的颜色,默认不开启
|
||||
- [x] 可配置转盘外环和抽奖按钮图
|
||||
- [x] 可配置每个奖品区块的奖品图片,**当图片是网络地址时,小程序端需要配置白名单,H5端需要允许跨域,奖品文字为竖向时不支持展示奖品图片**
|
||||
- [x] 奖品列表支持奇数,**奇数时需尽量能被 `360` 除尽**
|
||||
- [x] 可配置内圈与外圈的间距
|
||||
- [x] 可配置轮盘旋转或指针旋转
|
||||
- [x] 可配置画板是否缓存,默认不开启
|
||||
- [x] 更多配置请查看API说明
|
||||
|
||||
## 示例项目附加功能
|
||||
- [x] 中奖概率,**强烈推荐中奖概率应由后端控制**
|
||||
- [x] 抽奖次数
|
||||
- [x] 付费抽奖
|
||||
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 编译到小程序端时,请务必勾选ES6转ES5
|
||||
|
||||
- `@reset-index="prizeIndex = -1"` 必须默认写入到 `template` 中,不可删除
|
||||
|
||||
- 每个奖品区块的奖品图片尺寸不宜过大,图片越大,绘制的过程越慢,尽量将图片尺寸控制在 `300*300` 以内,且图片大小控制在 `100KB` 以内
|
||||
|
||||
- 关于中奖概率的配置,请下载示例项目,参照 `pages/index/index.vue` 中的代码进行配置
|
||||
|
||||
- 组件本身不涉及任何业务逻辑,与业务相关的代码建议都放在 `pages/index/index.vue` 中
|
||||
|
||||
|
||||
## 代码演示
|
||||
#### 基础用法
|
||||
```
|
||||
// template
|
||||
// @reset-index="prizeIndex = -1" 必须默认写入到 template 中,不可删除
|
||||
<almost-lottery
|
||||
:prizeList="prizeList"
|
||||
:prizeIndex="prizeIndex"
|
||||
@reset-index="prizeIndex = -1"
|
||||
@draw-start="handleDrawStart"
|
||||
@draw-end="handleDrawEnd"
|
||||
@finish="handleDrawFinish"
|
||||
v-if="prizeList.length"
|
||||
/>
|
||||
|
||||
// script
|
||||
import AlmostLottery from '@/uni_modules/almost-lottery/components/almost-lottery/almost-lottery.vue'
|
||||
export default {
|
||||
components: {
|
||||
AlmostLottery
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 以下是奖品配置数据
|
||||
// 奖品数据
|
||||
prizeList: [],
|
||||
// 奖品是否设有库存
|
||||
onStock: true,
|
||||
// 中奖下标
|
||||
prizeIndex: -1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 本次抽奖开始
|
||||
handleDrawStart () {
|
||||
// 这里需要处理你的中奖逻辑,并得出 prizeIndex
|
||||
// 请查看示例项目中的代码
|
||||
},
|
||||
// 本次抽奖结束
|
||||
handleDrawEnd () {
|
||||
// 完成抽奖后,这里处理你拿到结果后的逻辑
|
||||
// 请查看示例项目中的代码
|
||||
},
|
||||
// 抽奖转盘绘制完成
|
||||
handleDrawFinish (res) {
|
||||
// 抽奖转盘准备就绪后,这里处理你的逻辑
|
||||
// 请查看示例项目中的代码
|
||||
// console.log('抽奖转盘绘制完成', res)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
#### Props
|
||||
参数 | 说明 | 类型 | 默认值
|
||||
:---|:---|:---|:---
|
||||
canvasId | Canvas的标识,**多画板情况下需要配置不同的标识** | *`String`* | `'almostLotteryCanvas'`
|
||||
canvasWidth | Canvas的宽度 | *`Number`* | `280`
|
||||
canvasHeight | Canvas的高度 | *`Number`* | `280`
|
||||
outerWidth | 转盘外圈的宽度 | *`Number`* | `320`
|
||||
outerHeight | 转盘外圈的高度 | *`Number`* | `320`
|
||||
canvasMargin | 内圈与外圈的间距 | *`Number`* | `5`
|
||||
actionWidth | 抽奖按钮的宽度 | *`Number`* | `120`
|
||||
actionHeight | 抽奖按钮的高度 | *`Number`* | `120`
|
||||
prizeIndex | 获奖奖品在奖品列表中的序号,**每次抽奖结束后会自动重置为 `-1`** | *`Number`* | `-1`
|
||||
prizeList | 奖品列表,支持奇数(尽量能被 `360` 除尽),**为奇数时需要重设 `colors` 参数** | *`Array`* | -
|
||||
lotteryBg | 转盘外环图片 | `String` | `默认内置的本地图片`
|
||||
actionBg | 抽奖按钮图片 | `String` | `默认内置的本地图片`
|
||||
colors | 奖品区块对应的背景颜色,默认 2 个颜色相互交替,**也可以对每个区块设置不同颜色** | *`Array`* | `['#FFFFFF', '#FFE9AA']`
|
||||
prizeNameDrawed | 是否绘制奖品名称 | *`Boolean`* | `true`
|
||||
stroked | 是否开启奖品区块描边 | *`Boolean`* | `false`
|
||||
strDirection | 奖品名称展示方向,可选值 `'horizontal'` => 横向 `'vertical'` => 竖向 | *`String`* | `'horizontal'`
|
||||
strokeColor | 奖品区块边框颜色 | *`String`* | `'#FFE9AA'`
|
||||
rotateType | 旋转的类型,可选值 `'roulette'` => 轮盘旋转 `'pointer'` => 指针旋转 | *`String`* | `'roulette'`
|
||||
duration | 转盘旋转的动画时长,单位:秒 | *`Number`* | `8`
|
||||
ringCount | 旋转的圈数 | *`Number`* | `8`
|
||||
pointerPosition | 点击抽奖按钮指针的位置,可选值 `'edge'` => 指向边界 `'middle'` => 指向中间 | *`String`* | `'edge'`
|
||||
strFontColor | 奖品名称的颜色 | *`String`* | `'#C30B29'`
|
||||
strFontSize | 奖品名称的字号 | *`Number`* | `12`
|
||||
strLineHeight | 奖品名称多行情况下的行高 | *`Number`* | `1.2`
|
||||
strKey | 奖品名称所对应的键名 `key` ,比如 `{ name: '88元现金' }`,`strKey` 就是 `'name'` | *`String`* | `'name'`
|
||||
strMaxLen | 奖品名称长度限制,**文字竖向时不生效** | *`Number`* | `12`
|
||||
strLineLen | 奖品名称在多行情况下第一行文字的长度,**文字竖向时不生效** | *`Number`* | `6`
|
||||
strMarginOutside | 奖品文字相对轮盘边缘的距离 | *`Number`* | `strFontSize 的一半`
|
||||
imgMarginStr | 奖品图片相对奖品文字的距离 | *`Number`* | `25`
|
||||
imgWidth | 奖品图片的宽度 | *`Number`* | `30`
|
||||
imgHeight | 奖品图片的高度 | *`Number`* | `30`
|
||||
successMsg | 转盘绘制成功的提示 | *`String`* | `'奖品准备就绪,快来参与抽奖吧'`
|
||||
failMsg | 转盘绘制失败的提示 | *`String`* | `'奖品仍在准备中,请稍后再来...'`
|
||||
canvasCached | 是否开启缓存,避免在数据不变的情况下重复绘制,建议在生产环境中开启 | *`Boolean`* | `false`
|
||||
|
||||
#### Events
|
||||
事件名 | 说明 | 回调参数
|
||||
:---|:---|:---
|
||||
@reset-index | 每次抽奖结束后重置获奖的序号为 `-1`,**该事件必须默认写入到 `template` 中,不可删除** | -
|
||||
@draw-start | 转盘旋转开始时触发 | -
|
||||
@draw-end | 转盘旋转结束时触发 | -
|
||||
@finish | Canvas转盘绘制完成时触发 | `{ ok: 绘制是否成功, data: 转盘的图片, msg: 绘制结果的提示 }`
|
||||
|
||||
#### prizeList 数据结构
|
||||
键名 | 说明 | 类型
|
||||
:---|:---|:---
|
||||
prizeId | 奖品对应 `ID` | *`Number`*
|
||||
name | 奖品名称 | *`String`*
|
||||
stock | 奖品库存 | *`Number`*
|
||||
weight | 奖品权重 | *`Number`*
|
||||
prizeImage | 奖品图片地址,网络图片仅支持`http`和`https`协议 | *`String`*
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
303
uni_modules/almost-lottery/utils/almost-utils.js
Normal file
303
uni_modules/almost-lottery/utils/almost-utils.js
Normal file
@ -0,0 +1,303 @@
|
||||
/**
|
||||
* 存储 localStorage 数据
|
||||
* @param {String} name - 缓存数据的标识
|
||||
* @param {any} content - 缓存的数据内容
|
||||
*/
|
||||
export const setStore = (name, content) => {
|
||||
if (!name) return
|
||||
if (typeof content !== 'string') {
|
||||
content = JSON.stringify(content)
|
||||
}
|
||||
uni.setStorageSync(name, content)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 localStorage 数据
|
||||
* @param {String} name - 缓存数据的标识
|
||||
*/
|
||||
export const getStore = (name) => {
|
||||
if (!name) return
|
||||
return uni.getStorageSync(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除 localStorage 数据
|
||||
* @param {String} name - 缓存数据的标识
|
||||
*/
|
||||
export const clearStore = (name) => {
|
||||
if (name) {
|
||||
uni.removeStorageSync(name)
|
||||
} else {
|
||||
console.log('清理本地全部缓存')
|
||||
uni.clearStorageSync()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文本的长度
|
||||
* @param {String} text - 文本内容
|
||||
*/
|
||||
export const clacTextLen = (text) => {
|
||||
if (!text) return { byteLen: 0, realLen: 0 }
|
||||
text += ''
|
||||
let clacLen = 0
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if ((text.charCodeAt(i) < 0) || (text.charCodeAt(i) > 255)) {
|
||||
clacLen += 2
|
||||
} else {
|
||||
clacLen += 1
|
||||
}
|
||||
}
|
||||
// console.log(`当前文本 ${text} 的长度为 ${clacLen / 2}`)
|
||||
return {
|
||||
byteLen: clacLen,
|
||||
realLen: clacLen / 2
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件,并返回临时路径
|
||||
* @return {String} 临时路径
|
||||
* @param {String} fileUrl - 网络地址
|
||||
*/
|
||||
export const downloadFile = (fileUrl) => {
|
||||
return new Promise((resolve) => {
|
||||
uni.downloadFile({
|
||||
url: fileUrl,
|
||||
success: (res) => {
|
||||
// #ifdef MP-ALIPAY
|
||||
if (res.errMsg === 'downloadFile:ok') {
|
||||
resolve({
|
||||
ok: true,
|
||||
data: res.errMsg,
|
||||
tempFilePath: res.tempFilePath
|
||||
})
|
||||
} else {
|
||||
resolve({
|
||||
ok: false,
|
||||
data: res.errMsg,
|
||||
msg: '图片下载失败'
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY
|
||||
if (res.statusCode === 200) {
|
||||
resolve({
|
||||
ok: true,
|
||||
data: res.errMsg,
|
||||
tempFilePath: res.tempFilePath
|
||||
})
|
||||
} else {
|
||||
resolve({
|
||||
ok: false,
|
||||
data: res.errMsg,
|
||||
msg: '图片下载失败'
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
fail: (err) => {
|
||||
resolve({
|
||||
ok: false,
|
||||
data: err.errMsg,
|
||||
msg: '图片下载失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理应用已缓存的文件
|
||||
*/
|
||||
export const clearCacheFile = () => {
|
||||
// #ifndef H5
|
||||
uni.getSavedFileList({
|
||||
success: (res) => {
|
||||
let fileList = res.fileList
|
||||
if (fileList.length) {
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
uni.removeSavedFile({
|
||||
filePath: fileList[i].filePath,
|
||||
complete: () => {
|
||||
console.log('清除缓存已完成')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('getSavedFileList Fail')
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
clearStore()
|
||||
// #endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 图像转换工具,可用于图像和base64的转换
|
||||
// https://ext.dcloud.net.cn/plugin?id=123
|
||||
const getLocalFilePath = (path) => {
|
||||
if (
|
||||
path.indexOf('_www') === 0 ||
|
||||
path.indexOf('_doc') === 0 ||
|
||||
path.indexOf('_documents') === 0 ||
|
||||
path.indexOf('_downloads') === 0
|
||||
) return path
|
||||
|
||||
if (path.indexOf('/storage/emulated/0/') === 0) return path
|
||||
|
||||
if (path.indexOf('/storage/sdcard0/') === 0) return path
|
||||
|
||||
if (path.indexOf('/var/mobile/') === 0) return path
|
||||
|
||||
if (path.indexOf('file://') === 0) return path
|
||||
|
||||
if (path.indexOf('/') === 0) {
|
||||
// ios 无法获取本地路径
|
||||
let localFilePath = plus.os.name === 'iOS' ? path : plus.io.convertLocalFileSystemURL(path)
|
||||
if (localFilePath !== path) {
|
||||
return localFilePath
|
||||
} else {
|
||||
path = path.substring(1)
|
||||
}
|
||||
}
|
||||
|
||||
return '_www/' + path
|
||||
}
|
||||
|
||||
export const pathToBase64 = (path) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof window === 'object' && 'document' in window) {
|
||||
if (typeof FileReader === 'function') {
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open('GET', path, true)
|
||||
xhr.responseType = 'blob'
|
||||
xhr.onload = function() {
|
||||
if (this.status === 200) {
|
||||
let fileReader = new FileReader()
|
||||
fileReader.onload = function(e) {
|
||||
resolve(e.target.result)
|
||||
}
|
||||
fileReader.onerror = reject
|
||||
fileReader.readAsDataURL(this.response)
|
||||
}
|
||||
}
|
||||
xhr.onerror = reject
|
||||
xhr.send()
|
||||
return
|
||||
}
|
||||
let canvas = document.createElement('canvas')
|
||||
let c2x = canvas.getContext('2d')
|
||||
let img = new Image
|
||||
img.onload = function() {
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
c2x.drawImage(img, 0, 0)
|
||||
resolve(canvas.toDataURL())
|
||||
canvas.height = canvas.width = 0
|
||||
}
|
||||
img.onerror = reject
|
||||
img.src = path
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof plus === 'object') {
|
||||
let tempPath = getLocalFilePath(path)
|
||||
plus.io.resolveLocalFileSystemURL(tempPath, (entry) => {
|
||||
entry.file((file) => {
|
||||
let fileReader = new plus.io.FileReader()
|
||||
fileReader.onload = function(data) {
|
||||
resolve(data.target.result)
|
||||
}
|
||||
fileReader.onerror = function(error) {
|
||||
console.log(error)
|
||||
reject(error)
|
||||
}
|
||||
fileReader.readAsDataURL(file)
|
||||
}, (error) => {
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
reject(error)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
|
||||
wx.getFileSystemManager().readFile({
|
||||
filePath: path,
|
||||
encoding: 'base64',
|
||||
success: (res) => {
|
||||
resolve('data:image/png;base64,' + res.data)
|
||||
},
|
||||
fail: (error) => {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
reject(new Error('not support'))
|
||||
})
|
||||
}
|
||||
|
||||
export const base64ToPath = (base64) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof window === 'object' && 'document' in window) {
|
||||
base64 = base64.split(',')
|
||||
let type = base64[0].match(/:(.*?);/)[1]
|
||||
let str = atob(base64[1])
|
||||
let n = str.length
|
||||
let array = new Uint8Array(n)
|
||||
while (n--) {
|
||||
array[n] = str.charCodeAt(n)
|
||||
}
|
||||
return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], {
|
||||
type: type
|
||||
})))
|
||||
}
|
||||
let extName = base64.match(/data\:\S+\/(\S+);/)
|
||||
if (extName) {
|
||||
extName = extName[1]
|
||||
} else {
|
||||
reject(new Error('base64 error'))
|
||||
}
|
||||
let fileName = Date.now() + '.' + extName
|
||||
if (typeof plus === 'object') {
|
||||
let bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
|
||||
bitmap.loadBase64Data(base64, () => {
|
||||
let filePath = '_doc/uniapp_temp/' + fileName
|
||||
bitmap.save(filePath, {}, () => {
|
||||
bitmap.clear()
|
||||
resolve(filePath)
|
||||
}, (error) => {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
return
|
||||
}
|
||||
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
|
||||
let filePath = wx.env.USER_DATA_PATH + '/' + fileName
|
||||
wx.getFileSystemManager().writeFile({
|
||||
filePath: filePath,
|
||||
data: base64.replace(/^data:\S+\/\S+;base64,/, ''),
|
||||
encoding: 'base64',
|
||||
success: () => {
|
||||
resolve(filePath)
|
||||
},
|
||||
fail: (error) => {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
reject(new Error('not support'))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user