wsj
2026-10-17 5630cce7759c27d910fbeeec370a2e9f6b4b26b1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import { useState } from 'react';
import {
  CheckCircleFilled,
  LockOutlined,
  SafetyCertificateOutlined,
  UserOutlined,
} from '@ant-design/icons';
import { App as AntdApp, Button, Form, Input, Tag } from 'antd';
import { Navigate, useLocation, useNavigate } from 'react-router-dom';
import { useAuthStore } from '@/store/auth';
import type { LoginParams } from '@/types/auth';
import './Login.scss';
 
export default function Login() {
  const [form] = Form.useForm<LoginParams>();
  const [loading, setLoading] = useState(false);
 
  const token = useAuthStore((state) => state.token);
  const login = useAuthStore((state) => state.login);
  const navigate = useNavigate();
  const location = useLocation();
  const { message } = AntdApp.useApp();
 
  if (token) {
    return <Navigate to="/dashboard" replace />;
  }
 
  const from = (location.state as { from?: string } | null)?.from;
  const redirectTo = from && from.startsWith('/') && from !== '/login' ? from : '/dashboard';
 
  const handleFinish = async (values: LoginParams) => {
    setLoading(true);
    try {
      await login(values);
      message.success('登录成功,欢迎回来');
      navigate(redirectTo, { replace: true });
    } catch (error) {
      message.error(error instanceof Error ? error.message : '登录失败,请稍后重试');
    } finally {
      setLoading(false);
    }
  };
 
  const fillAccount = (username: string, password: string) => {
    form.setFieldsValue({ username, password });
  };
 
  return (
    <div className="login-page">
      <div className="login-shell">
        <section className="login-brand">
          <div className="login-brand__logo">
            <span>C</span>
            CRM Admin
          </div>
 
          <div className="login-brand__content">
            <h1>
              让团队协作
              <br />
              更简单、更高效
            </h1>
            <p>一套开箱即用的中后台管理框架,内置多级菜单、路由守卫与按钮级权限。</p>
            <ul>
              <li>
                <CheckCircleFilled /> 2-3 级嵌套路由与动态菜单
              </li>
              <li>
                <CheckCircleFilled /> 登录态校验与页面访问控制
              </li>
              <li>
                <CheckCircleFilled /> 按钮级权限码精细控制
              </li>
            </ul>
          </div>
 
          <div className="login-brand__footer">Vite · React · TypeScript · Ant Design</div>
        </section>
 
        <section className="login-panel">
          <div className="login-panel__inner">
            <div className="login-panel__header">
              <Tag color="purple" bordered={false}>
                CRM 企业管理平台
              </Tag>
              <h2>欢迎回来 👋</h2>
              <p>请登录你的账号以继续访问后台</p>
            </div>
 
            <Form
              form={form}
              layout="vertical"
              size="large"
              requiredMark={false}
              initialValues={{ username: 'admin', password: 'admin123' }}
              onFinish={handleFinish}
            >
              <Form.Item
                name="username"
                label="用户名"
                rules={[{ required: true, message: '请输入用户名' }]}
              >
                <Input
                  prefix={<UserOutlined />}
                  placeholder="请输入用户名"
                  autoComplete="username"
                />
              </Form.Item>
 
              <Form.Item
                name="password"
                label="密码"
                rules={[{ required: true, message: '请输入密码' }]}
              >
                <Input.Password
                  prefix={<LockOutlined />}
                  placeholder="请输入密码"
                  autoComplete="current-password"
                />
              </Form.Item>
 
              <div className="login-panel__helper">
                <span>登录即表示同意平台使用规范</span>
                <a onClick={() => message.info('请联系系统管理员重置密码')}>忘记密码?</a>
              </div>
 
              <Button type="primary" htmlType="submit" block loading={loading}>
                登 录
              </Button>
            </Form>
 
            <div className="login-demo">
              <div className="login-demo__title">
                <SafetyCertificateOutlined /> 演示账号
              </div>
              <div className="login-demo__accounts">
                <button type="button" onClick={() => fillAccount('admin', 'admin123')}>
                  <span>管理员</span>
                  <code>admin / admin123</code>
                </button>
                <button type="button" onClick={() => fillAccount('user', 'user123')}>
                  <span>普通用户</span>
                  <code>user / user123</code>
                </button>
              </div>
            </div>
          </div>
        </section>
      </div>
    </div>
  );
}