From 206b37edc11e5816e91b7811ae2aef93ea517eeb Mon Sep 17 00:00:00 2001 From: SemmyWong <67748948+semmywong@users.noreply.github.com> Date: Tue, 18 Jan 2022 09:52:29 +0800 Subject: [PATCH] feature/nocobase next password (#159) * feat: add Password Component into schema components * improve code Co-authored-by: chenos --- .../client/src/schema-component/antd/index.ts | 1 + .../antd/password/Password.tsx | 69 ++++++++ .../antd/password/PasswordStrength.tsx | 162 ++++++++++++++++++ .../antd/password/demos/demo1.tsx | 41 +++++ .../antd/password/demos/demo2.tsx | 45 +++++ .../schema-component/antd/password/index.md | 16 ++ .../schema-component/antd/password/index.ts | 1 + 7 files changed, 335 insertions(+) create mode 100644 packages/client/src/schema-component/antd/password/Password.tsx create mode 100644 packages/client/src/schema-component/antd/password/PasswordStrength.tsx create mode 100644 packages/client/src/schema-component/antd/password/demos/demo1.tsx create mode 100644 packages/client/src/schema-component/antd/password/demos/demo2.tsx create mode 100644 packages/client/src/schema-component/antd/password/index.ts diff --git a/packages/client/src/schema-component/antd/index.ts b/packages/client/src/schema-component/antd/index.ts index 2baa7d275..991032dd6 100644 --- a/packages/client/src/schema-component/antd/index.ts +++ b/packages/client/src/schema-component/antd/index.ts @@ -8,5 +8,6 @@ export * from './form'; export * from './form-item'; export * from './grid'; export * from './input'; +export * from './password'; export * from './radio'; export * from './menu'; \ No newline at end of file diff --git a/packages/client/src/schema-component/antd/password/Password.tsx b/packages/client/src/schema-component/antd/password/Password.tsx new file mode 100644 index 000000000..31257a7e6 --- /dev/null +++ b/packages/client/src/schema-component/antd/password/Password.tsx @@ -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 ( + + + {checkStrength && ( + + {(score) => { + return ( +
+
+
+
+
+
+
+ ); + }} + + )} + + ); + }, + mapReadPretty((props) => { + if (!props.value) { + return
; + } + return
********
; + }), +); + +export default Password; diff --git a/packages/client/src/schema-component/antd/password/PasswordStrength.tsx b/packages/client/src/schema-component/antd/password/PasswordStrength.tsx new file mode 100644 index 000000000..a910ab088 --- /dev/null +++ b/packages/client/src/schema-component/antd/password/PasswordStrength.tsx @@ -0,0 +1,162 @@ +import { isFn } from '@formily/shared'; +import React, { Fragment } from 'react'; + +type ReactRenderPropsChildren = React.ReactNode | ((props: T) => React.ReactElement); + +interface IPasswordStrengthProps { + value?: any; + children?: ReactRenderPropsChildren; +} + +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 = (props) => { + if (isFn(props.children)) { + return props.children(getStrength(String(props.value || ''))); + } else { + return {props.children}; + } +}; diff --git a/packages/client/src/schema-component/antd/password/demos/demo1.tsx b/packages/client/src/schema-component/antd/password/demos/demo1.tsx new file mode 100644 index 000000000..c7b6f0cb2 --- /dev/null +++ b/packages/client/src/schema-component/antd/password/demos/demo1.tsx @@ -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 ( + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/password/demos/demo2.tsx b/packages/client/src/schema-component/antd/password/demos/demo2.tsx new file mode 100644 index 000000000..b6c3710c9 --- /dev/null +++ b/packages/client/src/schema-component/antd/password/demos/demo2.tsx @@ -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 ( + + + + ); + }; + \ No newline at end of file diff --git a/packages/client/src/schema-component/antd/password/index.md b/packages/client/src/schema-component/antd/password/index.md index a25b4f20d..f2b0a9e43 100644 --- a/packages/client/src/schema-component/antd/password/index.md +++ b/packages/client/src/schema-component/antd/password/index.md @@ -6,3 +6,19 @@ group: --- # Password + +## Examples + +### Password + + + +### Check strength + + + +## API + +基于 antd 的 Password,新增的属性: + +- `checkStrength` 检测密码强度 diff --git a/packages/client/src/schema-component/antd/password/index.ts b/packages/client/src/schema-component/antd/password/index.ts new file mode 100644 index 000000000..74946a7c0 --- /dev/null +++ b/packages/client/src/schema-component/antd/password/index.ts @@ -0,0 +1 @@ +export * from './Password';