feature/nocobase next password (#159)
* feat: add Password Component into schema components * improve code Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
637b3165ca
commit
206b37edc1
@ -8,5 +8,6 @@ export * from './form';
|
|||||||
export * from './form-item';
|
export * from './form-item';
|
||||||
export * from './grid';
|
export * from './grid';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
|
export * from './password';
|
||||||
export * from './radio';
|
export * from './radio';
|
||||||
export * from './menu';
|
export * from './menu';
|
@ -0,0 +1,69 @@
|
|||||||
|
import { connect, mapReadPretty } from '@formily/react';
|
||||||
|
import { isValid } from '@formily/shared';
|
||||||
|
import { Input } from 'antd';
|
||||||
|
import { PasswordProps } from 'antd/lib/input';
|
||||||
|
import React from 'react';
|
||||||
|
import { PasswordStrength } from './PasswordStrength';
|
||||||
|
|
||||||
|
export interface IPasswordProps extends PasswordProps {
|
||||||
|
checkStrength: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Password = connect(
|
||||||
|
(props: IPasswordProps) => {
|
||||||
|
const { value, className, checkStrength, ...others } = props;
|
||||||
|
const blockStyle: React.CSSProperties = {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 1,
|
||||||
|
height: 8,
|
||||||
|
top: 0,
|
||||||
|
background: '#fff',
|
||||||
|
width: 1,
|
||||||
|
transform: 'translate(-50%, 0)',
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
<Input.Password {...others} value={value} />
|
||||||
|
{checkStrength && (
|
||||||
|
<PasswordStrength value={value}>
|
||||||
|
{(score) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: '#e0e0e0',
|
||||||
|
marginBottom: 3,
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ ...blockStyle, left: '20%' }} />
|
||||||
|
<div style={{ ...blockStyle, left: '40%' }} />
|
||||||
|
<div style={{ ...blockStyle, left: '60%' }} />
|
||||||
|
<div style={{ ...blockStyle, left: '80%' }} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
backgroundImage: '-webkit-linear-gradient(left, #ff5500, #ff9300)',
|
||||||
|
transition: 'all 0.35s ease-in-out',
|
||||||
|
height: 8,
|
||||||
|
width: '100%',
|
||||||
|
marginTop: 5,
|
||||||
|
clipPath: `polygon(0 0,${score}% 0,${score}% 100%,0 100%)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</PasswordStrength>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
mapReadPretty((props) => {
|
||||||
|
if (!props.value) {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
return <div>********</div>;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Password;
|
@ -0,0 +1,162 @@
|
|||||||
|
import { isFn } from '@formily/shared';
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
|
||||||
|
type ReactRenderPropsChildren<T = any> = React.ReactNode | ((props: T) => React.ReactElement);
|
||||||
|
|
||||||
|
interface IPasswordStrengthProps {
|
||||||
|
value?: any;
|
||||||
|
children?: ReactRenderPropsChildren<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNum = function (c) {
|
||||||
|
return c >= 48 && c <= 57;
|
||||||
|
};
|
||||||
|
const isLower = function (c) {
|
||||||
|
return c >= 97 && c <= 122;
|
||||||
|
};
|
||||||
|
const isUpper = function (c) {
|
||||||
|
return c >= 65 && c <= 90;
|
||||||
|
};
|
||||||
|
const isSymbol = function (c) {
|
||||||
|
return !(isLower(c) || isUpper(c) || isNum(c));
|
||||||
|
};
|
||||||
|
const isLetter = function (c) {
|
||||||
|
return isLower(c) || isUpper(c);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStrength = (val) => {
|
||||||
|
if (!val) return 0;
|
||||||
|
let num = 0;
|
||||||
|
let lower = 0;
|
||||||
|
let upper = 0;
|
||||||
|
let symbol = 0;
|
||||||
|
let MNS = 0;
|
||||||
|
let rep = 0;
|
||||||
|
let repC = 0;
|
||||||
|
let consecutive = 0;
|
||||||
|
let sequential = 0;
|
||||||
|
const len = () => num + lower + upper + symbol;
|
||||||
|
const callMe = () => {
|
||||||
|
let re = num > 0 ? 1 : 0;
|
||||||
|
re += lower > 0 ? 1 : 0;
|
||||||
|
re += upper > 0 ? 1 : 0;
|
||||||
|
re += symbol > 0 ? 1 : 0;
|
||||||
|
if (re > 2 && len() >= 8) {
|
||||||
|
return re + 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (let i = 0; i < val.length; i++) {
|
||||||
|
const c = val.charCodeAt(i);
|
||||||
|
if (isNum(c)) {
|
||||||
|
num++;
|
||||||
|
if (i !== 0 && i !== val.length - 1) {
|
||||||
|
MNS++;
|
||||||
|
}
|
||||||
|
if (i > 0 && isNum(val.charCodeAt(i - 1))) {
|
||||||
|
consecutive++;
|
||||||
|
}
|
||||||
|
} else if (isLower(c)) {
|
||||||
|
lower++;
|
||||||
|
if (i > 0 && isLower(val.charCodeAt(i - 1))) {
|
||||||
|
consecutive++;
|
||||||
|
}
|
||||||
|
} else if (isUpper(c)) {
|
||||||
|
upper++;
|
||||||
|
if (i > 0 && isUpper(val.charCodeAt(i - 1))) {
|
||||||
|
consecutive++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
symbol++;
|
||||||
|
if (i !== 0 && i !== val.length - 1) {
|
||||||
|
MNS++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let exists = false;
|
||||||
|
for (let j = 0; j < val.length; j++) {
|
||||||
|
if (val[i] === val[j] && i !== j) {
|
||||||
|
exists = true;
|
||||||
|
repC += Math.abs(val.length / (j - i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exists) {
|
||||||
|
rep++;
|
||||||
|
const unique = val.length - rep;
|
||||||
|
repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC);
|
||||||
|
}
|
||||||
|
if (i > 1) {
|
||||||
|
const last1 = val.charCodeAt(i - 1);
|
||||||
|
const last2 = val.charCodeAt(i - 2);
|
||||||
|
if (isLetter(c)) {
|
||||||
|
if (isLetter(last1) && isLetter(last2)) {
|
||||||
|
const v = val.toLowerCase();
|
||||||
|
const vi = v.charCodeAt(i);
|
||||||
|
const vi1 = v.charCodeAt(i - 1);
|
||||||
|
const vi2 = v.charCodeAt(i - 2);
|
||||||
|
if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) {
|
||||||
|
sequential++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isNum(c)) {
|
||||||
|
if (isNum(last1) && isNum(last2)) {
|
||||||
|
if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
|
||||||
|
sequential++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isSymbol(last1) && isSymbol(last2)) {
|
||||||
|
if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
|
||||||
|
sequential++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let sum = 0;
|
||||||
|
const length = len();
|
||||||
|
sum += 4 * length;
|
||||||
|
if (lower > 0) {
|
||||||
|
sum += 2 * (length - lower);
|
||||||
|
}
|
||||||
|
if (upper > 0) {
|
||||||
|
sum += 2 * (length - upper);
|
||||||
|
}
|
||||||
|
if (num !== length) {
|
||||||
|
sum += 4 * num;
|
||||||
|
}
|
||||||
|
sum += 6 * symbol;
|
||||||
|
sum += 2 * MNS;
|
||||||
|
sum += 2 * callMe();
|
||||||
|
if (length === lower + upper) {
|
||||||
|
sum -= length;
|
||||||
|
}
|
||||||
|
if (length === num) {
|
||||||
|
sum -= num;
|
||||||
|
}
|
||||||
|
sum -= repC;
|
||||||
|
sum -= 2 * consecutive;
|
||||||
|
sum -= 3 * sequential;
|
||||||
|
sum = sum < 0 ? 0 : sum;
|
||||||
|
sum = sum > 100 ? 100 : sum;
|
||||||
|
|
||||||
|
if (sum >= 80) {
|
||||||
|
return 100;
|
||||||
|
} else if (sum >= 60) {
|
||||||
|
return 80;
|
||||||
|
} else if (sum >= 40) {
|
||||||
|
return 60;
|
||||||
|
} else if (sum >= 20) {
|
||||||
|
return 40;
|
||||||
|
} else {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PasswordStrength: React.FC<IPasswordStrengthProps> = (props) => {
|
||||||
|
if (isFn(props.children)) {
|
||||||
|
return props.children(getStrength(String(props.value || '')));
|
||||||
|
} else {
|
||||||
|
return <Fragment>{props.children}</Fragment>;
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* title: Password
|
||||||
|
*/
|
||||||
|
import { FormItem } from '@formily/antd';
|
||||||
|
import { Password, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Editable`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Password',
|
||||||
|
'x-reactions': {
|
||||||
|
target: 'read',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Read pretty`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Password',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentProvider components={{ Password, FormItem }}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* title: Check strength
|
||||||
|
*/
|
||||||
|
import { FormItem } from '@formily/antd';
|
||||||
|
import { Password, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Editable`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Password',
|
||||||
|
'x-component-props': {
|
||||||
|
checkStrength: true,
|
||||||
|
},
|
||||||
|
'x-reactions': {
|
||||||
|
target: 'read',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Read pretty`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Password',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentProvider components={{ Password, FormItem }}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -6,3 +6,19 @@ group:
|
|||||||
---
|
---
|
||||||
|
|
||||||
# Password
|
# Password
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Password
|
||||||
|
|
||||||
|
<code src="./demos/demo1.tsx" />
|
||||||
|
|
||||||
|
### Check strength
|
||||||
|
|
||||||
|
<code src="./demos/demo2.tsx" />
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
基于 antd 的 Password,新增的属性:
|
||||||
|
|
||||||
|
- `checkStrength` 检测密码强度
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export * from './Password';
|
Loading…
Reference in New Issue
Block a user