├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README.md ├── lerna.json ├── package.json └── packages ├── core ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── examples │ ├── grid.html │ ├── imgs │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 100.png │ │ ├── 2.png │ │ ├── 20.png │ │ ├── 50.png │ │ └── bg.png │ ├── index.html │ ├── slot.html │ └── wheel.html ├── index.js ├── package.json ├── postinstall.js ├── rollup.config.build.js ├── rollup.config.dev.js ├── src │ ├── index.ts │ ├── lib │ │ ├── grid.ts │ │ ├── lucky.ts │ │ ├── slot.ts │ │ └── wheel.ts │ ├── observer │ │ ├── array.ts │ │ ├── dep.ts │ │ ├── index.ts │ │ ├── utils.ts │ │ └── watcher.ts │ ├── types │ │ ├── grid.ts │ │ ├── index.ts │ │ ├── slot.ts │ │ └── wheel.ts │ └── utils │ │ ├── image.ts │ │ ├── index.ts │ │ ├── math.ts │ │ ├── polyfill.js │ │ └── tween.ts ├── tsconfig.json └── types │ └── index.d.ts ├── mini ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── gulpfile.js ├── package.json ├── src │ ├── lucky-grid │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── lucky-wheel │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── slot-machine │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ └── utils.js ├── test │ ├── index.test.js │ ├── utils.js │ └── wx.test.js ├── tools │ ├── build.js │ ├── checkcomponents.js │ ├── checkwxss.js │ ├── config.js │ ├── demo │ │ ├── app.js │ │ ├── app.json │ │ ├── app.wxss │ │ ├── package.json │ │ ├── pages │ │ │ ├── cjl │ │ │ │ ├── img.js │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ │ ├── index │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ │ ├── jd │ │ │ │ ├── img.js │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ │ ├── slot │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ │ ├── xc │ │ │ │ ├── img.js │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ │ ├── xdf │ │ │ │ ├── img.js │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ │ ├── ymc │ │ │ │ ├── img.js │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ │ ├── yx │ │ │ │ ├── img.js │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ │ ├── yyjk │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ │ └── yyx │ │ │ │ ├── img.js │ │ │ │ ├── index.js │ │ │ │ ├── index.json │ │ │ │ ├── index.wxml │ │ │ │ └── index.wxss │ │ └── project.config.json │ └── utils.js └── tsconfig.json ├── react ├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── example │ ├── react16.8.html │ └── react18.0.html ├── package.json ├── rollup.config.build.js ├── rollup.config.dev.js └── src │ ├── app.js │ ├── components │ ├── LuckyGrid.js │ ├── LuckyWheel.js │ └── SlotMachine.js │ ├── demo │ ├── LuckyGrid.js │ ├── LuckyWheel.js │ └── SlotMachine.js │ └── index.js ├── taro ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package.json ├── react │ ├── LuckyGrid.js │ ├── LuckyWheel.js │ ├── SlotMachine.js │ └── index.js ├── utils │ ├── index.css │ └── index.js └── vue │ ├── LuckyGrid.vue │ ├── LuckyWheel.vue │ ├── SlotMachine.vue │ └── index.js ├── uni ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── lucky-grid.vue ├── lucky-wheel.vue ├── package.json ├── slot-machine.vue └── utils.js └── vue ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── composition-api.js ├── index.js ├── package.json ├── rollup.config.build.js ├── rollup.config.dev.js ├── shims-vue.d.ts ├── src ├── components │ ├── LuckyGrid.ts │ ├── LuckyWheel.ts │ └── SlotMachine.ts ├── index.ts └── utils │ └── h-demi.ts ├── tsconfig.json ├── types └── index.d.ts ├── vue-demi.js ├── vue2.html └── vue3.html /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | - 你当前是什么框架(必填): 11 | 12 | 13 | - 你使用的是哪个包(必填): 14 | 15 | 16 | - 你当前插件的版本(必填): 17 | 18 | 19 | - 当前环境是小程序还是浏览器(选填): 20 | 21 | 22 | - 详细描述你的bug: 23 | 24 | 25 | - 问题代码(重要): 26 | 27 | 28 | ``` 29 | // 代码开始, 别再放歪了行吗 30 | 31 | // 代码结束 32 | ``` 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | yarn.lock 4 | package-lock.json 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | packages/demo-taro-vue 26 | packages/demo-taro-react 27 | packages/uni-app-demo -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.0.4", 6 | "npmClient": "npm", 7 | "useWorkspaces": true 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lucky-canvas", 3 | "devDependencies": { 4 | "lerna": "^4.0.0" 5 | }, 6 | "scripts": { 7 | "install": "lerna bootstrap", 8 | "dev": "lerna exec --scope=lucky-canvas -- npm run dev", 9 | "dev:vue": "lerna run --scope=lucky-canvas --scope=@lucky-canvas/vue dev --parallel", 10 | "dev:react": "lerna run --scope=lucky-canvas --scope=@lucky-canvas/react dev --parallel", 11 | "dev:uni": "", 12 | "dev:taro": "", 13 | "dev:mini": "", 14 | "build": "lerna run build --sort --stream", 15 | "update-version": "lerna version --conventional-commits --no-push --no-changelog --no-git-tag-version", 16 | "publish-to-npm": "lerna publish from-package", 17 | "publish-beta": "lerna publish --no-git-tag-version --dist-tag beta" 18 | }, 19 | "private": true, 20 | "workspaces": [ 21 | "packages/*" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", { 5 | "modules": false 6 | } 7 | ] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/core/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // module.exports = { 2 | // "env": { 3 | // "browser": true, 4 | // "es6": true, 5 | // "node": true 6 | // }, 7 | // "parser": "@typescript-eslint/parser", 8 | // // "plugins": ['prettier'], 9 | // "extends": "eslint:recommended", 10 | // "globals": { 11 | // "Atomics": "readonly", 12 | // "SharedArrayBuffer": "readonly", 13 | // "ENV": true 14 | // }, 15 | // "parserOptions": { 16 | // "ecmaVersion": 2018, 17 | // "sourceType": "module" 18 | // }, 19 | // "rules": { 20 | // "linebreak-style": 'off' 21 | // } 22 | // } 23 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | yarn.lock 4 | package-lock.json 5 | dist 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | logo 5 |

lucky-canvas 抽奖插件

6 |

一个基于 JavaScript 的跨平台 ( 大转盘 / 九宫格 / 老虎机 ) 抽奖插件

7 |

8 | 9 | stars 10 | 11 | 12 | forks 13 | 14 | 15 | author 16 | 17 | 18 | license 19 | 20 |

21 |
22 | 23 |
24 | 25 | ## 官方文档 & Demo演示 26 | 27 | > **中文**:[https://100px.net/usage/js.html](https://100px.net/usage/js.html) 28 | 29 | > **English**:**If anyone can help translate the document, please contact me** `ldq404@qq.com` 30 | 31 | 32 |
33 | 34 | ## 在 JS / TS 中使用 35 | 36 | - [跳转官网 查看详情](https://100px.net/usage/js.html) 37 | 38 |
39 | 40 | ## 🙏🙏🙏 点个Star 41 | 42 | **如果您觉得这个项目还不错, 可以在 [Github](https://github.com/buuing/lucky-canvas) 上面帮我点个`star`, 支持一下作者 ☜(゚ヮ゚☜)** 43 | -------------------------------------------------------------------------------- /packages/core/examples/grid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /packages/core/examples/imgs/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/core/examples/imgs/0.png -------------------------------------------------------------------------------- /packages/core/examples/imgs/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/core/examples/imgs/1.png -------------------------------------------------------------------------------- /packages/core/examples/imgs/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/core/examples/imgs/10.png -------------------------------------------------------------------------------- /packages/core/examples/imgs/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/core/examples/imgs/100.png -------------------------------------------------------------------------------- /packages/core/examples/imgs/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/core/examples/imgs/2.png -------------------------------------------------------------------------------- /packages/core/examples/imgs/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/core/examples/imgs/20.png -------------------------------------------------------------------------------- /packages/core/examples/imgs/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/core/examples/imgs/50.png -------------------------------------------------------------------------------- /packages/core/examples/imgs/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/core/examples/imgs/bg.png -------------------------------------------------------------------------------- /packages/core/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |

官网文档: 100px.net

10 | 大转盘示例 11 | 14 | 九宫格示例 15 | 18 | 老虎机示例 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/core/examples/slot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /packages/core/examples/wheel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /packages/core/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.umd.js') 2 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lucky-canvas", 3 | "version": "1.7.26", 4 | "description": "一个基于原生 js 的(大转盘 / 九宫格 / 老虎机)抽奖插件", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.esm.js", 7 | "unpkg": "dist/index.umd.js", 8 | "jsdelivr": "dist/index.umd.js", 9 | "types": "types/index.d.ts", 10 | "scripts": { 11 | "dev": "rollup --config rollup.config.dev.js -w", 12 | "build": "rollup --config rollup.config.build.js" 13 | }, 14 | "homepage": "https://100px.net", 15 | "bugs": "https://github.com/LuckDraw/lucky-canvas/issues", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/LuckDraw/lucky-canvas.git", 19 | "directory": "packages/lucky-canvas" 20 | }, 21 | "author": "ldq ", 22 | "license": "Apache-2.0", 23 | "files": [ 24 | "dist", 25 | "types", 26 | "index.js" 27 | ], 28 | "keywords": [ 29 | "大转盘抽奖", 30 | "九宫格抽奖", 31 | "老虎机抽奖", 32 | "抽奖插件", 33 | "js抽奖", 34 | "移动端抽奖", 35 | "canvas抽奖" 36 | ], 37 | "devDependencies": { 38 | "@babel/core": "^7.12.3", 39 | "@babel/preset-env": "^7.12.1", 40 | "@babel/plugin-transform-runtime": "^7.16.4", 41 | "@babel/runtime": "^7.16.3", 42 | "core-js": "^3.19.2", 43 | "@rollup/plugin-commonjs": "^16.0.0", 44 | "@rollup/plugin-eslint": "^8.0.1", 45 | "@rollup/plugin-json": "^4.1.0", 46 | "@rollup/plugin-node-resolve": "^10.0.0", 47 | "@rollup/plugin-typescript": "^6.1.0", 48 | "@typescript-eslint/parser": "^4.14.0", 49 | "babel-plugin-external-helpers": "^6.22.0", 50 | "babel-preset-latest": "^6.24.1", 51 | "eslint": "^7.18.0", 52 | "eslint-plugin-prettier": "^3.3.1", 53 | "prettier": "^2.2.1", 54 | "rollup": "^2.33.1", 55 | "rollup-plugin-babel": "^4.4.0", 56 | "rollup-plugin-livereload": "^2.0.0", 57 | "rollup-plugin-serve": "^1.1.0", 58 | "rollup-plugin-terser": "^7.0.2", 59 | "rollup-plugin-delete": "^2.0.0", 60 | "rollup-plugin-dts": "^3.0.2", 61 | "rollup-plugin-typescript2": "^0.30.0", 62 | "tslib": "^2.3.1", 63 | "typescript": "^4.0.5" 64 | }, 65 | "dependencies": {} 66 | } 67 | -------------------------------------------------------------------------------- /packages/core/postinstall.js: -------------------------------------------------------------------------------- 1 | console.log(` \u001B[93m╭────────────────────────────────────────────────────╮\u001b[0m 2 | \u001B[93m│\u001b[0m \u001B[93m│\u001b[0m 3 | \u001B[93m│\u001b[0m \u001B[97m欢迎使用\u001B[0m \u001B[46mlucky-canvas\u001B[0m \u001B[97m抽奖插件\u001B[0m \u001B[93m│\u001b[0m 4 | \u001B[93m│\u001b[0m \u001B[96m官网文档: https://100px.net\u001B[0m \u001B[93m│\u001b[0m 5 | \u001B[93m│\u001b[0m \u001B[93m│\u001b[0m 6 | \u001B[93m│\u001b[0m \u001B[93m如果您用着顺手可以点个 Star 支持一下\u001B[0m \u001B[93m│\u001b[0m 7 | \u001B[93m│\u001b[0m \u001B[96mGitHub: https://github.com/LuckDraw/lucky-canvas\u001b[0m \u001B[93m│\u001b[0m 8 | \u001B[93m│\u001b[0m \u001B[93m│\u001b[0m 9 | \u001B[93m╰────────────────────────────────────────────────────╯\u001b[0m`) 10 | -------------------------------------------------------------------------------- /packages/core/rollup.config.build.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import ts from 'rollup-plugin-typescript2' 3 | import dts from 'rollup-plugin-dts' 4 | import json from '@rollup/plugin-json' 5 | import resolve from '@rollup/plugin-node-resolve' 6 | import commonjs from '@rollup/plugin-commonjs' 7 | import babel from 'rollup-plugin-babel' 8 | import { terser } from 'rollup-plugin-terser' 9 | import del from 'rollup-plugin-delete' 10 | import pkg from './package.json' 11 | 12 | export default [ 13 | { 14 | input: 'src/index.ts', 15 | output: [ 16 | { 17 | file: pkg.main, 18 | format: 'cjs', 19 | sourcemap: true, 20 | }, 21 | { 22 | file: pkg.module, 23 | format: 'esm', 24 | sourcemap: true, 25 | }, 26 | { 27 | file: pkg.jsdelivr, 28 | format: 'umd', 29 | name: 'LuckyCanvas', 30 | sourcemap: false, 31 | }, 32 | ], 33 | plugins: [ 34 | ts({ 35 | tsconfig: path.resolve(__dirname, './tsconfig.json'), 36 | extensions: ['.js', '.ts'], 37 | "declaration": true, 38 | }), 39 | json(), 40 | resolve(), 41 | commonjs(), 42 | babel({ 43 | runtimeHelpers: true, 44 | exclude: 'node_modules/**', 45 | }), 46 | terser() 47 | ] 48 | }, { 49 | input: "dist/src/index.d.ts", 50 | output: [ 51 | { 52 | file: "types/index.d.ts", 53 | format: "es" 54 | } 55 | ], 56 | plugins: [ 57 | dts(), 58 | del({ 59 | targets: ['dist/src'], 60 | hook: 'buildEnd' 61 | }) 62 | ], 63 | }, 64 | ] 65 | -------------------------------------------------------------------------------- /packages/core/rollup.config.dev.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import ts from 'rollup-plugin-typescript2' 3 | import json from '@rollup/plugin-json' 4 | import resolve from '@rollup/plugin-node-resolve' 5 | import commonjs from '@rollup/plugin-commonjs' 6 | import babel from 'rollup-plugin-babel' 7 | import livereload from 'rollup-plugin-livereload' 8 | import serve from 'rollup-plugin-serve' 9 | // import eslint from '@rollup/plugin-eslint' 10 | import pkg from './package.json' 11 | 12 | export default { 13 | input: 'src/index.ts', 14 | output: [ 15 | { 16 | file: pkg.jsdelivr, 17 | format: 'umd', 18 | name: 'LuckyCanvas', 19 | sourcemap: true, 20 | }, 21 | { 22 | file: pkg.module, 23 | format: 'es', 24 | sourcemap: true, 25 | }, 26 | ], 27 | plugins: [ 28 | resolve(), 29 | commonjs(), 30 | json(), 31 | // eslint({ 32 | // throwOnError: true, 33 | // throwOnWarning: true, 34 | // include: ['src/**'], 35 | // exclude: ['node_modules/**'] 36 | // }), 37 | ts({ 38 | tsconfig: path.resolve(__dirname, './tsconfig.json'), 39 | extensions: ['.js', '.ts'] 40 | }), 41 | babel({ 42 | runtimeHelpers: true, 43 | exclude: 'node_modules/**', 44 | }), 45 | livereload(), 46 | serve({ 47 | open: true, 48 | port: 13000, 49 | contentBase: './', 50 | openPage: '/examples/index.html' 51 | }), 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LuckyWheel } from './lib/wheel' 2 | export { default as LuckyGrid } from './lib/grid' 3 | export { default as SlotMachine } from './lib/slot' 4 | export { cutRound, opacity } from './utils/image' 5 | 6 | // export type { default as LuckyWheelConfig } from './types/wheel' 7 | // export type { default as LuckyGridConfig } from './types/grid' 8 | // export type { default as SlotMachineConfig } from './types/slot' 9 | -------------------------------------------------------------------------------- /packages/core/src/observer/array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 重写数组的原型方法 3 | */ 4 | const oldArrayProto = Array.prototype 5 | const newArrayProto = Object.create(oldArrayProto) 6 | const methods = ['push', 'pop', 'shift', 'unshift', 'sort', 'splice', 'reverse'] 7 | methods.forEach(method => { 8 | newArrayProto[method] = function (...args: any[]) { 9 | const res = oldArrayProto[method].apply(this, args) 10 | const luckyOb = this['__luckyOb__'] 11 | if (['push', 'unshift', 'splice'].includes(method)) luckyOb.walk(this) 12 | luckyOb.dep.notify() 13 | return res 14 | } 15 | }) 16 | 17 | export { newArrayProto } 18 | -------------------------------------------------------------------------------- /packages/core/src/observer/dep.ts: -------------------------------------------------------------------------------- 1 | import Watcher from './watcher' 2 | 3 | export default class Dep { 4 | static target: Watcher | null 5 | private subs: Array 6 | 7 | /** 8 | * 订阅中心构造器 9 | */ 10 | constructor () { 11 | this.subs = [] 12 | } 13 | 14 | /** 15 | * 收集依赖 16 | * @param {*} sub 17 | */ 18 | public addSub (sub: Watcher) { 19 | // 此处临时使用includes防重复添加 20 | if (!this.subs.includes(sub)) { 21 | this.subs.push(sub) 22 | } 23 | } 24 | 25 | /** 26 | * 派发更新 27 | */ 28 | public notify () { 29 | this.subs.forEach(sub => { 30 | sub.update() 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/observer/index.ts: -------------------------------------------------------------------------------- 1 | import Dep from './dep' 2 | import { hasProto, def } from './utils' 3 | import { newArrayProto } from './array' 4 | 5 | export default class Observer { 6 | value: any 7 | dep: Dep 8 | 9 | /** 10 | * 观察者构造器 11 | * @param value 12 | */ 13 | constructor (value: any) { 14 | // this.value = value 15 | this.dep = new Dep() 16 | // 将响应式对象代理到当前value上面, 并且将当前的enumerable设置为false 17 | def(value, '__luckyOb__', this) 18 | if (Array.isArray(value)) { // 如果是数组, 则重写原型方法 19 | if (hasProto) { 20 | value['__proto__'] = newArrayProto 21 | } else { 22 | Object.getOwnPropertyNames(newArrayProto).forEach(key => { 23 | def(value, key, newArrayProto[key]) 24 | }) 25 | } 26 | } 27 | this.walk(value) 28 | } 29 | 30 | walk (data: object | any[]) { 31 | Object.keys(data).forEach(key => { 32 | defineReactive(data, key, data[key]) 33 | }) 34 | } 35 | } 36 | 37 | /** 38 | * 处理响应式 39 | * @param { Object | Array } data 40 | */ 41 | export function observe (data: any): Observer | void { 42 | if (!data || typeof data !== 'object') return 43 | let luckyOb: Observer | void 44 | if ('__luckyOb__' in data) { 45 | luckyOb = data['__luckyOb__'] 46 | } else { 47 | luckyOb = new Observer(data) 48 | } 49 | return luckyOb 50 | } 51 | 52 | /** 53 | * 重写 setter / getter 54 | * @param {*} data 55 | * @param {*} key 56 | * @param {*} val 57 | */ 58 | export function defineReactive (data: any, key: string | number, val: any) { 59 | const dep = new Dep() 60 | const property = Object.getOwnPropertyDescriptor(data, key) 61 | if (property && property.configurable === false) { 62 | return 63 | } 64 | const getter = property && property.get 65 | const setter = property && property.set 66 | if ((!getter || setter) && arguments.length === 2) { 67 | val = data[key] 68 | } 69 | let childOb = observe(val) 70 | Object.defineProperty(data, key, { 71 | get: () => { 72 | const value = getter ? getter.call(data) : val 73 | if (Dep.target) { 74 | dep.addSub(Dep.target) 75 | if (childOb) { 76 | childOb.dep.addSub(Dep.target) 77 | } 78 | } 79 | return value 80 | }, 81 | set: (newVal) => { 82 | if (newVal === val) return 83 | val = newVal 84 | if (getter && !setter) return 85 | if (setter) { 86 | setter.call(data, newVal) 87 | } else { 88 | val = newVal 89 | } 90 | childOb = observe(newVal) 91 | dep.notify() 92 | } 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /packages/core/src/observer/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | import { isExpectType } from '../utils' 3 | 4 | export const hasProto = '__proto__' in {} 5 | 6 | export function def (obj: object, key: string | number, val: any, enumerable?: boolean) { 7 | Object.defineProperty(obj, key, { 8 | value: val, 9 | enumerable: !!enumerable, 10 | writable: true, 11 | configurable: true 12 | }) 13 | } 14 | 15 | export function parsePath (path: string) { 16 | path += '.' 17 | let segments: string[] = [], segment = '' 18 | for (let i = 0; i < path.length; i++) { 19 | let curr = path[i] 20 | if (/\[|\./.test(curr)) { 21 | segments.push(segment) 22 | segment = '' 23 | } else if (/\W/.test(curr)) { 24 | continue 25 | } else { 26 | segment += curr 27 | } 28 | } 29 | return function (data: object | any[]) { 30 | return segments.reduce((data, key) => { 31 | return data[key] 32 | }, data) 33 | } 34 | } 35 | 36 | export function traverse (value: any) { 37 | // const seenObjects = new Set() 38 | const dfs = (data: any) => { 39 | if (!isExpectType(data, 'array', 'object')) return 40 | Object.keys(data).forEach(key => { 41 | const value = data[key] 42 | dfs(value) 43 | }) 44 | } 45 | dfs(value) 46 | // seenObjects.clear() 47 | } -------------------------------------------------------------------------------- /packages/core/src/observer/watcher.ts: -------------------------------------------------------------------------------- 1 | import Lucky from '../lib/lucky' 2 | import Dep from './dep' 3 | import { parsePath, traverse } from './utils' 4 | 5 | export interface WatchOptType { 6 | handler?: () => Function 7 | immediate?: boolean 8 | deep?: boolean 9 | } 10 | 11 | let uid = 0 12 | export default class Watcher { 13 | id: number 14 | $lucky: Lucky 15 | expr: string | Function 16 | cb: Function 17 | deep: boolean 18 | getter: Function 19 | value: any 20 | 21 | /** 22 | * 观察者构造器 23 | * @param {*} $lucky 24 | * @param {*} expr 25 | * @param {*} cb 26 | */ 27 | constructor ($lucky: Lucky, expr: string | Function, cb: Function, options: WatchOptType = {}) { 28 | this.id = uid++ 29 | this.$lucky = $lucky 30 | this.expr = expr 31 | this.deep = !!options.deep 32 | if (typeof expr === 'function') { 33 | this.getter = expr 34 | } else { 35 | this.getter = parsePath(expr) 36 | } 37 | this.cb = cb 38 | this.value = this.get() 39 | } 40 | 41 | /** 42 | * 根据表达式获取新值 43 | */ 44 | get () { 45 | Dep.target = this 46 | const value = this.getter.call(this.$lucky, this.$lucky) 47 | // 处理深度监听 48 | if (this.deep) { 49 | traverse(value) 50 | } 51 | Dep.target = null 52 | return value 53 | } 54 | 55 | /** 56 | * 触发 watcher 更新 57 | */ 58 | update () { 59 | // get获取新值 60 | const newVal = this.get() 61 | // 读取之前存储的旧值 62 | const oldVal = this.value 63 | this.value = newVal 64 | // 触发 watch 回调 65 | this.cb.call(this.$lucky, newVal, oldVal) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/core/src/types/grid.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FontItemType, 3 | ImgItemType, 4 | BorderRadiusType, 5 | BackgroundType, 6 | ShadowType, 7 | FontExtendType 8 | } from './index' 9 | 10 | export type PrizeFontType = FontItemType & FontExtendType 11 | 12 | export type ButtonFontType = FontItemType & FontExtendType 13 | 14 | export type CellFontType = PrizeFontType | ButtonFontType 15 | 16 | export type BlockImgType = ImgItemType & {} 17 | 18 | export type PrizeImgType = ImgItemType & { 19 | activeSrc?: string 20 | } 21 | 22 | export type ButtonImgType = ImgItemType & {} 23 | 24 | export type CellImgType = PrizeImgType | ButtonImgType 25 | 26 | export type BlockType = { 27 | borderRadius?: BorderRadiusType 28 | background?: BackgroundType 29 | padding?: string 30 | paddingTop?: string | number 31 | paddingRight?: string | number 32 | paddingBottom?: string | number 33 | paddingLeft?: string | number 34 | imgs?: Array 35 | } 36 | 37 | export type CellType = { 38 | x: number 39 | y: number 40 | col?: number 41 | row?: number 42 | borderRadius?: BorderRadiusType 43 | background?: BackgroundType 44 | shadow?: ShadowType 45 | fonts?: Array 46 | imgs?: Array 47 | } 48 | 49 | export type PrizeType = CellType & { 50 | range?: number 51 | disabled?: boolean 52 | } 53 | 54 | export type ButtonType = CellType & { 55 | callback?: Function 56 | } 57 | 58 | export type DefaultConfigType = { 59 | gutter?: number 60 | speed?: number 61 | accelerationTime?: number 62 | decelerationTime?: number 63 | } 64 | 65 | export type DefaultStyleType = { 66 | borderRadius?: BorderRadiusType 67 | background?: BackgroundType 68 | shadow?: ShadowType 69 | fontColor?: PrizeFontType['fontColor'] 70 | fontSize?: PrizeFontType['fontSize'] 71 | fontStyle?: PrizeFontType['fontStyle'] 72 | fontWeight?: PrizeFontType['fontWeight'] 73 | lineHeight?: PrizeFontType['lineHeight'] 74 | wordWrap?: PrizeFontType['wordWrap'] 75 | lengthLimit?: PrizeFontType['lengthLimit'] 76 | lineClamp?: PrizeFontType['lineClamp'] 77 | } 78 | 79 | export type ActiveStyleType = { 80 | background?: BackgroundType 81 | shadow?: ShadowType 82 | fontColor?: PrizeFontType['fontColor'] 83 | fontSize?: PrizeFontType['fontSize'] 84 | fontStyle?: PrizeFontType['fontStyle'] 85 | fontWeight?: PrizeFontType['fontWeight'] 86 | lineHeight?: PrizeFontType['lineHeight'] 87 | } 88 | 89 | export type RowsType = number 90 | export type ColsType = number 91 | export type StartCallbackType = (e: MouseEvent, button?: ButtonType) => void 92 | export type EndCallbackType = (prize: object) => void 93 | 94 | export default interface LuckyGridConfig { 95 | width: string | number 96 | height: string | number 97 | rows?: RowsType 98 | cols?: ColsType 99 | blocks?: Array 100 | prizes?: Array 101 | buttons?: Array 102 | button?: ButtonType 103 | defaultConfig?: DefaultConfigType 104 | defaultStyle?: DefaultStyleType 105 | activeStyle?: ActiveStyleType 106 | start?: StartCallbackType 107 | end?: EndCallbackType 108 | } 109 | -------------------------------------------------------------------------------- /packages/core/src/types/index.ts: -------------------------------------------------------------------------------- 1 | // 字体类型 2 | export type FontItemType = { 3 | text: string 4 | top?: string | number 5 | left?: string | number 6 | fontColor?: string 7 | fontSize?: string 8 | fontStyle?: string 9 | fontWeight?: string 10 | lineHeight?: string 11 | } 12 | 13 | export type FontExtendType = { 14 | wordWrap?: boolean 15 | lengthLimit?: string | number 16 | lineClamp?: number 17 | } 18 | 19 | export type ImgType = HTMLImageElement | HTMLCanvasElement 20 | 21 | // 图片类型 22 | export type ImgItemType = { 23 | src: string 24 | top?: string | number 25 | left?: string | number 26 | width?: string 27 | height?: string 28 | formatter?: (img: ImgType) => ImgType 29 | $resolve?: Function 30 | $reject?: Function 31 | } 32 | 33 | export type BorderRadiusType = string | number 34 | export type BackgroundType = string 35 | export type ShadowType = string 36 | 37 | export type ConfigType = { 38 | // 临时处理元素类型, 当版本升到4.x之后就可以删掉了 39 | nodeType?: number 40 | // 配置 41 | flag: 'WEB' | 'MP-WX' | 'UNI-H5' | 'UNI-MP' | 'TARO-H5' | 'TARO-MP' 42 | el?: string 43 | divElement?: HTMLDivElement 44 | canvasElement?: HTMLCanvasElement 45 | ctx: CanvasRenderingContext2D 46 | dpr: number 47 | handleCssUnit?: (num: number, unit: string) => number 48 | // 覆盖方法 49 | rAF?: Function 50 | setTimeout: Function 51 | setInterval: Function 52 | clearTimeout: Function 53 | clearInterval: Function 54 | // 组件生命周期 55 | beforeCreate?: Function 56 | beforeResize?: Function 57 | afterResize?: Function 58 | beforeInit?: Function 59 | afterInit?: Function 60 | beforeDraw?: Function 61 | afterDraw?: Function 62 | afterStart?: Function 63 | } 64 | 65 | export type UserConfigType = Partial 66 | 67 | export type UniImageType = { 68 | path: string 69 | width: number 70 | height: number 71 | } 72 | 73 | export type Tuple = Res['length'] extends Len ? Res : Tuple 74 | -------------------------------------------------------------------------------- /packages/core/src/types/slot.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FontItemType, 3 | ImgItemType, 4 | BorderRadiusType, 5 | BackgroundType, 6 | FontExtendType 7 | } from './index' 8 | 9 | export type PrizeFontType = FontItemType & FontExtendType 10 | 11 | export type BlockImgType = ImgItemType & {} 12 | 13 | export type PrizeImgType = ImgItemType 14 | 15 | export type BlockType = { 16 | borderRadius?: BorderRadiusType 17 | background?: BackgroundType 18 | padding?: string 19 | paddingTop?: string | number 20 | paddingRight?: string | number 21 | paddingBottom?: string | number 22 | paddingLeft?: string | number 23 | imgs?: Array 24 | } 25 | 26 | export type PrizeType = { 27 | borderRadius?: BorderRadiusType 28 | background?: BackgroundType 29 | fonts?: Array 30 | imgs?: Array 31 | } 32 | 33 | export type SlotType = { 34 | order?: number[] 35 | speed?: number 36 | direction?: 1 | -1 37 | } 38 | 39 | export type DefaultConfigType = { 40 | /** 41 | * vertical 为纵向旋转 42 | * horizontal 为横向旋转 43 | */ 44 | mode?: 'vertical' | 'horizontal' 45 | /** 46 | * 当排列方向 = `vertical`时 47 | * 1 bottom to top 48 | * -1 top to bottom 49 | * 当排列方向 = `horizontal`时 50 | * 1 right to left 51 | * -1 left to right 52 | */ 53 | direction?: 1 | -1 54 | // 行间距 55 | rowSpacing?: number 56 | // 列间距 57 | colSpacing?: number 58 | // 速度 59 | speed?: number 60 | accelerationTime?: number 61 | decelerationTime?: number 62 | } 63 | 64 | export type DefaultStyleType = { 65 | borderRadius?: BorderRadiusType 66 | background?: BackgroundType 67 | fontColor?: PrizeFontType['fontColor'] 68 | fontSize?: PrizeFontType['fontSize'] 69 | fontStyle?: PrizeFontType['fontStyle'] 70 | fontWeight?: PrizeFontType['fontWeight'] 71 | lineHeight?: PrizeFontType['lineHeight'] 72 | wordWrap?: PrizeFontType['wordWrap'] 73 | lengthLimit?: PrizeFontType['lengthLimit'] 74 | lineClamp?: PrizeFontType['lineClamp'] 75 | } 76 | 77 | export type EndCallbackType = (prize: PrizeType | undefined) => void 78 | 79 | export default interface SlotMachineConfig { 80 | width: string | number 81 | height: string | number 82 | blocks?: Array 83 | prizes?: Array 84 | slots?: Array 85 | defaultConfig?: DefaultConfigType 86 | defaultStyle?: DefaultStyleType 87 | end?: EndCallbackType 88 | } 89 | -------------------------------------------------------------------------------- /packages/core/src/types/wheel.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FontItemType, 3 | ImgItemType, 4 | BackgroundType, 5 | FontExtendType 6 | } from './index' 7 | 8 | export type PrizeFontType = FontItemType & FontExtendType 9 | 10 | export type ButtonFontType = FontItemType & {} 11 | 12 | export type BlockImgType = ImgItemType & { 13 | rotate?: boolean 14 | } 15 | 16 | export type PrizeImgType = ImgItemType & {} 17 | 18 | export type ButtonImgType = ImgItemType & {} 19 | 20 | export type BlockType = { 21 | padding?: string 22 | background?: BackgroundType 23 | imgs?: Array 24 | } 25 | 26 | export type PrizeType = { 27 | range?: number 28 | background?: BackgroundType 29 | fonts?: Array 30 | imgs?: Array 31 | } 32 | 33 | export type ButtonType = { 34 | radius?: string 35 | pointer?: boolean 36 | background?: BackgroundType 37 | fonts?: Array 38 | imgs?: Array 39 | } 40 | 41 | export type DefaultConfigType = { 42 | gutter?: string | number 43 | offsetDegree?: number 44 | speed?: number 45 | speedFunction?: string 46 | accelerationTime?: number 47 | decelerationTime?: number 48 | stopRange?: number 49 | } 50 | 51 | export type DefaultStyleType = { 52 | background?: BackgroundType 53 | fontColor?: PrizeFontType['fontColor'] 54 | fontSize?: PrizeFontType['fontSize'] 55 | fontStyle?: PrizeFontType['fontStyle'] 56 | fontWeight?: PrizeFontType['fontWeight'] 57 | lineHeight?: PrizeFontType['lineHeight'] 58 | wordWrap?: PrizeFontType['wordWrap'] 59 | lengthLimit?: PrizeFontType['lengthLimit'] 60 | lineClamp?: PrizeFontType['lineClamp'] 61 | } 62 | 63 | export type StartCallbackType = (e: MouseEvent) => void 64 | export type EndCallbackType = (prize: object) => void 65 | 66 | export default interface LuckyWheelConfig { 67 | width: string | number 68 | height: string | number 69 | blocks?: Array 70 | prizes?: Array 71 | buttons?: Array 72 | defaultConfig?: DefaultConfigType 73 | defaultStyle?: DefaultStyleType 74 | start?: StartCallbackType 75 | end?: EndCallbackType 76 | } 77 | -------------------------------------------------------------------------------- /packages/core/src/utils/image.ts: -------------------------------------------------------------------------------- 1 | import { ImgType } from '../types/index' 2 | import { roundRectByArc } from './math' 3 | 4 | /** 5 | * 根据路径获取图片对象 6 | * @param { string } src 图片路径 7 | * @returns { Promise } 图片标签 8 | */ 9 | export const getImage = (src: string): Promise => { 10 | return new Promise((resolve, reject) => { 11 | const img = new Image() 12 | img.onload = () => resolve(img) 13 | img.onerror = err => reject(err) 14 | img.src = src 15 | }) 16 | } 17 | 18 | /** 19 | * 切割圆角 20 | * @param img 将要裁剪的图片对象 21 | * @param radius 裁剪的圆角半径 22 | * @returns 返回一个离屏 canvas 用于渲染 23 | */ 24 | export const cutRound = (img: ImgType, radius: number): ImgType => { 25 | const canvas = document.createElement('canvas') 26 | const ctx = canvas.getContext('2d')! 27 | const { width, height } = img 28 | canvas.width = width 29 | canvas.height = height 30 | roundRectByArc(ctx, 0, 0, width, height, radius) 31 | ctx.clip() 32 | ctx.drawImage(img, 0, 0, width, height) 33 | return canvas 34 | } 35 | 36 | /** 37 | * 透明度 38 | * @param img 将要处理的图片对象 39 | * @param opacity 透明度 40 | * @returns 返回一个离屏 canvas 用于渲染 41 | */ 42 | export const opacity = ( 43 | img: ImgType, 44 | opacity: number 45 | ): ImgType => { 46 | const canvas = document.createElement('canvas') 47 | const ctx = canvas.getContext('2d')! 48 | const { width, height } = img 49 | canvas.width = width 50 | canvas.height = height 51 | // 绘制图片, 部分浏览器不支持 filter 属性, 需要处理兼容 52 | if (typeof ctx.filter === 'string') { 53 | ctx.filter = `opacity(${opacity * 100}%)` 54 | ctx.drawImage(img, 0, 0, width, height) 55 | } else { 56 | ctx.drawImage(img, 0, 0, width, height) 57 | const imageData = ctx.getImageData(0, 0, width, height) 58 | const { data } = imageData 59 | const len = data.length 60 | for (let i = 0; i < len; i += 4) { 61 | const alpha = data[i + 3] 62 | if (alpha !== 0) data[i + 3] = alpha * opacity 63 | } 64 | ctx.putImageData(imageData, 0, 0) 65 | } 66 | return canvas 67 | } 68 | 69 | /** 70 | * 权重矩阵 71 | * @param radius 模糊半径 72 | * @param sigma 73 | * @returns 返回一个权重和为1的矩阵 74 | */ 75 | const getMatrix = (radius: number, sigma?: number): number[] => { 76 | sigma = sigma || radius / 3 77 | const r = Math.ceil(radius) 78 | const sigma_2 = sigma * sigma 79 | const sigma2_2 = 2 * sigma_2 80 | const denominator = 1 / (2 * Math.PI * sigma_2) 81 | const matrix = [] 82 | let total = 0 83 | // 计算权重矩阵 84 | for (let x = -r; x <= r; x++) { 85 | for (let y = -r; y <= r; y++) { 86 | // 套用二维高斯函数得到每个点的权重 87 | const res = denominator * Math.exp(-(x * x + y * y) / sigma2_2) 88 | matrix.push(res) 89 | total += res 90 | } 91 | } 92 | // 让矩阵中所有权重的和等于1 93 | for (let i = 0; i < matrix.length; i++) { 94 | matrix[i] /= total 95 | } 96 | return matrix 97 | } 98 | 99 | /** 100 | * 高斯模糊 101 | * @param img 将要处理的图片对象 102 | * @param radius 模糊半径 103 | * @returns 返回一个离屏 canvas 用于渲染 104 | */ 105 | export const blur = ( 106 | img: ImgType, 107 | radius: number 108 | ): ImgType => { 109 | const canvas = document.createElement('canvas') 110 | const ctx = canvas.getContext('2d')! 111 | const { width, height } = img 112 | // 设置图片宽高 113 | canvas.width = width 114 | canvas.height = height 115 | ctx.drawImage(img, 0, 0, width, height) 116 | const ImageData = ctx.getImageData(0, 0, width, height) 117 | const { data } = ImageData 118 | const matrix = getMatrix(radius) 119 | const r = Math.ceil(radius) 120 | const w = width * 4 121 | const cols = r * 2 + 1 122 | const len = data.length, matrixLen = matrix.length 123 | for (let i = 0; i < len; i += 4) { 124 | // 处理 125 | } 126 | console.log(ImageData) 127 | ctx.putImageData(ImageData, 0, 0) 128 | return canvas 129 | } 130 | 131 | export const getBase64Image = () => { 132 | 133 | } 134 | -------------------------------------------------------------------------------- /packages/core/src/utils/polyfill.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 由于部分低版本下的某些 app 可能会缺少某些原型方法, 这里增加兼容 3 | */ 4 | 5 | // ie11 不兼容 includes 方法 6 | if (!Array.prototype.includes) { 7 | Object.defineProperty(Array.prototype, 'includes', { 8 | value: function(valueToFind, fromIndex) { 9 | 10 | if (this == null) { 11 | throw new TypeError('"this" is null or not defined'); 12 | } 13 | 14 | // 1. Let O be ? ToObject(this value). 15 | var o = Object(this); 16 | 17 | // 2. Let len be ? ToLength(? Get(O, "length")). 18 | var len = o.length >>> 0; 19 | 20 | // 3. If len is 0, return false. 21 | if (len === 0) { 22 | return false; 23 | } 24 | 25 | // 4. Let n be ? ToInteger(fromIndex). 26 | // (If fromIndex is undefined, this step produces the value 0.) 27 | var n = fromIndex | 0; 28 | 29 | // 5. If n ≥ 0, then 30 | // a. Let k be n. 31 | // 6. Else n < 0, 32 | // a. Let k be len + n. 33 | // b. If k < 0, let k be 0. 34 | var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 35 | 36 | function sameValueZero(x, y) { 37 | return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)); 38 | } 39 | 40 | // 7. Repeat, while k < len 41 | while (k < len) { 42 | // a. Let elementK be the result of ? Get(O, ! ToString(k)). 43 | // b. If SameValueZero(valueToFind, elementK) is true, return true. 44 | if (sameValueZero(o[k], valueToFind)) { 45 | return true; 46 | } 47 | // c. Increase k by 1. 48 | k++; 49 | } 50 | 51 | // 8. Return false 52 | return false; 53 | } 54 | }); 55 | } 56 | 57 | // vivo x7 下网易云游戏 app 缺少 includes 方法 58 | if (!String.prototype.includes) { 59 | String.prototype.includes = function(search, start) { 60 | 'use strict'; 61 | if (typeof start !== 'number') { 62 | start = 0; 63 | } 64 | if (start + search.length > this.length) { 65 | return false; 66 | } else { 67 | return this.indexOf(search, start) !== -1; 68 | } 69 | }; 70 | } 71 | 72 | // vivo x7 下网易云游戏 app 缺少 find 方法 73 | if (!Array.prototype.find) { 74 | Object.defineProperty(Array.prototype, 'find', { 75 | value: function(predicate) { 76 | // 1. Let O be ? ToObject(this value). 77 | if (this == null) { 78 | throw new TypeError('"this" is null or not defined'); 79 | } 80 | var o = Object(this); 81 | // 2. Let len be ? ToLength(? Get(O, "length")). 82 | var len = o.length >>> 0; 83 | // 3. If IsCallable(predicate) is false, throw a TypeError exception. 84 | if (typeof predicate !== 'function') { 85 | throw new TypeError('predicate must be a function'); 86 | } 87 | // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. 88 | var thisArg = arguments[1]; 89 | // 5. Let k be 0. 90 | var k = 0; 91 | // 6. Repeat, while k < len 92 | while (k < len) { 93 | // a. Let Pk be ! ToString(k). 94 | // b. Let kValue be ? Get(O, Pk). 95 | // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). 96 | // d. If testResult is true, return kValue. 97 | var kValue = o[k]; 98 | if (predicate.call(thisArg, kValue, k, o)) { 99 | return kValue; 100 | } 101 | // e. Increase k by 1. 102 | k++; 103 | } 104 | // 7. Return undefined. 105 | return void 0; 106 | } 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /packages/core/src/utils/tween.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 缓动函数 3 | * t: current time(当前时间) 4 | * b: beginning value(初始值) 5 | * c: change in value(变化量) 6 | * d: duration(持续时间) 7 | * 8 | * 感谢张鑫旭大佬 https://github.com/zhangxinxu/Tween 9 | */ 10 | 11 | interface SpeedType { 12 | easeIn: (...arr: number[]) => number 13 | easeOut: (...arr: number[]) => number 14 | } 15 | 16 | // 二次方的缓动 17 | export const quad: SpeedType = { 18 | easeIn: function (t, b, c, d) { 19 | if (t >= d) t = d 20 | return c * (t /= d) * t + b 21 | }, 22 | easeOut: function (t, b, c, d) { 23 | if (t >= d) t = d 24 | return -c * (t /= d) * (t - 2) + b 25 | } 26 | } 27 | 28 | // 三次方的缓动 29 | export const cubic: SpeedType = { 30 | easeIn: function (t, b, c, d) { 31 | if (t >= d) t = d 32 | return c * (t /= d) * t * t + b 33 | }, 34 | easeOut: function (t, b, c, d) { 35 | if (t >= d) t = d 36 | return c * ((t = t / d - 1) * t * t + 1) + b 37 | } 38 | } 39 | 40 | // 四次方的缓动 41 | export const quart: SpeedType = { 42 | easeIn: function (t, b, c, d) { 43 | if (t >= d) t = d 44 | return c * (t /= d) * t * t * t + b 45 | }, 46 | easeOut: function (t, b, c, d) { 47 | if (t >= d) t = d 48 | return -c * ((t = t / d - 1) * t * t * t - 1) + b 49 | } 50 | } 51 | 52 | // 五次方的缓动 53 | export const quint: SpeedType = { 54 | easeIn: function (t, b, c, d) { 55 | if (t >= d) t = d 56 | return c * (t /= d) * t * t * t * t + b 57 | }, 58 | easeOut: function (t, b, c, d) { 59 | if (t >= d) t = d 60 | return c * ((t = t / d - 1) * t * t * t * t + 1) + b 61 | } 62 | } 63 | 64 | // 正弦曲线的缓动 65 | export const sine: SpeedType = { 66 | easeIn: function (t, b, c, d) { 67 | if (t >= d) t = d 68 | return -c * Math.cos(t / d * (Math.PI / 2)) + c + b 69 | }, 70 | easeOut: function (t, b, c, d) { 71 | if (t >= d) t = d 72 | return c * Math.sin(t / d * (Math.PI / 2)) + b 73 | } 74 | } 75 | 76 | // 指数曲线的缓动 77 | export const expo: SpeedType = { 78 | easeIn: function (t, b, c, d) { 79 | if (t >= d) t = d 80 | return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b 81 | }, 82 | easeOut: function (t, b, c, d) { 83 | if (t >= d) t = d 84 | return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b 85 | } 86 | } 87 | 88 | // 圆形曲线的缓动 89 | export const circ: SpeedType = { 90 | easeIn: function (t, b, c, d) { 91 | if (t >= d) t = d 92 | return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b 93 | }, 94 | easeOut: function (t, b, c, d) { 95 | if (t >= d) t = d 96 | return c * Math.sqrt(1 - (t = t / d - 1) * t) + b 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", // 编译后的es版本 4 | "module": "esnext", // 前端模块化规范 5 | "allowJs": true, // 允许引入js文件 6 | "strict": true, // 开启严格模式 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "resolveJsonModule": true, 14 | "sourceMap": true, 15 | "declaration": true 16 | }, 17 | "exclude": [ 18 | "node_modules/**" 19 | ], 20 | "include": [ 21 | "src/**/*" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/mini/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["module-resolver", { 4 | "root": ["./src"], 5 | "alias": {} 6 | }] 7 | ], 8 | "presets": [ 9 | ["env", {"loose": true, "modules": "commonjs"}] 10 | ] 11 | } -------------------------------------------------------------------------------- /packages/mini/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': [ 3 | 'airbnb-base', 4 | 'plugin:promise/recommended' 5 | ], 6 | 'parser': '@typescript-eslint/parser', 7 | 'plugins': ['@typescript-eslint'], 8 | 'parserOptions': { 9 | 'ecmaVersion': 9, 10 | 'ecmaFeatures': { 11 | 'jsx': false 12 | }, 13 | 'sourceType': 'module' 14 | }, 15 | 'env': { 16 | 'es6': true, 17 | 'node': true, 18 | 'jest': true 19 | }, 20 | 'plugins': [ 21 | 'import', 22 | 'node', 23 | 'promise' 24 | ], 25 | 'rules': { 26 | "no-console": "off", 27 | 'arrow-parens': 'off', 28 | 'comma-dangle': [ 29 | 'error', 30 | 'only-multiline' 31 | ], 32 | 'complexity': ['error', 20], 33 | 'func-names': 'off', 34 | 'global-require': 'off', 35 | 'handle-callback-err': [ 36 | 'error', 37 | '^(err|error)$' 38 | ], 39 | 'import/no-unresolved': [ 40 | 'error', 41 | { 42 | 'caseSensitive': true, 43 | 'commonjs': true, 44 | 'ignore': ['^[^.]'] 45 | } 46 | ], 47 | 'import/prefer-default-export': 'off', 48 | 'linebreak-style': 'off', 49 | 'no-catch-shadow': 'error', 50 | 'no-continue': 'off', 51 | 'no-div-regex': 'warn', 52 | 'no-else-return': 'off', 53 | 'no-param-reassign': 'off', 54 | 'no-plusplus': 'off', 55 | 'no-shadow': 'off', 56 | 'no-multi-assign': 'off', 57 | 'no-underscore-dangle': 'off', 58 | 'node/no-deprecated-api': 'error', 59 | 'node/process-exit-as-throw': 'error', 60 | 'operator-linebreak': [ 61 | 'error', 62 | 'after', 63 | { 64 | 'overrides': { 65 | ':': 'before', 66 | '?': 'before' 67 | } 68 | } 69 | ], 70 | 'prefer-arrow-callback': 'off', 71 | 'prefer-destructuring': 'off', 72 | 'prefer-template': 'off', 73 | 'quote-props': [ 74 | 1, 75 | 'as-needed', 76 | { 77 | 'unnecessary': true 78 | } 79 | ], 80 | 'semi': [ 81 | 'error', 82 | 'never' 83 | ], 84 | 'no-await-in-loop': 'off', 85 | 'no-restricted-syntax': 'off', 86 | 'promise/always-return': 'off', 87 | }, 88 | 'globals': { 89 | 'window': true, 90 | 'document': true, 91 | 'App': true, 92 | 'Page': true, 93 | 'Component': true, 94 | 'Behavior': true, 95 | 'wx': true, 96 | 'getCurrentPages': true, 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/mini/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | dist 4 | 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | dev 12 | node_modules 13 | coverage -------------------------------------------------------------------------------- /packages/mini/.npmignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /packages/mini/README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | logo 4 |

微信小程序 抽奖组件

5 |

一个基于微信小程序的 ( 大转盘 / 九宫格 / 老虎机 ) 抽奖插件

6 |

7 | 8 | stars 9 | 10 | 11 | forks 12 | 13 | 14 | author 15 | 16 | 17 | license 18 | 19 |

20 |
21 | 22 |
23 | 24 | ## 文档 - Document 25 | 26 | - **中文**:[https://100px.net](https://100px.net) 27 | 28 |
29 | 30 | ## 使用 - Usage 31 | 32 | - [**在 微信小程序 中使用**](https://100px.net/usage/wx.html) 33 | 34 |
35 | 36 | ## 🙏🙏🙏 点个Star 37 | 38 | **如果您觉得这个项目还不错, 可以在 [Github](https://github.com/buuing/lucky-canvas) 上面帮我点个`star`, 支持一下作者 ☜(゚ヮ゚☜)** 39 | 40 |
41 | -------------------------------------------------------------------------------- /packages/mini/gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const clean = require('gulp-clean') 3 | 4 | const config = require('./tools/config') 5 | const BuildTask = require('./tools/build') 6 | const id = require('./package.json').name || 'miniprogram-custom-component' 7 | 8 | // 构建任务实例 9 | // eslint-disable-next-line no-new 10 | new BuildTask(id, config.entry) 11 | 12 | // 清空生成目录和文件 13 | gulp.task('clean', gulp.series(() => gulp.src(config.distPath, {read: false, allowEmpty: true}).pipe(clean()), done => { 14 | if (config.isDev) { 15 | return gulp.src(config.demoDist, {read: false, allowEmpty: true}) 16 | .pipe(clean()) 17 | } 18 | 19 | return done() 20 | })) 21 | // 监听文件变化并进行开发模式构建 22 | gulp.task('watch', gulp.series(`${id}-watch`)) 23 | // 开发模式构建 24 | gulp.task('dev', gulp.series(`${id}-dev`)) 25 | // 生产模式构建 26 | gulp.task('default', gulp.series(`${id}-default`)) 27 | -------------------------------------------------------------------------------- /packages/mini/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lucky-canvas/mini", 3 | "version": "0.0.8", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "dev": "gulp dev --develop --watch", 8 | "build": "gulp", 9 | "dist": "npm run build", 10 | "clean-dev": "gulp clean --develop", 11 | "clean": "gulp clean", 12 | "test": "jest --bail", 13 | "test-debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --bail", 14 | "coverage": "jest ./test/* --coverage --bail", 15 | "lint": "eslint \"src/**/*.js\" --fix", 16 | "lint-tools": "eslint \"tools/**/*.js\" --rule \"import/no-extraneous-dependencies: false\" --fix" 17 | }, 18 | "files": [ 19 | "dist" 20 | ], 21 | "miniprogram": "dist", 22 | "jest": { 23 | "testEnvironment": "jsdom", 24 | "testURL": "https://jest.test", 25 | "collectCoverageFrom": [ 26 | "dist/**/*.js" 27 | ], 28 | "moduleDirectories": [ 29 | "node_modules", 30 | "dist" 31 | ] 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "" 36 | }, 37 | "author": "ldq", 38 | "license": "Apache-2.0", 39 | "devDependencies": { 40 | "@typescript-eslint/eslint-plugin": "^2.28.0", 41 | "@typescript-eslint/parser": "^2.28.0", 42 | "babel-core": "^6.26.3", 43 | "babel-loader": "^7.1.5", 44 | "babel-plugin-module-resolver": "^3.2.0", 45 | "babel-preset-env": "^1.7.0", 46 | "colors": "^1.3.1", 47 | "eslint": "^5.14.1", 48 | "eslint-config-airbnb-base": "13.1.0", 49 | "eslint-loader": "^2.1.2", 50 | "eslint-plugin-import": "^2.16.0", 51 | "eslint-plugin-node": "^7.0.1", 52 | "eslint-plugin-promise": "^3.8.0", 53 | "gulp": "^4.0.0", 54 | "gulp-clean": "^0.4.0", 55 | "gulp-if": "^2.0.2", 56 | "gulp-install": "^1.1.0", 57 | "gulp-less": "^4.0.1", 58 | "gulp-rename": "^1.4.0", 59 | "gulp-sourcemaps": "^2.6.5", 60 | "jest": "^23.5.0", 61 | "miniprogram-api-typings": "^2.10.3-1", 62 | "miniprogram-simulate": "^1.2.5", 63 | "thread-loader": "^2.1.3", 64 | "through2": "^2.0.3", 65 | "ts-loader": "^7.0.0", 66 | "typescript": "^3.8.3", 67 | "vinyl": "^2.2.0", 68 | "webpack": "^4.29.5", 69 | "webpack-node-externals": "^1.7.2" 70 | }, 71 | "dependencies": { 72 | "lucky-canvas": "~1.7.24" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/mini/src/lucky-grid/index.js: -------------------------------------------------------------------------------- 1 | import { LuckyGrid } from 'lucky-canvas' 2 | import { changeUnits, resolveImage, getImage } from '../utils' 3 | 4 | Component({ 5 | properties: { 6 | width: { type: String, value: '600rpx' }, 7 | height: { type: String, value: '600rpx' }, 8 | rows: { type: String, optionalTypes: [Number], value: '3' }, 9 | cols: { type: String, optionalTypes: [Number], value: '3' }, 10 | blocks: { type: Array, value: [] }, 11 | prizes: { type: Array, value: [] }, 12 | buttons: { type: Array, value: [] }, 13 | defaultConfig: { type: Object, value: {} }, 14 | defaultStyle: { type: Object, value: {} }, 15 | activeStyle: { type: Object, value: {} }, 16 | start: { type: null, value: () => {} }, 17 | end: { type: null, value: () => {} }, 18 | }, 19 | data: { 20 | lucky: null, 21 | isShow: false, 22 | luckyImg: '', 23 | showCanvas: true, 24 | }, 25 | observers: { 26 | 'prizes.**': function (newData, oldData) { 27 | this.lucky && (this.lucky.prizes = newData) 28 | }, 29 | 'buttons.**': function (newData, oldData) { 30 | this.lucky && (this.lucky.buttons = newData) 31 | }, 32 | }, 33 | ready() { 34 | wx.createSelectorQuery().in(this).select('#lucky-grid').fields({ 35 | node: true, size: true 36 | }).exec((res) => { 37 | if (!res[0] || !res[0].node) { 38 | console.error('lucky-canvas 获取不到 canvas 标签') 39 | return 40 | } 41 | const canvas = this.canvas = res[0].node 42 | const dpr = this.dpr = wx.getSystemInfoSync().pixelRatio 43 | const ctx = this.ctx = canvas.getContext('2d') 44 | const data = this.data 45 | canvas.width = res[0].width * dpr 46 | canvas.height = res[0].height * dpr 47 | ctx.scale(dpr, dpr) 48 | this.lucky = new LuckyGrid({ 49 | flag: 'MP-WX', 50 | ctx, 51 | dpr, 52 | width: res[0].width, 53 | height: res[0].height, 54 | // rAF: canvas.requestAnimationFrame, // 帧动画真机调试会报错! 55 | setTimeout, 56 | clearTimeout, 57 | setInterval, 58 | clearInterval, 59 | unitFunc: (num, unit) => changeUnits(num + unit), 60 | afterStart: () => { 61 | // 隐藏图片并显示canvas 62 | this.lucky.draw() 63 | this.setData({ 64 | luckyImg: '', 65 | showCanvas: true 66 | }) 67 | } 68 | }, { 69 | rows: data.rows, 70 | cols: data.cols, 71 | blocks: data.blocks, 72 | prizes: data.prizes, 73 | buttons: data.buttons, 74 | defaultConfig: data.defaultConfig, 75 | defaultStyle: data.defaultStyle, 76 | activeStyle: data.activeStyle, 77 | start: (...rest) => { 78 | this.triggerEvent('start', ...rest) 79 | }, 80 | end: (...rest) => { 81 | this.triggerEvent('end', ...rest) 82 | getImage.call(this).then(res => { 83 | this.setData({ luckyImg: res.tempFilePath }) 84 | }) 85 | }, 86 | }) 87 | // 为了保证 onload 回调准确 88 | this.setData({ isShow: true }) 89 | }) 90 | }, 91 | methods: { 92 | imgBindload(e) { 93 | const { name, index, i } = e.currentTarget.dataset 94 | const img = this.data[name][index].imgs[i] 95 | resolveImage(e, img, this.canvas) 96 | }, 97 | imgBindloadActive(e) { 98 | const { name, index, i } = e.currentTarget.dataset 99 | const img = this.data[name][index].imgs[i] 100 | resolveImage(e, img, this.canvas, 'activeSrc', '$activeResolve') 101 | }, 102 | luckyImgLoad() { 103 | this.setData({ showCanvas: false }) 104 | this.lucky.clearCanvas() 105 | }, 106 | handleClickOfImg(e) { 107 | const { clientX: x, clientY: y } = e.changedTouches[0] 108 | wx.createSelectorQuery().in(this).select('.lucky-img').fields({ 109 | rect: true 110 | }).exec((res) => { 111 | const { left, top } = res[0] 112 | this.toPlay(x - left, y - top) 113 | }) 114 | }, 115 | handleClickOfCanvas(e) { 116 | const { x, y } = e.changedTouches[0] 117 | this.toPlay(x, y) 118 | }, 119 | toPlay(x, y) { 120 | const ctx = this.ctx 121 | this.data.buttons.forEach(btn => { 122 | if (!btn) return 123 | ctx.beginPath() 124 | ctx.rect(...this.lucky.getGeometricProperty([ 125 | btn.x, 126 | btn.y, 127 | btn.col || 1, 128 | btn.row || 1 129 | ])) 130 | if (!ctx.isPointInPath(x * this.dpr, y * this.dpr)) return 131 | // 触发抽奖逻辑 132 | this.lucky.startCallback() 133 | }) 134 | }, 135 | init (...rest) { 136 | this.lucky.init(...rest) 137 | }, 138 | play(...rest) { 139 | this.lucky.play(...rest) 140 | }, 141 | stop(...rest) { 142 | this.lucky.stop(...rest) 143 | }, 144 | }, 145 | }) 146 | -------------------------------------------------------------------------------- /packages/mini/src/lucky-grid/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /packages/mini/src/lucky-grid/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /packages/mini/src/lucky-grid/index.wxss: -------------------------------------------------------------------------------- 1 | /* @lucky-canvas/mini/lucky-wheel/index.wxss */ 2 | .lucky-box { 3 | position: relative; 4 | overflow: hidden; 5 | margin: 0 auto; 6 | } 7 | .lucky-box .lucky-img { 8 | position: absolute; 9 | left: 0; 10 | top: 0; 11 | z-index: 100; 12 | } 13 | .lucky-box .lucky-canvas { 14 | position: absolute; 15 | left: 0; 16 | top: 0; 17 | z-index: 99; 18 | } 19 | -------------------------------------------------------------------------------- /packages/mini/src/lucky-wheel/index.js: -------------------------------------------------------------------------------- 1 | import { LuckyWheel } from 'lucky-canvas' 2 | import { changeUnits, resolveImage, getImage } from '../utils' 3 | 4 | Component({ 5 | properties: { 6 | width: { type: String, value: '600rpx' }, 7 | height: { type: String, value: '600rpx' }, 8 | blocks: { type: Array, value: [] }, 9 | prizes: { type: Array, value: [] }, 10 | buttons: { type: Array, value: [] }, 11 | defaultConfig: { type: Object, value: {} }, 12 | defaultStyle: { type: Object, value: {} }, 13 | start: { type: null, value: () => {} }, 14 | end: { type: null, value: () => {} }, 15 | }, 16 | data: { 17 | lucky: null, 18 | isShow: false, 19 | luckyImg: '', 20 | showCanvas: true, 21 | }, 22 | observers: { 23 | 'blocks.**': function (newData, oldData) { 24 | this.lucky && (this.lucky.blocks = newData) 25 | }, 26 | 'prizes.**': function (newData, oldData) { 27 | this.lucky && (this.lucky.prizes = newData) 28 | }, 29 | 'buttons.**': function (newData, oldData) { 30 | this.lucky && (this.lucky.buttons = newData) 31 | }, 32 | }, 33 | ready() { 34 | wx.createSelectorQuery().in(this).select('#lucky-wheel').fields({ 35 | node: true, size: true 36 | }).exec((res) => { 37 | if (!res[0] || !res[0].node) { 38 | return console.error('lucky-canvas 获取不到 canvas 标签') 39 | } 40 | const canvas = this.canvas = res[0].node 41 | const dpr = this.dpr = wx.getSystemInfoSync().pixelRatio 42 | const ctx = this.ctx = canvas.getContext('2d') 43 | const data = this.data 44 | canvas.width = res[0].width * dpr 45 | canvas.height = res[0].height * dpr 46 | ctx.scale(dpr, dpr) 47 | this.lucky = new LuckyWheel({ 48 | flag: 'MP-WX', 49 | ctx, 50 | dpr, 51 | width: res[0].width, 52 | height: res[0].height, 53 | // rAF: canvas.requestAnimationFrame, // 帧动画真机调试会报错! 54 | setTimeout, 55 | clearTimeout, 56 | setInterval, 57 | clearInterval, 58 | unitFunc: (num, unit) => changeUnits(num + unit), 59 | beforeCreate() { 60 | const Radius = Math.min(this.config.width, this.config.height) / 2 61 | // 设置坐标轴 62 | ctx.translate(Radius, Radius) 63 | }, 64 | beforeInit() { 65 | // 重置坐标轴 66 | ctx.translate(-this.Radius, -this.Radius) 67 | }, 68 | afterStart: () => { 69 | // 隐藏图片并显示canvas 70 | this.lucky.draw() 71 | this.setData({ 72 | luckyImg: '', 73 | showCanvas: true 74 | }) 75 | } 76 | }, { 77 | blocks: data.blocks, 78 | prizes: data.prizes, 79 | buttons: data.buttons, 80 | defaultConfig: data.defaultConfig, 81 | defaultStyle: data.defaultStyle, 82 | start: (...rest) => { 83 | this.triggerEvent('start', ...rest) 84 | }, 85 | end: (...rest) => { 86 | this.triggerEvent('end', ...rest) 87 | getImage.call(this).then(res => { 88 | this.setData({ luckyImg: res.tempFilePath }) 89 | }) 90 | }, 91 | }) 92 | // 为了保证 onload 回调准确 93 | this.setData({ isShow: true }) 94 | }) 95 | }, 96 | methods: { 97 | imgBindload(e) { 98 | const { name, index, i } = e.currentTarget.dataset 99 | const img = this.data[name][index].imgs[i] 100 | resolveImage(e, img, this.canvas) 101 | }, 102 | luckyImgLoad() { 103 | this.setData({ showCanvas: false }) 104 | this.lucky.clearCanvas() 105 | }, 106 | handleClickOfImg(e) { 107 | const { clientX: x, clientY: y } = e.changedTouches[0] 108 | wx.createSelectorQuery().in(this).select('.lucky-img').fields({ 109 | rect: true 110 | }).exec((res) => { 111 | const { left, top } = res[0] 112 | this.toPlay(x - left, y - top) 113 | }) 114 | }, 115 | handleClickOfCanvas(e) { 116 | const { x, y } = e.changedTouches[0] 117 | this.toPlay(x, y) 118 | }, 119 | toPlay(x, y) { 120 | const ctx = this.ctx 121 | ctx.beginPath() 122 | ctx.arc(0, 0, this.lucky.maxBtnRadius, 0, Math.PI * 2, false) 123 | if (!ctx.isPointInPath(x * this.dpr, y * this.dpr)) return 124 | // 触发抽奖逻辑 125 | this.lucky.startCallback() 126 | }, 127 | init (...rest) { 128 | this.lucky.init(...rest) 129 | }, 130 | play(...rest) { 131 | this.lucky.play(...rest) 132 | }, 133 | stop(...rest) { 134 | this.lucky.stop(...rest) 135 | }, 136 | }, 137 | }) 138 | -------------------------------------------------------------------------------- /packages/mini/src/lucky-wheel/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /packages/mini/src/lucky-wheel/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /packages/mini/src/lucky-wheel/index.wxss: -------------------------------------------------------------------------------- 1 | /* @lucky-canvas/mini/lucky-wheel/index.wxss */ 2 | .lucky-box { 3 | position: relative; 4 | overflow: hidden; 5 | margin: 0 auto; 6 | } 7 | .lucky-box .lucky-img { 8 | position: absolute; 9 | left: 0; 10 | top: 0; 11 | z-index: 100; 12 | } 13 | .lucky-box .lucky-canvas { 14 | position: absolute; 15 | left: 0; 16 | top: 0; 17 | z-index: 99; 18 | } 19 | -------------------------------------------------------------------------------- /packages/mini/src/slot-machine/index.js: -------------------------------------------------------------------------------- 1 | import { SlotMachine } from 'lucky-canvas' 2 | import { changeUnits, resolveImage, getImage } from '../utils' 3 | 4 | Component({ 5 | properties: { 6 | width: { type: String, value: '600rpx' }, 7 | height: { type: String, value: '600rpx' }, 8 | blocks: { type: Array, value: [] }, 9 | prizes: { type: Array, value: [] }, 10 | slots: { type: Array, value: [] }, 11 | defaultConfig: { type: Object, value: {} }, 12 | defaultStyle: { type: Object, value: {} }, 13 | end: { type: null, value: () => {} }, 14 | }, 15 | data: { 16 | lucky: null, 17 | isShow: false, 18 | luckyImg: '', 19 | showCanvas: true, 20 | }, 21 | observers: { 22 | 'blocks.**': function (newData, oldData) { 23 | this.lucky && (this.lucky.blocks = newData) 24 | }, 25 | 'prizes.**': function (newData, oldData) { 26 | this.lucky && (this.lucky.prizes = newData) 27 | }, 28 | 'slots.**': function (newData, oldData) { 29 | this.lucky && (this.lucky.slots = newData) 30 | }, 31 | }, 32 | ready() { 33 | wx.createSelectorQuery().in(this).select('#slot-machine').fields({ 34 | node: true, size: true 35 | }).exec((res) => { 36 | if (!res[0] || !res[0].node) { 37 | return console.error('lucky-canvas 获取不到 canvas 标签') 38 | } 39 | const canvas = this.canvas = res[0].node 40 | const dpr = this.dpr = wx.getSystemInfoSync().pixelRatio 41 | const ctx = this.ctx = canvas.getContext('2d') 42 | const data = this.data 43 | canvas.width = res[0].width * dpr 44 | canvas.height = res[0].height * dpr 45 | ctx.scale(dpr, dpr) 46 | this.lucky = new SlotMachine({ 47 | flag: 'MP-WX', 48 | ctx, 49 | dpr, 50 | offscreenCanvas: wx.createOffscreenCanvas({ type: '2d', width: 300, height: 150 }), 51 | // rAF: canvas.requestAnimationFrame, // 帧动画真机调试会报错! 52 | setTimeout, 53 | clearTimeout, 54 | setInterval, 55 | clearInterval, 56 | unitFunc: (num, unit) => changeUnits(num + unit), 57 | afterStart: () => { 58 | // 隐藏图片并显示canvas 59 | this.lucky.draw() 60 | this.setData({ 61 | luckyImg: '', 62 | showCanvas: true 63 | }) 64 | } 65 | }, { 66 | width: res[0].width, 67 | height: res[0].height, 68 | blocks: data.blocks, 69 | prizes: data.prizes, 70 | slots: data.slots, 71 | defaultConfig: data.defaultConfig, 72 | defaultStyle: data.defaultStyle, 73 | end: (...rest) => { 74 | this.triggerEvent('end', ...rest) 75 | getImage.call(this).then(res => { 76 | this.setData({ luckyImg: res.tempFilePath }) 77 | }) 78 | }, 79 | }) 80 | // 为了保证 onload 回调准确 81 | this.setData({ isShow: true }) 82 | }) 83 | }, 84 | methods: { 85 | imgBindload(e) { 86 | const { name, index, i } = e.currentTarget.dataset 87 | const img = this.data[name][index].imgs[i] 88 | resolveImage(e, img, this.canvas) 89 | }, 90 | luckyImgLoad() { 91 | this.setData({ showCanvas: false }) 92 | this.lucky.clearCanvas() 93 | }, 94 | init (...rest) { 95 | this.lucky.init(...rest) 96 | }, 97 | play(...rest) { 98 | this.lucky.play(...rest) 99 | }, 100 | stop(...rest) { 101 | this.lucky.stop(...rest) 102 | }, 103 | }, 104 | }) 105 | -------------------------------------------------------------------------------- /packages/mini/src/slot-machine/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /packages/mini/src/slot-machine/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/mini/src/slot-machine/index.wxss: -------------------------------------------------------------------------------- 1 | /* @lucky-canvas/mini/slot-machine/index.wxss */ 2 | .lucky-box { 3 | position: relative; 4 | overflow: hidden; 5 | margin: 0 auto; 6 | } 7 | .lucky-box .lucky-img { 8 | position: absolute; 9 | left: 0; 10 | top: 0; 11 | z-index: 100; 12 | } 13 | .lucky-box .lucky-canvas { 14 | position: absolute; 15 | left: 0; 16 | top: 0; 17 | z-index: 99; 18 | } 19 | -------------------------------------------------------------------------------- /packages/mini/src/utils.js: -------------------------------------------------------------------------------- 1 | const windowWidth = wx.getSystemInfoSync().windowWidth 2 | 3 | export const rpx2px = (value) => { 4 | if (typeof value === 'string') value = Number(value.replace(/[a-z]*/g, '')) 5 | return windowWidth / 750 * value 6 | } 7 | 8 | export const changeUnits = (value) => { 9 | value = String(value) 10 | return Number(value.replace(/^(\-*[0-9.]*)([a-z%]*)$/, (value, num, unit) => { 11 | switch (unit) { 12 | case 'px': 13 | num *= 1 14 | break 15 | case 'rpx': 16 | num = rpx2px(num) 17 | break 18 | default: 19 | num *= 1 20 | break 21 | } 22 | return num 23 | })) 24 | } 25 | 26 | export const resolveImage = (e, img, canvas, srcName = 'src', resolveName = '$resolve') => { 27 | const imgObj = canvas.createImage() 28 | imgObj.onload = () => { 29 | img[resolveName](imgObj) 30 | } 31 | imgObj.src = img[srcName] 32 | } 33 | 34 | export function getImage() { 35 | return new Promise((resolve, reject) => { 36 | wx.canvasToTempFilePath({ 37 | canvas: this.canvas, 38 | success: res => resolve(res), 39 | fail: err => reject(err) 40 | }) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /packages/mini/test/index.test.js: -------------------------------------------------------------------------------- 1 | const _ = require('./utils') 2 | 3 | test('render', async () => { 4 | const componentId = _.load('index', 'comp') 5 | const component = _.render(componentId, {prop: 'index.test.properties'}) 6 | 7 | const parent = document.createElement('parent-wrapper') 8 | component.attach(parent) 9 | 10 | expect(_.match(component.dom, 'index.test.properties-falseother.properties-other')).toBe(true) 11 | 12 | await _.sleep(10) 13 | 14 | expect(_.match(component.dom, 'index.test.properties-trueother.properties-other')).toBe(true) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/mini/test/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const simulate = require('miniprogram-simulate') 4 | const config = require('../tools/config') 5 | 6 | // const dir = config.srcPath // 使用源码进行测试,对于 debug 和代码覆盖率检测会比较友好 7 | const dir = config.distPath // 使用构建后代码进行测试,如果使用了 typescript 进行开发,必须选择此目录 8 | 9 | try { 10 | fs.accessSync(dir) 11 | } catch (err) { 12 | console.error('请先执行 npm run build 再进行单元测试!!!') 13 | } 14 | 15 | const oldLoad = simulate.load 16 | simulate.load = function (componentPath, ...args) { 17 | if (typeof componentPath === 'string') componentPath = path.join(dir, componentPath) 18 | return oldLoad(componentPath, ...args) 19 | } 20 | 21 | module.exports = simulate 22 | 23 | // adjust the simulated wx api 24 | const oldGetSystemInfoSync = global.wx.getSystemInfoSync 25 | global.wx.getSystemInfoSync = function() { 26 | const res = oldGetSystemInfoSync() 27 | res.SDKVersion = '2.4.1' 28 | 29 | return res 30 | } 31 | -------------------------------------------------------------------------------- /packages/mini/test/wx.test.js: -------------------------------------------------------------------------------- 1 | const _ = require('./utils') 2 | 3 | test('wx.getSystemInfo', async () => { 4 | wx.getSystemInfo({ 5 | success(res) { 6 | expect(res.errMsg).toBe('getSystemInfo:ok') 7 | }, 8 | complete(res) { 9 | expect(res.errMsg).toBe('getSystemInfo:ok') 10 | }, 11 | }) 12 | }) 13 | 14 | test('wx.getSystemInfoSync', async () => { 15 | const info = wx.getSystemInfoSync() 16 | expect(info.SDKVersion).toBe('2.4.1') 17 | expect(info.version).toBe('6.6.3') 18 | }) 19 | -------------------------------------------------------------------------------- /packages/mini/tools/checkcomponents.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const _ = require('./utils') 4 | const config = require('./config') 5 | 6 | const srcPath = config.srcPath 7 | let hasCheckCompoenntMap = {} 8 | 9 | /** 10 | * 获取 json 路径相关信息 11 | */ 12 | function getJsonPathInfo(jsonPath) { 13 | const dirPath = path.dirname(jsonPath) 14 | const fileName = path.basename(jsonPath, '.json') 15 | const relative = path.relative(srcPath, dirPath) 16 | const fileBase = path.join(relative, fileName) 17 | 18 | return { 19 | dirPath, fileName, relative, fileBase 20 | } 21 | } 22 | 23 | /** 24 | * 检测是否包含其他自定义组件 25 | */ 26 | const checkProps = ['usingComponents', 'componentGenerics'] 27 | async function checkIncludedComponents(jsonPath, componentListMap) { 28 | const json = _.readJson(jsonPath) 29 | if (!json) throw new Error(`json is not valid: "${jsonPath}"`) 30 | 31 | const {dirPath, fileName, fileBase} = getJsonPathInfo(jsonPath) 32 | if (hasCheckCompoenntMap[fileBase]) return 33 | hasCheckCompoenntMap[fileBase] = true 34 | 35 | for (let i = 0, len = checkProps.length; i < len; i++) { 36 | const checkProp = checkProps[i] 37 | const checkPropValue = json[checkProp] || {} 38 | const keys = Object.keys(checkPropValue) 39 | 40 | for (let j = 0, jlen = keys.length; j < jlen; j++) { 41 | const key = keys[j] 42 | let value = typeof checkPropValue[key] === 'object' ? checkPropValue[key].default : checkPropValue[key] 43 | if (!value || typeof value === 'boolean') continue 44 | 45 | value = _.transformPath(value, path.sep) 46 | 47 | // 检查相对路径 48 | const componentPath = `${path.join(dirPath, value)}.json` 49 | const isExists = await _.checkFileExists(componentPath) 50 | if (isExists) { 51 | await checkIncludedComponents(componentPath, componentListMap) 52 | } 53 | } 54 | } 55 | 56 | const wholeFileBase = path.join(dirPath, fileName) 57 | let jsExt = '.js' 58 | const isJsFileExists = await _.checkFileExists(wholeFileBase + '.ts') 59 | if (isJsFileExists) { 60 | jsExt = '.ts' 61 | } 62 | 63 | // 进入存储 64 | componentListMap.wxmlFileList.push(`${fileBase}.wxml`) 65 | componentListMap.wxssFileList.push(`${fileBase}.wxss`) 66 | componentListMap.jsonFileList.push(`${fileBase}.json`) 67 | componentListMap.jsFileList.push(`${fileBase}${jsExt}`) 68 | 69 | componentListMap.jsFileMap[fileBase] = `${wholeFileBase}${jsExt}` 70 | } 71 | 72 | module.exports = async function (entry) { 73 | const componentListMap = { 74 | wxmlFileList: [], 75 | wxssFileList: [], 76 | jsonFileList: [], 77 | jsFileList: [], 78 | 79 | jsFileMap: {}, // 为 webpack entry 所用 80 | } 81 | 82 | const isExists = await _.checkFileExists(entry) 83 | if (!isExists) { 84 | const {dirPath, fileName, fileBase} = getJsonPathInfo(entry) 85 | 86 | const wholeFileBase = path.join(dirPath, fileName) 87 | let jsExt = '.js' 88 | const isJsFileExists = await _.checkFileExists(wholeFileBase + '.ts') 89 | if (isJsFileExists) { 90 | jsExt = '.ts' 91 | } 92 | componentListMap.jsFileList.push(`${fileBase}${jsExt}`) 93 | componentListMap.jsFileMap[fileBase] = `${wholeFileBase}${jsExt}` 94 | 95 | return componentListMap 96 | } 97 | 98 | hasCheckCompoenntMap = {} 99 | await checkIncludedComponents(entry, componentListMap) 100 | 101 | return componentListMap 102 | } 103 | -------------------------------------------------------------------------------- /packages/mini/tools/checkwxss.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const through = require('through2') 3 | const Vinyl = require('vinyl') 4 | 5 | const _ = require('./utils') 6 | 7 | /** 8 | * 获取 import 列表 9 | */ 10 | function getImportList(wxss, filePath) { 11 | const reg = /@import\s+(?:(?:"([^"]+)")|(?:'([^']+)'));/ig 12 | const importList = [] 13 | let execRes = reg.exec(wxss) 14 | 15 | while (execRes && (execRes[1] || execRes[2])) { 16 | importList.push({ 17 | code: execRes[0], 18 | path: path.join(path.dirname(filePath), execRes[1] || execRes[2]), 19 | }) 20 | execRes = reg.exec(wxss) 21 | } 22 | 23 | return importList 24 | } 25 | 26 | /** 27 | * 获取 wxss 内容 28 | */ 29 | async function getContent(wxss, filePath, cwd) { 30 | let importList = [] 31 | 32 | if (wxss) { 33 | const currentImportList = getImportList(wxss, filePath) 34 | 35 | for (const item of currentImportList) { 36 | // 替换掉 import 语句,不让 less 编译 37 | wxss = wxss.replace(item.code, `/* *updated for miniprogram-custom-component* ${item.code} */`) 38 | 39 | // 处理依赖的 wxss 40 | const importWxss = await _.readFile(item.path) 41 | const importInfo = await getContent(importWxss, item.path, cwd) 42 | 43 | // 获取依赖列表 44 | importList.push(new Vinyl({ 45 | cwd, 46 | path: item.path, 47 | contents: Buffer.from(importInfo.wxss, 'utf8'), 48 | })) 49 | importList = importList.concat(importInfo.importList) 50 | } 51 | } 52 | 53 | return { 54 | wxss, 55 | importList, 56 | } 57 | } 58 | 59 | module.exports = { 60 | start() { 61 | return through.obj(function (file, enc, cb) { 62 | if (file.isBuffer()) { 63 | getContent(file.contents.toString('utf8'), file.path, file.cwd).then(res => { 64 | const {wxss, importList} = res 65 | 66 | importList.forEach(importFile => this.push(importFile)) 67 | 68 | file.contents = Buffer.from(wxss, 'utf8') 69 | this.push(file) 70 | // eslint-disable-next-line promise/no-callback-in-promise 71 | cb() 72 | }).catch(err => { 73 | // eslint-disable-next-line no-console 74 | console.warn(`deal with ${file.path} failed: ${err.stack}`) 75 | this.push(file) 76 | // eslint-disable-next-line promise/no-callback-in-promise 77 | cb() 78 | }) 79 | } else { 80 | this.push(file) 81 | cb() 82 | } 83 | }) 84 | }, 85 | 86 | end() { 87 | return through.obj(function (file, enc, cb) { 88 | if (file.isBuffer) { 89 | const reg = /\/\*\s\*updated for miniprogram-custom-component\*\s(@import\s+(?:(?:"([^"]+)")|(?:'([^"]+)'));)\s\*\//ig 90 | const wxss = file.contents.toString('utf8').replace(reg, (all, $1) => $1) 91 | 92 | file.contents = Buffer.from(wxss, 'utf8') 93 | this.push(file) 94 | cb() 95 | } else { 96 | this.push(file) 97 | cb() 98 | } 99 | }) 100 | }, 101 | } 102 | -------------------------------------------------------------------------------- /packages/mini/tools/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const webpack = require('webpack') 4 | const nodeExternals = require('webpack-node-externals') 5 | 6 | const isDev = process.argv.indexOf('--develop') >= 0 7 | const isWatch = process.argv.indexOf('--watch') >= 0 8 | const demoSrc = path.resolve(__dirname, './demo') 9 | const demoDist = path.resolve(__dirname, '../dev') 10 | const src = path.resolve(__dirname, '../src') 11 | const dev = path.join(demoDist, 'components') 12 | const dist = path.resolve(__dirname, '../dist') 13 | 14 | module.exports = { 15 | entry: ['lucky-wheel/index', 'lucky-grid/index', 'slot-machine/index'], 16 | 17 | isDev, 18 | isWatch, 19 | srcPath: src, // 源目录 20 | distPath: isDev ? dev : dist, // 目标目录 21 | 22 | demoSrc, // demo 源目录 23 | demoDist, // demo 目标目录 24 | 25 | wxss: { 26 | less: false, // 使用 less 来编写 wxss 27 | sourcemap: false, // 生成 less sourcemap 28 | }, 29 | 30 | js: { 31 | webpack: true, // 使用 webpack 来构建 js 32 | }, 33 | 34 | webpack: { 35 | mode: 'production', 36 | output: { 37 | filename: '[name].js', 38 | libraryTarget: 'commonjs2', 39 | }, 40 | target: 'node', 41 | externals: [nodeExternals()], // 忽略 node_modules 42 | module: { 43 | rules: [{ 44 | test: /\.js$/i, 45 | use: [{ 46 | loader: 'thread-loader', 47 | }, { 48 | loader: 'babel-loader', 49 | options: { 50 | cacheDirectory: true, 51 | }, 52 | }], 53 | exclude: /node_modules/ 54 | }, { 55 | test: /\.ts$/, 56 | exclude: /node_modules/, 57 | use: [{ 58 | loader: 'thread-loader', 59 | }, { 60 | loader: 'babel-loader', 61 | options: { 62 | cacheDirectory: true, 63 | }, 64 | }, { 65 | loader: 'ts-loader', 66 | options: { 67 | appendTsSuffixTo: [/\.vue$/], 68 | happyPackMode: true, 69 | }, 70 | }], 71 | }], 72 | }, 73 | resolve: { 74 | modules: [src, 'node_modules', path.join(__dirname, 'node_modules')], 75 | extensions: ['.js', '.json'], 76 | }, 77 | plugins: [ 78 | new webpack.DefinePlugin({}), 79 | new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}), 80 | ], 81 | optimization: { 82 | minimize: false, 83 | }, 84 | devtool: 'source-map', // 生成 js sourcemap 85 | performance: { 86 | hints: 'warning', 87 | assetFilter: assetFilename => assetFilename.endsWith('.js') 88 | } 89 | }, 90 | 91 | copy: [], // 将会复制到目标目录 92 | } 93 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/app.js: -------------------------------------------------------------------------------- 1 | App({}) 2 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index", 4 | "pages/yyjk/index", 5 | "pages/ymc/index", 6 | "pages/cjl/index", 7 | "pages/yx/index", 8 | "pages/yyx/index", 9 | "pages/jd/index", 10 | "pages/xdf/index", 11 | "pages/xc/index" 12 | ], 13 | "window":{ 14 | "backgroundTextStyle":"light", 15 | "navigationBarBackgroundColor": "#dc415f", 16 | "navigationBarTitleText": "lucky-canvas 抽奖示例", 17 | "navigationBarTextStyle":"white" 18 | }, 19 | "usingComponents": { 20 | "lucky-wheel": "./components/lucky-wheel/index", 21 | "lucky-grid": "./components/lucky-grid/index", 22 | "slot-machine": "./components/slot-machine/index" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/app.wxss: -------------------------------------------------------------------------------- 1 | .box { 2 | width: 100%; 3 | min-height: 100vh; 4 | position: relative; 5 | background-color: #dc415f; 6 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 100 100'%3E%3Crect x='0' y='0' width='57' height='57' fill='%23ce3250'/%3E%3C/svg%3E"); 7 | color: #fe95a8; 8 | } 9 | .my-lucky { 10 | margin-top: 100rpx; 11 | width: 600rpx; 12 | position: absolute; 13 | left: 50%; 14 | transform: translate(-50%); 15 | } 16 | .mask { 17 | width: 100%; 18 | height: 100%; 19 | background: rgba(0, 0, 0, 0.5); 20 | position: fixed; 21 | top: 0; 22 | left: 0; 23 | z-index: 999; 24 | } 25 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/package.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/cjl/index.js: -------------------------------------------------------------------------------- 1 | import imgs from './img' 2 | Page({ 3 | data: { 4 | prizes: [], 5 | buttons: [{ 6 | radius: '40px', 7 | imgs: [{ src: imgs['button.png'], width: '105%', top: '-180%' }] 8 | }], 9 | blocks: [ 10 | { padding: '20px', imgs: [{ src: imgs['bg.png'], width: '100%' }] }, 11 | { padding: '1px', background: '#fa3e3f' }, 12 | { padding: '10px', background: '#f9d400' }, 13 | { padding: '1px', background: '#e76f51' }, 14 | ], 15 | defaultStyle: { 16 | fontColor: '#303133', 17 | fontSize: '10px', 18 | }, 19 | defaultConfig: { 20 | gutter: '1px', 21 | }, 22 | }, 23 | onReady () { 24 | this.getPrizesList() 25 | }, 26 | getPrizesList () { 27 | const prizes = [] 28 | let data = [ 29 | { name: '谢谢参与', img: imgs['0.png'], color: '#d7d7d7' }, 30 | { name: '礼物', img: imgs['1.png'], color: '#fef43e' }, 31 | { name: '抽奖券', img: imgs['2.png'], color: '#ef7683' }, 32 | { name: '元宝', img: imgs['3.png'], color: '#d7d7d7' }, 33 | { name: '元宝', img: imgs['4.png'], color: '#fef43e' }, 34 | { name: '抽奖券', img: imgs['2.png'], color: '#ef7683' }, 35 | { name: '元宝', img: imgs['6.png'], color: '#d7d7d7' }, 36 | { name: '抽奖券', img: imgs['2.png'], color: '#fef43e' }, 37 | ] 38 | data.forEach((item, index) => { 39 | prizes.push({ 40 | name: item.name, 41 | background: item.color, 42 | fonts: [{ text: item.name, top: '10%' }], 43 | imgs:[{ src: item.img, width: '30%', top: '35%' }], 44 | }) 45 | }) 46 | this.setData({ 47 | prizes 48 | }) 49 | }, 50 | wheelStart () { 51 | // 获取抽奖组件实例 52 | const child = this.selectComponent('#lucky-wheel') 53 | // 调用play方法开始旋转 54 | child.play() 55 | // 用定时器模拟请求接口 56 | setTimeout(() => { 57 | // 3s 后得到中奖索引 58 | const index = Math.random() * 8 >> 0 59 | // 调用stop方法然后缓慢停止 60 | child.stop(index) 61 | }, 3000) 62 | }, 63 | wheelEnd (event) { 64 | // 中奖奖品详情 65 | wx.showModal({ 66 | title: '提示', 67 | content: '恭喜你获得大奖: ' + event.detail.name, 68 | success (res) {} 69 | }) 70 | }, 71 | }) -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/cjl/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/cjl/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/cjl/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/mini/tools/demo/pages/cjl/index.wxss -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/index/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | data: { 3 | prizes: [], 4 | wheel: { 5 | defaultStyle: { 6 | fontColor: '#ff625b', 7 | fontSize: '16px' 8 | }, 9 | blocks: [ 10 | { padding: '20rpx', background: '#ffc27a' }, 11 | { padding: '10rpx', background: '#ff4a4c' }, 12 | ], 13 | buttons: [ 14 | { radius: '50px', background: '#ff4a4c' }, 15 | { radius: '45px', background: '#fff' }, 16 | { radius: '41px', background: '#f6c66f', pointer: true }, 17 | { 18 | radius: '35px', background: '#ffdea0', 19 | imgs: [{ src: 'https://100px.net/assets/img/button.2f4ac3e9.png', width: '70%', top: '-55%' }] 20 | } 21 | ], 22 | }, 23 | grid: { 24 | rows: 3, 25 | cols: 4, 26 | blocks: [{ padding: '5px', background: '#ff4a4c', borderRadius: 10 }], 27 | prizes: [ 28 | { x: 0, y: 0, fonts: [{ text: '0元', top: 20 }], imgs: [{ src: 'https://100px.net/assets/img/4.1349538d.png', activeSrc: 'https://100px.net/assets/img/00.a8cd98c2.png', width: '50%' }] }, 29 | { x: 1, y: 0, fonts: [{ text: '1元', top: 20 }] }, 30 | { x: 2, y: 0, fonts: [{ text: '2元', top: 20 }] }, 31 | { x: 3, y: 0, fonts: [{ text: '3元', top: 20 }] }, 32 | { x: 3, y: 1, fonts: [{ text: '4元', top: 20 }] }, 33 | { x: 3, y: 2, fonts: [{ text: '5元', top: 20 }] }, 34 | { x: 2, y: 2, fonts: [{ text: '6元', top: 20 }] }, 35 | { x: 1, y: 2, fonts: [{ text: '7元', top: 20 }] }, 36 | { x: 0, y: 2, fonts: [{ text: '8元', top: 20 }] }, 37 | { x: 0, y: 1, fonts: [{ text: '9元', top: 20 }] } 38 | ], 39 | buttons: [{ x: 1, y: 1, col: 2, fonts: [{ text: '抽奖按钮', top: 20 }], imgs: [{ src: 'https://100px.net/assets/img/4.1349538d.png', width: '50%' }] }], 40 | } 41 | }, 42 | onReady () { 43 | this.setData({ 44 | prizes: [] 45 | }); 46 | setTimeout(() => { 47 | [ 48 | { title: '1元红包', background: '#ffd185', fonts: [{ text: '1元红包', top: '10%' }], imgs: [{ src: 'https://100px.net/assets/img/0.efbe4dff.png', width: '35%', top: '35%' }] }, 49 | { title: '100元红包', background: '#f9e3bb', fonts: [{ text: '100元红包', top: '10%' }], imgs: [{ src: 'https://100px.net/assets/img/1.de299995.png', width: '35%', top: '35%' }] }, 50 | { title: '0.5元红包', background: '#ffd185', fonts: [{ text: '0.5元红包', top: '10%' }], imgs: [{ src: 'https://100px.net/assets/img/2.8f1949c9.png', width: '35%', top: '35%' }] }, 51 | { title: '2元红包', background: '#f9e3bb', fonts: [{ text: '2元红包', top: '10%' }], imgs: [{ src: 'https://100px.net/assets/img/3.9307595d.png', width: '35%', top: '35%' }] }, 52 | { title: '10元红包', background: '#ffd185', fonts: [{ text: '10元红包', top: '10%' }], imgs: [{ src: 'https://100px.net/assets/img/4.1349538d.png', width: '35%', top: '35%' }] }, 53 | { title: '50元红包', background: '#f9e3bb', fonts: [{ text: '50元红包', top: '10%' }], imgs: [{ src: 'https://100px.net/assets/img/5.b92ceb2f.png', width: '35%', top: '35%' }] }, 54 | { title: '0.3元红包', background: '#ffd185', fonts: [{ text: '0.3元红包', top: '10%' }], imgs: [{ src: 'https://100px.net/assets/img/6.02483a09.png', width: '35%', top: '35%' }] }, 55 | { title: '5元红包', background: '#f9e3bb', fonts: [{ text: '5元红包', top: '10%' }], imgs: [{ src: 'https://100px.net/assets/img/7.48cda152.png', width: '35%', top: '35%' }] }, 56 | ].forEach(_ => { 57 | this.setData({ 58 | prizes: [...this.data.prizes, _] 59 | }); 60 | }) 61 | }, 1000); 62 | }, 63 | wheelStart () { 64 | // 获取抽奖组件实例 65 | const child = this.selectComponent('#lucky-wheel') 66 | // 调用play方法开始旋转 67 | child.play() 68 | // 用定时器模拟请求接口 69 | setTimeout(() => { 70 | // 3s 后得到中奖索引 71 | const index = Math.random() * 6 >> 0 72 | // 调用stop方法然后缓慢停止 73 | child.stop(index) 74 | }, 3000) 75 | }, 76 | wheelEnd (event) { 77 | // 中奖奖品详情 78 | console.log(event.detail) 79 | }, 80 | gridStart () { 81 | // 获取抽奖组件实例 82 | const child = this.selectComponent('#lucky-grid') 83 | // 调用play方法开始旋转 84 | child.play() 85 | // 用定时器模拟请求接口 86 | setTimeout(() => { 87 | // 3s 后得到中奖索引 88 | const index = Math.random() * 6 >> 0 89 | // 调用stop方法然后缓慢停止 90 | child.stop(index) 91 | }, 3000) 92 | }, 93 | gridEnd (event) { 94 | // 中奖奖品详情 95 | console.log(event.detail) 96 | } 97 | }) -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 该小程序为示例 demo | 官网: 100px.net 4 | 5 | 6 | 大转盘默认 Demo 7 | 九宫格默认 Demo 8 | 仿抽奖乐大转盘 9 | 仿网易严选九宫格 10 | 仿携程转盘抽奖 11 | 12 | 仿网易云游戏九宫格 13 | 仿京东大转盘抽京豆 14 | 仿新东方小程序抽奖 15 | 16 | 17 | 18 | 本插件开源免费, 如果用着顺手可以 github 上面点个 star 支持我一下 19 | Github: 官网右上角有链接 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | .box { 2 | background-color: #dc415f; 3 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 100 100'%3E%3Crect x='0' y='0' width='57' height='57' fill='%23ce3250'/%3E%3C/svg%3E"); 4 | color: #fe95a8; 5 | min-height: 100vh; 6 | } 7 | .center { 8 | text-align: center; 9 | } 10 | .ul { 11 | padding: 0 50rpx; 12 | display: flex; 13 | justify-content: space-between; 14 | flex-wrap: wrap; 15 | } 16 | .list { 17 | color: #ff4a4c; 18 | text-align: center; 19 | width: 48%; 20 | padding: 0rpx 0; 21 | margin: 20rpx 0; 22 | background: #f9e3bb; 23 | border-radius: 10rpx; 24 | box-shadow: 0 2px 12px 0 rgba(0,0,0,.1); 25 | 26 | } 27 | navigator { 28 | display: block; 29 | width: 100%; 30 | height: 100%; 31 | padding: 20rpx 0; 32 | font-size: 30rpx; 33 | } -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/jd/index.js: -------------------------------------------------------------------------------- 1 | import imgs from './img' 2 | Page({ 3 | data: { 4 | blocks: [ 5 | { padding: '30px', imgs: [{ src: imgs['bg.png'], width: '100%' }] }, 6 | ], 7 | prizes: [], 8 | buttons: [ 9 | { radius: '40px', imgs: [{ src: imgs['button.png'], width: '140%', top: '-140%' }] }, 10 | { radius: '15%', imgs: [{ src: imgs['btn.png'], width: '100%', top: '-830%' }] } 11 | ], 12 | defaultStyle: { 13 | fontColor: '#000', 14 | fontSize: '13px', 15 | fontStyle: 'SimHei', 16 | }, 17 | defaultConfig: { 18 | offsetDegree: 22.5 19 | }, 20 | }, 21 | onReady () { 22 | this.getPrizesList() 23 | }, 24 | getPrizesList () { 25 | const prizes = [] 26 | let data = [ 27 | { name: '10个京豆', img: imgs['1.png'], color: '#F8DEF8' }, 28 | { name: '5个京豆', img: imgs['1.png'], color: '#FEF3FC' }, 29 | { name: '1个京豆', img: imgs['1.png'], color: '#F8DEF8' }, 30 | { name: '谢谢参与', img: imgs['0.png'], color: '#FEF3FC' }, 31 | { name: '10个京豆', img: imgs['1.png'], color: '#F8DEF8' }, 32 | { name: '5个京豆', img: imgs['1.png'], color: '#FEF3FC' }, 33 | { name: '1个京豆', img: imgs['1.png'], color: '#F8DEF8' }, 34 | { name: '谢谢参与', img: imgs['0.png'], color: '#FEF3FC' }, 35 | ] 36 | data.forEach((item, index) => { 37 | prizes.push({ 38 | name: item.name, 39 | background: item.color, 40 | fonts: [{ text: item.name, top: '12%' }], 41 | imgs:[{ src: item.img, width: '45%', top: '35%' }], 42 | }) 43 | }) 44 | this.setData({ 45 | prizes 46 | }) 47 | }, 48 | wheelStart () { 49 | // 获取抽奖组件实例 50 | const child = this.selectComponent('#lucky-wheel') 51 | // 调用play方法开始旋转 52 | child.play() 53 | // 用定时器模拟请求接口 54 | setTimeout(() => { 55 | // 3s 后得到中奖索引 56 | const index = Math.random() * 8 >> 0 57 | // 调用stop方法然后缓慢停止 58 | child.stop(index) 59 | }, 3000) 60 | }, 61 | wheelEnd (event) { 62 | // 中奖奖品详情 63 | wx.showModal({ 64 | title: '提示', 65 | content: '恭喜你获得大奖: ' + event.detail.name, 66 | success (res) {} 67 | }) 68 | }, 69 | }) -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/jd/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/jd/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/jd/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/mini/tools/demo/pages/jd/index.wxss -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/slot/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | data: { 3 | maskShow: false, 4 | prizes: [], 5 | slots: [], 6 | defaultStyle: { 7 | fontColor: '#ff625b', 8 | fontSize: '16px' 9 | }, 10 | blocks: [ 11 | { padding: '20rpx', background: '#ffc27a' }, 12 | { padding: '10rpx', background: '#ff4a4c' }, 13 | ], 14 | }, 15 | onReady () { 16 | this.setData({ 17 | prizes: [] 18 | }); 19 | setTimeout(() => { 20 | [ 21 | { 22 | title: '1元红包', background: '#ffd185', fonts: [{ text: '1元红包', top: '10%' }], 23 | }, 24 | { 25 | title: '100元红包', background: '#f9e3bb', fonts: [{ text: '100元红包', top: '10%' }], 26 | }, 27 | { 28 | title: '0.5元红包', background: '#ffd185', fonts: [{ text: '0.5元红包', top: '10%' }], 29 | }, 30 | { 31 | title: '2元红包', background: '#f9e3bb', fonts: [{ text: '2元红包', top: '10%' }], 32 | }, 33 | { 34 | title: '10元红包', background: '#ffd185', fonts: [{ text: '10元红包', top: '10%' }], 35 | }, 36 | { 37 | title: '50元红包', background: '#f9e3bb', fonts: [{ text: '50元红包', top: '10%' }], 38 | }, 39 | { 40 | title: '0.3元红包', background: '#ffd185', fonts: [{ text: '0.3元红包', top: '10%' }], 41 | }, 42 | { 43 | title: '5元红包', background: '#f9e3bb', fonts: [{ text: '5元红包', top: '10%' }], 44 | }, 45 | ].forEach(_ => { 46 | this.setData({ 47 | prizes: [...this.data.prizes, _] 48 | }); 49 | }) 50 | this.setData({ 51 | slots: [ 52 | {}, 53 | { direction: -1 }, 54 | {}, 55 | ] 56 | }) 57 | }, 50); 58 | }, 59 | handleStart () { 60 | // 获取抽奖组件实例 61 | const child = this.selectComponent('#slot-machine') 62 | // 调用play方法开始旋转 63 | child.play() 64 | // 用定时器模拟请求接口 65 | setTimeout(() => { 66 | // 3s 后得到中奖索引 67 | const index = Math.random() * 6 >> 0 68 | // 调用stop方法然后缓慢停止 69 | child.stop(index) 70 | }, 3000) 71 | }, 72 | handleEnd (event) { 73 | // 中奖奖品详情 74 | this.setData({ maskShow: true }) 75 | wx.showModal({ 76 | title: '提示', 77 | content: '恭喜你获得大奖: ' + event.detail.title, 78 | success (res) {} 79 | }) 80 | }, 81 | maskHidden () { 82 | this.setData({ maskShow: false }) 83 | }, 84 | }) -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/slot/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/slot/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 开始111 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/slot/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/mini/tools/demo/pages/slot/index.wxss -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/xc/index.js: -------------------------------------------------------------------------------- 1 | import imgs from './img' 2 | Page({ 3 | data: { 4 | prizes: [ 5 | { name: '免费住酒店' }, 6 | { name: '房型升级' }, 7 | { name: '免费取消' }, 8 | { name: '延迟退房' }, 9 | { name: '免费早餐' }, 10 | { name: '明天再来' }, 11 | ], 12 | buttons: [{ 13 | radius: '25%', 14 | fonts: [{ text: '开始\n抽奖', fontColor: '#fff', top: '-50%', fontSize: '16px' }], 15 | imgs: [{ src: imgs['btn.png'], width: '120%', top: '-175%' }] 16 | }], 17 | blocks: [ 18 | { imgs: [{ src: imgs['bg.png'], width: '100%', rotate: true }] }, 19 | ], 20 | defaultConfig: { 21 | offsetDegree: 30 22 | }, 23 | }, 24 | onReady () { 25 | }, 26 | wheelStart () { 27 | // 获取抽奖组件实例 28 | const child = this.selectComponent('#lucky-wheel') 29 | // 调用play方法开始旋转 30 | child.play() 31 | // 用定时器模拟请求接口 32 | setTimeout(() => { 33 | // 3s 后得到中奖索引 34 | const index = Math.random() * 8 >> 0 35 | // 调用stop方法然后缓慢停止 36 | child.stop(index) 37 | }, 3000) 38 | }, 39 | wheelEnd (event) { 40 | // 中奖奖品详情 41 | wx.showModal({ 42 | title: '提示', 43 | content: '恭喜你获得大奖: ' + event.detail.name, 44 | success (res) {} 45 | }) 46 | }, 47 | }) -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/xc/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/xc/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/xc/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/mini/tools/demo/pages/xc/index.wxss -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/xdf/index.js: -------------------------------------------------------------------------------- 1 | import imgs from './img' 2 | Page({ 3 | data: { 4 | blocks: [ 5 | { padding: '3px', background: '#92c53a', borderRadius: '8px' }, 6 | { padding: '2px', background: '#bdd333', borderRadius: '8px' }, 7 | { padding: '7px', background: '#0D6630', borderRadius: '8px' }, 8 | ], 9 | prizes: [ 10 | { 11 | x: 0, y: 0, 12 | fonts: [{ text: '谢谢参与', top: '68%' }], 13 | imgs: [{ src: imgs['0.png'], width: '50%', top: '12%' }], 14 | }, 15 | { 16 | x: 1, y: 0, 17 | fonts: [{ text: '1个星球币', top: '68%' }], 18 | imgs: [{ src: imgs['1.png'], width: '50%', top: '12%' }], 19 | }, 20 | { 21 | x: 2, y: 0, 22 | fonts: [{ text: '5个星球币', top: '68%' }], 23 | imgs: [{ src: imgs['1.png'], width: '50%', top: '12%' }], 24 | }, 25 | { 26 | x: 2, y: 1, 27 | fonts: [{ text: '10个星球币', top: '68%' }], 28 | imgs: [{ src: imgs['1.png'], width: '50%', top: '12%' }], 29 | }, 30 | { 31 | x: 2, y: 2, 32 | fonts: [{ text: '谢谢参与', top: '68%' }], 33 | imgs: [{ src: imgs['0.png'], width: '50%', top: '12%' }], 34 | }, 35 | { 36 | x: 1, y: 2, 37 | fonts: [{ text: '1个星球币', top: '68%' }], 38 | imgs: [{ src: imgs['1.png'], width: '50%', top: '12%' }], 39 | }, 40 | { 41 | x: 0, y: 2, 42 | fonts: [{ text: '5个星球币', top: '68%' }], 43 | imgs: [{ src: imgs['1.png'], width: '50%', top: '12%' }], 44 | }, 45 | { 46 | x: 0, y: 1, 47 | fonts: [{ text: '10个星球币', top: '68%' }], 48 | imgs: [{ src: imgs['1.png'], width: '50%', top: '12%' }], 49 | }, 50 | ], 51 | buttons: [{ 52 | x: 1, y: 1, 53 | imgs: [{ src: imgs['button.png'], width: '100%', height: '100%' }] 54 | }], 55 | defaultConfig: { 56 | gutter: 8 57 | }, 58 | defaultStyle: { 59 | background: '#f4f7cc', 60 | borderRadius: '13px', 61 | fontColor: '#d55a07', 62 | fontSize: '11px', 63 | shadow: '0 9 0.1 #E9E967', 64 | }, 65 | activeStyle: { 66 | background: '#e9e967', 67 | shadow: '0 9 0.1 #E1E13B', 68 | } 69 | }, 70 | onReady () { 71 | }, 72 | gridStart () { 73 | // 获取抽奖组件实例 74 | const child = this.selectComponent('#lucky-grid') 75 | // 调用play方法开始旋转 76 | child.play() 77 | // 用定时器模拟请求接口 78 | setTimeout(() => { 79 | // 3s 后得到中奖索引 80 | const index = Math.random() * 6 >> 0 81 | // 调用stop方法然后缓慢停止 82 | child.stop(index) 83 | }, 3000) 84 | }, 85 | gridEnd (event) { 86 | // 中奖奖品详情 87 | wx.showModal({ 88 | title: '提示', 89 | content: '恭喜你获得大奖: ' + event.detail.fonts[0].text, 90 | success (res) {} 91 | }) 92 | } 93 | }) 94 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/xdf/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/xdf/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/xdf/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/mini/tools/demo/pages/xdf/index.wxss -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/ymc/index.js: -------------------------------------------------------------------------------- 1 | import imgs from './img' 2 | Page({ 3 | data: { 4 | maskShow: false, 5 | prizes: [], 6 | defaultStyle: { 7 | fontColor: '#ff625b', 8 | fontSize: '16px' 9 | }, 10 | blocks: [ 11 | { padding: '20rpx', background: '#ffc27a' }, 12 | { padding: '10rpx', background: '#ff4a4c' }, 13 | ], 14 | buttons: [ 15 | { radius: '50px', background: '#ff4a4c' }, 16 | { radius: '45px', background: '#fff' }, 17 | { radius: '41px', background: '#f6c66f', pointer: true }, 18 | { 19 | radius: '35px', background: '#ffdea0', 20 | imgs: [{ src: imgs['button.png'], width: '70%', top: '-55%' }] 21 | } 22 | ], 23 | }, 24 | onReady () { 25 | this.setData({ 26 | prizes: [] 27 | }); 28 | setTimeout(() => { 29 | [ 30 | { 31 | title: '1元红包', background: '#ffd185', fonts: [{ text: '1元红包', top: '10%' }], 32 | imgs: [{ src: imgs['0.png'], width: '35%', top: '35%' }] 33 | }, 34 | { 35 | title: '100元红包', background: '#f9e3bb', fonts: [{ text: '100元红包', top: '10%' }], 36 | imgs: [{ src: imgs['1.png'], width: '35%', top: '35%' }] 37 | }, 38 | { 39 | title: '0.5元红包', background: '#ffd185', fonts: [{ text: '0.5元红包', top: '10%' }], 40 | imgs: [{ src: imgs['2.png'], width: '35%', top: '35%' }] 41 | }, 42 | { 43 | title: '2元红包', background: '#f9e3bb', fonts: [{ text: '2元红包', top: '10%' }], 44 | imgs: [{ src: imgs['3.png'], width: '35%', top: '35%' }] 45 | }, 46 | { 47 | title: '10元红包', background: '#ffd185', fonts: [{ text: '10元红包', top: '10%' }], 48 | imgs: [{ src: imgs['4.png'], width: '35%', top: '35%' }] 49 | }, 50 | { 51 | title: '50元红包', background: '#f9e3bb', fonts: [{ text: '50元红包', top: '10%' }], 52 | imgs: [{ src: imgs['5.png'], width: '35%', top: '35%' }] 53 | }, 54 | { 55 | title: '0.3元红包', background: '#ffd185', fonts: [{ text: '0.3元红包', top: '10%' }], 56 | imgs: [{ src: imgs['6.png'], width: '35%', top: '35%' }] 57 | }, 58 | { 59 | title: '5元红包', background: '#f9e3bb', fonts: [{ text: '5元红包', top: '10%' }], 60 | imgs: [{ src: imgs['7.png'], width: '35%', top: '35%' }] 61 | }, 62 | ].forEach(_ => { 63 | this.setData({ 64 | prizes: [...this.data.prizes, _] 65 | }); 66 | }) 67 | }, 50); 68 | }, 69 | wheelStart () { 70 | // 获取抽奖组件实例 71 | const child = this.selectComponent('#lucky-wheel') 72 | // 调用play方法开始旋转 73 | child.play() 74 | // 用定时器模拟请求接口 75 | setTimeout(() => { 76 | // 3s 后得到中奖索引 77 | const index = Math.random() * 6 >> 0 78 | // 调用stop方法然后缓慢停止 79 | child.stop(index) 80 | }, 3000) 81 | }, 82 | wheelEnd (event) { 83 | // 中奖奖品详情 84 | this.setData({ maskShow: true }) 85 | wx.showModal({ 86 | title: '提示', 87 | content: '恭喜你获得大奖: ' + event.detail.title, 88 | success (res) {} 89 | }) 90 | }, 91 | maskHidden () { 92 | this.setData({ maskShow: false }) 93 | }, 94 | }) -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/ymc/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/ymc/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/ymc/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/mini/tools/demo/pages/ymc/index.wxss -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yx/index.js: -------------------------------------------------------------------------------- 1 | import imgs from './img' 2 | Page({ 3 | data: { 4 | prizes: [], 5 | blocks: [ 6 | { padding: '1px', background: '#e2cea3', borderRadius: '13px' }, 7 | { padding: '5px 0px', background: '#f3ecdc', borderRadius: '13px' }, 8 | { padding: '1px', background: '#e2cea3', borderRadius: '8px' }, 9 | { padding: '15px 10px', background: '#fffcf5', borderRadius: '8px' }, 10 | ], 11 | buttons: [{ 12 | x: 1, y: 1, background: 'rgba(0, 0, 0, 0)', 13 | imgs: [ 14 | { src: imgs['btn.png'], width: '90%', top: '5%' } 15 | ] 16 | }], 17 | defaultStyle: { 18 | background: '#ffefd6', 19 | borderRadius: '5px', 20 | fontColor: '#755c28', 21 | fontSize: '10px', 22 | lineHeight: '12px' 23 | }, 24 | activeStyle: { 25 | background: '#de7247', 26 | fontColor: '#ffefd6', 27 | } 28 | }, 29 | onReady () { 30 | // 模拟接口异步请求奖品列表 31 | this.getPrizesList() 32 | }, 33 | getPrizesList () { 34 | const prizes = [] 35 | let axis = [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [1, 2], [0, 2], [0, 1]] 36 | let data = ['电热烘干毛巾架', '10元满减红包', '2积分', '胖喵焖烧罐', '5元满减红包', '多层置物架', '3元直减红包', '全场满99减10'] 37 | axis.forEach((item, index) => { 38 | prizes.push({ 39 | x: item[0], y: item[1], 40 | title: data[index], 41 | imgs: [{ 42 | width: '100%', 43 | height: '100%', 44 | src: imgs[`default-${index}.png`], 45 | activeSrc: imgs[`active-${index}.png`] 46 | }] 47 | }) 48 | }) 49 | this.setData({ prizes }) 50 | }, 51 | gridStart () { 52 | // 获取抽奖组件实例 53 | const child = this.selectComponent('#lucky-grid') 54 | // 调用play方法开始旋转 55 | child.play() 56 | // 用定时器模拟请求接口 57 | setTimeout(() => { 58 | // 3s 后得到中奖索引 59 | const index = Math.random() * 6 >> 0 60 | // 调用stop方法然后缓慢停止 61 | child.stop(index) 62 | }, 3000) 63 | }, 64 | gridEnd (event) { 65 | // 中奖奖品详情 66 | wx.showModal({ 67 | title: '提示', 68 | content: '恭喜你获得大奖: ' + event.detail.title, 69 | success (res) {} 70 | }) 71 | } 72 | }) 73 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yx/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yx/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yx/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/mini/tools/demo/pages/yx/index.wxss -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yyjk/index.js: -------------------------------------------------------------------------------- 1 | import imgs from '../ymc/img' 2 | Page({ 3 | data: { 4 | maskShow: false, 5 | prizes: [], 6 | blocks: [ 7 | { padding: '15px', background: '#ffc27a', borderRadius: 28 }, 8 | { padding: '4px', background: '#ff4a4c', borderRadius: 23 }, 9 | { padding: '4px', background: '#ff625b', borderRadius: 20 }, 10 | ], 11 | buttons: [{ 12 | x: 1, y: 1, 13 | background: 'linear-gradient(270deg, #FFDCB8, #FDC689)', 14 | shadow: '0 5 1 #e89b4f', 15 | imgs: [ 16 | { src: imgs['button.png'], width: '70%', top: '20%' }, 17 | ] 18 | }], 19 | activeStyle: { 20 | background: 'linear-gradient(270deg, #FFDCB8, #FDC689)', 21 | shadow: '' 22 | }, 23 | defaultConfig: { 24 | gutter: 5, 25 | }, 26 | defaultStyle: { 27 | borderRadius: 15, 28 | fontColor: '#DF424B', 29 | fontSize: '14px', 30 | textAlign: 'center', 31 | background: '#fff', 32 | shadow: '0 5 1 #ebf1f4' 33 | }, 34 | }, 35 | onReady () { 36 | // 模拟接口异步请求奖品列表 37 | const prizes = [] 38 | const data = [ 39 | { name: '1元红包', img: imgs['0.png'] }, 40 | { name: '100元红包', img: imgs['1.png'] }, 41 | { name: '0.5元红包', img: imgs['2.png'] }, 42 | { name: '2元红包', img: imgs['3.png'] }, 43 | { name: '10元红包', img: imgs['4.png'] }, 44 | { name: '50元红包', img: imgs['5.png'] }, 45 | { name: '0.3元红包', img: imgs['6.png'] }, 46 | { name: '5元红包', img: imgs['7.png'] } 47 | ] 48 | let axis = [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [1, 2], [0, 2], [0, 1]] 49 | for (let i = 0; i < 8; i++) { 50 | let item = data[i] 51 | prizes.push({ 52 | name: item.name, 53 | index: i, x: axis[i][0], y: axis[i][1], 54 | fonts: [{ text: item.name, top: '70%' }], 55 | imgs: [{ src: item.img, width: '53%', top: '8%' }] 56 | }) 57 | } 58 | this.setData({ 59 | prizes 60 | }) 61 | }, 62 | gridStart () { 63 | // 获取抽奖组件实例 64 | const child = this.selectComponent('#lucky-grid') 65 | // 调用play方法开始旋转 66 | child.play() 67 | // 用定时器模拟请求接口 68 | setTimeout(() => { 69 | // 3s 后得到中奖索引 70 | const index = Math.random() * 6 >> 0 71 | // 调用stop方法然后缓慢停止 72 | child.stop(index) 73 | }, 3000) 74 | }, 75 | gridEnd (event) { 76 | this.setData({ maskShow: true }) 77 | // 中奖奖品详情 78 | wx.showModal({ 79 | title: '提示', 80 | content: '恭喜你获得大奖: ' + event.detail.name, 81 | success (res) {} 82 | }) 83 | }, 84 | maskHidden () { 85 | this.setData({ maskShow: false }) 86 | }, 87 | }) -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yyjk/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yyjk/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yyjk/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/mini/tools/demo/pages/yyjk/index.wxss -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yyx/index.js: -------------------------------------------------------------------------------- 1 | import imgs from './img' 2 | Page({ 3 | data: { 4 | prizes: [], 5 | blocks: [ 6 | { padding: '1px', background: '#192b2c', borderRadius: '10px' }, 7 | { padding: '2px', background: '#316d70', borderRadius: '10px' }, 8 | { padding: '5px', background: '#183233', borderRadius: '10px' }, 9 | ], 10 | buttons: [{ 11 | x: 1, y: 1, background: 'rgba(0, 0, 0, 0)', 12 | fonts: [ 13 | { text: '剩余次数:???次', fontColor: '#ca620d', top: '75%' } 14 | ], 15 | imgs: [ 16 | { src: imgs['btn.png'], width: '100%', top: '0' } 17 | ] 18 | }], 19 | defaultStyle: { 20 | fontColor: '#AFFFD7', 21 | fontSize: '10px', 22 | lineHeight: '13px', 23 | wordWrap: false 24 | }, 25 | activeStyle: { 26 | fontColor: '#fff', 27 | } 28 | }, 29 | onReady () { 30 | // 模拟接口异步请求奖品列表 31 | this.getPrizesList() 32 | }, 33 | getPrizesList () { 34 | const prizes = [] 35 | let axis = [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [1, 2], [0, 2], [0, 1]] 36 | let data = [ 37 | { name: 'steam游戏任选\n(限款)', img: imgs['1.png'], top: '60%' }, 38 | { name: '百元京东卡', img: imgs['2.png'], top: '69%' }, 39 | { name: '暴雪游戏30点数', img: imgs['3.png'], top: '69%' }, 40 | { name: '云币2888', img: imgs['4.png'], top: '69%' }, 41 | { name: '手游月卡', img: imgs['5.png'], top: '69%' }, 42 | { name: '连续3日\n手游时长+1h', img: imgs['6.png'], top: '60%' }, 43 | { name: '手游时长+30min', img: imgs['6.png'], top: '69%' }, 44 | { name: '端游时长+1h', img: imgs['7.png'], top: '69%' }, 45 | ] 46 | data.forEach((item, index) => { 47 | prizes.push({ 48 | x: axis[index][0], y: axis[index][1], 49 | title: item.name, 50 | fonts: [{ text: item.name, top: item.top }], 51 | imgs: [ 52 | { 53 | src: imgs['cell.png'], 54 | activeSrc: imgs['active.png'], 55 | width: '100%', 56 | height: '100%', 57 | }, 58 | { 59 | src: item.img, 60 | width: '70%', 61 | top: '3%' 62 | } 63 | ] 64 | }) 65 | }) 66 | this.setData({ prizes }) 67 | }, 68 | gridStart () { 69 | // 获取抽奖组件实例 70 | const child = this.selectComponent('#lucky-grid') 71 | // 调用play方法开始旋转 72 | child.play() 73 | // 用定时器模拟请求接口 74 | setTimeout(() => { 75 | // 3s 后得到中奖索引 76 | const index = Math.random() * 6 >> 0 77 | // 调用stop方法然后缓慢停止 78 | child.stop(index) 79 | }, 3000) 80 | }, 81 | gridEnd (event) { 82 | // 中奖奖品详情 83 | wx.showModal({ 84 | title: '提示', 85 | content: '恭喜你获得大奖: ' + event.detail.title, 86 | success (res) {} 87 | }) 88 | } 89 | }) 90 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yyx/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yyx/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | -------------------------------------------------------------------------------- /packages/mini/tools/demo/pages/yyx/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buuing/lucky-canvas/096b39ba753a6a4db51b0e4ecce140ebeff4e59b/packages/mini/tools/demo/pages/yyx/index.wxss -------------------------------------------------------------------------------- /packages/mini/tools/demo/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件。", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": true, 9 | "postcss": true, 10 | "minified": true, 11 | "newFeature": true, 12 | "nodeModules": true 13 | }, 14 | "compileType": "miniprogram", 15 | "libVersion": "2.10.4", 16 | "appid": "", 17 | "projectname": "miniprogram-demo", 18 | "isGameTourist": false, 19 | "condition": { 20 | "search": { 21 | "current": -1, 22 | "list": [] 23 | }, 24 | "conversation": { 25 | "current": -1, 26 | "list": [] 27 | }, 28 | "game": { 29 | "currentL": -1, 30 | "list": [] 31 | }, 32 | "miniprogram": { 33 | "current": -1, 34 | "list": [] 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /packages/mini/tools/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | // eslint-disable-next-line no-unused-vars 5 | const colors = require('colors') 6 | const through = require('through2') 7 | 8 | /** 9 | * 异步函数封装 10 | */ 11 | function wrap(func, scope) { 12 | return function (...args) { 13 | if (args.length) { 14 | const temp = args.pop() 15 | if (typeof temp !== 'function') { 16 | args.push(temp) 17 | } 18 | } 19 | 20 | return new Promise(function (resolve, reject) { 21 | args.push(function (err, data) { 22 | if (err) reject(err) 23 | else resolve(data) 24 | }) 25 | 26 | func.apply((scope || null), args) 27 | }) 28 | } 29 | } 30 | 31 | const accessSync = wrap(fs.access) 32 | const statSync = wrap(fs.stat) 33 | const renameSync = wrap(fs.rename) 34 | const mkdirSync = wrap(fs.mkdir) 35 | const readFileSync = wrap(fs.readFile) 36 | const writeFileSync = wrap(fs.writeFile) 37 | 38 | /** 39 | * 调整路径分隔符 40 | */ 41 | function transformPath(filePath, sep = '/') { 42 | return filePath.replace(/[\\/]/g, sep) 43 | } 44 | 45 | /** 46 | * 检查文件是否存在 47 | */ 48 | async function checkFileExists(filePath) { 49 | try { 50 | await accessSync(filePath) 51 | return true 52 | } catch (err) { 53 | return false 54 | } 55 | } 56 | 57 | /** 58 | * 递归创建目录 59 | */ 60 | async function recursiveMkdir(dirPath) { 61 | const prevDirPath = path.dirname(dirPath) 62 | try { 63 | await accessSync(prevDirPath) 64 | } catch (err) { 65 | // 上一级目录不存在 66 | await recursiveMkdir(prevDirPath) 67 | } 68 | 69 | try { 70 | await accessSync(dirPath) 71 | 72 | const stat = await statSync(dirPath) 73 | if (stat && !stat.isDirectory()) { 74 | // 目标路径存在,但不是目录 75 | await renameSync(dirPath, `${dirPath}.bak`) // 将此文件重命名为 .bak 后缀 76 | await mkdirSync(dirPath) 77 | } 78 | } catch (err) { 79 | // 目标路径不存在 80 | await mkdirSync(dirPath) 81 | } 82 | } 83 | 84 | /** 85 | * 读取 json 86 | */ 87 | function readJson(filePath) { 88 | try { 89 | // eslint-disable-next-line import/no-dynamic-require 90 | const content = require(filePath) 91 | delete require.cache[require.resolve(filePath)] 92 | return content 93 | } catch (err) { 94 | return null 95 | } 96 | } 97 | 98 | /** 99 | * 读取文件 100 | */ 101 | async function readFile(filePath) { 102 | try { 103 | return await readFileSync(filePath, 'utf8') 104 | } catch (err) { 105 | // eslint-disable-next-line no-console 106 | return console.error(err) 107 | } 108 | } 109 | 110 | /** 111 | * 写文件 112 | */ 113 | async function writeFile(filePath, data) { 114 | try { 115 | await recursiveMkdir(path.dirname(filePath)) 116 | return await writeFileSync(filePath, data, 'utf8') 117 | } catch (err) { 118 | // eslint-disable-next-line no-console 119 | return console.error(err) 120 | } 121 | } 122 | 123 | /** 124 | * 时间格式化 125 | */ 126 | function format(time, reg) { 127 | const date = typeof time === 'string' ? new Date(time) : time 128 | const map = {} 129 | map.yyyy = date.getFullYear() 130 | map.yy = ('' + map.yyyy).substr(2) 131 | map.M = date.getMonth() + 1 132 | map.MM = (map.M < 10 ? '0' : '') + map.M 133 | map.d = date.getDate() 134 | map.dd = (map.d < 10 ? '0' : '') + map.d 135 | map.H = date.getHours() 136 | map.HH = (map.H < 10 ? '0' : '') + map.H 137 | map.m = date.getMinutes() 138 | map.mm = (map.m < 10 ? '0' : '') + map.m 139 | map.s = date.getSeconds() 140 | map.ss = (map.s < 10 ? '0' : '') + map.s 141 | 142 | return reg.replace(/\byyyy|yy|MM|M|dd|d|HH|H|mm|m|ss|s\b/g, $1 => map[$1]) 143 | } 144 | 145 | /** 146 | * 日志插件 147 | */ 148 | function logger(action = 'copy') { 149 | return through.obj(function (file, enc, cb) { 150 | const type = path.extname(file.path).slice(1).toLowerCase() 151 | 152 | // eslint-disable-next-line no-console 153 | console.log(`[${format(new Date(), 'yyyy-MM-dd HH:mm:ss').grey}] [${action.green} ${type.green}] ${'=>'.cyan} ${file.path}`) 154 | 155 | this.push(file) 156 | cb() 157 | }) 158 | } 159 | 160 | /** 161 | * 比较数组是否相等 162 | */ 163 | function compareArray(arr1, arr2) { 164 | if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false 165 | if (arr1.length !== arr2.length) return false 166 | 167 | for (let i = 0, len = arr1.length; i < len; i++) { 168 | if (arr1[i] !== arr2[i]) return false 169 | } 170 | 171 | return true 172 | } 173 | 174 | /** 175 | * 合并两个对象 176 | */ 177 | function merge(obj1, obj2) { 178 | Object.keys(obj2).forEach(key => { 179 | if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) { 180 | obj1[key] = obj1[key].concat(obj2[key]) 181 | } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') { 182 | obj1[key] = Object.assign(obj1[key], obj2[key]) 183 | } else { 184 | obj1[key] = obj2[key] 185 | } 186 | }) 187 | 188 | return obj1 189 | } 190 | 191 | /** 192 | * 获取 id 193 | */ 194 | let seed = +new Date() 195 | function getId() { 196 | return ++seed 197 | } 198 | 199 | module.exports = { 200 | wrap, 201 | transformPath, 202 | 203 | checkFileExists, 204 | readJson, 205 | readFile, 206 | writeFile, 207 | 208 | logger, 209 | format, 210 | compareArray, 211 | merge, 212 | getId, 213 | } 214 | -------------------------------------------------------------------------------- /packages/mini/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es2015", 5 | "lib": ["es2015", "es2017", "dom"], 6 | "noImplicitAny": false, 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "baseUrl": ".", 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata":true, 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true 15 | }, 16 | "files": [ 17 | "node_modules/miniprogram-api-typings/index.d.ts" 18 | ], 19 | "include": [ 20 | "src/**/*.ts" 21 | ], 22 | "exclude": [ 23 | "node_modules" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /packages/react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ] 6 | } -------------------------------------------------------------------------------- /packages/react/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | yarn.lock 4 | package-lock.json 5 | dist 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /packages/react/.npmignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /packages/react/README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | logo 4 |

lucky-canvas 抽奖插件

5 |

一个基于 JavaScript 的跨平台 ( 大转盘 / 九宫格 / 老虎机 ) 抽奖插件

6 |

7 | 8 | stars 9 | 10 | 11 | forks 12 | 13 | 14 | author 15 | 16 | 17 | license 18 | 19 |

20 |
21 | 22 |
23 | 24 | ## 官方文档 25 | 26 | > **中文**:[https://100px.net](https://100px.net) 27 | 28 |
29 | 30 | ## 在 React 中使用 31 | 32 | - [跳转官网 查看详情](https://100px.net/usage/react.html) 33 | 34 |
35 | 36 | ## 🙏🙏🙏 点个Star 37 | 38 | **如果您觉得这个项目还不错, 可以在 [Github](https://github.com/buuing/lucky-canvas) 上面帮我点个`star`, 支持一下作者 ☜(゚ヮ゚☜)** 39 | 40 |
41 | -------------------------------------------------------------------------------- /packages/react/example/react16.8.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Lucky-canva-react Demo 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 |
20 | 21 | 73 | 74 | -------------------------------------------------------------------------------- /packages/react/example/react18.0.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Lucky-canva-react Demo 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 |
20 | 21 | 74 | 75 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lucky-canvas/react", 3 | "version": "0.1.13", 4 | "description": "react ( 大转盘 / 九宫格 / 老虎机 ) 抽奖插件", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.esm.js", 7 | "unpkg": "dist/index.umd.js", 8 | "jsdelivr": "dist/index.umd.js", 9 | "types": "types/index.d.ts", 10 | "scripts": { 11 | "dev": "rollup --config rollup.config.dev.js -w", 12 | "build": "rollup --config rollup.config.build.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/LuckDraw/lucky-canvas.git" 17 | }, 18 | "keywords": [ 19 | "react抽奖插件", 20 | "大转盘抽奖", 21 | "九宫格抽奖", 22 | "老虎机抽奖" 23 | ], 24 | "author": "ldq ", 25 | "license": "Apache-2.0", 26 | "bugs": { 27 | "url": "https://github.com/LuckDraw/lucky-canvas/issues" 28 | }, 29 | "homepage": "https://100px.net", 30 | "files": [ 31 | "dist", 32 | "types" 33 | ], 34 | "devDependencies": { 35 | "@babel/core": "^7.12.10", 36 | "@babel/preset-env": "^7.12.11", 37 | "@babel/preset-react": "^7.12.10", 38 | "@rollup/plugin-commonjs": "^17.0.0", 39 | "@rollup/plugin-json": "^4.1.0", 40 | "@rollup/plugin-node-resolve": "^11.1.0", 41 | "rollup": "^2.38.0", 42 | "rollup-plugin-babel": "^4.4.0", 43 | "rollup-plugin-livereload": "^2.0.5", 44 | "rollup-plugin-serve": "^1.1.0", 45 | "rollup-plugin-terser": "^7.0.2" 46 | }, 47 | "dependencies": { 48 | "lucky-canvas": "^1.7.27" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/react/rollup.config.build.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' 2 | import json from '@rollup/plugin-json' 3 | import resolve from '@rollup/plugin-node-resolve' 4 | import commonjs from '@rollup/plugin-commonjs' 5 | import babel from 'rollup-plugin-babel' 6 | import { terser } from 'rollup-plugin-terser' 7 | 8 | export default { 9 | input: './src/index.js', 10 | output: [ 11 | { 12 | file: pkg.main, 13 | format: 'cjs', 14 | sourcemap: true, 15 | }, 16 | { 17 | file: pkg.module, 18 | format: 'esm', 19 | sourcemap: true, 20 | }, 21 | { 22 | file: pkg.jsdelivr, 23 | format: 'umd', 24 | name: 'ReactLuckyCanvas', 25 | sourcemap: true, 26 | globals: { 27 | 'lucky-canvas': 'LuckyCanvas', 28 | }, 29 | }, 30 | ], 31 | plugins: [ 32 | babel(), 33 | resolve(), 34 | commonjs(), 35 | json(), 36 | terser(), 37 | ], 38 | external: ['react'], 39 | } 40 | -------------------------------------------------------------------------------- /packages/react/rollup.config.dev.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' 2 | import json from '@rollup/plugin-json' 3 | import resolve from '@rollup/plugin-node-resolve' 4 | import commonjs from '@rollup/plugin-commonjs' 5 | import babel from 'rollup-plugin-babel' 6 | import livereload from 'rollup-plugin-livereload' 7 | import serve from 'rollup-plugin-serve' 8 | 9 | export default { 10 | input: './src/app.js', 11 | output: [ 12 | { 13 | file: pkg.module, 14 | format: 'esm', 15 | sourcemap: true, 16 | }, 17 | { 18 | file: pkg.jsdelivr, 19 | format: 'umd', 20 | name: 'ReactLuckyCanvas', 21 | sourcemap: true, 22 | globals: { 23 | 'lucky-canvas': 'LuckyCanvas', 24 | }, 25 | }, 26 | ], 27 | plugins: [ 28 | babel({ 29 | exclude: "node_modules/**" 30 | }), 31 | resolve(), 32 | commonjs(), 33 | json(), 34 | livereload(), 35 | serve({ 36 | open: true, 37 | port: 8000, 38 | contentBase: './', 39 | openPage: '/example/react16.8.html', 40 | }), 41 | ], 42 | external: ['react'], 43 | } 44 | -------------------------------------------------------------------------------- /packages/react/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import LuckyWheel from './demo/LuckyWheel'; 4 | import LuckyGrid from './demo/LuckyGrid'; 5 | import SlotMachine from './demo/SlotMachine'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | -------------------------------------------------------------------------------- /packages/react/src/components/LuckyGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { LuckyGrid as Grid } from 'lucky-canvas' 3 | import { name, version } from '../../package.json' 4 | 5 | export default class LuckyGrid extends React.Component { 6 | constructor (props) { 7 | super(props) 8 | this.myLucky = React.createRef() 9 | this.lucky = undefined 10 | } 11 | componentDidMount () { 12 | // 当前已经有实例时,不再进行,解决在`react18 + strictMode`下组件componentDidMount会被触发两次的问题 13 | if (this.lucky) { 14 | return 15 | } 16 | this.myLucky.current.setAttribute('package', `${name}@${version}`) 17 | try { 18 | this.initLucky() 19 | this.props.onSuccess && this.props.onSuccess() 20 | } catch (err) { 21 | this.props.onError && this.props.onError(err) 22 | } finally { 23 | this.props.onFinally && this.props.onFinally(err) 24 | } 25 | } 26 | componentDidUpdate (prevProps) { 27 | if (!this.lucky) return 28 | if (this.props.width !== prevProps.width) { 29 | this.lucky.width = this.props.width 30 | } 31 | if (this.props.height !== prevProps.height) { 32 | this.lucky.height = this.props.height 33 | } 34 | if (this.props.cols !== prevProps.cols) { 35 | this.lucky.cols = this.props.cols 36 | } 37 | if (this.props.rows !== prevProps.rows) { 38 | this.lucky.rows = this.props.rows 39 | } 40 | if (this.props.blocks !== prevProps.blocks) { 41 | this.lucky.blocks = this.props.blocks 42 | } 43 | if (this.props.prizes !== prevProps.prizes) { 44 | this.lucky.prizes = this.props.prizes 45 | } 46 | if (this.props.buttons !== prevProps.buttons) { 47 | this.lucky.buttons = this.props.buttons 48 | } 49 | } 50 | initLucky () { 51 | this.lucky = new Grid({ 52 | flag: 'WEB', 53 | divElement: this.myLucky.current 54 | }, { 55 | ...this.props, 56 | start: (...rest) => { 57 | this.props.onStart && this.props.onStart(...rest) 58 | }, 59 | end: (...rest) => { 60 | this.props.onEnd && this.props.onEnd(...rest) 61 | } 62 | }) 63 | } 64 | init (...rest) { 65 | this.lucky.init(...rest) 66 | } 67 | play (...rest) { 68 | this.lucky.play(...rest) 69 | } 70 | stop (...rest) { 71 | this.lucky.stop(...rest) 72 | } 73 | render () { 74 | return
75 | } 76 | } 77 | 78 | LuckyGrid.defaultProps = { 79 | width: '', 80 | height: '', 81 | cols: 3, 82 | rows: 3, 83 | blocks: [], 84 | prizes: [], 85 | buttons: [], 86 | defaultStyle: {}, 87 | activeStyle: {}, 88 | defaultConfig: {}, 89 | } 90 | -------------------------------------------------------------------------------- /packages/react/src/components/LuckyWheel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { LuckyWheel as Wheel } from 'lucky-canvas' 3 | import { name, version } from '../../package.json' 4 | 5 | export default class LuckyWheel extends React.Component { 6 | constructor (props) { 7 | super(props) 8 | this.myLucky = React.createRef() 9 | this.lucky = undefined 10 | } 11 | componentDidMount () { 12 | // 当前已经有实例时,不再进行,解决在`react18 + strictMode`下组件componentDidMount会被触发两次的问题 13 | if (this.lucky) { 14 | return 15 | } 16 | this.myLucky.current.setAttribute('package', `${name}@${version}`) 17 | try { 18 | this.initLucky() 19 | this.props.onSuccess && this.props.onSuccess() 20 | } catch (err) { 21 | this.props.onError && this.props.onError(err) 22 | } finally { 23 | this.props.onFinally && this.props.onFinally() 24 | } 25 | } 26 | componentDidUpdate (prevProps) { 27 | if (!this.lucky) return 28 | if (this.props.width !== prevProps.width) { 29 | this.lucky.width = this.props.width 30 | } 31 | if (this.props.height !== prevProps.height) { 32 | this.lucky.height = this.props.height 33 | } 34 | if (this.props.blocks !== prevProps.blocks) { 35 | this.lucky.blocks = this.props.blocks 36 | } 37 | if (this.props.prizes !== prevProps.prizes) { 38 | this.lucky.prizes = this.props.prizes 39 | } 40 | if (this.props.buttons !== prevProps.buttons) { 41 | this.lucky.buttons = this.props.buttons 42 | } 43 | } 44 | initLucky () { 45 | this.lucky = new Wheel({ 46 | flag: 'WEB', 47 | divElement: this.myLucky.current 48 | }, { 49 | ...this.props, 50 | start: (...rest) => { 51 | this.props.onStart && this.props.onStart(...rest) 52 | }, 53 | end: (...rest) => { 54 | this.props.onEnd && this.props.onEnd(...rest) 55 | } 56 | }) 57 | } 58 | init (...rest) { 59 | this.lucky.init(...rest) 60 | } 61 | play (...rest) { 62 | this.lucky.play(...rest) 63 | } 64 | stop (...rest) { 65 | this.lucky.stop(...rest) 66 | } 67 | render () { 68 | return
69 | } 70 | } 71 | 72 | LuckyWheel.defaultProps = { 73 | width: '', 74 | height: '', 75 | prizes: [], 76 | blocks: [], 77 | buttons: [], 78 | defaultStyle: {}, 79 | defaultConfig: {}, 80 | } 81 | -------------------------------------------------------------------------------- /packages/react/src/components/SlotMachine.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SlotMachine as Slot } from 'lucky-canvas' 3 | import { name, version } from '../../package.json' 4 | 5 | export default class SlotMachine extends React.Component { 6 | constructor (props) { 7 | super(props) 8 | this.myLucky = React.createRef() 9 | this.lucky = undefined 10 | } 11 | componentDidMount () { 12 | // 当前已经有实例时,不再进行,解决在`react18 + strictMode`下组件componentDidMount会被触发两次的问题 13 | if (this.lucky) { 14 | return 15 | } 16 | this.myLucky.current.setAttribute('package', `${name}@${version}`) 17 | try { 18 | this.initLucky() 19 | this.props.onSuccess && this.props.onSuccess() 20 | } catch (err) { 21 | this.props.onError && this.props.onError(err) 22 | } finally { 23 | this.props.onFinally && this.props.onFinally(err) 24 | } 25 | } 26 | componentDidUpdate (prevProps) { 27 | if (!this.lucky) return 28 | if (this.props.width !== prevProps.width) { 29 | this.lucky.width = this.props.width 30 | } 31 | if (this.props.height !== prevProps.height) { 32 | this.lucky.height = this.props.height 33 | } 34 | if (this.props.blocks !== prevProps.blocks) { 35 | this.lucky.blocks = this.props.blocks 36 | } 37 | if (this.props.prizes !== prevProps.prizes) { 38 | this.lucky.prizes = this.props.prizes 39 | } 40 | if (this.props.slots !== prevProps.slots) { 41 | this.lucky.slots = this.props.slots 42 | } 43 | } 44 | initLucky () { 45 | this.lucky = new Slot({ 46 | flag: 'WEB', 47 | divElement: this.myLucky.current 48 | }, { 49 | ...this.props, 50 | start: (...rest) => { 51 | this.props.onStart && this.props.onStart(...rest) 52 | }, 53 | end: (...rest) => { 54 | this.props.onEnd && this.props.onEnd(...rest) 55 | } 56 | }) 57 | } 58 | init (...rest) { 59 | this.lucky.init(...rest) 60 | } 61 | play (...rest) { 62 | this.lucky.play(...rest) 63 | } 64 | stop (...rest) { 65 | this.lucky.stop(...rest) 66 | } 67 | render () { 68 | return
69 | } 70 | } 71 | 72 | SlotMachine.defaultProps = { 73 | width: '', 74 | height: '', 75 | blocks: [], 76 | prizes: [], 77 | slots: [], 78 | defaultStyle: {}, 79 | defaultConfig: {}, 80 | } 81 | -------------------------------------------------------------------------------- /packages/react/src/demo/LuckyGrid.js: -------------------------------------------------------------------------------- 1 | import LuckyGrid from '../components/LuckyGrid.js' 2 | import React from 'react' 3 | 4 | export default class GridDemo extends React.Component { 5 | constructor () { 6 | super() 7 | this.myLucky = React.createRef() 8 | 9 | const data = [ 10 | { name: '1元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 11 | { name: '100元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 12 | { name: '0.5元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 13 | { name: '2元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 14 | { name: '10元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 15 | { name: '50元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 16 | { name: '0.3元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 17 | { name: '5元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' } 18 | ] 19 | let axis = [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [1, 2], [0, 2], [0, 1]] 20 | const prizes = [] 21 | for (let i = 0; i < 8; i++) { 22 | let item = data[i] 23 | prizes.push({ 24 | name: item.name, 25 | index: i, x: axis[i][0], y: axis[i][1], 26 | fonts: [{ text: item.name, top: '70%' }], 27 | imgs: [{ src: item.img, width: '53%', top: '8%' }] 28 | }) 29 | } 30 | this.state = { 31 | blocks: [ 32 | { padding: '10px', background: '#ffc27a' }, 33 | { padding: '10px', paddingRight: '90px', background: '#ff4a4c' }, 34 | { padding: '0px', background: '#fff' } 35 | ], 36 | prizes, 37 | activeStyle: { 38 | background: 'linear-gradient(270deg, #FFDCB8, #FDC689)', 39 | shadow: '' 40 | }, 41 | buttons: [{ 42 | x: 1, y: 1, 43 | background: 'linear-gradient(270deg, #FFDCB8, #FDC689)', 44 | shadow: '0 5 1 #e89b4f', 45 | fonts: [ 46 | { text: `1 次`, fontColor: '#fff', top: '73%', fontSize: '11px' }, 47 | ], 48 | imgs: [ 49 | // { src: 'https://100px.net/assets/img/button.2f4ac3e9.png', width: '65%', top: '12%' }, 50 | // { src: './img/btn.png', width: '50%', top: '73%' } 51 | ] 52 | } 53 | ], 54 | defaultStyle: { 55 | borderRadius: 15, 56 | fontColor: '#DF424B', 57 | fontSize: '14px', 58 | textAlign: 'center', 59 | background: '#fff', 60 | shadow: '0 5 1 #ebf1f4' 61 | }, 62 | } 63 | } 64 | render () { 65 | return { 74 | this.myLucky.current.play() 75 | setTimeout(() => { 76 | const index = Math.random() * 6 >> 0 77 | this.myLucky.current.stop(index) 78 | }, 2500) 79 | }} 80 | onEnd={prize => { 81 | alert('恭喜获得大奖:' + prize.name) 82 | }} 83 | > 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/react/src/demo/LuckyWheel.js: -------------------------------------------------------------------------------- 1 | import LuckyWheel from '../components/LuckyWheel.js' 2 | import React from 'react' 3 | 4 | export default class Wheel extends React.Component { 5 | constructor () { 6 | super() 7 | this.myLucky = React.createRef() 8 | this.state = { 9 | currPrize: '哈哈哈', 10 | blocks: [ 11 | ], 12 | prizes: [ 13 | { title: '1元红包', background: '#f9e3bb', fonts: [{ text: '1元红包', top: '18%' }] }, 14 | { title: '100元红包', background: '#f8d384', fonts: [{ text: '100元红包', top: '18%' }] }, 15 | { title: '0.5元红包', background: '#f9e3bb', fonts: [{ text: '0.5元红包', top: '18%' }] }, 16 | { title: '2元红包', background: '#f8d384', fonts: [{ text: '2元红包', top: '18%' }] }, 17 | { title: '10元红包', background: '#f9e3bb', fonts: [{ text: '10元红包', top: '18%' }] }, 18 | { title: '50元红包', background: '#f8d384', fonts: [{ text: '50元红包', top: '18%' }] }, 19 | ], 20 | buttons: [ 21 | { 22 | radius: '35px', background: '#ffdea0', 23 | fonts: [{ text: '开始\n抽奖', fontSize: '18px', top: -18 }] 24 | } 25 | ], 26 | } 27 | } 28 | render () { 29 | return
30 | 当前中奖: {this.state.currPrize} 31 | { 40 | console.log(this.state.currPrize) 41 | this.myLucky.current.play() 42 | setTimeout(() => { 43 | const index = Math.random() * 6 >> 0 44 | this.myLucky.current.stop(index) 45 | }, 2500) 46 | }} 47 | onEnd={prize => { 48 | this.setState({ 49 | currPrize: prize.title 50 | }) 51 | }} 52 | > 53 |
54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/react/src/demo/SlotMachine.js: -------------------------------------------------------------------------------- 1 | import SlotMachine from '../components/SlotMachine.js' 2 | import React from 'react' 3 | 4 | export default class SlotDemo extends React.Component { 5 | constructor () { 6 | super() 7 | this.myLucky = React.createRef() 8 | 9 | const data = [ 10 | { name: '1元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 11 | { name: '100元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 12 | { name: '0.5元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 13 | { name: '2元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 14 | { name: '10元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 15 | { name: '50元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 16 | { name: '0.3元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' }, 17 | { name: '5元红包', img: 'https://unpkg.com/buuing@0.0.1/imgs/lucky-canvas.png' } 18 | ] 19 | let axis = [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [1, 2], [0, 2], [0, 1]] 20 | const prizes = [] 21 | for (let i = 0; i < 8; i++) { 22 | let item = data[i] 23 | prizes.push({ 24 | name: item.name, 25 | index: i, x: axis[i][0], y: axis[i][1], 26 | fonts: [{ text: item.name, top: '70%' }], 27 | imgs: [{ src: item.img, width: '100%', top: '0%' }] 28 | }) 29 | } 30 | this.state = { 31 | blocks: [ 32 | { padding: '10px', background: '#ffc27a' }, 33 | { padding: '10px', paddingRight: '90px', background: '#ff4a4c' }, 34 | { padding: '0px', background: '#fff' } 35 | ], 36 | prizes, 37 | slots: [ 38 | {}, 39 | {}, 40 | {}, 41 | ], 42 | defaultStyle: { 43 | borderRadius: 15, 44 | fontColor: '#DF424B', 45 | fontSize: '14px', 46 | textAlign: 'center', 47 | background: '#fff', 48 | shadow: '0 5 1 #ebf1f4' 49 | }, 50 | } 51 | } 52 | render () { 53 | return
54 | { 63 | this.myLucky.current.play() 64 | setTimeout(() => { 65 | const index = Math.random() * 6 >> 0 66 | this.myLucky.current.stop(index) 67 | }, 2500) 68 | }} 69 | onEnd={prize => { 70 | alert('恭喜获得大奖:' + prize.name) 71 | }} 72 | > 73 | 76 |
77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/react/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as LuckyWheel } from './components/LuckyWheel.js' 2 | export { default as LuckyGrid } from './components/LuckyGrid.js' 3 | export { default as SlotMachine } from './components/SlotMachine.js' 4 | -------------------------------------------------------------------------------- /packages/taro/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | yarn.lock 4 | package-lock.json 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /packages/taro/.npmignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /packages/taro/README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | logo 4 |

@lucky-canvas/taro 抽奖插件

5 |

一个基于 Taro 的 ( 大转盘 / 九宫格 / 老虎机 ) 抽奖插件

6 |

7 | 8 | stars 9 | 10 | 11 | forks 12 | 13 | 14 | author 15 | 16 | 17 | license 18 | 19 |

20 |
21 | 22 |
23 | 24 | ## 官方文档 25 | 26 | > **中文**:[https://100px.net/usage/taro.html](https://100px.net/usage/taro.html) 27 | 28 |
29 | 30 | ## 在 Taro 中使用 31 | 32 | ### 安装 33 | 34 | > 为了确保相关依赖安装正确, 你必须通过 npm / yarn 来安装 35 | 36 | ```shell 37 | # npm 安装: 38 | npm install @lucky-canvas/taro@latest 39 | 40 | # yarn 安装: 41 | yarn add @lucky-canvas/taro@latest 42 | ``` 43 | 44 | 45 |
46 | 47 | ### 使用 48 | 49 | #### taro-vue 简单示例 50 | 51 | - [点击查看 taro-vue 完整示例](https://100px.net/usage/taro.html) 52 | 53 | ```html 54 | 68 | 69 | 75 | ``` 76 | 77 |
78 | 79 | #### taro-react 简单示例 80 | 81 | - [点击查看 taro-react 完整示例](https://100px.net/usage/taro.html) 82 | 83 | ```js 84 | import React from 'react' 85 | import { View } from '@tarojs/components' 86 | import { LuckyWheel, LuckyGrid, SlotMachine } from '@lucky-canvas/taro/react' 87 | 88 | export default class Index extends React.Component { 89 | render () { 90 | return 91 | 92 | {/* 大转盘抽奖 */} 93 | 94 | 95 | {/* 大转盘抽奖 */} 96 | 97 | 98 | {/* 老虎机抽奖 */} 99 | 100 | 101 | 102 | } 103 | } 104 | ``` 105 | 106 |
107 | 108 | ## 完整文档: https://100px.net 109 | 110 |
111 | 112 | ## 🙏🙏🙏 点个Star 113 | 114 | ### **如果您觉得这个项目还不错, 可以在 [Github](https://github.com/buuing/lucky-canvas) 上面帮我点个`star`, 支持一下作者 ☜(゚ヮ゚☜) ☜(゚ヮ゚☜)** 115 | 116 |
117 | -------------------------------------------------------------------------------- /packages/taro/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lucky-canvas/taro", 3 | "version": "0.0.13", 4 | "description": "基于 taro 实现的大转盘 / 九宫格 / 老虎机抽奖插件", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/LuckDraw/lucky-canvas.git" 12 | }, 13 | "keywords": [ 14 | "taro抽奖插件", 15 | "taro大转盘抽奖", 16 | "taro九宫格抽奖" 17 | ], 18 | "author": "ldq ", 19 | "license": "Apache-2.0", 20 | "files": [ 21 | "vue", 22 | "react", 23 | "utils", 24 | "taro2" 25 | ], 26 | "bugs": { 27 | "url": "https://github.com/LuckDraw/lucky-canvas/issues" 28 | }, 29 | "homepage": "https://100px.net", 30 | "dependencies": { 31 | "lucky-canvas": "~1.7.19" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/taro/react/index.js: -------------------------------------------------------------------------------- 1 | export { default as LuckyWheel } from './LuckyWheel.js' 2 | export { default as LuckyGrid } from './LuckyGrid.js' 3 | export { default as SlotMachine } from './SlotMachine.js' 4 | -------------------------------------------------------------------------------- /packages/taro/utils/index.css: -------------------------------------------------------------------------------- 1 | .lucky-box { 2 | position: relative; 3 | overflow: hidden; 4 | margin: 0 auto; 5 | } 6 | .lucky-box .lucky-canvas { 7 | position: absolute; 8 | pointer-events: none; 9 | left: 0; 10 | top: 0; 11 | } 12 | .lucky-box .lucky-wheel-btn { 13 | position: absolute; 14 | left: 50%; 15 | top: 50%; 16 | transform: translate(-50%, -50%); 17 | background: rgba(0, 0, 0, 0); 18 | border-radius: 50%; 19 | cursor: pointer; 20 | } 21 | .lucky-box .lucky-grid-btn { 22 | position: absolute; 23 | background: rgba(0, 0, 0, 0); 24 | border-radius: 0; 25 | cursor: pointer; 26 | } 27 | .lucky-box .lucky-imgs { 28 | width: 0; 29 | height: 0; 30 | visibility: hidden; 31 | } -------------------------------------------------------------------------------- /packages/taro/utils/index.js: -------------------------------------------------------------------------------- 1 | import Taro from '@tarojs/taro' 2 | 3 | const windowWidth = Taro.getSystemInfoSync().windowWidth 4 | 5 | export const getFlag = () => { 6 | let flag 7 | switch (process.env.TARO_ENV) { 8 | case 'h5': 9 | flag = 'WEB' 10 | break 11 | case 'weapp': 12 | flag = 'MP-WX' 13 | break 14 | case 'qq': 15 | flag = 'MP-WX' 16 | break 17 | case 'rn': 18 | flag = 'TARO-RN' 19 | break 20 | default: 21 | flag = 'MP-WX' 22 | break 23 | } 24 | return flag 25 | } 26 | 27 | export const rpx2px = (value) => { 28 | if (typeof value === 'string') value = Number(value.replace(/[a-z]*/g, '')) 29 | return windowWidth / 750 * value 30 | } 31 | 32 | export const changeUnits = (value) => { 33 | value = String(value) 34 | return Number(value.replace(/^(\-*[0-9.]*)([a-z%]*)$/, (value, num, unit) => { 35 | switch (unit) { 36 | case 'px': 37 | num *= 1 38 | break 39 | case 'rpx': 40 | num = rpx2px(num) 41 | break 42 | default: 43 | num *= 1 44 | break 45 | } 46 | return num 47 | })) 48 | } 49 | 50 | export const resolveImage = async (img, canvas, srcName = 'src', resolveName = '$resolve', cb) => { 51 | let imgObj = canvas.createImage() 52 | // 成功回调 53 | imgObj.onload = () => { 54 | if (typeof cb === 'function') cb() 55 | img[resolveName](imgObj) 56 | } 57 | // 失败回调 58 | imgObj.onerror = (err) => { 59 | console.error(err) 60 | // img['$reject']() 61 | } 62 | // 设置src 63 | imgObj.src = img[srcName] 64 | } 65 | 66 | // export const resolveImage = async (res, img, imgName = 'src', resolveName = '$resolve') => { 67 | // const src = img[imgName] 68 | // // 如果是base64就调用base64src()方法把图片写入本地, 然后渲染临时路径 69 | // if (/^data:image\/([a-z]+);base64,/.test(src)) { 70 | // const path = await base64src(src) 71 | // img[resolveName]({ ...res.detail, path }) 72 | // return 73 | // } 74 | // // 如果是网络图片, 则通过getImageInfo()方法获取图片宽高 75 | // Taro.getImageInfo({ 76 | // src: src, 77 | // success: (imgObj) => img[resolveName](imgObj), 78 | // fail: () => console.error('API `Taro.getImageInfo` 加载图片失败', src) 79 | // }) 80 | // } 81 | 82 | export function getImage (canvasId, canvas) { 83 | return new Promise((resolve, reject) => { 84 | Taro.canvasToTempFilePath({ 85 | canvas: canvas, 86 | canvasId: canvasId, 87 | success: (res) => resolve(res), 88 | fail: (err) => reject(err), 89 | }, this) 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /packages/taro/vue/SlotMachine.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 194 | 195 | 198 | -------------------------------------------------------------------------------- /packages/taro/vue/index.js: -------------------------------------------------------------------------------- 1 | export { default as LuckyWheel } from './LuckyWheel.vue' 2 | export { default as LuckyGrid } from './LuckyGrid.vue' 3 | export { default as SlotMachine } from './SlotMachine.vue' 4 | -------------------------------------------------------------------------------- /packages/uni/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | yarn.lock 4 | package-lock.json 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | components.zip 24 | components/ 25 | -------------------------------------------------------------------------------- /packages/uni/.npmignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /packages/uni/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | logo 5 |

lucky-canvas 抽奖插件

6 |

一个基于 JavaScript 的跨平台 ( 大转盘 / 九宫格 / 老虎机 ) 抽奖插件

7 |

8 | 9 | stars 10 | 11 | 12 | forks 13 | 14 | 15 | author 16 | 17 | 18 | license 19 | 20 |

21 |
22 | 23 | 24 | |适配框架|npm下载量|CDN使用量| 25 | | :-: | :-: | :-: | 26 | |[`JS` / `JQ` 中使用](https://100px.net/usage/js.html)|downloads|downloads| 27 | |[`Vue` 中使用](https://100px.net/usage/vue.html)|downloads|downloads| 28 | |[`React` 中使用](https://100px.net/usage/react.html)|downloads|-| 29 | |[`UniApp` 中使用](https://100px.net/usage/uni.html)|downloads|-| 30 | |[`Taro3.x` 中使用](https://100px.net/usage/taro.html)|downloads|-| 31 | |[`微信小程序` 中使用](https://100px.net/usage/wx.html)|downloads|-| 32 | 33 |
34 | 35 | ## 官方文档 & Demo演示 36 | 37 | > **中文**:[https://100px.net](https://100px.net) 38 | 39 | > **English**:**If anyone can help translate the document, please contact me** `ldq404@qq.com` 40 | 41 |
42 | 43 | ## 在 uni-app 中使用 44 | 45 | ### 1. 安装插件 46 | 47 | - 你可以选择通过 `HBuilderX` 导入插件: [https://ext.dcloud.net.cn/plugin?id=3499](https://ext.dcloud.net.cn/plugin?id=3499) 48 | 49 | - 也可以选择通过 `npm` / `yarn` 安装 50 | 51 | ```shell 52 | # npm 安装: 53 | npm install @lucky-canvas/uni 54 | 55 | # yarn 安装: 56 | yarn add @lucky-canvas/uni 57 | ``` 58 | 59 |
60 | 61 | ### 2. 引入并使用 62 | 63 | ```html 64 | 65 | 66 | 71 | 72 | 77 | 78 | ``` 79 | 80 | ```js 81 | // npm 下载会默认到 node_modules 里面,直接引入包名即可 82 | import LuckyWheel from '@lucky-canvas/uni/lucky-wheel' // 大转盘 83 | import LuckyGrid from '@lucky-canvas/uni/lucky-grid' // 九宫格 84 | 85 | // 如果你是通过 HBuilderX 导入插件,那你需要指定一下路径 86 | // import LuckyWheel from '@/components/@lucky-canvas/uni/lucky-wheel' // 大转盘 87 | // import LuckyGrid from '@/components/@lucky-canvas/uni/lucky-grid' // 九宫格 88 | 89 | export default { 90 | // 注册组件 91 | components: { LuckyWheel, LuckyGrid }, 92 | } 93 | ``` 94 | 95 |
96 | 97 | ### 3. 我提供了一个最基本的 demo 供你用于尝试 98 | 99 | 由于 uni-app 渲染 md 的时候会出问题,所以我把 demo 代码放到了文档里 100 | 101 | - [https://100px.net/usage/uni-app.html](https://100px.net/usage/uni-app.html) 102 | 103 |
104 | 105 | ### **4. 补充说明** 106 | 107 | - [**如果用着顺手, 可以在 Github 上面点个 支持一下(●'◡'●)**](https://github.com/buuing/lucky-canvas) 108 | 109 | - 另外: 如果你修复了某些bug或兼容, 欢迎提给我, 我会把你展示到官网的贡献者列表当中 110 | 111 | 112 |
113 | 114 | ### 5. 常见问题 115 | 116 | 1. 转盘层级太高了, 我的弹窗盖不住怎么办? 117 | 118 | > 答: 因为小程序里canvas是原生组件顶层渲染, 我无法控制canvas的层级, 如果你想盖住它也肯简单, 你可以百度搜索``组件 119 | 120 | 2. 你这些素材, 图片组件从哪下载? 121 | 122 | > 答: 官网里的任何图片素材, 所使用到的图片资源均为学习交流使用, 请勿将其用于商业用途, 由此产生的任何商业纠纷我这边概不负责 123 | 124 | 3. xxx属性怎么使用? xxx方法怎么调用? 125 | 126 | > 答: 自己去看文档, 不然难道要我把代码给你写好吗? 127 | 128 | 4. 这个属性的效果与官网的描述不一致? 129 | 130 | > 答: 可能有bug, 你可以去github上的issues去提问 (请认真填写模板) 131 | 132 | 5. 为什么这个插件不支持app和其他小程序 133 | 134 | > 答: 没时间, 但是希望志同道合的同学来一起参与uniapp的兼容开发 135 | 136 | --- 137 | 138 | 作者留言: 为了使我自己保持心情愉悦, 低于5星的提问我用浏览器插件都屏蔽了 139 | 140 | 不要通过插件市场赞赏!!! 141 | -------------------------------------------------------------------------------- /packages/uni/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lucky-canvas/uni", 3 | "version": "0.0.13", 4 | "description": "uni-app【大转盘 / 九宫格 / 老虎机】抽奖插件", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [ 9 | "uni-app抽奖" 10 | ], 11 | "files": [ 12 | "lucky-wheel.vue", 13 | "lucky-grid.vue", 14 | "slot-machine.vue", 15 | "utils.js", 16 | "demo.vue" 17 | ], 18 | "author": "ldq ", 19 | "license": "Apache-2.0", 20 | "dependencies": { 21 | "lucky-canvas": "~1.7.19" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/uni/utils.js: -------------------------------------------------------------------------------- 1 | let windowWidth = uni.getSystemInfoSync().windowWidth 2 | // uni-app@2.9起, 屏幕最多适配到960, 超出则按375计算 3 | if (windowWidth > 960) windowWidth = 375 4 | 5 | export const rpx2px = (value) => { 6 | if (typeof value === 'string') value = Number(value.replace(/[a-z]*/g, '')) 7 | return windowWidth / 750 * value 8 | } 9 | 10 | export const changeUnits = (value) => { 11 | value = String(value) 12 | return Number(value.replace(/^(\-*[0-9.]*)([a-z%]*)$/, (value, num, unit) => { 13 | switch (unit) { 14 | case 'px': 15 | num *= 1 16 | break 17 | case 'rpx': 18 | num = rpx2px(num) 19 | break 20 | default: 21 | num *= 1 22 | break 23 | } 24 | return num 25 | })) 26 | } 27 | 28 | export const resolveImage = async (img, canvas, srcName = 'src', resolveName = '$resolve') => { 29 | const imgObj = canvas.createImage() 30 | // 成功回调 31 | imgObj.onload = () => { 32 | img[resolveName](imgObj) 33 | } 34 | // 失败回调 35 | imgObj.onerror = (err) => { 36 | console.error(err) 37 | // img['$reject']() 38 | } 39 | // 设置src 40 | imgObj.src = img[srcName] 41 | } 42 | 43 | // 旧版canvas引入图片的方法 44 | // export const resolveImage = async (res, img, imgName = 'src', resolveName = '$resolve') => { 45 | // const src = img[imgName] 46 | // const $resolve = img[resolveName] 47 | // // #ifdef MP 48 | // // 如果是base64就调用base64src()方法把图片写入本地, 然后渲染临时路径 49 | // if (/^data:image\/([a-z]+);base64,/.test(src)) { 50 | // const path = await base64src(src) 51 | // $resolve({ ...res.detail, path }) 52 | // return 53 | // } 54 | // // #endif 55 | // // 如果是本地图片, 直接返回 56 | // if (src.indexOf('http') !== 0) { 57 | // $resolve({ ...res.detail, path:src }) 58 | // return 59 | // } 60 | // // 如果是网络图片, 则通过getImageInfo()方法获取图片宽高 61 | // uni.getImageInfo({ 62 | // src: src, 63 | // success: (imgObj) => $resolve(imgObj), 64 | // fail: () => console.error('API `uni.getImageInfo` 加载图片失败', src) 65 | // }) 66 | // } 67 | 68 | export function getImage(canvasId, canvas) { 69 | return new Promise((resolve, reject) => { 70 | uni.canvasToTempFilePath({ 71 | canvas, 72 | canvasId, 73 | success: res => resolve(res), 74 | fail: err => reject(err) 75 | }, this) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /packages/vue/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | *.zip 4 | *.tgz 5 | node_modules 6 | dist 7 | yarn.lock 8 | package-lock.json 9 | 10 | # local env files 11 | .env.local 12 | .env.*.local 13 | 14 | # Log files 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? -------------------------------------------------------------------------------- /packages/vue/.npmignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /packages/vue/README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | logo 4 |

lucky-canvas 抽奖插件

5 |

一个基于 JavaScript 的跨平台 ( 大转盘 / 九宫格 / 老虎机 ) 抽奖插件

6 |

7 | 8 | stars 9 | 10 | 11 | forks 12 | 13 | 14 | author 15 | 16 | 17 | license 18 | 19 |

20 |
21 | 22 |
23 | 24 | |适配框架|npm包|最新版本|npm下载量|CDN使用量| 25 | | :-: | :-: | :-: | :-: | :-: | 26 | |`JS` / `JQ`|[lucky-canvas](https://100px.net/usage/js.html)|version|downloads|downloads| 27 | |`Vue2.x` / `Vue3.x`|[@lucky-canvas/vue](https://100px.net/usage/vue.html)|version|downloads|downloads| 28 | |`React`|[@lucky-canvas/react](https://100px.net/usage/react.html)|version|downloads|-| 29 | |`UniApp`|[@lucky-canvas/uni](https://100px.net/usage/uni.html)|version|downloads|-| 30 | |`Taro3.x`|[@lucky-canvas/taro](https://100px.net/usage/taro.html)|version|downloads|-| 31 | |`微信小程序`|[@lucky-canvas/mini](https://100px.net/usage/wx.html)|version|downloads|-| 32 | 33 |
34 | 35 |
36 | 37 | ## 在 vue2.x / vue3.x 中使用 38 | 39 | - [跳转官网 查看详情](https://100px.net/usage/vue.html) 40 | 41 |
42 | 43 | ## 🙏🙏🙏 点个Star 44 | 45 | **如果您觉得这个项目还不错, 可以在 [Github](https://github.com/LuckDraw/lucky-canvas) 上面帮我点个`star`, 支持一下作者 ☜(゚ヮ゚☜)** 46 | 47 | -------------------------------------------------------------------------------- /packages/vue/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.esm.js') -------------------------------------------------------------------------------- /packages/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lucky-canvas/vue", 3 | "version": "0.1.11", 4 | "description": "一个支持 vue2 / vue3 的(大转盘 / 九宫格 / 老虎机)luckydraw 抽奖插件", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.esm.js", 7 | "unpkg": "dist/index.umd.js", 8 | "jsdelivr": "dist/index.umd.js", 9 | "types": "types/index.d.ts", 10 | "keywords": [ 11 | "vue3抽奖", 12 | "大转盘抽奖", 13 | "九宫格抽奖", 14 | "抽奖插件", 15 | "web抽奖", 16 | "移动端抽奖", 17 | "canvas抽奖" 18 | ], 19 | "author": "ldq ", 20 | "license": "Apache-2.0", 21 | "files": [ 22 | "dist", 23 | "types", 24 | "index.js" 25 | ], 26 | "scripts": { 27 | "dev": "rollup --config rollup.config.dev.js -w", 28 | "build": "rollup --config rollup.config.build.js", 29 | "switch:2": "vue-demi-switch 2 vue2", 30 | "switch:3": "vue-demi-switch 3" 31 | }, 32 | "homepage": "https://100px.net", 33 | "dependencies": { 34 | "@vue/composition-api": "^1.0.0", 35 | "lucky-canvas": "^1.7.23", 36 | "vue-demi": "^0.7.4" 37 | }, 38 | "peerDependencies": { 39 | "vue": "^2.0.0 || >=3.0.0-rc.0" 40 | }, 41 | "peerDependenciesMeta": { 42 | "@vue/composition-api": { 43 | "optional": true 44 | } 45 | }, 46 | "devDependencies": { 47 | "@rollup/plugin-commonjs": "^20.0.0", 48 | "@rollup/plugin-json": "^4.1.0", 49 | "@rollup/plugin-node-resolve": "^13.0.5", 50 | "@rollup/plugin-typescript": "^8.2.1", 51 | "@vue/compiler-sfc": "^3.0.11", 52 | "acorn-jsx": "^5.3.1", 53 | "postcss": "^8.2.8", 54 | "rollup": "^2.43.0", 55 | "rollup-plugin-delete": "^2.0.0", 56 | "rollup-plugin-dts": "^4.0.0", 57 | "rollup-plugin-postcss": "^4.0.0", 58 | "rollup-plugin-terser": "^7.0.2", 59 | "rollup-plugin-typescript2": "0.30.0", 60 | "typescript": "^4.2.3", 61 | "vue": "^3.0.11" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/vue/rollup.config.build.js: -------------------------------------------------------------------------------- 1 | import ts from 'rollup-plugin-typescript2' 2 | import dts from 'rollup-plugin-dts' 3 | import commonjs from '@rollup/plugin-commonjs' 4 | import json from '@rollup/plugin-json' 5 | import resolve from '@rollup/plugin-node-resolve' 6 | import PostCSS from 'rollup-plugin-postcss' 7 | import del from 'rollup-plugin-delete' 8 | import { terser } from 'rollup-plugin-terser' 9 | import pkg from './package.json' 10 | 11 | export default [ 12 | { 13 | input: 'src/index.ts', 14 | output: [ 15 | { 16 | file: pkg.main, 17 | format: 'cjs', 18 | sourcemap: true, 19 | }, 20 | { 21 | file: pkg.module, 22 | format: 'esm', 23 | sourcemap: true, 24 | }, 25 | { 26 | file: pkg.jsdelivr, 27 | format: 'umd', 28 | name: 'VueLuckyCanvas', 29 | sourcemap: false, 30 | globals: { 31 | 'vue-demi': 'VueDemi', 32 | 'lucky-canvas': 'LuckyCanvas', 33 | }, 34 | }, 35 | ], 36 | plugins: [ 37 | ts(), 38 | json(), 39 | resolve(), 40 | commonjs(), 41 | PostCSS(), 42 | terser(), 43 | ], 44 | external: ['vue-demi'], 45 | }, { 46 | input: "dist/src/index.d.ts", 47 | output: [ 48 | { 49 | file: "types/index.d.ts", 50 | format: "es" 51 | } 52 | ], 53 | plugins: [ 54 | dts(), 55 | del({ 56 | targets: 'dist/src', 57 | hook: 'buildEnd' 58 | }) 59 | ], 60 | }, 61 | ] 62 | -------------------------------------------------------------------------------- /packages/vue/rollup.config.dev.js: -------------------------------------------------------------------------------- 1 | import ts from 'rollup-plugin-typescript2' 2 | import commonjs from '@rollup/plugin-commonjs' 3 | import json from '@rollup/plugin-json' 4 | import resolve from '@rollup/plugin-node-resolve' 5 | import PostCSS from 'rollup-plugin-postcss' 6 | import pkg from './package.json' 7 | 8 | export default [ 9 | { 10 | input: 'src/index.ts', 11 | output: [ 12 | { 13 | file: pkg.module, 14 | format: 'esm', 15 | sourcemap: true, 16 | }, 17 | { 18 | file: pkg.jsdelivr, 19 | format: 'umd', 20 | name: 'VueLuckyCanvas', 21 | sourcemap: true, 22 | globals: { 23 | 'vue-demi': 'VueDemi', 24 | 'lucky-canvas': 'LuckyCanvas', 25 | }, 26 | }, 27 | ], 28 | plugins: [ 29 | ts(), 30 | json(), 31 | resolve(), 32 | commonjs(), 33 | PostCSS(), 34 | ], 35 | external: ['vue-demi'], 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /packages/vue/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | 7 | declare module '*.json' { 8 | const value: { 9 | name: string 10 | version: string 11 | } 12 | export default value 13 | } 14 | -------------------------------------------------------------------------------- /packages/vue/src/components/LuckyGrid.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue-demi' 2 | import { LuckyGrid } from 'lucky-canvas' 3 | import h from "../utils/h-demi" 4 | // @ts-ignore 5 | import { name, version } from '../../package.json' 6 | 7 | export default defineComponent({ 8 | name: 'LuckyGrid', 9 | props: { 10 | width: { 11 | type: [String, Number], 12 | }, 13 | height: { 14 | type: [String, Number], 15 | }, 16 | cols: { 17 | type: [String, Number], 18 | default: 3 19 | }, 20 | rows: { 21 | type: [String, Number], 22 | default: 3 23 | }, 24 | blocks: { 25 | type: Array, 26 | default: () => [] 27 | }, 28 | prizes: { 29 | type: Array, 30 | default: () => [] 31 | }, 32 | buttons: { 33 | type: Array, 34 | default: () => [] 35 | }, 36 | button: { // 老版本要兼容这个属性 37 | type: Object 38 | }, 39 | defaultStyle: { 40 | type: Object, 41 | default: () => ({}) 42 | }, 43 | activeStyle: { 44 | type: Object, 45 | default: () => ({}) 46 | }, 47 | defaultConfig: { 48 | type: Object, 49 | default: () => ({}) 50 | }, 51 | }, 52 | emits: [ 53 | 'start', 54 | 'end', 55 | 'success', 56 | 'error', 57 | 'finally', 58 | ], 59 | watch: { 60 | cols (newData, oldData) { 61 | this.lucky && ((this.lucky as any).cols = newData) 62 | }, 63 | rows (newData, oldData) { 64 | this.lucky && ((this.lucky as any).rows = newData) 65 | }, 66 | blocks (newData, oldData) { 67 | this.lucky && ((this.lucky as any).blocks = newData) 68 | }, 69 | prizes (newData, oldData) { 70 | this.lucky && ((this.lucky as any).prizes = newData) 71 | }, 72 | buttons (newData, oldData) { 73 | this.lucky && ((this.lucky as any).buttons = newData) 74 | }, 75 | button (newData, oldData) { 76 | this.lucky && ((this.lucky as any).button = newData) 77 | }, 78 | }, 79 | data() { 80 | return { 81 | lucky: null as LuckyGrid | null, 82 | }; 83 | }, 84 | mounted () { 85 | // 添加版本信息到标签上, 方便定位版本问题 86 | if (this.$refs.myLucky) { 87 | const dom = this.$refs.myLucky as HTMLDivElement 88 | dom.setAttribute('package', `${name}@${version}`) 89 | } 90 | // 开始创建组件 91 | try { 92 | this.initLucky() 93 | this.$emit('success') 94 | } catch (err) { 95 | this.$emit('error', err) 96 | } finally { 97 | this.$emit('finally') 98 | } 99 | }, 100 | methods: { 101 | initLucky () { 102 | this.lucky = new LuckyGrid({ 103 | flag: 'WEB', 104 | width: String(this.width), 105 | height: String(this.height), 106 | divElement: this.$refs.myLucky as HTMLDivElement, 107 | rAF: window.requestAnimationFrame, 108 | setTimeout: window.setTimeout, 109 | setInterval: window.setInterval, 110 | clearTimeout: window.clearTimeout, 111 | clearInterval: window.clearInterval, 112 | }, { 113 | ...this.$props as any, 114 | start: (e, btn) => { 115 | this.$emit('start', e, btn) 116 | }, 117 | end: (prize) => { 118 | this.$emit('end', prize) 119 | }, 120 | }) 121 | }, 122 | init () { 123 | this.lucky && this.lucky.init() 124 | }, 125 | /** 126 | * play方法可以让抽奖开始旋转 127 | */ 128 | play () { 129 | this.lucky?.play() 130 | }, 131 | /** 132 | * stop方法可以传递一个中奖索引, 来停止游戏 133 | * @param index 中奖索引 134 | */ 135 | stop (index?: number) { 136 | this.lucky?.stop(index) 137 | }, 138 | }, 139 | render() { 140 | return h('div', { ref: 'myLucky' }) 141 | } 142 | }) 143 | -------------------------------------------------------------------------------- /packages/vue/src/components/LuckyWheel.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue-demi' 2 | import { LuckyWheel } from 'lucky-canvas' 3 | import h from "../utils/h-demi" 4 | // @ts-ignore 5 | import { name, version } from '../../package.json' 6 | 7 | export default defineComponent({ 8 | name: 'LuckyWheel', 9 | props: { 10 | width: { 11 | type: [String, Number], 12 | }, 13 | height: { 14 | type: [String, Number], 15 | }, 16 | blocks: { 17 | type: Array, 18 | default: () => [] 19 | }, 20 | prizes: { 21 | type: Array, 22 | default: () => [] 23 | }, 24 | buttons: { 25 | type: Array, 26 | default: () => [] 27 | }, 28 | defaultStyle: { 29 | type: Object, 30 | default: () => ({}) 31 | }, 32 | defaultConfig: { 33 | type: Object, 34 | default: () => ({}) 35 | } 36 | }, 37 | emits: [ 38 | 'start', 39 | 'end', 40 | 'success', 41 | 'error', 42 | 'finally', 43 | ], 44 | watch: { 45 | blocks (newData, oldData) { 46 | this.lucky && ((this.lucky as any).blocks = newData) 47 | }, 48 | prizes (newData, oldData) { 49 | this.lucky && ((this.lucky as any).prizes = newData) 50 | }, 51 | buttons (newData, oldData) { 52 | this.lucky && ((this.lucky as any).buttons = newData) 53 | }, 54 | }, 55 | data() { 56 | return { 57 | lucky: null as LuckyWheel | null, 58 | }; 59 | }, 60 | mounted () { 61 | // 添加版本信息到标签上, 方便定位版本问题 62 | if (this.$refs.myLucky) { 63 | const dom = this.$refs.myLucky as HTMLDivElement 64 | dom.setAttribute('package', `${name}@${version}`) 65 | } 66 | // 开始创建组件 67 | try { 68 | this.initLucky() 69 | this.$emit('success') 70 | } catch (err) { 71 | this.$emit('error', err) 72 | } finally { 73 | this.$emit('finally') 74 | } 75 | }, 76 | methods: { 77 | initLucky () { 78 | this.lucky = new LuckyWheel({ 79 | flag: 'WEB', 80 | width: String(this.width), 81 | height: String(this.height), 82 | divElement: this.$refs.myLucky as HTMLDivElement, 83 | rAF: window.requestAnimationFrame, 84 | setTimeout: window.setTimeout, 85 | setInterval: window.setInterval, 86 | clearTimeout: window.clearTimeout, 87 | clearInterval: window.clearInterval, 88 | }, { 89 | ...this.$props as any, 90 | start: (e) => { 91 | this.$emit('start', e) 92 | }, 93 | end: (prize) => { 94 | this.$emit('end', prize) 95 | }, 96 | }) 97 | }, 98 | init () { 99 | this.lucky && this.lucky.init() 100 | }, 101 | /** 102 | * play方法可以让抽奖开始旋转 103 | */ 104 | play () { 105 | this.lucky?.play() 106 | }, 107 | /** 108 | * stop方法可以传递一个中奖索引, 来停止游戏 109 | * @param index 中奖索引 110 | */ 111 | stop (index?: number) { 112 | this.lucky?.stop(index) 113 | }, 114 | }, 115 | render() { 116 | return h('div', { ref: 'myLucky' }) 117 | } 118 | }) 119 | -------------------------------------------------------------------------------- /packages/vue/src/components/SlotMachine.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue-demi' 2 | import { SlotMachine } from 'lucky-canvas' 3 | import h from "../utils/h-demi" 4 | // @ts-ignore 5 | import { name, version } from '../../package.json' 6 | 7 | export default defineComponent({ 8 | name: 'SlotMachine', 9 | props: { 10 | width: { 11 | type: [String, Number], 12 | }, 13 | height: { 14 | type: [String, Number], 15 | }, 16 | blocks: { 17 | type: Array, 18 | default: () => [] 19 | }, 20 | prizes: { 21 | type: Array, 22 | default: () => [] 23 | }, 24 | slots: { 25 | type: Array, 26 | default: () => [] 27 | }, 28 | defaultStyle: { 29 | type: Object, 30 | default: () => ({}) 31 | }, 32 | defaultConfig: { 33 | type: Object, 34 | default: () => ({}) 35 | }, 36 | }, 37 | watch: { 38 | blocks (newData, oldData) { 39 | this.lucky && ((this.lucky as any).blocks = newData) 40 | }, 41 | slots (newData, oldData) { 42 | this.lucky && ((this.lucky as any).slots = newData) 43 | }, 44 | prizes (newData, oldData) { 45 | this.lucky && ((this.lucky as any).prizes = newData) 46 | }, 47 | }, 48 | data() { 49 | return { 50 | lucky: null as SlotMachine | null, 51 | }; 52 | }, 53 | mounted () { 54 | // 添加版本信息到标签上, 方便定位版本问题 55 | if (this.$refs.myLucky) { 56 | const dom = this.$refs.myLucky as HTMLDivElement 57 | dom.setAttribute('package', `${name}@${version}`) 58 | } 59 | // 开始创建组件 60 | try { 61 | this.initLucky() 62 | this.$emit('success') 63 | } catch (err) { 64 | this.$emit('error', err) 65 | } finally { 66 | this.$emit('finally') 67 | } 68 | }, 69 | methods: { 70 | initLucky () { 71 | this.lucky = new SlotMachine({ 72 | flag: 'WEB', 73 | width: String(this.width), 74 | height: String(this.height), 75 | divElement: this.$refs.myLucky as HTMLDivElement, 76 | rAF: window.requestAnimationFrame, 77 | setTimeout: window.setTimeout, 78 | setInterval: window.setInterval, 79 | clearTimeout: window.clearTimeout, 80 | clearInterval: window.clearInterval, 81 | }, { 82 | ...this.$props as any, 83 | start: (e: Event) => { 84 | this.$emit('start', e) 85 | }, 86 | end: (btn) => { 87 | this.$emit('end', btn) 88 | }, 89 | }) 90 | }, 91 | init () { 92 | this.lucky && this.lucky.init() 93 | }, 94 | /** 95 | * play方法可以让抽奖开始旋转 96 | */ 97 | play () { 98 | this.lucky?.play() 99 | }, 100 | /** 101 | * stop方法可以传递一个中奖索引, 来停止游戏 102 | * @param index 中奖索引 103 | */ 104 | stop (index: number) { 105 | this.lucky?.stop(index) 106 | }, 107 | }, 108 | render() { 109 | return h('div', { ref: 'myLucky' }) 110 | } 111 | }) 112 | -------------------------------------------------------------------------------- /packages/vue/src/index.ts: -------------------------------------------------------------------------------- 1 | import { isVue2 } from "vue-demi" 2 | import LuckyWheel from "./components/LuckyWheel" 3 | import LuckyGrid from "./components/LuckyGrid" 4 | import SlotMachine from "./components/SlotMachine" 5 | 6 | 7 | const install = (app: { component: Function }) => { 8 | app.component('LuckyWheel', LuckyWheel) 9 | app.component('LuckyGrid', LuckyGrid) 10 | app.component('SlotMachine', SlotMachine) 11 | } 12 | 13 | if (typeof window !== 'undefined' && (window as any).Vue && isVue2) { 14 | install((window as any).Vue) 15 | } 16 | 17 | export { install, LuckyWheel, LuckyGrid, SlotMachine } 18 | export default { install } 19 | -------------------------------------------------------------------------------- /packages/vue/src/utils/h-demi.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { h as hDemi, isVue2 } from 'vue-demi' 3 | 4 | interface Options { 5 | props?: Object, 6 | domProps?: Object 7 | on?: Object 8 | } 9 | 10 | const adaptOnsV3 = (ons: Object) => { 11 | if (!ons) return null 12 | return Object.entries(ons).reduce((ret, [key, handler]) => { 13 | key = key.charAt(0).toUpperCase() + key.slice(1) 14 | key = `on${key}` 15 | return { ...ret, [key]: handler } 16 | }, {}) 17 | } 18 | 19 | const h = (type: String | Object, options: Options & any = {}, chidren?: any) => { 20 | if (isVue2) return hDemi(type, options, chidren) 21 | const { props, domProps, on, ...extraOptions } = options 22 | let ons = adaptOnsV3(on) 23 | const params = { ...extraOptions, ...props, ...domProps, ...ons } 24 | return hDemi(type, params, chidren) 25 | } 26 | 27 | const slot = (s, attrs?) => { 28 | if (typeof s == 'function') return s(attrs) 29 | return s 30 | } 31 | 32 | export { slot } 33 | 34 | export default h -------------------------------------------------------------------------------- /packages/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "module": "ESNext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "removeComments": true, 10 | "skipLibCheck": false, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "resolveJsonModule": true, 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "declaration": true, 17 | "declarationDir": "./types", 18 | "outDir": "./dist", 19 | }, 20 | "include": [ 21 | "src/**/*.ts", 22 | "src/**/*.tsx", 23 | "src/**/*.vue", 24 | "shims-vue.d.ts", 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } -------------------------------------------------------------------------------- /packages/vue/vue-demi.js: -------------------------------------------------------------------------------- 1 | ;(function (window) { 2 | if (window.VueDemi) { 3 | return 4 | } 5 | var VueDemi = {} 6 | var Vue = window.Vue 7 | if (Vue) { 8 | if (Vue.version.slice(0, 2) === '2.') { 9 | var VueCompositionAPI = window.VueCompositionAPI 10 | if (VueCompositionAPI) { 11 | for (var key in VueCompositionAPI) { 12 | VueDemi[key] = VueCompositionAPI[key] 13 | } 14 | VueDemi.isVue2 = true 15 | VueDemi.isVue3 = false 16 | VueDemi.install = function (){} 17 | VueDemi.Vue = Vue 18 | VueDemi.Vue2 = Vue 19 | VueDemi.version = Vue.version 20 | } else { 21 | console.error( 22 | '[vue-demi] no VueCompositionAPI instance found, please be sure to import `@vue/composition-api` before `vue-demi`.' 23 | ) 24 | } 25 | } else if (Vue.version.slice(0, 2) === '3.') { 26 | for (var key in Vue) { 27 | VueDemi[key] = Vue[key] 28 | } 29 | VueDemi.isVue2 = false 30 | VueDemi.isVue3 = true 31 | VueDemi.install = function (){} 32 | VueDemi.Vue = Vue 33 | VueDemi.Vue2 = undefined 34 | VueDemi.version = Vue.version 35 | VueDemi.set = function(target, key, val) { 36 | if (Array.isArray(target)) { 37 | target.length = Math.max(target.length, key) 38 | target.splice(key, 1, val) 39 | return val 40 | } 41 | target[key] = val 42 | return val 43 | } 44 | VueDemi.del = function(target, key) { 45 | if (Array.isArray(target)) { 46 | target.splice(key, 1) 47 | return 48 | } 49 | delete target[key] 50 | } 51 | } else { 52 | console.error('[vue-demi] Vue version ' + Vue.version + ' is unsupported.') 53 | } 54 | } else { 55 | console.error( 56 | '[vue-demi] no Vue instance found, please be sure to import `vue` before `vue-demi`.' 57 | ) 58 | } 59 | window.VueDemi = VueDemi 60 | })(window); -------------------------------------------------------------------------------- /packages/vue/vue2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | vue2.x 8 | 9 | 10 | 21 |
22 | 33 |
34 | 35 | 36 | 37 | 38 | 76 | 77 | -------------------------------------------------------------------------------- /packages/vue/vue3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | vue3.x 8 | 9 | 10 |
11 | 21 |
22 | 23 | 24 | 25 | 26 | 64 | 65 | --------------------------------------------------------------------------------