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")],
},
],
},