Pārlūkot izejas kodu

引入抽奖插件

xiaohaizhao 1 gadu atpakaļ
vecāks
revīzija
f53fb0d4d7

BIN
components/.DS_Store


BIN
components/@lucky-canvas/.DS_Store


+ 201 - 0
components/@lucky-canvas/uni/LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [2021] [Li Dong Qi]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 138 - 0
components/@lucky-canvas/uni/README.md

@@ -0,0 +1,138 @@
+<br />
+
+<div align="center">
+  <img src="https://cdn.jsdelivr.net/gh/buuing/cdn/imgs/lucky-canvas.jpg" width="210" alt="logo" />
+  <h1>lucky-canvas 抽奖插件</h1>
+  <p>一个基于 JavaScript 的跨平台 ( 大转盘 / 九宫格 / 老虎机 ) 抽奖插件</p>
+  <p>
+    <a href="https://github.com/buuing/lucky-canvas/stargazers" target="_black">
+      <img src="https://img.shields.io/github/stars/buuing/lucky-canvas?color=%23ffba15&logo=github&style=flat-square" alt="stars" />
+    </a>
+    <a href="https://github.com/buuing/lucky-canvas/network/members" target="_black">
+      <img src="https://img.shields.io/github/forks/buuing/lucky-canvas?color=%23ffba15&logo=github&style=flat-square" alt="forks" />
+    </a>
+    <a href="https://github.com/buuing" target="_black">
+      <img src="https://img.shields.io/badge/Author-%20buuing%20-7289da.svg?&logo=github&style=flat-square" alt="author" />
+    </a>
+    <a href="https://github.com/buuing/lucky-canvas/blob/master/LICENSE" target="_black">
+      <img src="https://img.shields.io/github/license/buuing/lucky-canvas?color=%232dce89&logo=github&style=flat-square" alt="license" />
+    </a>
+  </p>
+</div>
+
+
+|适配框架|npm下载量|CDN使用量|
+| :-: | :-: | :-: |
+|[`JS` / `JQ` 中使用](https://100px.net/usage/js.html)|<a href="https://www.npmjs.com/package/lucky-canvas" target="_black"><img src="https://img.shields.io/npm/dm/lucky-canvas?color=%23ffba15&logo=npm&style=flat-square" alt="downloads" /></a>|<a href="https://www.jsdelivr.com/package/npm/lucky-canvas" target="_black"><img src="https://data.jsdelivr.com/v1/package/npm/lucky-canvas/badge" alt="downloads" /></a>|
+|[`Vue` 中使用](https://100px.net/usage/vue.html)|<a href="https://www.npmjs.com/package/@lucky-canvas/vue" target="_black"><img src="https://img.shields.io/npm/dm/@lucky-canvas/vue?color=%23ffba15&logo=npm&style=flat-square" alt="downloads" /></a>|<a href="https://www.jsdelivr.com/package/npm/@lucky-canvas/vue" target="_black"><img src="https://data.jsdelivr.com/v1/package/npm/@lucky-canvas/vue/badge" alt="downloads" /></a>|
+|[`React` 中使用](https://100px.net/usage/react.html)|<a href="https://www.npmjs.com/package/@lucky-canvas/react" target="_black"><img src="https://img.shields.io/npm/dm/@lucky-canvas/react?color=%23ffba15&logo=npm&style=flat-square" alt="downloads" /></a>|-|
+|[`UniApp` 中使用](https://100px.net/usage/uni.html)|<a href="https://www.npmjs.com/package/@lucky-canvas/uni" target="_black"><img src="https://img.shields.io/npm/dm/@lucky-canvas/uni?color=%23ffba15&logo=npm&style=flat-square" alt="downloads" /></a>|-|
+|[`Taro3.x` 中使用](https://100px.net/usage/taro.html)|<a href="https://www.npmjs.com/package/@lucky-canvas/taro" target="_black"><img src="https://img.shields.io/npm/dm/@lucky-canvas/taro?color=%23ffba15&logo=npm&style=flat-square" alt="downloads" /></a>|-|
+|[`微信小程序` 中使用](https://100px.net/usage/wx.html)|<a href="https://www.npmjs.com/package/@lucky-canvas/mini" target="_black"><img src="https://img.shields.io/npm/dm/@lucky-canvas/mini?color=%23ffba15&logo=npm&style=flat-square" alt="downloads" /></a>|-|
+
+<br />
+
+## 官方文档 & Demo演示
+
+> **中文**:[https://100px.net](https://100px.net)
+
+> **English**:**If anyone can help translate the document, please contact me** `ldq404@qq.com`
+  
+<br />
+
+## 在 uni-app 中使用
+
+### 1. 安装插件
+
+- 你可以选择通过 `HBuilderX` 导入插件: [https://ext.dcloud.net.cn/plugin?id=3499](https://ext.dcloud.net.cn/plugin?id=3499)
+
+- 也可以选择通过 `npm` / `yarn` 安装
+
+```shell
+# npm 安装:
+npm install @lucky-canvas/uni
+
+# yarn 安装:
+yarn add @lucky-canvas/uni
+```
+
+<br />
+
+### 2. 引入并使用
+
+```html
+<view>
+  <!-- 大转盘抽奖 -->
+  <LuckyWheel
+    width="600rpx"
+    height="600rpx"
+    ...你的配置
+  />
+  <!-- 九宫格抽奖 -->
+  <LuckyGrid
+    width="600rpx"
+    height="600rpx"
+    ...你的配置
+  />
+</view>
+```
+
+```js
+// npm 下载会默认到 node_modules 里面,直接引入包名即可
+import LuckyWheel from '@lucky-canvas/uni/lucky-wheel' // 大转盘
+import LuckyGrid from '@lucky-canvas/uni/lucky-grid' // 九宫格
+
+// 如果你是通过 HBuilderX 导入插件,那你需要指定一下路径
+// import LuckyWheel from '@/components/@lucky-canvas/uni/lucky-wheel' // 大转盘
+// import LuckyGrid from '@/components/@lucky-canvas/uni/lucky-grid' // 九宫格
+
+export default {
+  // 注册组件
+  components: { LuckyWheel, LuckyGrid },
+}
+```
+
+<br />
+
+### 3. 我提供了一个最基本的 demo 供你用于尝试
+
+由于 uni-app 渲染 md 的时候会出问题,所以我把 demo 代码放到了文档里
+
+- [https://100px.net/document/uni-app.html](https://100px.net/document/uni-app.html)
+
+<br />
+
+### **4. 补充说明**
+
+- [**如果用着顺手, 可以在 Github 上面点个 <img height="22" align="top" src="https://img.shields.io/github/stars/buuing/lucky-canvas" /> 支持一下(●'◡'●)**](https://github.com/buuing/lucky-canvas)
+
+- 另外: 如果你修复了某些bug或兼容, 欢迎提给我, 我会把你展示到官网的贡献者列表当中
+
+
+<br />
+
+### 5. 常见问题
+
+1. 转盘层级太高了, 我的弹窗盖不住怎么办?
+
+> 答: 因为小程序里canvas是原生组件顶层渲染, 我无法控制canvas的层级, 如果你想盖住它也肯简单, 你可以百度搜索`<cover>`组件
+
+2. 你这些素材, 图片组件从哪下载?
+
+> 答: 官网里的任何图片素材, 所使用到的图片资源均为学习交流使用, 请勿将其用于商业用途, 由此产生的任何商业纠纷我这边概不负责
+
+3. xxx属性怎么使用? xxx方法怎么调用?
+
+> 答: 自己去看文档, 不然难道要我把代码给你写好吗?
+
+4. 这个属性的效果与官网的描述不一致?
+
+> 答: 可能有bug, 你可以去github上的issues去提问 (请认真填写模板)
+
+5. 为什么这个插件不支持app和其他小程序
+
+> 答: 没时间, 但是希望志同道合的同学来一起参与uniapp的兼容开发
+
+---
+
+<font color="blue">作者留言: 为了使我自己保持心情愉悦, 低于5星的提问我用浏览器插件都屏蔽了</font>

+ 317 - 0
components/@lucky-canvas/uni/lucky-grid.vue

@@ -0,0 +1,317 @@
+<template>
+  <view v-if="isShow" class="lucky-box" :style="{ width: boxWidth + 'px', height: boxHeight + 'px' }">
+    <canvas
+      type="2d"
+      id="lucky-grid"
+      canvas-id="lucky-grid"
+      :style="{ width: boxWidth + 'px', height: boxHeight + 'px' }"
+    ></canvas>
+    <image
+      v-if="imgSrc"
+      :src="imgSrc"
+      @load="myLucky.clearCanvas()"
+      :style="{ width: boxWidth + 'px', height: boxHeight + 'px' }"
+    ></image>
+    <!-- #ifdef APP-PLUS -->
+    <view v-if="btnShow">
+      <view class="lucky-grid-btn" v-for="(btn, index) in btns" :key="index" @click="toPlay(btn, index)" :style="{
+        top: btn.top + 'px',
+        left: btn.left + 'px',
+        width: btn.width + 'px',
+        height: btn.height + 'px',
+      }"></view>
+    </view>
+    <!-- #endif -->
+    <!-- #ifndef APP-PLUS -->
+    <view v-if="btnShow">
+      <cover-view class="lucky-grid-btn" v-for="(btn, index) in btns" :key="index" @click="toPlay(btn, index)" :style="{
+        top: btn.top + 'px',
+        left: btn.left + 'px',
+        width: btn.width + 'px',
+        height: btn.height + 'px',
+      }"></cover-view>
+    </view>
+    <!-- #endif -->
+    <!-- #ifndef H5 -->
+    <view v-if="myLucky">
+      <div class="lucky-imgs">
+        <div v-for="(block, index) in blocks" :key="index">
+          <div v-if="block.imgs">
+            <div v-for="(img, i) in block.imgs" :key="i">
+              <image :src="img.src" :data-index="index" :data-i="i" @load="e => imgBindload(e, 'blocks')"></image>
+              <image :src="img.activeSrc" :data-index="index" :data-i="i" @load="e => imgBindloadActive(e, 'blocks')"></image>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="lucky-imgs">
+        <div v-for="(prize, index) in prizes" :key="index">
+          <div v-if="prize.imgs">
+            <div v-for="(img, i) in prize.imgs" :key="i">
+              <image :src="img.src" :data-index="index" :data-i="i" @load="e => imgBindload(e, 'prizes')"></image>
+              <image :src="img.activeSrc" :data-index="index" :data-i="i" @load="e => imgBindloadActive(e, 'prizes')"></image>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="lucky-imgs">
+        <div v-for="(btn, index) in buttons" :key="index">
+          <div v-if="btn.imgs">
+            <image v-for="(img, i) in btn.imgs" :key="i" :src="img.src" :data-index="index" :data-i="i" @load="e => imgBindload(e, 'buttons')"></image>
+          </div>
+        </div>
+      </div>
+      <div class="lucky-imgs">
+        <span v-if="button && button.imgs">
+          <image v-for="(img, i) in button.imgs" :key="i" :src="img.src" :data-i="i" @load="e => imgBindloadBtn(e, 'button')"></image>
+        </span>
+      </div>
+    </view>
+    <!-- #endif -->
+  </view>
+</template>
+
+<script>
+  import { changeUnits, resolveImage, getImage } from './utils.js'
+  import { LuckyGrid } from '../../lucky-canvas'
+  export default {
+    name: 'lucky-grid',
+    data () {
+      return {
+        imgSrc: '',
+        myLucky: null,
+        canvas: null,
+        isShow: false,
+        boxWidth: 100,
+        boxHeight: 100,
+        dpr: 1,
+        btns: [],
+        btnShow: false,
+      }
+    },
+    props: {
+      width: {
+        type: String,
+        default: '600rpx'
+      },
+      height: {
+        type: String,
+        default: '600rpx'
+      },
+      cols: {
+        type: [String, Number],
+        default: 3,
+      },
+      rows: {
+        type: [String, Number],
+        default: 3,
+      },
+      blocks: {
+        type: Array,
+        default: () => []
+      },
+      prizes: {
+        type: Array,
+        default: () => []
+      },
+      buttons: {
+        type: Array,
+        default: () => []
+      },
+      button: {
+        type: Object,
+        default: undefined
+      },
+      defaultConfig: {
+        type: Object,
+        default: () => ({})
+      },
+      defaultStyle: {
+        type: Object,
+        default: () => ({})
+      },
+      activeStyle: {
+        type: Object,
+        default: () => ({})
+      }
+    },
+    mounted () {
+      // #ifdef APP-PLUS
+      console.error('该抽奖插件的最新版暂不支持app端, 请通过npm安装旧版本【npm i uni-luck-draw@1.3.9】')
+      // #endif
+      // #ifndef APP-PLUS
+      this.initLucky()
+      // #endif
+    },
+    watch: {
+      cols (newData) {
+        this.myLucky && (this.myLucky.cols = newData)
+      },
+      rows (newData) {
+        this.myLucky && (this.myLucky.rows = newData)
+      },
+      blocks (newData) {
+        this.myLucky && (this.myLucky.blocks = newData)
+      },
+      prizes (newData) {
+        this.myLucky && (this.myLucky.prizes = newData)
+      },
+      buttons (newData) {
+        this.myLucky && (this.myLucky.buttons = newData)
+      },
+      button (newData) {
+        this.myLucky && (this.myLucky.button = newData)
+      },
+      defaultStyle (newData) {
+        this.myLucky && (this.myLucky.defaultStyle = newData)
+      },
+      defaultConfig (newData) {
+        this.myLucky && (this.myLucky.defaultConfig = newData)
+      },
+      activeStyle (newData) {
+        this.myLucky && (this.myLucky.activeStyle = newData)
+      },
+    },
+    methods: {
+      async imgBindload (res, name) {
+        const { index, i } = res.currentTarget.dataset
+        const img = this[name][index].imgs[i]
+        resolveImage(img, this.canvas)
+      },
+      async imgBindloadActive (res, name) {
+        const { index, i } = res.currentTarget.dataset
+        const img = this[name][index].imgs[i]
+        resolveImage(img, this.canvas, 'activeSrc', '$activeResolve')
+      },
+      async imgBindloadBtn (res, name) {
+        const { i } = res.currentTarget.dataset
+        const img = this[name].imgs[i]
+        resolveImage(img, this.canvas)
+      },
+      getImage () {
+        return getImage.call(this, 'lucky-grid', this.canvas)
+      },
+      hideCanvas () {
+        // #ifdef MP
+        this.getImage().then(res => {
+          this.imgSrc = res.tempFilePath
+        })
+        // #endif
+      },
+      initLucky () {
+        this.boxWidth = changeUnits(this.width)
+        this.boxHeight = changeUnits(this.height)
+        this.isShow = true
+        // 某些情况下获取不到 canvas
+        this.$nextTick(() => {
+          setTimeout(() => {
+            this.draw()
+          })
+        })
+      },
+      draw () {
+        const _this = this
+        uni.createSelectorQuery().in(this).select('#lucky-grid').fields({
+          node: true, size: true
+        }).exec((res) => {
+          // #ifdef H5
+          res[0].node = document.querySelector('#lucky-grid canvas')
+          // #endif
+          if (!res[0] || !res[0].node) return console.error('lucky-canvas 获取不到 canvas 标签')
+          const { node, width, height } = res[0]
+          const canvas = this.canvas = node
+          const ctx = this.ctx = canvas.getContext('2d')
+          const dpr = this.dpr = uni.getSystemInfoSync().pixelRatio
+          // #ifndef H5
+          canvas.width = width * dpr
+          canvas.height = height * dpr
+          ctx.scale(dpr, dpr)
+          // #endif
+          const myLucky = this.myLucky = new LuckyGrid({
+            // #ifdef H5
+            flag: 'WEB',
+            // #endif
+            // #ifdef MP
+            flag: 'MP-WX',
+            // #endif
+            ctx,
+            dpr,
+            setTimeout,
+            clearTimeout,
+            setInterval,
+            clearInterval,
+            // #ifdef H5
+            rAF: requestAnimationFrame,
+            // #endif
+            unitFunc: (num, unit) => changeUnits(num + unit),
+            afterInit: function () {
+              [..._this.$props.buttons, _this.$props.button].forEach((btn, index) => {
+                if (!btn) return
+                const [left, top, width, height] = this.getGeometricProperty([
+                  btn.x,
+                  btn.y,
+                  btn.col || 1,
+                  btn.row || 1
+                ])
+                _this.btns[index] = { top, left, width, height }
+              })
+              _this.$forceUpdate()
+            },
+            afterStart: () => {
+              this.imgSrc = ''
+            },
+          }, {
+            ...this.$props,
+            width,
+            height,
+            start: (...rest) => {
+              this.$emit('start', ...rest)
+            },
+            end: (...rest) => {
+              this.$emit('end', ...rest)
+              this.hideCanvas()
+            },
+          })
+          this.btnShow = true
+        })
+      },
+      toPlay (btn, index) {
+        this.myLucky.startCallback(btn, this.$props.buttons[index])
+      },
+      init () {
+        this.myLucky.init()
+      },
+      play (...rest) {
+        this.myLucky.play(...rest)
+      },
+      stop (...rest) {
+        this.myLucky.stop(...rest)
+      },
+    },
+  }
+</script>
+
+<style scoped>
+  .lucky-box {
+    position: relative;
+    overflow: hidden;
+    margin: 0 auto;
+  }
+  .lucky-box canvas {
+    position: absolute;
+    pointer-events: none;
+    left: 0;
+    top: 0;
+  }
+  .lucky-grid-btn {
+    position: absolute;
+    background: rgba(0, 0, 0, 0);
+    border-radius: 0;
+    cursor: pointer;
+  }
+  .lucky-imgs {
+    width: 0;
+    height: 0;
+    visibility: hidden;
+  }
+</style>

+ 255 - 0
components/@lucky-canvas/uni/lucky-wheel.vue

@@ -0,0 +1,255 @@
+<template>
+  <view v-if="isShow" class="lucky-box" :style="{ width: boxWidth + 'px', height: boxHeight + 'px' }">
+    <canvas
+      type="2d"
+      id="lucky-wheel"
+      canvas-id="lucky-wheel"
+      :style="{ width: boxWidth + 'px', height: boxHeight + 'px' }"
+    ></canvas>
+    <image
+      v-if="imgSrc"
+      :src="imgSrc"
+      @load="myLucky.clearCanvas()"
+      :style="{ width: boxWidth + 'px', height: boxHeight + 'px' }"
+    ></image>
+    <!-- #ifdef APP-PLUS -->
+    <view class="lucky-wheel-btn" @click="toPlay" :style="{ width: btnWidth + 'px', height: btnHeight + 'px' }"></view>
+    <!-- #endif -->
+    <!-- #ifndef APP-PLUS -->
+    <cover-view class="lucky-wheel-btn" @click="toPlay" :style="{ width: btnWidth + 'px', height: btnHeight + 'px' }"></cover-view>
+    <!-- #endif -->
+    <!-- #ifndef H5 -->
+    <view v-if="myLucky">
+      <div class="lucky-imgs">
+        <div v-for="(block, index) in blocks" :key="index">
+          <div v-if="block.imgs">
+            <image v-for="(img, i) in block.imgs" :key="i" :src="img.src" @load="e => imgBindload(e, 'blocks', index, i)"></image>
+          </div>
+        </div>
+      </div>
+      <div class="lucky-imgs">
+        <div v-for="(prize, index) in prizes" :key="index">
+          <div v-if="prize.imgs">
+            <image v-for="(img, i) in prize.imgs" :key="i" :src="img.src" @load="e => imgBindload(e, 'prizes', index, i)"></image>
+          </div>
+        </div>
+      </div>
+      <div class="lucky-imgs">
+        <div v-for="(btn, index) in buttons" :key="index">
+          <div v-if="btn.imgs">
+            <image v-for="(img, i) in btn.imgs" :key="i" :src="img.src" @load="e => imgBindload(e, 'buttons', index, i)"></image>
+          </div>
+        </div>
+      </div>
+    </view>
+    <!-- #endif -->
+  </view>
+</template>
+
+<script>
+  import { changeUnits, resolveImage, getImage } from './utils.js'
+  import { LuckyWheel } from '../../lucky-canvas'
+  export default {
+    name: 'lucky-wheel',
+    data () {
+      return {
+        imgSrc: '',
+        myLucky: null,
+        canvas: null,
+        isShow: false,
+        boxWidth: 100,
+        boxHeight: 100,
+        btnWidth: 0,
+        btnHeight: 0,
+        dpr: 1,
+      }
+    },
+    props: {
+      width: {
+        type: String,
+        default: '600rpx'
+      },
+      height: {
+        type: String,
+        default: '600rpx'
+      },
+      blocks: {
+        type: Array,
+        default: () => []
+      },
+      prizes: {
+        type: Array,
+        default: () => []
+      },
+      buttons: {
+        type: Array,
+        default: () => []
+      },
+      defaultConfig: {
+        type: Object,
+        default: () => ({})
+      },
+      defaultStyle: {
+        type: Object,
+        default: () => ({})
+      },
+    },
+    mounted () {
+      // #ifdef APP-PLUS
+      console.error('该抽奖插件的最新版暂不支持app端, 请通过npm安装旧版本【npm i uni-luck-draw@1.3.9】')
+      // #endif
+      // #ifndef APP-PLUS
+      this.initLucky()
+      // #endif
+    },
+    watch: {
+      blocks (newData) {
+        this.myLucky && (this.myLucky.blocks = newData)
+      },
+      prizes (newData) {
+        this.myLucky && (this.myLucky.prizes = newData)
+      },
+      buttons (newData) {
+        this.myLucky && (this.myLucky.buttons = newData)
+      },
+      defaultStyle (newData) {
+        this.myLucky && (this.myLucky.defaultStyle = newData)
+      },
+      defaultConfig (newData) {
+        this.myLucky && (this.myLucky.defaultConfig = newData)
+      },
+    },
+    methods: {
+      async imgBindload (res, name, index, i) {
+        const img = this[name][index].imgs[i]
+        resolveImage(img, this.canvas)
+      },
+      getImage () {
+        return getImage.call(this, 'lucky-wheel', this.canvas)
+      },
+      hideCanvas () {
+        // #ifdef MP
+        this.getImage().then(res => {
+          this.imgSrc = res.tempFilePath
+        })
+        // #endif
+      },
+      initLucky () {
+        this.boxWidth = changeUnits(this.width)
+        this.boxHeight = changeUnits(this.height)
+        this.isShow = true
+        // 某些情况下获取不到 canvas
+        this.$nextTick(() => {
+          setTimeout(() => {
+            this.draw()
+          })
+        })
+      },
+      draw () {
+        const _this = this
+        uni.createSelectorQuery().in(this).select('#lucky-wheel').fields({
+          node: true, size: true
+        }).exec((res) => {
+          // #ifdef H5
+          res[0].node = document.querySelector('#lucky-wheel canvas')
+          // #endif
+          if (!res[0] || !res[0].node) return console.error('lucky-canvas 获取不到 canvas 标签')
+          const { node, width, height } = res[0]
+          const canvas = this.canvas = node
+          const ctx = this.ctx = canvas.getContext('2d')
+          const dpr = this.dpr = uni.getSystemInfoSync().pixelRatio
+          // #ifndef H5
+          canvas.width = width * dpr
+          canvas.height = height * dpr
+          ctx.scale(dpr, dpr)
+          // #endif
+          const Radius = Math.min(width, height) / 2
+          const myLucky = this.myLucky = new LuckyWheel({
+            // #ifdef H5
+            flag: 'WEB',
+            // #endif
+            // #ifdef MP
+            flag: 'MP-WX',
+            // #endif
+            ctx,
+            dpr,
+            setTimeout,
+            clearTimeout,
+            setInterval,
+            clearInterval,
+            // #ifdef H5
+            rAF: requestAnimationFrame,
+            // #endif
+            unitFunc: (num, unit) => changeUnits(num + unit),
+            beforeCreate: function () {
+              ctx.translate(Radius, Radius)
+            },
+            beforeResize: function () {
+              ctx.translate(-Radius, -Radius)
+            },
+            afterInit: function () {
+              // 动态设置按钮
+              _this.btnWidth = this.maxBtnRadius * 2
+              _this.btnHeight = this.maxBtnRadius * 2
+              _this.$forceUpdate()
+            },
+            afterStart: () => {
+              this.imgSrc = ''
+            },
+          }, {
+            ...this.$props,
+            width,
+            height,
+            start: (...rest) => {
+              this.$emit('start', ...rest)
+            },
+            end: (...rest) => {
+              this.$emit('end', ...rest)
+              this.hideCanvas()
+            },
+          })
+        })
+      },
+      toPlay (e) {
+        this.myLucky.startCallback()
+      },
+      init () {
+        this.myLucky.init()
+      },
+      play (...rest) {
+        this.myLucky.play(...rest)
+      },
+      stop (...rest) {
+        this.myLucky.stop(...rest)
+      },
+    },
+  }
+</script>
+
+<style scoped>
+  .lucky-box {
+    position: relative;
+    overflow: hidden;
+    margin: 0 auto;
+  }
+  .lucky-box canvas {
+    position: absolute;
+    pointer-events: none;
+    left: 0;
+    top: 0;
+  }
+  .lucky-wheel-btn {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    background: rgba(0, 0, 0, 0);
+    border-radius: 50%;
+    cursor: pointer;
+  }
+  .lucky-imgs {
+    width: 0;
+    height: 0;
+    visibility: hidden;
+  }
+</style>

+ 23 - 0
components/@lucky-canvas/uni/package.json

@@ -0,0 +1,23 @@
+{
+  "name": "@lucky-canvas/uni",
+  "version": "0.0.10",
+  "description": "uni-app【大转盘 / 九宫格 / 老虎机】抽奖插件",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [
+    "uni-app抽奖"
+  ],
+  "files": [
+    "lucky-wheel.vue",
+    "lucky-grid.vue",
+    "slot-machine.vue",
+    "utils.js",
+    "demo.vue"
+  ],
+  "author": "ldq <ldq404@qq.com>",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "lucky-canvas": "~1.7.19"
+  }
+}

+ 214 - 0
components/@lucky-canvas/uni/slot-machine.vue

@@ -0,0 +1,214 @@
+<template>
+  <view v-if="isShow" class="lucky-box" :style="{ width: boxWidth + 'px', height: boxHeight + 'px' }">
+    <canvas
+      type="2d"
+      id="slot-machine"
+      canvas-id="slot-machine"
+      :style="{ width: boxWidth + 'px', height: boxHeight + 'px' }"
+    ></canvas>
+    <image
+      v-if="imgSrc"
+      :src="imgSrc"
+      @load="myLucky.clearCanvas()"
+      :style="{ width: boxWidth + 'px', height: boxHeight + 'px' }"
+    ></image>
+    <!-- #ifndef H5 -->
+    <view v-if="myLucky">
+      <div class="lucky-imgs">
+        <div v-for="(block, index) in blocks" :key="index">
+          <div v-if="block.imgs">
+            <image v-for="(img, i) in block.imgs" :key="i" :src="img.src" @load="e => imgBindload(e, 'blocks', index, i)"></image>
+          </div>
+        </div>
+      </div>
+      <div class="lucky-imgs">
+        <div v-for="(prize, index) in prizes" :key="index">
+          <div v-if="prize.imgs">
+            <image v-for="(img, i) in prize.imgs" :key="i" :src="img.src" @load="e => imgBindload(e, 'prizes', index, i)"></image>
+          </div>
+        </div>
+      </div>
+    </view>
+    <!-- #endif -->
+  </view>
+</template>
+
+<script>
+  import { changeUnits, resolveImage, getImage } from './utils.js'
+  import { SlotMachine } from '../../lucky-canvas'
+  export default {
+    name: 'slot-machine',
+    data () {
+      return {
+        imgSrc: '',
+        myLucky: null,
+        canvas: null,
+        isShow: false,
+        boxWidth: 100,
+        boxHeight: 100,
+        btnWidth: 0,
+        btnHeight: 0,
+        dpr: 1,
+      }
+    },
+    props: {
+      width: {
+        type: String,
+        default: '600rpx'
+      },
+      height: {
+        type: String,
+        default: '600rpx'
+      },
+      blocks: {
+        type: Array,
+        default: () => []
+      },
+      prizes: {
+        type: Array,
+        default: () => []
+      },
+      slots: {
+        type: Array,
+        default: () => []
+      },
+      defaultConfig: {
+        type: Object,
+        default: () => ({})
+      },
+      defaultStyle: {
+        type: Object,
+        default: () => ({})
+      },
+    },
+    mounted () {
+      // #ifndef APP-PLUS
+      this.initLucky()
+      // #endif
+    },
+    watch: {
+      blocks (newData) {
+        this.myLucky && (this.myLucky.blocks = newData)
+      },
+      prizes (newData) {
+        this.myLucky && (this.myLucky.prizes = newData)
+      },
+      slots (newData) {
+        this.myLucky && (this.myLucky.slots = newData)
+      },
+      defaultStyle (newData) {
+        this.myLucky && (this.myLucky.defaultStyle = newData)
+      },
+      defaultConfig (newData) {
+        this.myLucky && (this.myLucky.defaultConfig = newData)
+      },
+    },
+    methods: {
+      async imgBindload (res, name, index, i) {
+        const img = this[name][index].imgs[i]
+        resolveImage(img, this.canvas)
+      },
+      getImage () {
+        return getImage.call(this, 'slot-machine', this.canvas)
+      },
+      hideCanvas () {
+        // #ifdef MP
+        this.getImage().then(res => {
+          this.imgSrc = res.tempFilePath
+        })
+        // #endif
+      },
+      initLucky () {
+        this.boxWidth = changeUnits(this.width)
+        this.boxHeight = changeUnits(this.height)
+        this.isShow = true
+        // 某些情况下获取不到 canvas
+        this.$nextTick(() => {
+          setTimeout(() => {
+            this.draw()
+          })
+        })
+      },
+      draw () {
+        const _this = this
+        uni.createSelectorQuery().in(this).select('#slot-machine').fields({
+          node: true, size: true
+        }).exec((res) => {
+          // #ifdef H5
+          res[0].node = document.querySelector('#slot-machine canvas')
+          // #endif
+          if (!res[0] || !res[0].node) return console.error('lucky-canvas 获取不到 canvas 标签')
+          const { node, width, height } = res[0]
+          const canvas = this.canvas = node
+          const ctx = this.ctx = canvas.getContext('2d')
+          const dpr = this.dpr = uni.getSystemInfoSync().pixelRatio
+          // #ifndef H5
+          canvas.width = width * dpr
+          canvas.height = height * dpr
+          ctx.scale(dpr, dpr)
+          // #endif
+          const myLucky = this.myLucky = new SlotMachine({
+            // #ifdef H5
+            flag: 'WEB',
+            // #endif
+            // #ifdef MP
+            flag: 'MP-WX',
+            // #endif
+            ctx,
+            dpr,
+            // #ifndef H5
+            offscreenCanvas: uni.createOffscreenCanvas({ type: '2d' }),
+            // #endif
+            setTimeout,
+            clearTimeout,
+            setInterval,
+            clearInterval,
+            // #ifdef H5
+            rAF: requestAnimationFrame,
+            // #endif
+            unitFunc: (num, unit) => changeUnits(num + unit),
+            afterStart: () => {
+              this.imgSrc = ''
+            },
+          }, {
+            ...this.$props,
+            width,
+            height,
+            end: (...rest) => {
+              this.$emit('end', ...rest)
+              this.hideCanvas()
+            },
+          })
+        })
+      },
+      init () {
+        this.myLucky.init()
+      },
+      play (...rest) {
+        this.myLucky.play(...rest)
+      },
+      stop (...rest) {
+        this.myLucky.stop(...rest)
+      },
+    },
+  }
+</script>
+
+<style scoped>
+  .lucky-box {
+    position: relative;
+    overflow: hidden;
+    margin: 0 auto;
+  }
+  .lucky-box canvas {
+    position: absolute;
+    pointer-events: none;
+    left: 0;
+    top: 0;
+  }
+  .lucky-imgs {
+    width: 0;
+    height: 0;
+    visibility: hidden;
+  }
+</style>

+ 82 - 0
components/@lucky-canvas/uni/utils.js

@@ -0,0 +1,82 @@
+let windowWidth = uni.getSystemInfoSync().windowWidth
+// uni-app@2.9起, 屏幕最多适配到960, 超出则按375计算
+if (windowWidth > 960) windowWidth = 375
+
+export const rpx2px = (value) => {
+  if (typeof value === 'string') value = Number(value.replace(/[a-z]*/g, ''))
+  return windowWidth / 750 * value
+}
+
+export const changeUnits = (value) => {
+  return Number(value.replace(/^(\-*[0-9.]*)([a-z%]*)$/, (value, num, unit) => {
+    switch (unit) {
+      case 'px':
+        num *= 1
+        break
+      case 'rpx':
+        num = rpx2px(num)
+        break
+      default:
+        num *= 1
+        break
+    }
+    return num
+  }))
+}
+
+export const resolveImage = async (img, canvas, srcName = 'src', resolveName = '$resolve') => {
+  let imgObj
+  // 区分 H5 和小程序
+  if (window) {
+    imgObj = new Image()
+  } else {
+    imgObj = canvas.createImage()
+  }
+  // 成功回调
+  imgObj.onload = () => {
+    img[resolveName](imgObj)
+  }
+  // 失败回调
+  imgObj.onerror = (err) => {
+    console.error(err)
+    // img['$reject']()
+  }
+  // 设置src
+  imgObj.src = img[srcName]
+}
+
+// 旧版canvas引入图片的方法
+// export const resolveImage = async (res, img, imgName = 'src', resolveName = '$resolve') => {
+//   const src = img[imgName]
+//   const $resolve = img[resolveName]
+//   // #ifdef MP
+//   // 如果是base64就调用base64src()方法把图片写入本地, 然后渲染临时路径
+//   if (/^data:image\/([a-z]+);base64,/.test(src)) {
+//     const path = await base64src(src)
+//     $resolve({ ...res.detail, path })
+//     return
+//   }
+//   // #endif
+//   // 如果是本地图片, 直接返回
+//   if (src.indexOf('http') !== 0) {
+//     $resolve({ ...res.detail, path:src })
+//     return
+//   }
+//   // 如果是网络图片, 则通过getImageInfo()方法获取图片宽高
+//   uni.getImageInfo({
+//     src: src,
+//     success: (imgObj) => $resolve(imgObj),
+//     fail: () => console.error('API `uni.getImageInfo` 加载图片失败', src)
+//   })
+// }
+
+export function getImage(canvasId, canvas) {
+  return new Promise((resolve, reject) => {
+    uni.canvasToTempFilePath({
+      canvas,
+      canvasId,
+      success: res => resolve(res),
+      fail: err => reject(err)
+    }, this)
+  })
+}

+ 201 - 0
components/lucky-canvas/LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [2021] [Li Dong Qi]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 42 - 0
components/lucky-canvas/README.md

@@ -0,0 +1,42 @@
+
+
+<div align="center">
+  <img src="https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png" width="128" alt="logo" />
+  <h1>lucky-canvas 抽奖插件</h1>
+  <p>一个基于 JavaScript 的跨平台 ( 大转盘 / 九宫格 / 老虎机 ) 抽奖插件</p>
+  <p>
+    <a href="https://github.com/buuing/lucky-canvas/stargazers" target="_black">
+      <img src="https://img.shields.io/github/stars/buuing/lucky-canvas?color=%23ffba15&logo=github&style=flat-square" alt="stars" />
+    </a>
+    <a href="https://github.com/buuing/lucky-canvas/network/members" target="_black">
+      <img src="https://img.shields.io/github/forks/buuing/lucky-canvas?color=%23ffba15&logo=github&style=flat-square" alt="forks" />
+    </a>
+    <a href="https://github.com/buuing" target="_black">
+      <img src="https://img.shields.io/badge/Author-%20buuing%20-7289da.svg?&logo=github&style=flat-square" alt="author" />
+    </a>
+    <a href="https://github.com/buuing/lucky-canvas/blob/master/LICENSE" target="_black">
+      <img src="https://img.shields.io/github/license/buuing/lucky-canvas?color=%232dce89&logo=github&style=flat-square" alt="license" />
+    </a>
+  </p>
+</div>
+
+<br />
+
+## 官方文档 & Demo演示
+
+> **中文**:[https://100px.net/usage/js.html](https://100px.net/usage/js.html)  
+
+> **English**:**If anyone can help translate the document, please contact me** `ldq404@qq.com`
+
+
+<br />
+
+## 在 JS / TS 中使用
+
+- [跳转官网 查看详情](https://100px.net/usage/js.html)
+
+<br />
+
+## 🙏🙏🙏 点个Star
+
+**如果您觉得这个项目还不错, 可以在 [Github](https://github.com/buuing/lucky-canvas) 上面帮我点个`star`, 支持一下作者 ☜(゚ヮ゚☜)**

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 15 - 0
components/lucky-canvas/dist/index.cjs.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
components/lucky-canvas/dist/index.cjs.js.map


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 14 - 0
components/lucky-canvas/dist/index.esm.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
components/lucky-canvas/dist/index.esm.js.map


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 14 - 0
components/lucky-canvas/dist/index.umd.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
components/lucky-canvas/dist/index.umd.js.map


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 14 - 0
components/lucky-canvas/dist/lucky-canvas.js


+ 1 - 0
components/lucky-canvas/index.js

@@ -0,0 +1 @@
+module.exports = require('./dist/index.umd.js')

+ 66 - 0
components/lucky-canvas/package.json

@@ -0,0 +1,66 @@
+{
+  "name": "lucky-canvas",
+  "version": "1.7.26",
+  "description": "一个基于原生 js 的(大转盘 / 九宫格 / 老虎机)抽奖插件",
+  "main": "dist/index.cjs.js",
+  "module": "dist/index.esm.js",
+  "unpkg": "dist/index.umd.js",
+  "jsdelivr": "dist/index.umd.js",
+  "types": "types/index.d.ts",
+  "scripts": {
+    "dev": "rollup --config rollup.config.dev.js -w",
+    "build": "rollup --config rollup.config.build.js"
+  },
+  "homepage": "https://100px.net",
+  "bugs": "https://github.com/LuckDraw/lucky-canvas/issues",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/LuckDraw/lucky-canvas.git",
+    "directory": "packages/lucky-canvas"
+  },
+  "author": "ldq <ldq404@qq.com>",
+  "license": "Apache-2.0",
+  "files": [
+    "dist",
+    "types",
+    "index.js"
+  ],
+  "keywords": [
+    "大转盘抽奖",
+    "九宫格抽奖",
+    "老虎机抽奖",
+    "抽奖插件",
+    "js抽奖",
+    "移动端抽奖",
+    "canvas抽奖"
+  ],
+  "devDependencies": {
+    "@babel/core": "^7.12.3",
+    "@babel/preset-env": "^7.12.1",
+    "@babel/plugin-transform-runtime": "^7.16.4",
+    "@babel/runtime": "^7.16.3",
+    "core-js": "^3.19.2",
+    "@rollup/plugin-commonjs": "^16.0.0",
+    "@rollup/plugin-eslint": "^8.0.1",
+    "@rollup/plugin-json": "^4.1.0",
+    "@rollup/plugin-node-resolve": "^10.0.0",
+    "@rollup/plugin-typescript": "^6.1.0",
+    "@typescript-eslint/parser": "^4.14.0",
+    "babel-plugin-external-helpers": "^6.22.0",
+    "babel-preset-latest": "^6.24.1",
+    "eslint": "^7.18.0",
+    "eslint-plugin-prettier": "^3.3.1",
+    "prettier": "^2.2.1",
+    "rollup": "^2.33.1",
+    "rollup-plugin-babel": "^4.4.0",
+    "rollup-plugin-livereload": "^2.0.0",
+    "rollup-plugin-serve": "^1.1.0",
+    "rollup-plugin-terser": "^7.0.2",
+    "rollup-plugin-delete": "^2.0.0",
+    "rollup-plugin-dts": "^3.0.2",
+    "rollup-plugin-typescript2": "^0.30.0",
+    "tslib": "^2.3.1",
+    "typescript": "^4.0.5"
+  },
+  "dependencies": {}
+}

+ 752 - 0
components/lucky-canvas/types/index.d.ts

@@ -0,0 +1,752 @@
+declare type FontItemType = {
+    text: string;
+    top?: string | number;
+    left?: string | number;
+    fontColor?: string;
+    fontSize?: string;
+    fontStyle?: string;
+    fontWeight?: string;
+    lineHeight?: string;
+};
+declare type FontExtendType = {
+    wordWrap?: boolean;
+    lengthLimit?: string | number;
+    lineClamp?: number;
+};
+declare type ImgType = HTMLImageElement | HTMLCanvasElement;
+declare type ImgItemType = {
+    src: string;
+    top?: string | number;
+    left?: string | number;
+    width?: string;
+    height?: string;
+    formatter?: (img: ImgType) => ImgType;
+    $resolve?: Function;
+    $reject?: Function;
+};
+declare type BorderRadiusType = string | number;
+declare type BackgroundType = string;
+declare type ShadowType = string;
+declare type ConfigType = {
+    nodeType?: number;
+    flag: 'WEB' | 'MP-WX' | 'UNI-H5' | 'UNI-MP' | 'TARO-H5' | 'TARO-MP';
+    el?: string;
+    divElement?: HTMLDivElement;
+    canvasElement?: HTMLCanvasElement;
+    ctx: CanvasRenderingContext2D;
+    dpr: number;
+    handleCssUnit?: (num: number, unit: string) => number;
+    rAF?: Function;
+    setTimeout: Function;
+    setInterval: Function;
+    clearTimeout: Function;
+    clearInterval: Function;
+    beforeCreate?: Function;
+    beforeResize?: Function;
+    afterResize?: Function;
+    beforeInit?: Function;
+    afterInit?: Function;
+    beforeDraw?: Function;
+    afterDraw?: Function;
+    afterStart?: Function;
+};
+declare type RequireKey = 'width' | 'height';
+declare type UserConfigType = Partial<Omit<ConfigType, RequireKey>> & Required<Pick<ConfigType, RequireKey>>;
+declare type Tuple<T, Len extends number, Res extends T[] = []> = Res['length'] extends Len ? Res : Tuple<T, Len, [...Res, T]>;
+
+interface WatchOptType {
+    handler?: () => Function;
+    immediate?: boolean;
+    deep?: boolean;
+}
+
+declare class Lucky {
+    static version: string;
+    protected readonly version: string;
+    protected readonly config: ConfigType;
+    protected readonly ctx: CanvasRenderingContext2D;
+    protected htmlFontSize: number;
+    protected rAF: Function;
+    protected boxWidth: number;
+    protected boxHeight: number;
+    protected data: {
+        width: string | number;
+        height: string | number;
+    };
+    /**
+     * 公共构造器
+     * @param config
+     */
+    constructor(config: string | HTMLDivElement | UserConfigType, data: {
+        width: string | number;
+        height: string | number;
+    });
+    /**
+     * 初始化组件大小/单位
+     */
+    protected resize(): void;
+    /**
+     * 初始化方法
+     */
+    protected initLucky(): void;
+    /**
+     * 鼠标点击事件
+     * @param e 事件参数
+     */
+    protected handleClick(e: MouseEvent): void;
+    /**
+     * 根标签的字体大小
+     */
+    protected setHTMLFontSize(): void;
+    clearCanvas(): void;
+    /**
+     * 设备像素比
+     * window 环境下自动获取, 其余环境手动传入
+     */
+    protected setDpr(): void;
+    /**
+     * 重置盒子和canvas的宽高
+     */
+    private resetWidthAndHeight;
+    /**
+     * 根据 dpr 缩放 canvas 并处理位移
+     */
+    protected zoomCanvas(): void;
+    /**
+     * 从 window 对象上获取一些方法
+     */
+    private initWindowFunction;
+    isWeb(): boolean;
+    /**
+     * 异步加载图片并返回图片的几何信息
+     * @param src 图片路径
+     * @param info 图片信息
+     */
+    protected loadImg(src: string, info: ImgItemType, resolveName?: string): Promise<ImgType>;
+    /**
+     * 公共绘制图片的方法
+     * @param imgObj 图片对象
+     * @param rectInfo: [x轴位置, y轴位置, 渲染宽度, 渲染高度]
+     */
+    protected drawImage(ctx: CanvasRenderingContext2D, imgObj: ImgType, ...rectInfo: [...Tuple<number, 4>, ...Partial<Tuple<number, 4>>]): void;
+    /**
+     * 计算图片的渲染宽高
+     * @param imgObj 图片标签元素
+     * @param imgInfo 图片信息
+     * @param maxWidth 最大宽度
+     * @param maxHeight 最大高度
+     * @return [渲染宽度, 渲染高度]
+     */
+    protected computedWidthAndHeight(imgObj: ImgType, imgInfo: ImgItemType, maxWidth: number, maxHeight: number): [number, number];
+    /**
+     * 转换单位
+     * @param { string } value 将要转换的值
+     * @param { number } denominator 分子
+     * @return { number } 返回新的字符串
+     */
+    protected changeUnits(value: string, denominator?: number): number;
+    /**
+     * 获取长度
+     * @param length 将要转换的长度
+     * @param maxLength 最大长度
+     * @return 返回长度
+     */
+    protected getLength(length: string | number | undefined, maxLength?: number): number;
+    /**
+     * 获取相对(居中)X坐标
+     * @param width
+     * @param col
+     */
+    protected getOffsetX(width: number, maxWidth?: number): number;
+    protected getOffscreenCanvas(width: number, height: number): {
+        _offscreenCanvas: HTMLCanvasElement;
+        _ctx: CanvasRenderingContext2D;
+    } | void;
+    /**
+     * 添加一个新的响应式数据 (临时)
+     * @param data 数据
+     * @param key 属性
+     * @param value 新值
+     */
+    $set(data: object, key: string | number, value: any): void;
+    /**
+     * 添加一个属性计算 (临时)
+     * @param data 源数据
+     * @param key 属性名
+     * @param callback 回调函数
+     */
+    protected $computed(data: object, key: string, callback: Function): void;
+    /**
+     * 添加一个观察者 create user watcher
+     * @param expr 表达式
+     * @param handler 回调函数
+     * @param watchOpt 配置参数
+     * @return 卸载当前观察者的函数 (暂未返回)
+     */
+    protected $watch(expr: string | Function, handler: Function | WatchOptType, watchOpt?: WatchOptType): Function;
+}
+
+declare type PrizeFontType$2 = FontItemType & FontExtendType;
+declare type ButtonFontType$1 = FontItemType & {};
+declare type BlockImgType$2 = ImgItemType & {
+    rotate?: boolean;
+};
+declare type PrizeImgType$2 = ImgItemType & {};
+declare type ButtonImgType$1 = ImgItemType & {};
+declare type BlockType$2 = {
+    padding?: string;
+    background?: BackgroundType;
+    imgs?: Array<BlockImgType$2>;
+};
+declare type PrizeType$2 = {
+    range?: number;
+    background?: BackgroundType;
+    fonts?: Array<PrizeFontType$2>;
+    imgs?: Array<PrizeImgType$2>;
+};
+declare type ButtonType$1 = {
+    radius?: string;
+    pointer?: boolean;
+    background?: BackgroundType;
+    fonts?: Array<ButtonFontType$1>;
+    imgs?: Array<ButtonImgType$1>;
+};
+declare type DefaultConfigType$2 = {
+    gutter?: string | number;
+    offsetDegree?: number;
+    speed?: number;
+    speedFunction?: string;
+    accelerationTime?: number;
+    decelerationTime?: number;
+    stopRange?: number;
+};
+declare type DefaultStyleType$2 = {
+    background?: BackgroundType;
+    fontColor?: PrizeFontType$2['fontColor'];
+    fontSize?: PrizeFontType$2['fontSize'];
+    fontStyle?: PrizeFontType$2['fontStyle'];
+    fontWeight?: PrizeFontType$2['fontWeight'];
+    lineHeight?: PrizeFontType$2['lineHeight'];
+    wordWrap?: PrizeFontType$2['wordWrap'];
+    lengthLimit?: PrizeFontType$2['lengthLimit'];
+    lineClamp?: PrizeFontType$2['lineClamp'];
+};
+declare type StartCallbackType$1 = (e: MouseEvent) => void;
+declare type EndCallbackType$2 = (prize: object) => void;
+interface LuckyWheelConfig {
+    width: string | number;
+    height: string | number;
+    blocks?: Array<BlockType$2>;
+    prizes?: Array<PrizeType$2>;
+    buttons?: Array<ButtonType$1>;
+    defaultConfig?: DefaultConfigType$2;
+    defaultStyle?: DefaultStyleType$2;
+    start?: StartCallbackType$1;
+    end?: EndCallbackType$2;
+}
+
+declare class LuckyWheel extends Lucky {
+    private blocks;
+    private prizes;
+    private buttons;
+    private defaultConfig;
+    private defaultStyle;
+    private _defaultConfig;
+    private _defaultStyle;
+    private startCallback?;
+    private endCallback?;
+    private Radius;
+    private prizeRadius;
+    private prizeDeg;
+    private prizeAng;
+    private rotateDeg;
+    private maxBtnRadius;
+    private startTime;
+    private endTime;
+    private stopDeg;
+    private endDeg;
+    private FPS;
+    /**
+     * 游戏当前的阶段
+     * step = 0 时, 游戏尚未开始
+     * step = 1 时, 此时处于加速阶段
+     * step = 2 时, 此时处于匀速阶段
+     * step = 3 时, 此时处于减速阶段
+     */
+    private step;
+    /**
+     * 中奖索引
+     * prizeFlag = undefined 时, 处于开始抽奖阶段, 正常旋转
+     * prizeFlag >= 0 时, 说明stop方法被调用, 并且传入了中奖索引
+     * prizeFlag === -1 时, 说明stop方法被调用, 并且传入了负值, 本次抽奖无效
+     */
+    private prizeFlag;
+    private ImageCache;
+    /**
+     * 大转盘构造器
+     * @param config 配置项
+     * @param data 抽奖数据
+     */
+    constructor(config: UserConfigType, data: LuckyWheelConfig);
+    protected resize(): void;
+    protected initLucky(): void;
+    /**
+     * 初始化数据
+     * @param data
+     */
+    private initData;
+    /**
+     * 初始化属性计算
+     */
+    private initComputed;
+    /**
+     * 初始化观察者
+     */
+    private initWatch;
+    /**
+     * 初始化 canvas 抽奖
+     */
+    init(): Promise<void>;
+    private initImageCache;
+    /**
+     * canvas点击事件
+     * @param e 事件参数
+     */
+    protected handleClick(e: MouseEvent): void;
+    /**
+     * 根据索引单独加载指定图片并缓存
+     * @param cellName 模块名称
+     * @param cellIndex 模块索引
+     * @param imgName 模块对应的图片缓存
+     * @param imgIndex 图片索引
+     */
+    private loadAndCacheImg;
+    private drawBlock;
+    /**
+     * 开始绘制
+     */
+    protected draw(): void;
+    /**
+     * 刻舟求剑
+     */
+    private carveOnGunwaleOfAMovingBoat;
+    /**
+     * 对外暴露: 开始抽奖方法
+     */
+    play(): void;
+    /**
+     * 对外暴露: 缓慢停止方法
+     * @param index 中奖索引
+     */
+    stop(index?: number): void;
+    /**
+     * 实际开始执行方法
+     * @param num 记录帧动画执行多少次
+     */
+    private run;
+    /**
+     * 换算渲染坐标
+     * @param x
+     * @param y
+     */
+    protected conversionAxis(x: number, y: number): [number, number];
+}
+
+declare type PrizeFontType$1 = FontItemType & FontExtendType;
+declare type ButtonFontType = FontItemType & FontExtendType;
+declare type BlockImgType$1 = ImgItemType & {};
+declare type PrizeImgType$1 = ImgItemType & {
+    activeSrc?: string;
+};
+declare type ButtonImgType = ImgItemType & {};
+declare type BlockType$1 = {
+    borderRadius?: BorderRadiusType;
+    background?: BackgroundType;
+    padding?: string;
+    paddingTop?: string | number;
+    paddingRight?: string | number;
+    paddingBottom?: string | number;
+    paddingLeft?: string | number;
+    imgs?: Array<BlockImgType$1>;
+};
+declare type CellType<T, U> = {
+    x: number;
+    y: number;
+    col?: number;
+    row?: number;
+    borderRadius?: BorderRadiusType;
+    background?: BackgroundType;
+    shadow?: ShadowType;
+    fonts?: Array<T>;
+    imgs?: Array<U>;
+};
+declare type PrizeType$1 = CellType<PrizeFontType$1, PrizeImgType$1> & {
+    range?: number;
+    disabled?: boolean;
+};
+declare type ButtonType = CellType<ButtonFontType, ButtonImgType> & {
+    callback?: Function;
+};
+declare type DefaultConfigType$1 = {
+    gutter?: number;
+    speed?: number;
+    accelerationTime?: number;
+    decelerationTime?: number;
+};
+declare type DefaultStyleType$1 = {
+    borderRadius?: BorderRadiusType;
+    background?: BackgroundType;
+    shadow?: ShadowType;
+    fontColor?: PrizeFontType$1['fontColor'];
+    fontSize?: PrizeFontType$1['fontSize'];
+    fontStyle?: PrizeFontType$1['fontStyle'];
+    fontWeight?: PrizeFontType$1['fontWeight'];
+    lineHeight?: PrizeFontType$1['lineHeight'];
+    wordWrap?: PrizeFontType$1['wordWrap'];
+    lengthLimit?: PrizeFontType$1['lengthLimit'];
+    lineClamp?: PrizeFontType$1['lineClamp'];
+};
+declare type ActiveStyleType = {
+    background?: BackgroundType;
+    shadow?: ShadowType;
+    fontColor?: PrizeFontType$1['fontColor'];
+    fontSize?: PrizeFontType$1['fontSize'];
+    fontStyle?: PrizeFontType$1['fontStyle'];
+    fontWeight?: PrizeFontType$1['fontWeight'];
+    lineHeight?: PrizeFontType$1['lineHeight'];
+};
+declare type RowsType = number;
+declare type ColsType = number;
+declare type StartCallbackType = (e: MouseEvent, button?: ButtonType) => void;
+declare type EndCallbackType$1 = (prize: object) => void;
+interface LuckyGridConfig {
+    width: string | number;
+    height: string | number;
+    rows?: RowsType;
+    cols?: ColsType;
+    blocks?: Array<BlockType$1>;
+    prizes?: Array<PrizeType$1>;
+    buttons?: Array<ButtonType>;
+    button?: ButtonType;
+    defaultConfig?: DefaultConfigType$1;
+    defaultStyle?: DefaultStyleType$1;
+    activeStyle?: ActiveStyleType;
+    start?: StartCallbackType;
+    end?: EndCallbackType$1;
+}
+
+declare class LuckyGrid extends Lucky {
+    private rows;
+    private cols;
+    private blocks;
+    private prizes;
+    private buttons;
+    private button?;
+    private defaultConfig;
+    private defaultStyle;
+    private activeStyle;
+    private _defaultConfig;
+    private _defaultStyle;
+    private _activeStyle;
+    private startCallback?;
+    private endCallback?;
+    private cellWidth;
+    private cellHeight;
+    private startTime;
+    private endTime;
+    private currIndex;
+    private stopIndex;
+    private endIndex;
+    private demo;
+    private timer;
+    private FPS;
+    /**
+     * 游戏当前的阶段
+     * step = 0 时, 游戏尚未开始
+     * step = 1 时, 此时处于加速阶段
+     * step = 2 时, 此时处于匀速阶段
+     * step = 3 时, 此时处于减速阶段
+     */
+    private step;
+    /**
+     * 中奖索引
+     * prizeFlag = undefined 时, 处于开始抽奖阶段, 正常旋转
+     * prizeFlag >= 0 时, 说明stop方法被调用, 并且传入了中奖索引
+     * prizeFlag === -1 时, 说明stop方法被调用, 并且传入了负值, 本次抽奖无效
+     */
+    private prizeFlag;
+    private cells;
+    private prizeArea;
+    private ImageCache;
+    /**
+     * 九宫格构造器
+     * @param config 配置项
+     * @param data 抽奖数据
+     */
+    constructor(config: UserConfigType, data: LuckyGridConfig);
+    protected resize(): void;
+    protected initLucky(): void;
+    /**
+     * 初始化数据
+     * @param data
+     */
+    private initData;
+    /**
+     * 初始化属性计算
+     */
+    private initComputed;
+    /**
+     * 初始化观察者
+     */
+    private initWatch;
+    /**
+     * 初始化 canvas 抽奖
+     */
+    init(): Promise<void>;
+    private initImageCache;
+    /**
+     * canvas点击事件
+     * @param e 事件参数
+     */
+    protected handleClick(e: MouseEvent): void;
+    /**
+     * 根据索引单独加载指定图片并缓存
+     * @param cellName 模块名称
+     * @param cellIndex 模块索引
+     * @param imgName 模块对应的图片缓存
+     * @param imgIndex 图片索引
+     */
+    private loadAndCacheImg;
+    /**
+     * 绘制九宫格抽奖
+     */
+    protected draw(): void;
+    /**
+     * 处理背景色
+     * @param x
+     * @param y
+     * @param width
+     * @param height
+     * @param background
+     * @param isActive
+     */
+    private handleBackground;
+    /**
+     * 刻舟求剑
+     */
+    private carveOnGunwaleOfAMovingBoat;
+    /**
+     * 对外暴露: 开始抽奖方法
+     */
+    play(): void;
+    /**
+     * 对外暴露: 缓慢停止方法
+     * @param index 中奖索引
+     */
+    stop(index?: number): void;
+    /**
+     * 实际开始执行方法
+     * @param num 记录帧动画执行多少次
+     */
+    private run;
+    /**
+     * 计算奖品格子的几何属性
+     * @param { array } [...矩阵坐标, col, row]
+     * @return { array } [...真实坐标, width, height]
+     */
+    private getGeometricProperty;
+    /**
+     * 换算渲染坐标
+     * @param x
+     * @param y
+     */
+    protected conversionAxis(x: number, y: number): [number, number];
+}
+
+declare type PrizeFontType = FontItemType & FontExtendType;
+declare type BlockImgType = ImgItemType & {};
+declare type PrizeImgType = ImgItemType;
+declare type BlockType = {
+    borderRadius?: BorderRadiusType;
+    background?: BackgroundType;
+    padding?: string;
+    paddingTop?: string | number;
+    paddingRight?: string | number;
+    paddingBottom?: string | number;
+    paddingLeft?: string | number;
+    imgs?: Array<BlockImgType>;
+};
+declare type PrizeType = {
+    borderRadius?: BorderRadiusType;
+    background?: BackgroundType;
+    fonts?: Array<PrizeFontType>;
+    imgs?: Array<PrizeImgType>;
+};
+declare type SlotType = {
+    order?: number[];
+    speed?: number;
+    direction?: 1 | -1;
+};
+declare type DefaultConfigType = {
+    /**
+     * vertical 为纵向旋转
+     * horizontal 为横向旋转
+     */
+    mode?: 'vertical' | 'horizontal';
+    /**
+     * 当排列方向 = `vertical`时
+     *    1 bottom to top
+     *   -1 top to bottom
+     * 当排列方向 = `horizontal`时
+     *    1 right to left
+     *   -1 left to right
+     */
+    direction?: 1 | -1;
+    rowSpacing?: number;
+    colSpacing?: number;
+    speed?: number;
+    accelerationTime?: number;
+    decelerationTime?: number;
+};
+declare type DefaultStyleType = {
+    borderRadius?: BorderRadiusType;
+    background?: BackgroundType;
+    fontColor?: PrizeFontType['fontColor'];
+    fontSize?: PrizeFontType['fontSize'];
+    fontStyle?: PrizeFontType['fontStyle'];
+    fontWeight?: PrizeFontType['fontWeight'];
+    lineHeight?: PrizeFontType['lineHeight'];
+    wordWrap?: PrizeFontType['wordWrap'];
+    lengthLimit?: PrizeFontType['lengthLimit'];
+    lineClamp?: PrizeFontType['lineClamp'];
+};
+declare type EndCallbackType = (prize: PrizeType | undefined) => void;
+interface SlotMachineConfig {
+    width: string | number;
+    height: string | number;
+    blocks?: Array<BlockType>;
+    prizes?: Array<PrizeType>;
+    slots?: Array<SlotType>;
+    defaultConfig?: DefaultConfigType;
+    defaultStyle?: DefaultStyleType;
+    end?: EndCallbackType;
+}
+
+declare class SlotMachine extends Lucky {
+    private blocks;
+    private prizes;
+    private slots;
+    private defaultConfig;
+    private _defaultConfig;
+    private defaultStyle;
+    private _defaultStyle;
+    private endCallback;
+    private _offscreenCanvas?;
+    private cellWidth;
+    private cellHeight;
+    private cellAndSpacing;
+    private widthAndSpacing;
+    private heightAndSpacing;
+    private FPS;
+    private scroll;
+    private stopScroll;
+    private endScroll;
+    private startTime;
+    private endTime;
+    /**
+     * 游戏当前的阶段
+     * step = 0 时, 游戏尚未开始
+     * step = 1 时, 此时处于加速阶段
+     * step = 2 时, 此时处于匀速阶段
+     * step = 3 时, 此时处于减速阶段
+     */
+    private step;
+    /**
+     * 中奖索引
+     * prizeFlag = undefined 时, 处于开始抽奖阶段, 正常旋转
+     * prizeFlag >= 0 时, 说明stop方法被调用, 并且传入了中奖索引
+     * prizeFlag === -1 时, 说明stop方法被调用, 并且传入了负值, 本次抽奖无效
+     */
+    private prizeFlag;
+    private prizeArea?;
+    private ImageCache;
+    /**
+     * 老虎机构造器
+     * @param config 配置项
+     * @param data 抽奖数据
+     */
+    constructor(config: UserConfigType, data: SlotMachineConfig);
+    protected resize(): void;
+    protected initLucky(): void;
+    /**
+     * 初始化数据
+     * @param data
+     */
+    private initData;
+    /**
+     * 初始化属性计算
+     */
+    private initComputed;
+    /**
+     * 初始化观察者
+     */
+    private initWatch;
+    /**
+     * 初始化 canvas 抽奖
+     */
+    init(): Promise<void>;
+    private initImageCache;
+    /**
+     * 根据索引单独加载指定图片并缓存
+     * @param cellName 模块名称
+     * @param cellIndex 模块索引
+     * @param imgName 模块对应的图片缓存
+     * @param imgIndex 图片索引
+     */
+    private loadAndCacheImg;
+    /**
+     * 绘制离屏canvas
+     */
+    protected drawOffscreenCanvas(): void;
+    /**
+     * 绘制背景区域
+     */
+    protected drawBlocks(): SlotMachine['prizeArea'];
+    /**
+     * 绘制老虎机抽奖
+     */
+    protected draw(): void;
+    /**
+     * 刻舟求剑
+     */
+    private carveOnGunwaleOfAMovingBoat;
+    /**
+     * 对外暴露: 开始抽奖方法
+     */
+    play(): void;
+    stop(index: number | number[]): void;
+    /**
+     * 让游戏动起来
+     * @param num 记录帧动画执行多少次
+     */
+    private run;
+    private displacement;
+    private displacementWidthOrHeight;
+}
+
+/**
+ * 切割圆角
+ * @param img 将要裁剪的图片对象
+ * @param radius 裁剪的圆角半径
+ * @returns 返回一个离屏 canvas 用于渲染
+ */
+declare const cutRound: (img: ImgType, radius: number) => ImgType;
+/**
+ * 透明度
+ * @param img 将要处理的图片对象
+ * @param opacity 透明度
+ * @returns 返回一个离屏 canvas 用于渲染
+ */
+declare const opacity: (img: ImgType, opacity: number) => ImgType;
+
+export { LuckyGrid, LuckyWheel, SlotMachine, cutRound, opacity };

+ 19 - 0
package (2).json

@@ -0,0 +1,19 @@
+{
+    "id": "lucky-canvas",
+    "displayName": "lucky-canvas【大转盘|九宫格|老虎机】抽奖插件 ",
+    "version": "v0.0.10_4",
+    "description": "奖品/文字/图片/颜色/按钮均可配置,支持同步/异步抽奖,概率前/后端可控,自动根据dpr调整清晰度",
+    "keywords": [
+        "抽奖",
+        "大转盘抽奖",
+        "九宫格抽奖",
+        "小程序抽奖",
+        "老虎机抽奖"
+    ],
+    "dcloudext": {
+        "category": [
+            "前端组件",
+            "通用组件"
+        ]
+    }
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels