Parcourir la source

perf: 密码修改页面优化

BaiLuoYan il y a 2 jours
Parent
commit
9cb47096c5

+ 42 - 0
src/components/ForceChangePasswordModal/PasswordFields.tsx

@@ -0,0 +1,42 @@
+import { ProFormText } from '@ant-design/pro-components';
+import type { Rule } from 'antd/es/form';
+import { memo } from 'react';
+
+const passwordRules: Rule[] = [
+  {
+    required: true,
+    validator(_, value) {
+      if (!value) return Promise.reject(new Error('请输入新密码'));
+      if (value.length < 8) return Promise.reject(new Error('密码长度不能少于8位'));
+      if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?])/.test(value))
+        return Promise.reject(new Error('密码必须包含大小写字母、数字和特殊字符'));
+      return Promise.resolve();
+    },
+  },
+];
+
+const confirmPasswordRules: Rule[] = [
+  { required: true, message: '请输入确认密码' },
+  ({ getFieldValue }) => ({
+    validator(_, value) {
+      if (!value || getFieldValue('newPassword') === value) {
+        return Promise.resolve();
+      }
+      return Promise.reject(new Error('两次输入的密码不一致'));
+    },
+  }),
+];
+
+export const PasswordFields = memo(function PasswordFields() {
+  return (
+    <>
+      <ProFormText.Password
+        name="oldPassword"
+        label="旧密码"
+        rules={[{ required: true, message: '请输入旧密码' }]}
+      />
+      <ProFormText.Password name="newPassword" label="新密码" rules={passwordRules} />
+      <ProFormText.Password name="confirmPassword" label="确认密码" rules={confirmPasswordRules} />
+    </>
+  );
+});

+ 2 - 1
src/components/ForceChangePasswordModal/index.tsx

@@ -1,4 +1,3 @@
-import { PasswordFields } from '@/pages/Sys/ModifyPassword/components/PasswordFields';
 import * as api from '@/services/login';
 import { message } from '@/utils/antdAppInstance';
 import { setToken } from '@/utils/authUtils';
@@ -6,6 +5,8 @@ import { useModel } from '@umijs/max';
 import { Form, Modal } from 'antd';
 import { flushSync } from 'react-dom';
 
