diff --git a/README.md b/README.md index 3ed9f85..632aa3d 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,15 @@ import Vcode from 'react-vcode'; ```` +## 3. 服务端渲染 +``` +import Vcode from 'react-vcode'; -## 3. 自定义参数 + +``` + 需要自己加个id, 不然服务端渲染和本地渲染,id变了会报错,因为Vcode内部使用了随机值 + +## 4. 自定义参数 可自行设置覆盖原有值 @@ -75,9 +82,9 @@ options:{ // 验证码相关自定义参数 '#008888', ],  lineHeightMin: 1, // 线的粗细最小值 -  lineHeightMax: 1, // 线的粗细最大值 -  lineWidthMin: 20, // 线的长度最小值 -  lineWidthMax: 60, // 线的长度最大值 +  lineHeightMax: 2, // 线的粗细最大值 +  lineWidthMin: 40, // 线的长度最小值 +  lineWidthMax: 100, // 线的长度最大值 } // 例子: @@ -89,14 +96,14 @@ options:{ // 验证码相关自定义参数 /> ```` -## 4. 手动刷新验证码 +## 5. 手动刷新验证码 ```javascript this.vcode = obj} /> this.vcode.onClick(); // 调用内部的onClick方法可刷新验证码 ``` -## 5. 额外说明 +## 6. 额外说明 - 之前用过一个验证码插件叫 vcode.js, 不知道作者。 本react-vcode是通过vcode.js的源码进行修改加工,转成了react组件。感谢原作者。 diff --git a/example/src/index.css b/example/src/index.css new file mode 100644 index 0000000..6701b6b --- /dev/null +++ b/example/src/index.css @@ -0,0 +1,3 @@ +.vcode{ + width: 300px; +} \ No newline at end of file diff --git a/example/src/index.js b/example/src/index.js index cdcffaa..bf35d51 100644 --- a/example/src/index.js +++ b/example/src/index.js @@ -3,6 +3,8 @@ import Vcode from "../../dist/index.js"; import ReactDom from "react-dom"; import ImgTest1 from "../assets/test1.png"; import ImgTest2 from "../assets/test2.png"; +import './index.css'; + class Test extends React.Component { constructor(props) { super(props); @@ -11,7 +13,7 @@ class Test extends React.Component { input2: "", // 第2个input的值 vcode2: "-1", // 第2个vcode的值 code: "", - width: 200, + width: 100, }; } @@ -64,7 +66,7 @@ class Test extends React.Component {
this.onInput2Change(e)} maxLength={20} /> - this.onVcode2Change(v)} value={this.state.code} width={this.state.width} /> + this.onVcode2Change(v)} onClick={() => console.log('触发onClick') } value={this.state.code} width={this.state.width} className={'vcode'}/> {this.state.input2 === this.state.vcode2 ? "输入正确" : "输入错误"}

