uniapp+uView幸运大转盘

父组件代码:

<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)

            }

        },

        onLoad() {

            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'))

    })

}

样式仅供参考,所用到的图片:

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值