父组件代码:
<template>
<view class="almost-lottery" :class="popupShow2 ? 'mask' :''">
<view class="d-content">
<view class="almost-lottery__wheel">
<view class="lottery">
<almost-lottery :lottery-size="lotteryConfig.lotterySize" :action-size="lotteryConfig.actionSize"
:lotteryBg="lotteryBg" :ring-count="2" :duration="1" :img-circled="true" :strFontColors="['#f00']"
:canvasCached="true" :prize-list="prizeList" :prize-index="prizeIndex" @reset-index="prizeIndex = -1"
@draw-start="handleDrawStart" @draw-end="handleDrawEnd" @finish="handleDrawFinish"
v-if="prizeList.length" />
</view>
<view class="b-tip2">
<!-- uView的NoticeBar滚动通知组件 -->
<u-notice-bar fontSize="22rpx" :customStyle="{padding:'12rpx 6rpx 0 30rpx',width:'380rpx',height:'44rpx',borderRadius:'0 22px 22px 0'}" :text="text1" direction="column" icon="" color="#fff" bgColor="rgba(0,0,0,.3)"></u-notice-bar>
</view>
<view class="l-btn">
{{integroreduce}}
</view>
<view class="r-btn" @click="getMyLuckDraw">
我的奖品
</view>
<image src="/static/images/dizuo.png" mode="" class="a-diz" style="width: 742rpx;height: 498rpx;text-align: center;"></image>
</view>
</view>
<view class="d-list">
1.<strong>积分消耗</strong> :每进行一次大转盘抽奖消耗10积分;<br/>
2.<strong>奖品说明</strong>:权益奖品会在10分钟内发放到对应获奖账户;实体物品需在【我的奖品】进行领取;<br/>
3.<strong>奖品领取</strong>:需在【我的奖品】中填写联系人、联系电话及联系地址,一旦完成领取,不可撤销/更改;奖品将在领取后14个工作日内完成奖品发放;<br/>
4.<strong>奖品有效期</strong>:凡通过大转盘抽奖获取的实体物品,需在中奖后7天内完成领取,逾期视为放弃奖励;<br/>
5.<strong>特殊说明</strong>:凡通过不正当手段获取的积分,本司有权撤销该用户相应积分,并追回其在大转盘抽奖获取的权益/物品,且保留追究该用户责任的权利。<br/>
</view>
<u-popup :show="popupShow1" mode="center" bgColor="transparent">
<view class="p-result">
<view class="r-title" v-if="isStatus">恭喜您中奖</view>
<view class="r-title" v-else>很遗憾</view>
<view class="r-grid">
<image :src="mainImgPath" mode="aspectFill" v-if="mainImgPath"></image>
<text v-if="productName">{{productName}}</text>
<text v-else>未中奖谢谢参与</text>
</view>
<view class="receive-btn" v-if="isPrize">点击领取</view>
<view class="receive-btn" v-else @click="again">再抽一次</view>
<image src="/static/images/close-icon2.png" mode="" @click="closePopup" class="r-close"></image>
</view>
</u-popup>
<u-popup :show="popupShow2" mode="bottom" bgColor="transparent">
<view style="width: 750rpx;background-color: #F1F2F4;border-radius: 30rpx 30rpx 0 0;overflow: hidden;">
<view class="p-header">
<view class="h-left">我的奖品</view>
<view class="h-right" @click="popupShow2 = false">
<image src="/static/images/close-icon.png" mode=""></image>
</view>
</view>
<view class="p-list">
<scroll-view scroll-y="true" style="max-height: 920rpx;">
<view class="p-item" v-for="(item,index) in myprizeList" :key="index">
<u--image :showLoading="true" :src="item.mainImgPath" width="170rpx" height="170rpx" radius='10rpx' @click="click"></u--image>
<view class="i-right">
<view class="r-title">
<view class="t-left">{{item.productName}}</view>
<view class="t-right" v-if="item.status == 0 && item.productName.indexOf('积分') == -1 && item.productName.indexOf('研报') == -1">7天内有效</view>
</view>
<view class="r-num">X1</view>
<view class="r-check">
<view class="c-time">获奖时间: {{item.updatetime.slice(0,10)}}</view>
<view class="c-btn1" v-if="item.status == 0 && item.productName.indexOf('积分') == -1 && item.productName.indexOf('研报') == -1">待领取</view>
<view class="c-btn2" v-else-if="item.status == 1 && item.produceAttr == 1">查看</view>
<view class="c-btn3" v-else-if="item.productName.indexOf('积分') != -1">已领取</view>
<view class="c-btn3" v-else-if="item.productName.indexOf('研报') != -1">已领取</view>
<view class="c-btn3" v-else-if="item.status == -1">已过期</view>
</view>
</view>
</view>
<view style="height: 100rpx;width: 100%;" v-if="myprizeList.length>0"></view>
</scroll-view>
<view style="height: 610rpx;display: flex;flex-direction: column;justify-content: center;align-items: center;" v-if="myprizeList.length == 0">
<u--image src="/static/images/nocomment.png" width="320rpx" height="268rpx"></u--image>
<text style="font-size: 24rpx;font-weight: 500;color: #999999;margin-top: 22rpx;">暂无中奖记录~</text>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import {
luckdrawactivityinfos,
luckdrawproductinfo,
luckDraw,
MyLuckDraw,
GetLuckDraws,
getUserinfo,
getintegroreduce
} from "@/api/luckDraw.js" // 后端接口
import AlmostLottery from './almost-lottery.vue'
import {
clearCacheFile,
clearStore
} from './almost-utils.js'
export default {
components: {
AlmostLottery
},
data() {
return {
// 开启调试模式
isDev: true,
// 以下是转盘配置相关数据
lotteryConfig: {
// 抽奖转盘的整体尺寸,单位rpx
lotterySize: 600,
// 抽奖按钮的尺寸,单位rpx
actionSize: 150
},
// 以下是转盘 UI 配置
// 转盘外环图,如有需要,请参考替换为自己的设计稿
lotteryBg: require('@/static/images/zhuanpan.png'),
// 以下是奖品配置数据
// 奖品数据
prizeList: [],
// 奖品是否设有库存
onStock: true,
// 中奖下标
prizeIndex: -1,
// 是否正在抽奖中,避免重复触发
prizeing: false,
top: 0,
opacity: 0,
scrollTop: 0.5,
popupShow1: false, // 中奖结果弹框
popupShow2: false, // 奖品弹框
text1: [], // 中奖滚动信息
userId:'', // 用户id
productName:'', // 奖品名称
mainImgPath:'', // 奖品图片
isPrize:true, // 是否有奖品
isStatus:true, // 是否展示中奖
integroreduce:'', // 用户积分
myprizeList:[], // 中奖奖品列表
integroValue:'', // 积分值
productID:'', // 奖品id
produceAttr:'', // 奖品列表
userName:'', // 用户名
}
},
computed: {
isApple() {
return uni.getSystemInfoSync().platform === 'ios'
}
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
},
methods: {
// 获取轮播列表
async getLuckDraws() {
let arr = []
let res = await GetLuckDraws({
luckDrawActivityID: this.LuckDrawActivityID
})
res.result.forEach(item=>{
arr.push(`恭喜${item.userName ? item.userName :'**'}获得${item.productName}奖品!`)
})
this.text1 = arr
},
// 获取我的奖品
async getMyLuckDraw() {
let res = await MyLuckDraw({
token: sessionStorage.getItem("token"),
status:-1,
pageIndex:1,
pageSize: 1000
})
this.popupShow2 = true
this.myprizeList = res.result
},
//获取总积分
async getTotal() {
let res = await getintegroreduce({
token: sessionStorage.getItem("token")
})
if (res.isSuccess) {
this.integroreduce = res.resultData
} else {
this.integroreduce = 0
}
},
again() {
this.popupShow1 = false
this.handleDrawStart()
this.productName = ''
this.mainImgPath = ''
},
closePopup() {
this.popupShow1 = false
this.productName = '',
this.mainImgPath = ''
},
async getluckdrawactivityinfos() {
let data = await getUserinfo({
token: sessionStorage.getItem("token")
})
this.userId = data.result.UserID
this.userName = data.result.Name
let res = await luckdrawactivityinfos()
this.LuckDrawActivityID = res.result.luckDrawActivityID
this.integroValue = res.result.integralValue
this.getPrizeList()
},
// 获取奖品列表
async getPrizeList() {
let res = await luckdrawproductinfo({
LuckDrawActivityID:this.LuckDrawActivityID
})
if(res.status == 1) {
let data = res.result
if (data.length) {
this.prizeList = data
}
} else {
uni.hideLoading()
uni.showToast({
title: '获取奖品失败',
mask: true,
icon: 'none'
})
}
},
initNavigation(e) {
this.opacity = e.opacity;
this.top = e.top;
},
opacityChange(e) {
this.opacity = e.opacity;
},
// 重新生成
handleInitCanvas() {
clearCacheFile()
clearStore()
this.prizeList = []
this.getPrizeList()
},
// 本次抽奖开始
handleDrawStart() {
if (this.prizeing) return
this.prizeing = true
this.remoteGetPrizeIndex()
},
async remoteGetPrizeIndex() {
let res = await luckDraw({
userId:this.userId,
LuckDrawActivityID:this.LuckDrawActivityID,
integroValue:this.integroValue,
userName:this.userName,
token: sessionStorage.getItem("token")
})
if(res.status == 1) {
this.isStatus = true
this.getTotal()
if( res.result.productName.indexOf('积分') != -1 || res.result.productName.indexOf('研报') != -1) {
this.isPrize = false
}else {
this.isPrize = true
}
let productID = res.result.productID
this.productName = res.result.productName
this.mainImgPath = res.result.mainImgPath
this.produceAttr = res.result.produceAttr
this.productID = res.result.productID
let list = [...this.prizeList]
// 拿到后端返回的 productID 后,开始循环比对得出那个中奖的数据
for (let i = 0; i < list.length; i++) {
let item = list[i]
if (item.productID === productID) {
// 中奖下标
this.prizeIndex = i
break
}
}
}else if(res.message == '谢谢惠顾!') {
this.getTotal()
this.isPrize = false
this.isStatus = false
let list = [...this.prizeList]
for (let i = 0; i < list.length; i++) {
let item = list[i]
if (item.productID === null) {
// 中奖下标
this.prizeIndex = i
break
}
}
}else{
uni.showToast({
icon:'none',
title: res.message
})
this.prizeing = false
}
},
// 本次抽奖结束
handleDrawEnd() {
this.popupShow1 = true
this.prizeing = false
},
// 抽奖转盘绘制完成
handleDrawFinish(res) {
if (res.ok) {
// 计算结束绘制的时间
if (console.timeEnd) {
console.timeEnd('绘制转盘用时')
}
}
let stoTimer = setTimeout(() => {
stoTimer = null
uni.hideLoading()
uni.showToast({
title: res.msg,
mask: true,
icon: 'none'
})
}, 50)
}
},
onl oad() {
this.text1 = []
this.prizeList = []
this.getTotal()
this.getluckdrawactivityinfos()
this.getLuckDraws()
},
onUnload() {
uni.hideLoading()
}
}
</script>
<style lang="less" scoped>
page {
background-color: #E8251E;
padding-bottom: 60rpx;
}
.mask {
overflow: hidden;
position:fixed;
height: 100%;
width: 100%;
}
.p-result {
position: relative;
width: 668rpx;
height: 744rpx;
background: url('/static/images/result-icon.png');
background-size: 100% 100%;
background-repeat: no-repeat;
.r-title {
text-align: center;
margin-top: 60rpx;
font-size: 46rpx;
font-family: PingFang;
font-weight: bold;
color: #C72B2A;
letter-spacing: 2px;
}
.r-grid {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 400rpx;
height: 300rpx;
background: linear-gradient(0deg, #FFEAC3, #FFF6EF);
border-radius: 20rpx;
margin: 110rpx auto 0;
image {
width: 160rpx;
height: 160rpx;
margin-bottom: 10rpx;
}
text {
font-size: 34rpx;
font-family: PingFang;
font-weight: bold;
color: #DC3F24;
line-height: 60rpx;
letter-spacing: 1px;
}
}
.receive-btn {
display: flex;
justify-content: center;
width: 300rpx;
height: 80rpx;
background: url('/static/images/receive-btn.png');
background-size: 100% 100%;
background-repeat: no-repeat;
margin: 60rpx auto 0;
font-size: 30rpx;
font-family: PingFang;
font-weight: bold;
color: #C72B2A;
padding-top: 16rpx;
box-sizing: border-box;
}
.r-close {
position: absolute;
width: 60rpx;
height: 60rpx;
left: 0;
right: 0;
bottom: -100rpx;
margin: auto;
}
}
.p-header {
width: 750rpx;
height: 110rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 40rpx 0 30rpx;
box-sizing: border-box;
background-color: #fff;
.h-left {
font-size: 34rpx;
font-family: PingFang;
font-weight: bold;
color: #333333;
}
.h-right {
width: 40rpx;
height: 40rpx;
display: flex;
justify-content: center;
align-items: center;
image {
width: 24rpx;
height: 24rpx;
}
}
}
.p-list {
padding: 0 20rpx 0 24rpx;
width: 720rpx;
background: #FFFFFF;
border-radius: 14rpx 14rpx 0 0;
box-sizing: border-box;
margin: 16rpx auto 0;
.p-item {
display: flex;
align-items: center;
height: 230rpx;
image {
width: 170rpx;
height: 170rpx;
border-radius: 10rpx;
}
.i-right {
flex: 1;
margin-left: 30rpx;
.r-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 22rpx;
line-height: 1;
.t-left {
font-size: 32rpx;
font-family: PingFang;
font-weight: bold;
color: #333333;
}
.t-right {
font-size: 26rpx;
font-family: PingFang SC;
font-weight: 500;
color: #DC3F24;
}
}
.r-num {
font-size: 26rpx;
font-family: PingFang;
font-weight: 500;
color: #999999;
margin-top: 30rpx;
line-height: 1;
}
.r-check {
display: flex;
justify-content: space-between;
align-items: center;
.c-time {
font-size: 24rpx;
font-family: PingFang SC;
font-weight: 500;
color: #999999;
}
.c-btn1 {
display: flex;
align-items: center;
justify-content: center;
width: 160rpx;
height: 60rpx;
background: linear-gradient(90deg, #FF853C, #FC5A4A);
border-radius: 30rpx;
font-size: 28rpx;
font-family: PingFang;
font-weight: bold;
color: #fff;
}
.c-btn3 {
display: flex;
align-items: center;
justify-content: center;
width: 160rpx;
height: 60rpx;
background: linear-gradient(90deg, #E0E3E7, #DBDDE1);;
border-radius: 30rpx;
font-size: 28rpx;
font-family: PingFang;
font-weight: bold;
color: #fff;
}
.c-btn2 {
display: flex;
align-items: center;
justify-content: center;
width: 160rpx;
height: 60rpx;
border: 1px solid #DC3F24;
border-radius: 30rpx;
font-size: 28rpx;
font-family: PingFang;
font-weight: bold;
color: #DC3F24;
}
}
}
}
}
.d-content {
width: 750rpx;
height: 1505rpx;
position: relative;
background: url('/static/images/d-bgm.jpg');
background-size: cover;
background-repeat: no-repeat;
overflow: hidden;
}
.b-tip1 {
text-align: center;
position: absolute;
top: 0rpx;
right: 24rpx;
width: 60rpx;
height: 76rpx;
background: linear-gradient(90deg, #F8E79B, #FFC15B);
border-radius: 0px 0px 30rpx 30rpx;
font-size: 20rpx;
font-weight: 500;
color: #D0271F;
line-height: 1.2;
padding: 10rpx 6rpx 0;
box-sizing: border-box;
}
.b-tip2 {
position: absolute;
bottom: 10rpx;
width: 300rpx;
height: 44rpx;
z-index: 110;
}
.tui-header-icon {
width: 100%;
position: fixed;
top: 0;
padding: 0 12rpx;
display: flex;
align-items: center;
height: 32px;
transform: translateZ(0);
z-index: 99999;
box-sizing: border-box;
}
.almost-lottery__wheel {
position: relative;
margin-top: 280rpx;
.lottery {
position: absolute;
left: 0;
right: 0;
margin: auto;
z-index: 10;
}
.l-btn {
box-sizing: border-box;
padding-top: 18rpx;
padding-left:100rpx;
position: absolute;
bottom: 70rpx;
left: 20rpx;
width: 224rpx;
height: 96rpx;
background:url('/static/images/btn1.png');
background-repeat: repeat;
background-size: 100% 100%;
z-index: 100;
color: #f00;
font-size: 34rpx;
font-weight: bold;
}
.r-btn {
box-sizing: border-box;
padding-top: 18rpx;
padding-left: 40rpx;
right: 20rpx;
bottom: 70rpx;
position: absolute;
width: 254rpx;
height: 96rpx;
background:url('/static/images/btn2.png');
background-repeat: repeat;
background-size: 100% 100%;
z-index: 100;
color: #f00;
font-size: 32rpx;
font-weight: bold;
}
.a-diz {
margin-top: 300rpx;
}
}
.d-list {
position:relative;
width: 690rpx;
height: 827rpx;
background: url('/static/images/rule.png');
background-size: 100% 100%;
background-repeat: no-repeat;
margin: -400rpx auto 0 ;
padding: 130rpx 30rpx 0;
font-size: 32rpx;
color: #666;
line-height: 42rpx;
box-sizing: border-box;
font-family: PingFang;
}
</style>
AlmostLottery子组件代码:
<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: 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 './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: 148
},
// 奖品列表
prizeList: {
type: Array,
required: true,
validator: (value) => {
return value.length > 1
}
},
// 中奖奖品在列表中的下标
prizeIndex: {
type: Number,
required: true
},
// 奖品区块对应背景颜色
colors: {
type: Array,
default: () => [
'#FFFFFF',
'#FFE1C1'
]
},
// 转盘外环背景图
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:40
},
// 奖品图片距离奖品文字的距离
imgMarginStr: {
type: Number,
default: 60
},
// 奖品文字多行情况下的行高
strLineHeight: {
type: Number,
default: 1.2
},
// 奖品文字总长度限制
strMaxLen: {
type: Number,
default: 12
},
// 奖品文字多行情况下第一行文字长度
strLineLen: {
type: Number,
default: 4
},
// 奖品图片的宽
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,
// 旋转动画时间 单位 s
transitionDuration: 0,
// 是否正在旋转
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
}
},
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)
// 绘制奖品名称
let rewardName = this.strLimit(prizeItem.product_name)
// 设置文字颜色
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) {
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 () {
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()
}
this.transitionDuration = this.duration
}, 50)
}
},
mounted() {
this.$nextTick(() => {
let delay = 50
// 小程序平台需要更多的延时才能获取到准确的元素 Size 信息
// // #ifdef MP
// delay = 300
// // #endif
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 {
transition: transform cubic-bezier(.34, .12, .05, .95);
}
</style>
almost-utils.js文件代码:
/**
* 存储 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} ctx - 图片网络地址
* @param {String} img - 图片地址
* @param {String} x - x 轴偏移量
* @param {String} y - y 轴偏移量
* @param {String} w - 宽
* @param {String} h - 高
*/
export const circleImg = (ctx, img, x, y, w, h) => {
let r = Math.floor(w/2)
let cx = x + r
let cy = y + r
ctx.save()
ctx.beginPath()
ctx.arc(cx, cy, r, 0, Math.PI * 2)
ctx.fill()
ctx.clip()
ctx.drawImage(img, x, y, w, h)
ctx.restore()
}
/**
* 计算文本的长度
* @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) => {
resolve({
ok: true,
data: res.errMsg,
tempFilePath: res.tempFilePath
})
},
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
}
// 图像转换工具,可用于图像和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'))
})
}