diff --git a/package.json b/package.json index 528be63..1899155 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-vcode", - "version": "1.0.8", + "version": "1.0.11", "description": "a react verification code component", "main": "dist/index.js", "files": [ @@ -9,7 +9,8 @@ "scripts": { "dev": "webpack-dev-server --config webpack.config.js", "build:babel": "babel src -d lib", - "build": "webpack --config webpack.build.config.js --progress --profile --colors" + "build": "webpack --config webpack.build.config.js --progress --profile --colors", + "prettier": "prettier --write \"src/index.tsx\"" }, "types": "dist/index.d.ts", "typings": "dist/index.d.ts", @@ -33,25 +34,31 @@ "react-dom": "^16.13.1" }, "devDependencies": { - "@babel/cli": "^7.10.5", - "@babel/core": "^7.11.4", + "@babel/cli": "^7.11.6", + "@babel/core": "^7.11.6", "@babel/plugin-proposal-decorators": "^7.10.5", - "@babel/plugin-transform-runtime": "^7.11.0", - "@babel/preset-env": "^7.11.0", + "@babel/plugin-transform-runtime": "^7.11.5", + "@babel/preset-env": "^7.11.5", "@babel/preset-react": "^7.10.4", "@babel/runtime": "^7.11.2", - "@types/react": "^16.9.48", + "@types/react": "^16.9.49", "@types/react-dom": "^16.9.8", + "@typescript-eslint/eslint-plugin": "^4.1.0", + "@typescript-eslint/parser": "^4.1.0", "autoprefixer": "^9.8.6", "awesome-typescript-loader": "^5.2.1", "babel-loader": "^8.1.0", - "css-loader": "^4.2.2", - "eslint": "^7.7.0", + "css-loader": "^4.3.0", + "eslint": "^7.8.1", "eslint-config-prettier": "^6.11.0", "eslint-loader": "^4.0.2", "eslint-plugin-prettier": "^3.1.4", - "file-loader": "^6.0.0", - "source-map-loader": "^1.0.2", + "eslint-plugin-react": "^7.20.6", + "eslint-plugin-react-hooks": "^4.1.0", + "file-loader": "^6.1.0", + "postcss-loader": "^4.0.2", + "prettier": "^2.1.1", + "source-map-loader": "^1.1.0", "style-loader": "^1.2.1", "typescript": "^4.0.2", "url-loader": "^4.1.0", diff --git a/src/index.tsx b/src/index.tsx index dae1e85..0ba94a4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,18 +7,22 @@ interface Props { value?: string; // 由父级传入指定的字符串生成code width?: number; // 多宽 px height?: number; // 多高 px - style?: object; // 自定义style + style?: { + [propName: string]: any; + }; // 自定义style className?: string; // 各种class options?: OptionsProps; // 自定义各参数 - onChange?: Function; // 每次生成新的验证码时,将验证码的值传到上级 - onClick?: Function; // 用户每次点击时触发 + onChange?: (p: string | null) => any; // 每次生成新的验证码时,将验证码的值传到上级 + onClick?: () => any; // 用户每次点击时触发 } interface State { id: string; width: number; height: number; len: number; - style: object; + style: { + [propName: string]: any; + }; options: Options; } @@ -66,7 +70,44 @@ export default class Vcode extends React.PureComponent { options: (() => { // 初始化参数 const a: Options = { - codes: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "o", "p", "q", "r", "s", "t", "x", "u", "v", "y", "z", "w", "n", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + codes: [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "o", + "p", + "q", + "r", + "s", + "t", + "x", + "u", + "v", + "y", + "z", + "w", + "n", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + ], fontSizeMin: 22, // 字体尺寸最小值 fontSizeMax: 26, // 字体尺寸最大值 colors: [ @@ -96,9 +137,9 @@ export default class Vcode extends React.PureComponent { "#008888", ], lineHeightMin: 1, // 线的粗细最小值 - lineHeightMax: 1, // 线的粗细最大值 - lineWidthMin: 20, // 线的长度最小值 - lineWidthMax: 60, // 线的长度最大值 + lineHeightMax: 2, // 线的粗细最大值 + lineWidthMin: 40, // 线的长度最小值 + lineWidthMax: 100, // 线的长度最大值 }; if (this.props.options) { return Object.assign({}, a, this.props.options); @@ -109,16 +150,20 @@ export default class Vcode extends React.PureComponent { } /** 组件初始化完毕时触发 **/ - componentDidMount() { + componentDidMount(): void { this.onDraw(this.props.value); } /** 组件参数改变 **/ - componentDidUpdate(prevP: Props) { + componentDidUpdate(prevP: Props): void { if (this.props.value !== prevP.value) { this.onDraw(this.props.value); } - if (this.props.width !== prevP.width || this.props.height !== prevP.height || this.props.style !== prevP.style) { + if ( + this.props.width !== prevP.width || + this.props.height !== prevP.height || + this.props.style !== prevP.style + ) { this.setState({ width: this.props.width || 150, height: this.props.height || 40, @@ -131,7 +176,7 @@ export default class Vcode extends React.PureComponent { } /** 用户点击了验证码图片 **/ - onClick() { + onClick(): void { // 如果用户没有设置值,就直接重新生成 if (!this.props.value) { this.onDraw(this.props.value); @@ -143,21 +188,37 @@ export default class Vcode extends React.PureComponent { * 随机生成一个Code的CSS样式 * @param uW 每个字符所占的宽度 * @param i 当前字符的下标 + * @param maxW 最大偏移值 * @return CSS字符串 */ - codeCss(uW: number, i: number): string { + codeCss(uW: number, i: number, maxW: number): string { + const transStr = `rotate(${this.randint( + -15, + 15, + true + )}deg) translateY(${this.randint(-55, -45, true)}%)`; return [ - `font-size:${this.randint(this.state.options.fontSizeMin, this.state.options.fontSizeMax)}px`, - `color:${this.state.options.colors[this.randint(0, this.state.options.colors.length - 1)]}`, + `font-size:${this.randint( + this.state.options.fontSizeMin, + this.state.options.fontSizeMax + )}px`, + `color:${ + this.state.options.colors[ + this.randint(0, this.state.options.colors.length - 1) + ] + }`, "position: absolute", - `left:${this.randint(uW * i, uW * i + uW - uW / 2)}px`, + `left:${Math.max( + Math.min(this.randint(uW * i, uW * i + uW / 2, true), maxW), + uW / 4 + )}px`, "top:50%", - `transform:rotate(${this.randint(-15, 15)}deg) translateY(-50%)`, - `-o-transform:rotate(${this.randint(-15, 15)}deg) translateY(-50%)`, - `-ms-transform:rotate(${this.randint(-15, 15)}deg) translateY(-50%)`, - `-moz-transform:rotate(${this.randint(-15, 15)}deg) translateY(-50%)`, - `-webkit-transform:rotate(${this.randint(-15, 15)}deg) translateY(-50%)`, - `font-family:${this.state.options.fonts[this.randint(0, this.state.options.fonts.length - 1)]}`, + `transform:${transStr};-o-transform:${transStr};-ms-transform:${transStr};-moz-transform:${transStr};-webkit-transform:${transStr}`, + `font-family:${ + this.state.options.fonts[ + this.randint(0, this.state.options.fonts.length - 1) + ] + }`, "font-weight:bold", "z-index:2", ].join(";"); @@ -168,21 +229,29 @@ export default class Vcode extends React.PureComponent { * @return CSS字符串 */ lineCss(): string { + const transStr = `rotate(${this.randint(-30, 30)}deg)`; return [ "position: absolute", `opacity:${this.randint(3, 8) / 10}`, - `width:${this.randint(this.state.options.lineWidthMin, this.state.options.lineWidthMax)}px`, - `height:${this.randint(this.state.options.lineHeightMin, this.state.options.lineHeightMax)}px`, - `background:${this.state.options.lineColors[this.randint(0, this.state.options.lineColors.length - 1)]}`, - `left:${this.randint(-this.state.options.lineWidthMin / 2, this.state.width)}px`, + `width:${this.randint( + this.state.options.lineWidthMin, + this.state.options.lineWidthMax + )}px`, + `height:${this.randint( + this.state.options.lineHeightMin, + this.state.options.lineHeightMax + )}px`, + `background:${ + this.state.options.lineColors[ + this.randint(0, this.state.options.lineColors.length - 1) + ] + }`, + `left:${this.randint( + -this.state.options.lineWidthMin / 2, + this.state.width + )}px`, `top:${this.randint(0, this.state.height)}px`, - `transform:rotate(${this.randint(-30, 30)}deg)`, - `-o-transform:rotate(${this.randint(-30, 30)}deg)`, - `-ms-transform:rotate(${this.randint(-30, 30)}deg)`, - `-moz-transform:rotate(${this.randint(-30, 30)}deg)`, - `-webkit-transform:rotate(${this.randint(-30, 30)}deg)`, - `font-family:${this.state.options.fonts[this.randint(0, this.state.options.fonts.length - 1)]}`, - `font-weight:${this.randint(400, 900)}`, + `transform:${transStr};-o-transform:${transStr};-ms-transform:${transStr};-moz-transform:${transStr};-webkit-transform:${transStr}`, ].join(";"); } @@ -190,11 +259,13 @@ export default class Vcode extends React.PureComponent { * 绘制 * @param value 需要生成的字符值,不传则随机生成 * */ - onDraw(value: string | undefined) { + onDraw(value: string | undefined): string | null { let c = ""; // 存储生成的code const div = document.getElementById(this.state.id); - const isImg: boolean = /^http[s]*:\/\/|\.jpg$|\.png$|\.jpeg$|\.gif$|\.bmp$|\.webp$|^data:image/.test(value || ""); // 是否是图片 + const isImg: boolean = /^http[s]*:\/\/|\.jpg$|\.png$|\.jpeg$|\.gif$|\.bmp$|\.webp$|^data:image/.test( + value || "" + ); // 是否是图片 if (div) { div.innerHTML = ""; } @@ -202,7 +273,11 @@ export default class Vcode extends React.PureComponent { if (isImg) { // 用户传递了一张图片 const dom = document.createElement("img"); - dom.style.cssText = ["display: block", "max-width:100%", "max-height:100%"].join(";"); + dom.style.cssText = [ + "display: block", + "max-width:100%", + "max-height:100%", + ].join(";"); dom.src = value as string; div && div.appendChild(dom); this.props.onChange && this.props.onChange(null); @@ -210,13 +285,18 @@ export default class Vcode extends React.PureComponent { } // 不是图片而是普通字符串, 如果value存在说明是用户自定义的字符串 - let length = value ? value.length : this.state.len; // 字符的长度 + const length = value ? value.length : this.state.len; // 字符的长度 + const uW: number = this.state.width / length; // 每个字符能够占据的范围宽度 + const maxW = this.state.width - uW / 4; // 最大可偏移距离 - const uW: number = this.state.width / length / 1.01; // 每个字符占的宽度 for (let i = 0; i < length; i++) { const dom = document.createElement("span"); - dom.style.cssText = this.codeCss(uW, i); - const temp = value ? value[i] : this.state.options.codes[Math.round(Math.random() * (this.state.options.codes.length - 1))]; + dom.style.cssText = this.codeCss(uW, i, maxW); + const temp = value + ? value[i] + : this.state.options.codes[ + Math.round(Math.random() * (this.state.options.codes.length - 1)) + ]; dom.innerHTML = String(temp); c = `${c}${temp}`; div && div.appendChild(dom); @@ -233,13 +313,20 @@ export default class Vcode extends React.PureComponent { } /** 生成范围随机数 **/ - randint(n: number, m: number): number { + randint(n: number, m: number, t?: boolean): number { const c = m - n + 1; const num = Math.random() * c + n; - return Math.floor(num); + return t ? num : Math.floor(num); } render() { - return
this.onClick()} />; + return ( +
this.onClick()} + /> + ); } } diff --git a/tsconfig.json b/tsconfig.json index a7a3fa2..9e55782 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "noImplicitReturns": true, "noImplicitAny": false, "noFallthroughCasesInSwitch": true, + "outDir": "dist", "lib": ["es2018", "dom"] }, diff --git a/webpack.build.config.js b/webpack.build.config.js index d6faaed..d3c05b5 100644 --- a/webpack.build.config.js +++ b/webpack.build.config.js @@ -15,6 +15,7 @@ module.exports = { filename: "[name].js", library: "vcode", libraryTarget: "umd", + globalObject: 'this' //libraryExport: 'default', }, externals: { diff --git a/webpack.config.js b/webpack.config.js index 87e3c10..8345752 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -16,7 +16,7 @@ module.exports = { { test: /\.css?$/, use: ["style-loader", "css-loader", "postcss-loader"], - include: [path.join(__dirname, "src")], + include: [path.join(__dirname, "example")], }, { test: /\.(png|jpg|gif)$/, @@ -26,7 +26,7 @@ module.exports = { { test: /\.(eot|woff|svg|ttf|woff2|appcache|mp3|pdf|png)(\?|$)/, use: ["file-loader?name=files/[name].[ext]"], - include: [path.join(__dirname, "src")], + include: [path.join(__dirname, "example")], }, ], },