瀏覽代碼

feat: 新增密码强度组件

lanjianrong 4 年之前
父節點
當前提交
5e740049c1

+ 3 - 1
package.json

@@ -124,6 +124,7 @@
     "@types/react-router-dom": "^5.1.5",
     "@types/redux": "^3.6.0",
     "@types/redux-thunk": "^2.1.0",
+    "@types/zxcvbn": "^4.4.0",
     "@typescript-eslint/eslint-plugin": "^4.1.1",
     "@typescript-eslint/parser": "^4.1.1",
     "babel-jest": "^24.9.0",
@@ -208,6 +209,7 @@
     "react-activation": "^0.7.0",
     "react-dom": "^16.13.1",
     "react-router": "^5.2.0",
-    "react-router-dom": "^5.2.0"
+    "react-router-dom": "^5.2.0",
+    "zxcvbn": "^4.4.2"
   }
 }

+ 67 - 0
src/components/StrengthMeter/index.scss

@@ -0,0 +1,67 @@
+.zh-strength-meter {
+  position: relative;
+
+  &-bar {
+    position: relative;
+    height: 4px;
+    margin: 10px auto 6px;
+    background: rgba(0, 0, 0, 0.25);
+    border-radius: 3px;
+
+    &::before,
+    &::after {
+      position: absolute;
+      z-index: 10;
+      display: block;
+      width: 20%;
+      height: inherit;
+      content: '';
+      background: transparent;
+      border-color: #ffffff;
+      border-style: solid;
+      border-width: 0 5px 0 5px;
+    }
+
+    &::before {
+      left: 20%;
+    }
+
+    &::after {
+      right: 20%;
+    }
+
+    &--fill {
+      position: absolute;
+      width: 0;
+      height: inherit;
+      background: transparent;
+      border-radius: inherit;
+      transition: width 0.5s ease-in-out, background 0.25s;
+
+      &[data-score='0'] {
+        width: 20%;
+        background: #e74242;
+      }
+
+      &[data-score='1'] {
+        width: 40%;
+        background: #ed6f6f;
+      }
+
+      &[data-score='2'] {
+        width: 60%;
+        background: #efbd47;
+      }
+
+      &[data-score='3'] {
+        width: 80%;
+        background: rgba(85, 209, 135, 0.5);
+      }
+
+      &[data-score='4'] {
+        width: 100%;
+        background: #55d187;
+      }
+    }
+  }
+}

+ 22 - 0
src/components/StrengthMeter/index.tsx

@@ -0,0 +1,22 @@
+import { Input } from 'antd'
+import { PasswordProps } from 'antd/lib/input'
+import React, { ChangeEvent, useState } from 'react'
+import zxcvbn from 'zxcvbn'
+import './index.scss'
+
+const Index: React.FC<PasswordProps> = (props) => {
+  const [ score, setScore ] = useState<number | null>(null)
+  const handleChangeEvent = (event: ChangeEvent<HTMLInputElement>) => {
+    setScore(event.target.value ? zxcvbn(event.target.value, []).score : null)
+  }
+  return (
+    <div className="zh-strength-meter">
+      <Input.Password {...props} onChange={handleChangeEvent}/>
+      <div className="zh-strength-meter-bar">
+      <div className="zh-strength-meter-bar--fill" data-score={score} />
+    </div>
+    </div>
+  )
+}
+
+export default Index

+ 29 - 14
src/pages/Account/Safe/index.tsx

@@ -4,21 +4,19 @@ import { Button, Form, Input, message } from 'antd'
 import styles from './index.module.scss'
 import consts from '@/utils/consts'
 import { apiChangePassword } from './api'