+import { PasswordFields } from './PasswordFields';
+
 export const ForceChangePasswordModal = () => {
   const { initialState, setInitialState } = useModel('@@initialState');
   const [form] = Form.useForm();

+ 2 - 2
src/components/RightContent/AvatarDropdown.tsx

@@ -44,9 +44,9 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
   const { initialState, setInitialState } = useModel('@@initialState');
 
   const loginOut = async () => {
-    await fetchLogout();
-    toLoginPage();
+    await fetchLogout().catch(() => {});
     removeToken();
+    toLoginPage();
   };
 
   const { styles } = useStyles();

+ 6 - 3
src/pages/Sys/Login/hooks/useCapWidget.ts

@@ -9,9 +9,12 @@ export const useCapWidget = () => {
   const [capError, setCapError] = useState('');
 
   useEffect(() => {
-    loginApi.fetchCapEndpoint().then((res) => {
-      setCapEndpoint(res.data?.data ?? '');
-    });
+    loginApi
+      .fetchCapEndpoint()
+      .then((res) => {
+        setCapEndpoint(res.data?.data ?? '');
+      })
+      .catch(() => {});
   }, []);
 
   useEffect(() => {

+ 1 - 0
src/pages/Sys/Login/hooks/useCaptcha.ts

@@ -10,6 +10,7 @@ export const useCaptcha = () => {
     loginApi
       .fetchCaptcha()
       .then((res) => setCaptchaInfo(res.data ?? null))
+      .catch(() => {})
       .finally(() => setCaptchaLoading(false));
   }, []);
 

+ 0 - 34
src/pages/Sys/ModifyPassword/components/PasswordFields.tsx

@@ -1,34 +0,0 @@
-import { ProFormText } from '@ant-design/pro-components';
-import { memo } from 'react';
-
-import { confirmPasswordRules, passwordRules } from '../lib/rules';
-
-export const PasswordFields = memo(function PasswordFields() {
-  return (
-    <div className="flex justify-center">
-      <div className="w-90">
-        <ProFormText
-          name="oldPassword"
-          label="旧密码"
-          rules={[{ required: true, message: '请输入旧密码' }]}
-          formItemProps={{ layout: 'horizontal' }}
-          fieldProps={{ type: 'password' }}
-        />
-        <ProFormText
-          name="newPassword"
-          label="新密码"
-          rules={passwordRules}
-          formItemProps={{ layout: 'horizontal' }}
-          fieldProps={{ type: 'password' }}
-        />
-        <ProFormText
-          name="confirmPassword"
-          label="确认密码"
-          rules={confirmPasswordRules}
-          formItemProps={{ layout: 'horizontal' }}
-          fieldProps={{ type: 'password' }}
-        />
-      </div>
-    </div>
-  );
-});

+ 52 - 0
src/pages/Sys/ModifyPassword/components/SecurityTips.tsx

@@ -0,0 +1,52 @@
+import { CheckCircleOutlined, LockOutlined } from '@ant-design/icons';
+import { memo } from 'react';
+
+interface RuleCheck {
+  label: string;
+  test: (v: string) => boolean;
+}
+
+const ruleChecks: RuleCheck[] = [
+  { label: '长度 8 - 72 位', test: (v) => v.length >= 8 && v.length <= 72 },
+  { label: '包含大写字母(A-Z)', test: (v) => /[A-Z]/.test(v) },
+  { label: '包含小写字母(a-z)', test: (v) => /[a-z]/.test(v) },
+  { label: '包含数字(0-9)', test: (v) => /\d/.test(v) },
+  {
+    label: '包含特殊字符(如 !@#$%^&*)',
+    test: (v) => /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(v),
+  },
+];
+
+interface SecurityTipsProps {
+  newPassword?: string;
+}
+
+export const SecurityTips = memo(function SecurityTips({ newPassword = '' }: SecurityTipsProps) {
+  return (
+    <div className="h-full flex flex-col justify-center px-6 py-8 bg-(--ant-color-fill-quaternary) rounded-lg">
+      <div className="flex items-center gap-2 mb-4">
+        <LockOutlined className="text-xl text-(--ant-color-primary)" />
+        <span className="text-base font-medium">密码安全要求</span>
+      </div>
+      <ul className="flex flex-col gap-3 m-0 p-0 list-none">
+        {ruleChecks.map(({ label, test }) => {
+          const passed = newPassword.length > 0 && test(newPassword);
+          return (
+            <li
+              key={label}
+              className="flex items-center gap-2 text-sm text-(--ant-color-text-secondary)"
+            >
+              <CheckCircleOutlined
+                className="shrink-0 transition-colors"
+                style={{
+                  color: passed ? 'var(--ant-color-primary)' : 'var(--ant-color-text-quaternary)',
+                }}
+              />
+              {label}
+            </li>
+          );
+        })}
+      </ul>
+    </div>
+  );
+});

+ 21 - 13
src/pages/Sys/ModifyPassword/index.tsx

@@ -1,25 +1,33 @@
+import { PasswordFields } from '@/components/ForceChangePasswordModal/PasswordFields';
 import { SaveOutlined } from '@ant-design/icons';
-import { Button, Card, Form } from 'antd';
+import { PageContainer } from '@ant-design/pro-components';
+import { Button, Card, Col, Form, Row } from 'antd';
 
-import { PasswordFields } from './components/PasswordFields';
+import { SecurityTips } from './components/SecurityTips';
 import { useModifyPassword } from './hooks/useModifyPassword';
 
 const ModifyPassword = () => {
   const { form, loading, handleSave } = useModifyPassword();
+  const newPassword: string = Form.useWatch('newPassword', form) ?? '';
 
   return (
-    <div className="p-6">
-      <Card title="修改密码" className="max-w-200 mx-auto">
-        <Form form={form} layout="vertical" labelCol={{ span: 8 }}>
-          <PasswordFields />
-        </Form>
-        <div className="text-center mt-6">
-          <Button type="primary" icon={<SaveOutlined />} onClick={handleSave} loading={loading}>
-            保存修改
-          </Button>
-        </div>
+    <PageContainer title="修改密码">
+      <Card>
+        <Row gutter={48} align="stretch">
+          <Col xs={24} md={10} lg={8}>
+            <SecurityTips newPassword={newPassword} />
+          </Col>
+          <Col xs={24} md={14} lg={16}>
+            <Form form={form} layout="vertical" className="max-w-120">
+              <PasswordFields />
+              <Button type="primary" icon={<SaveOutlined />} onClick={handleSave} loading={loading}>
+                保存修改
+              </Button>
+            </Form>
+          </Col>
+        </Row>
       </Card>
-    </div>
+    </PageContainer>
   );
 };
 

+ 0 - 22
src/pages/Sys/ModifyPassword/lib/rules.ts

@@ -1,22 +0,0 @@
-import type { Rule } from 'antd/es/form';
-
-export const passwordRules: Rule[] = [
-  { required: true, message: '请输入新密码' },
-  { min: 8, message: '密码长度不能少于8位' },
-  {
-    pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?])/,
-    message: '密码必须包含大小写字母、数字和特殊字符',
-  },
-];
-
-export const confirmPasswordRules: Rule[] = [
-  { required: true, message: '请输入确认密码' },
-  ({ getFieldValue }) => ({
-    validator(_, value) {
-      if (!value || getFieldValue('newPassword') === value) {
-        return Promise.resolve();
-      }
-      return Promise.reject(new Error('两次输入的密码不一致'));
-    },
-  }),
-];