From 8c05c8446ccc63a76596d52430e044d13f3d926c Mon Sep 17 00:00:00 2001 From: XiaoLin Date: Sat, 24 Oct 2020 21:13:32 +0800 Subject: [PATCH] feature: v1.0.0 --- .browserslistrc | 1 + .editorconfig | 5 + .eslintrc.js | 26 +- README.CN.md | 22 +- README.md | 29 ++- babel.config.js | 2 +- package.json | 44 +++- postcss.config.js | 11 +- src/App.vue | 106 +++++--- src/app.scss | 5 +- src/components/fortuneWheel/index.scss | 36 +-- src/components/fortuneWheel/index.vue | 245 ++++++++++-------- .../fortuneWheel/{index.js => install.js} | 0 src/components/index.js | 29 --- src/{main.js => main.ts} | 4 +- src/shims-tsx.d.ts | 13 + src/shims-vue.d.ts | 5 + src/styles/reset.css | 231 +---------------- src/utils/number.js | 60 ----- tests/unit/example.spec.ts | 13 + tsconfig.json | 41 +++ vue.config.js | 5 - 22 files changed, 395 insertions(+), 538 deletions(-) create mode 100644 .editorconfig rename src/components/fortuneWheel/{index.js => install.js} (100%) delete mode 100644 src/components/index.js rename src/{main.js => main.ts} (60%) create mode 100644 src/shims-tsx.d.ts create mode 100644 src/shims-vue.d.ts delete mode 100755 src/utils/number.js create mode 100644 tests/unit/example.spec.ts create mode 100644 tsconfig.json delete mode 100644 vue.config.js diff --git a/.browserslistrc b/.browserslistrc index d6471a3..214388f 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -1,2 +1,3 @@ > 1% last 2 versions +not dead diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7053c49 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +[*.{js,jsx,ts,tsx,vue}] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintrc.js b/.eslintrc.js index 1c6179f..44b47b9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,15 +3,27 @@ module.exports = { env: { node: true }, - 'extends': [ + extends: [ 'plugin:vue/essential', - 'eslint:recommended' + '@vue/standard', + '@vue/typescript/recommended' ], + parserOptions: { + ecmaVersion: 2020 + }, rules: { - 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' }, - parserOptions: { - parser: 'babel-eslint' - } + overrides: [ + { + files: [ + '**/__tests__/*.{j,t}s?(x)', + '**/tests/unit/**/*.spec.{j,t}s?(x)' + ], + env: { + mocha: true + } + } + ] } diff --git a/README.CN.md b/README.CN.md index d46d832..c7d7a7f 100644 --- a/README.CN.md +++ b/README.CN.md @@ -122,9 +122,9 @@ export default { }, // 模拟请求后端接口, verified: 是否通过验证, duration: 延迟时间 DoServiceVerify(verified, duration) { - return new Promise((resove) => { + return new Promise((resolve) => { setTimeout(() => { - resove(verified) + resolve(verified) }, duration) }) } @@ -145,15 +145,15 @@ export default { | useWeight | 是否以权重计算概率 | Boolean | false | | disabled | 是否禁用 (禁用后, 点击按钮不会旋转) | Boolean | false | | verify | 是否开启验证 | Boolean | false | -| radius | 圆的半径 (type: canvas) | Number | 250 | -| textRadius | 文本距圆心的距离 (type: canvas) | Number | 190 | -| textLength | 奖品一行几个字符, 超出换行 (最多两行) | Number | 6 | -| lineHeight | 文本行高 (type: canvas) | Number | 20 | -| borderWidth | 圆的外边框 (type: canvas) | Number | 0 | -| borderColor | 外边框的色值 (type: canvas) | String | transparent | -| btnText | 按钮文本 (type: canvas) | String | GO | -| btnWidth | 按钮的宽 (px) | Number | 140 | -| fontSize | 奖品字号 (px) | Number | 34 | +| canvas.radius | 圆的半径 (type: canvas) | Number | 250 | +| canvas.textRadius | 文本距圆心的距离 (type: canvas) | Number | 190 | +| canvas.textLength | 奖品一行几个字符, 超出换行 (最多两行) | Number | 6 | +| canvas.lineHeight | 文本行高 (type: canvas) | Number | 20 | +| canvas.borderWidth | 圆的外边框 (type: canvas) | Number | 0 | +| canvas.borderColor | 外边框的色值 (type: canvas) | String | transparent | +| canvas.btnText | 按钮文本 (type: canvas) | String | GO | +| canvas.btnWidth | 按钮的宽 (px) | Number | 140 | +| canvas.fontSize | 奖品字号 (px) | Number | 34 | | duration | 完成一次旋转的时间 (单位 ms) | Number | 6000 | | timingFun | 旋转过渡的 css 时间函数 | String | cubic-bezier(0.36, 0.95, 0.64, 1) | | angleBase | 旋转圈数 (angleBase * 360 为一次旋转的总角度, 为负数时可反向旋转) | Number | 10 | diff --git a/README.md b/README.md index ff2509a..474ff1a 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,7 @@ https://xiaolin1995.github.io/vue-fortune-wheel/demo/ { + return new Promise((resolve) => { setTimeout(() => { - resove(verified) + resolve(verified) }, duration) }) } @@ -145,15 +148,15 @@ export default { | useWeight | Whether to calculate probability by weight | Boolean | false | | disabled | Whether to disable (after disabled, click the button will not rotate) | Boolean | false | | verify | Whether to enable verification mode | Boolean | false | -| radius | Radius of circle (type: canvas) | Number | 250 | -| textRadius | The distance of the text from the center of the circle (type: canvas) | Number | 190 | -| textLength | A few characters in one line of the prize, beyond the line break (maximum two lines)| Number | 6 | -| lineHeight | Text line height (type: canvas) | Number | 20 | -| borderWidth | Round outer border (type: canvas) | Number | 0 | -| borderColor | Color value of the outer border (type: canvas) | String | transparent | -| btnText | Button text (type: canvas) | String | GO | -| btnWidth | Button width (px) | Number | 140 | -| fontSize | Prize size (px) | Number | 34 | +| canvas.radius | Radius of circle (type: canvas) | Number | 250 | +| canvas.textRadius | The distance of the text from the center of the circle (type: canvas) | Number | 190 | +| canvas.textLength | A few characters in one line of the prize, beyond the line break (maximum two lines)| Number | 6 | +| canvas.lineHeight | Text line height (type: canvas) | Number | 20 | +| canvas.borderWidth | Round outer border (type: canvas) | Number | 0 | +| canvas.borderColor | Color value of the outer border (type: canvas) | String | transparent | +| canvas.btnText | Button text (type: canvas) | String | GO | +| canvas.btnWidth | Button width (px) | Number | 140 | +| canvas.fontSize | Prize size (px) | Number | 34 | | duration | Time to complete one rotation (unit: ms) | Number | 6000 | | timingFun | Css time function of rotation transition | String | cubic-bezier(0.36, 0.95, 0.64, 1) | | angleBase | Number of rotations (angleBase * 360 is the total angle of one rotation, it can be reversed when it is a negative number) | Number | 10 | diff --git a/babel.config.js b/babel.config.js index ba17966..e955840 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,5 +1,5 @@ module.exports = { presets: [ - '@vue/app' + '@vue/cli-plugin-babel/preset' ] } diff --git a/package.json b/package.json index fa692f5..5e0d312 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-fortune-wheel", - "version": "0.3.4", + "version": "1.0.0", "author": "Xiaolin Zhu", "license": "MIT", "bugs": { @@ -22,22 +22,40 @@ "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", + "test:unit": "vue-cli-service test:unit", "lint": "vue-cli-service lint", - "build:lib": "vue-cli-service build --target lib --name vue-fortune-wheel --dest lib src/components/fortuneWheel/index.js" + "build:lib": "vue-cli-service build --target lib --name vue-fortune-wheel --dest lib src/components/FortuneWheel/install.js" }, "dependencies": { - "core-js": "^2.6.5", - "vue": "^2.6.10" + "core-js": "^3.6.5", + "lodash": "^4.17.20", + "normalize.css": "^8.0.1", + "vue": "^2.6.11" }, "devDependencies": { - "@vue/cli-plugin-babel": "^3.12.0", - "@vue/cli-plugin-eslint": "^3.12.0", - "@vue/cli-service": "^3.12.0", - "babel-eslint": "^10.0.1", - "eslint": "^5.16.0", - "eslint-plugin-vue": "^5.0.0", - "sass": "^1.19.0", - "sass-loader": "^8.0.0", - "vue-template-compiler": "^2.6.10" + "@types/chai": "^4.2.11", + "@types/lodash": "^4.14.162", + "@types/mocha": "^5.2.4", + "@typescript-eslint/eslint-plugin": "^2.33.0", + "@typescript-eslint/parser": "^2.33.0", + "@vue/cli-plugin-babel": "~4.5.0", + "@vue/cli-plugin-eslint": "~4.5.0", + "@vue/cli-plugin-typescript": "~4.5.0", + "@vue/cli-plugin-unit-mocha": "~4.5.0", + "@vue/cli-service": "~4.5.0", + "@vue/eslint-config-standard": "^5.1.2", + "@vue/eslint-config-typescript": "^5.0.2", + "@vue/test-utils": "^1.0.3", + "chai": "^4.1.2", + "eslint": "^6.7.2", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.0", + "eslint-plugin-vue": "^6.2.2", + "sass": "^1.26.5", + "sass-loader": "^8.0.2", + "typescript": "~3.9.3", + "vue-template-compiler": "^2.6.11" } } diff --git a/postcss.config.js b/postcss.config.js index 961986e..2646167 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,5 +1,14 @@ module.exports = { plugins: { - autoprefixer: {} + autoprefixer: { + overrideBrowserslist: [ + 'Android 4.1', + 'iOS 7.1', + 'Chrome > 31', + 'ff > 31', + 'ie >= 8' + //'last 10 versions', // 所有主流浏览器最近10版本用 + ] + } } } diff --git a/src/App.vue b/src/App.vue index 3be57a8..4f24052 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,39 +1,38 @@ - - diff --git a/src/app.scss b/src/app.scss index d9acc2b..ef410ec 100644 --- a/src/app.scss +++ b/src/app.scss @@ -1,6 +1,9 @@ #app { padding: 60px 0; text-align: center; + &, *, *::before, *::after { + box-sizing: border-box; + } } h2 { @@ -46,4 +49,4 @@ button { height: 16px; border-radius: 3px; } -} +} \ No newline at end of file diff --git a/src/components/fortuneWheel/index.scss b/src/components/fortuneWheel/index.scss index 5ce3db6..aa911f6 100644 --- a/src/components/fortuneWheel/index.scss +++ b/src/components/fortuneWheel/index.scss @@ -1,43 +1,27 @@ -.vue-fortuneWheel { - display: inline-block; +.fw-container { position: relative; + display: inline-block; font-size: 0; overflow: hidden; - user-select: none; - max-width: 100%; - &, * { - box-sizing: border-box; - } -} - -.fortuneWheel-box { - transition-property: transform; canvas, img { display: block; width: 100%; } } -.fortuneWheel-wrapper { +.fw-btn { position: absolute; top: 0; left: 0; - width: 100%; - height: 100%; + z-index: 1; display: flex; align-items: center; justify-content: center; + width: 100%; + height: 100%; } -.btn-container { - position: absolute; - cursor: pointer; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.fortuneWheel-btn{ +.fw-btn__btn { position: relative; width: 100%; height: 100%; @@ -80,4 +64,8 @@ transform: translate(-50%) translateY(6px); z-index: 10; } -} \ No newline at end of file +} + +.fw-btn__image { + display: inline-block; +} diff --git a/src/components/fortuneWheel/index.vue b/src/components/fortuneWheel/index.vue index fdeb635..20f6a08 100644 --- a/src/components/fortuneWheel/index.vue +++ b/src/components/fortuneWheel/index.vue @@ -1,32 +1,89 @@ - - diff --git a/src/components/fortuneWheel/index.js b/src/components/fortuneWheel/install.js similarity index 100% rename from src/components/fortuneWheel/index.js rename to src/components/fortuneWheel/install.js diff --git a/src/components/index.js b/src/components/index.js deleted file mode 100644 index 55bc5fe..0000000 --- a/src/components/index.js +++ /dev/null @@ -1,29 +0,0 @@ - -// 导入单个组件 -import FortuneWheel from './fortuneWheel/index.vue' - -// 以数组的结构保存组件,便于遍历 -const components = [ - FortuneWheel -] - -// 定义 install 方法 -const install = function (Vue) { - if (install.installed) return - install.installed = true - // 遍历并注册全局组件 - components.map(component => { - Vue.component(component.name, component) - }) -} - -if (typeof window !== 'undefined' && window.Vue) { - install(window.Vue) -} - -export default { - // 导出的对象必须具备一个 install 方法 - install, - // 组件列表 - ...components -} \ No newline at end of file diff --git a/src/main.js b/src/main.ts similarity index 60% rename from src/main.js rename to src/main.ts index 63eb05f..30cdf90 100644 --- a/src/main.js +++ b/src/main.ts @@ -1,8 +1,10 @@ import Vue from 'vue' import App from './App.vue' +import 'normalize.css' +import './styles/reset.css' Vue.config.productionTip = false new Vue({ - render: h => h(App), + render: h => h(App) }).$mount('#app') diff --git a/src/shims-tsx.d.ts b/src/shims-tsx.d.ts new file mode 100644 index 0000000..a175b0d --- /dev/null +++ b/src/shims-tsx.d.ts @@ -0,0 +1,13 @@ +import Vue, { VNode } from 'vue' + +declare global { + namespace JSX { + // tslint:disable no-empty-interface + interface Element extends VNode {} + // tslint:disable no-empty-interface + interface ElementClass extends Vue {} + interface IntrinsicElements { + [elem: string]: any; + } + } +} diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts new file mode 100644 index 0000000..d8df517 --- /dev/null +++ b/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import Vue from 'vue' + export default Vue +} +declare module 'lodash/sumBy' diff --git a/src/styles/reset.css b/src/styles/reset.css index c04c903..c43c848 100644 --- a/src/styles/reset.css +++ b/src/styles/reset.css @@ -1,230 +1,5 @@ -@charset "UTF-8"; - -/* -* reset -*/ - -@-ms-viewport { - width: device-width; -} - -*, *::before, *::after { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -* { - margin: 0; - padding: 0; -} - -html { - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - -ms-overflow-style: scrollbar; - -webkit-tap-highlight-color: transparent; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-size: 10px; -} - body { - font-family: "PingFang SC", "Microsoft YaHei", Arial, "San Francisco", 'Hiragino Sans GB', "Helvetica Neue", Helvetica, sans-serif; - font-size: 1.4rem; - font-weight: normal; - line-height: 1.3; - color: #2A2A2A; - background-color: #fff; -} - -article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { - display: block; -} - -address { - font-style: normal; - line-height: inherit; -} - -b, strong { - font-weight: bolder; -} - -small { - font-size: 80%; -} - -sub, sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} - -sub { - bottom: -.25em; -} - -sup { - top: -.5em; -} - -pre, code, kbd, samp { - font-family: monospace; - font-size: 1em; -} - -summary { - display: list-item; -} - -fieldset { - min-width: 0; - border: 0; -} - -legend { - display: block; - width: 100%; - max-width: 100%; - margin-bottom: .5rem; - font-size: 1.5rem; - line-height: inherit; - color: inherit; - white-space: normal; -} - -progress { - vertical-align: baseline; -} - -table { - border-collapse: collapse; -} - -caption { - padding-top: 0.75rem; - padding-bottom: 0.75rem; - color: #868e96; - text-align: left; - caption-side: bottom; -} - -ul, ol, dl { - list-style: none; -} - -hr { - box-sizing: content-box; - height: 0; - overflow: visible; -} - -a { - color: #111; - text-decoration: none; - background-color: transparent; - -webkit-text-decoration-skip: objects; -} - -a:not([href]):not([tabindex]):focus { - outline: 0; -} - -img { - vertical-align: middle; - border-style: none; - max-width: 100%; -} - -svg:not(:root) { - overflow: hidden; -} - -a, area, button, [role="button"], input, label, select, summary, textarea { - -ms-touch-action: manipulation; - touch-action: manipulation; -} - -input, button, select, optgroup, textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; - outline: 0; -} - -button, input { - overflow: hidden; - border:1px solid #dcdcdc; - background:none; -} - -button, select { - text-transform: none; -} - -button, html [type="button"], [type="reset"], [type="submit"] { - -webkit-appearance: button; - cursor: pointer; -} - -button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { - padding: 0; - border-style: none; -} - -input[type="radio"], input[type="checkbox"] { - box-sizing: border-box; - padding: 0; -} - -input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"] { - -webkit-appearance: listbox; -} - -textarea { - overflow: auto; - resize: vertical; -} - -[type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { - height: auto; -} - -[type="search"] { - outline-offset: -2px; - -webkit-appearance: none; -} - -[type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -::-webkit-file-upload-button { - font: inherit; - -webkit-appearance: button; -} - -output { - display: inline-block; -} - -template,[hidden] { - display: none !important; -} - - -hr { - margin-top: 20px; - margin-bottom: 20px; - border: 0; - border-top: 1px solid #eeeeee; + margin: 0; + padding: 0; + overflow-x: hidden; } - -h1, h2, h3, h4, h5, h6 { - font-family: inherit; - font-weight: bold; - line-height: 1.1; - color: inherit; -} \ No newline at end of file diff --git a/src/utils/number.js b/src/utils/number.js deleted file mode 100755 index bc456c6..0000000 --- a/src/utils/number.js +++ /dev/null @@ -1,60 +0,0 @@ - -/** - * 返回指定范围内的随机整数。 - * @param {number} min 最小值 - * @param {number} max 最大值 - * @example utilscore.randomNum(5,10) // => 5 || 6 || 7 || 8 || 9 || 10 - */ -export const randomNum = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min - -/** - * 根据函数映射每个元素,然后返回数组的和 - * @param {Array} arr - * @param {Function} fn - * @example utilscore.sumBy([{num:1},{num:2},{num:3},{num:4},{num:5}],(row)=>row.num) // => 15 - */ -export const sumBy = (arr, fn) => arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => addNum(acc, val), 0) - -/** - * 加法运算 - * @param {Number} a - * @param {Number} b - * @example utilscore.addNum(0.3 , 0.6) // => 0.9 - */ -export const addNum = (a, b) => { - var c, d, e - try { - c = a.toString().split(".")[1].length - } catch (f) { - c = 0 - } - try { - d = b.toString().split(".")[1].length - } catch (f) { - d = 0 - } - return e = Math.pow(10, Math.max(c, d)), (mulNum(a, e) + mulNum(b, e)) / e -} - -/** - * 乘法运算 - * @param {Number} a - * @param {Number} b - * @example utilscore.mulNum(0.3 , 1.5) // => 0.45 - */ -export const mulNum = (a, b) => { - var c = 0, - d = a.toString(), - e = b.toString() - try { - c += d.split(".")[1].length - } catch (f) { - // console.log(f) - } - try { - c += e.split(".")[1].length - } catch (f) { - // console.log(f) - } - return Number(d.replace(".", "")) * Number(e.replace(".", "")) / Math.pow(10, c) -} diff --git a/tests/unit/example.spec.ts b/tests/unit/example.spec.ts new file mode 100644 index 0000000..c585ea3 --- /dev/null +++ b/tests/unit/example.spec.ts @@ -0,0 +1,13 @@ +import { expect } from 'chai' +import { shallowMount } from '@vue/test-utils' +import HelloWorld from '@/components/HelloWorld.vue' + +describe('HelloWorld.vue', () => { + it('renders props.msg when passed', () => { + const msg = 'new message' + const wrapper = shallowMount(HelloWorld, { + propsData: { msg } + }) + expect(wrapper.text()).to.include(msg) + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..25566f5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "strict": true, + "jsx": "preserve", + "importHelpers": true, + "moduleResolution": "node", + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true, + "baseUrl": ".", + "types": [ + "webpack-env", + "mocha", + "chai" + ], + "paths": { + "@/*": [ + "src/*" + ] + }, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "tests/**/*.ts", + "tests/**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/vue.config.js b/vue.config.js deleted file mode 100644 index 3afbcf2..0000000 --- a/vue.config.js +++ /dev/null @@ -1,5 +0,0 @@ - -module.exports = { - publicPath: process.env.NODE_ENV === 'production' ? './' : '/', - productionSourceMap: false -} \ No newline at end of file