-
+import StrengthMeter from '@/components/StrengthMeter'
 
 export default function Index() {
   const [ form ] = Form.useForm()
   const [ loading, setLoading ] = useState<boolean>(false)
-
+  // const [ score, setScore ] = useState<number>(0)
   // 保存表单信息
-  const changePassword = async (values:any) => {
+  const changePassword = async (values: any) => {
     setLoading(true)
-
     const { code = -1 } = await apiChangePassword(values)
     if (code === consts.RET_CODE.SUCCESS) {
       message.success("更新成功!")
     }
-
     setLoading(false)
   }
 
@@ -30,18 +28,35 @@ export default function Index() {
           <Form.Item name="password" label="原密码" rules={[ { required: true, message: '请输入原密码' } ]}>
             <Input.Password />
           </Form.Item>
-          <Form.Item name="newPassword" label="新密码" rules={[ { required: true, message: '请输入新密码' } ]}>
-          <Input.Password />
+          <Form.Item name="newPassword" label="新密码" rules={[ { required: true, message: '请输入新密码' }, () => ({
+            validator(_, value: string) {
+              // setScore(pswRef && pswRef.current?.state.value ? zxcvbn(pswRef.current.state.value, []).score : 0)
+              if (!value) return Promise.resolve()
+              if (/(?=.*[0-9])(?=.*[a-zA-Z]).{6,30}/.test(value)) {
+                return Promise.resolve()
+              } else {
+                return Promise.reject('密码必须带有字母与数字的组合且大于6位')
+              }
+            }
+          }) ]}>
+            <StrengthMeter />
           </Form.Item>
-          <Form.Item name="confirmPassword" label="确认新密码" rules={[ { required: true, message: '请输入确认新密码' } ]}>
-          <Input.Password />
+          <Form.Item name="confirmPassword" label="确认新密码" rules={[ { required: true, message: '请输入确认新密码' }, ({ getFieldValue }) => ({
+            validator(_, value) {
+              if (getFieldValue('newPassword') !== value) {
+                return Promise.reject('密码不一致')
+              }
+              return Promise.resolve()
+            }
+          }) ]}>
+            <Input.Password />
           </Form.Item>
 
-          <Button type="primary" size="small" loading={loading}  onClick={() => {
-              form.validateFields().then(values => {
-                 changePassword(values)
-              })
-            }}>保存修改</Button>
+          <Button type="primary" size="small" loading={loading} onClick={() => {
+            form.validateFields().then(values => {
+              changePassword(values)
+            })
+          }}>保存修改</Button>
         </Form>
       </div>
     </div>

+ 31 - 22
src/pages/Management/Setting/components/PswModal.tsx

@@ -6,6 +6,7 @@ import { Form, Input, message, Modal } from 'antd'
 import React, { useEffect, useState } from 'react'
 import { apiAccountChange } from '../api'
 import styles from '../index.module.scss'
+import StrengthMeter from '@/components/StrengthMeter'
 interface iPswModalProps {
   visible: boolean
   onCancel: () => void
@@ -16,13 +17,13 @@ interface iValues {
   ps_account: string
   ps_password: string
 }
-const PswModal:React.FC<iPswModalProps> = ({ visible, onCancel, userInfo: { id, account } }) => {
+const PswModal: React.FC<iPswModalProps> = ({ visible, onCancel, userInfo: { id, account } }) => {
   const [ loading, setLoading ] = useState(false)
   const [ form ] = Form.useForm()
   const onCreate = async (values: iValues) => {
     setLoading(true)
     const { code = -1 } = await apiAccountChange(values)
-    if ( code === consts.RET_CODE.SUCCESS) {
+    if (code === consts.RET_CODE.SUCCESS) {
       message.success('修改成功')
       onCancel()
     }
@@ -32,7 +33,7 @@ const PswModal:React.FC<iPswModalProps> = ({ visible, onCancel, userInfo: { id,
     form.setFieldsValue({ id, ps_account: account })
   }, [ visible ])
 
-  const pswHandler = () => {
+  const handlePsw = () => {
     const ps_password = generatePsw(12)
     form.setFieldsValue({ ps_password })
   }
@@ -52,26 +53,34 @@ const PswModal:React.FC<iPswModalProps> = ({ visible, onCancel, userInfo: { id,
             onCreate(values)
           })
         }}
+      >
+
+        <Form
+          form={form}
+          layout="vertical"
+          className={styles.FormContent}
         >
-          <Form
-            form={form}
-            layout="vertical"
-            className={styles.FormContent}
-            >
-            <Form.Item name="id" hidden>
-              <Input />
-            </Form.Item>
-            <Form.Item name="ps_account" label="登录账号" rules={[ { required: true, message: '请输入账号' } ]}>
-              <Input />
-            </Form.Item>
-            <Form.Item name="ps_password" label="登录密码" rules={[ { required: true, message: '请输入登录密码' } ]}>
-              <Input.Password
-                placeholder="密码支持英文数字及符号"
-                addonAfter={<span className="pi-pd-lr-11" onClick={() => pswHandler()}>随机密码</span>}
-                iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)}
-              />
-        </Form.Item>
-          </Form>
+          <Form.Item name="id" hidden>
+            <Input />
+          </Form.Item>
+          <Form.Item name="ps_account" label="登录账号" rules={[ { required: true, message: '请输入账号' } ]}>
+            <Input />
+          </Form.Item>
+          <Form.Item name="ps_password" label="登录密码" rules={[ { required: true, message: '请输入登录密码' }, () => ({
+            validator(_, value) {
+              if (/(?=.*[0-9])(?=.*[a-zA-Z]).{6,30}/.test(value)) {
+                return Promise.resolve()
+              }
+              return Promise.reject('密码必须带有字母与数字的组合且大于6位')
+            }
+          }) ]}>
+            <StrengthMeter placeholder="密码支持英文数字及符号"
+              addonAfter={<span className="pi-pd-lr-11" onClick={() => handlePsw()}>随机密码</span>}
+              allowClear
+              iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} />
+
+          </Form.Item>
+        </Form>
       </Modal>
     </div>
   )