Skip to content

Commit e17300a

Browse files
committed
feat: 支持 gitee、github 登录
1 parent 88c183e commit e17300a

File tree

17 files changed

+750
-412
lines changed

17 files changed

+750
-412
lines changed

config/config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ export default defineConfig({
9595
* @doc https://umijs.org/docs/max/i18n
9696
*/
9797
locale: {
98-
// default zh-CN
9998
default: 'zh-CN',
10099
antd: true,
101100
// default true, when it is true, will use `navigator.language` overwrite default

config/routes.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,6 @@ export default [
3030
icon: 'ClusterOutlined',
3131
routes: [
3232
{ path: '/org-management', redirect: '/org-management/org' },
33-
{
34-
path: '/org-management/org',
35-
name: 'org.list',
36-
component: './Organization',
37-
},
38-
{
39-
path: '/org-management/org/:instanceId',
40-
name: 'org.edit',
41-
component: './Organization/Edit',
42-
hideInMenu: true,
43-
parentKeys: ['/org-management', '/org-management/org'],
44-
},
4533
{
4634
path: '/org-management/user',
4735
name: 'user.list',
@@ -54,6 +42,18 @@ export default [
5442
hideInMenu: true,
5543
parentKeys: ['/org-management', '/org-management/user'],
5644
},
45+
{
46+
path: '/org-management/org',
47+
name: 'org.list',
48+
component: './Organization',
49+
},
50+
{
51+
path: '/org-management/org/:instanceId',
52+
name: 'org.edit',
53+
component: './Organization/Edit',
54+
hideInMenu: true,
55+
parentKeys: ['/org-management', '/org-management/org'],
56+
},
5757
],
5858
},
5959
// {

src/components/Icon/GiteeIcon.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Icon from '@ant-design/icons';
2+
import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
3+
4+
const GiteeSvg = () => {
5+
return (
6+
<svg
7+
className="icon"
8+
viewBox="0 0 1024 1024"
9+
version="1.1"
10+
xmlns="http://www.w3.org/2000/svg"
11+
width="28"
12+
height="28"
13+
>
14+
<path
15+
d="M512 1024C229.248 1024 0 794.752 0 512S229.248 0 512 0s512 229.248 512 512-229.248 512-512 512z m259.168-568.896h-290.752a25.28 25.28 0 0 0-25.28 25.28l-0.032 63.232c0 13.952 11.296 25.28 25.28 25.28h177.024a25.28 25.28 0 0 1 25.28 25.28v12.64a75.84 75.84 0 0 1-75.84 75.84h-240.224a25.28 25.28 0 0 1-25.28-25.28v-240.192a75.84 75.84 0 0 1 75.84-75.84h353.92a25.28 25.28 0 0 0 25.28-25.28l0.064-63.2a25.312 25.312 0 0 0-25.28-25.312H417.184a189.632 189.632 0 0 0-189.632 189.6v353.952c0 13.952 11.328 25.28 25.28 25.28h372.928a170.656 170.656 0 0 0 170.656-170.656v-145.376a25.28 25.28 0 0 0-25.28-25.28z"
16+
fill="currentColor"
17+
></path>
18+
</svg>
19+
);
20+
};
21+
22+
export const GiteeIcon = (props: Partial<CustomIconComponentProps>) => (
23+
<Icon component={GiteeSvg} {...props} />
24+
);

src/enums/appEnum.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const DEFAULT_APP = 'built-in-app';
2+
3+
export enum IdentityProviderType {
4+
GitHub = 'GitHub',
5+
Gitee = 'Gitee',
6+
WeChat = 'WeChat',
7+
LDAP = 'LDAP',
8+
WeChatMiniProgram = 'WeChatMiniProgram',
9+
}

src/enums/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './appEnum';
2+
export * from './cacheEnum';
3+
export * from './httpEnum';
4+
export * from './pageEnum';

src/pages/Login/Captcha.tsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import React from 'react';
2+
import { message } from 'antd';
3+
import { createStyles } from 'antd-style';
4+
import { ProFormCaptcha, ProFormText } from '@ant-design/pro-components';
5+
import { LockOutlined, MobileOutlined } from '@ant-design/icons';
6+
import { useIntl, FormattedMessage } from '@umijs/max';
7+
8+
import { getFakeCaptcha } from '@/services/system/login';
9+
10+
const useStyle = createStyles(({ token }) => {
11+
return {
12+
main: {
13+
['.icon']: {
14+
color: token.colorPrimary,
15+
fontSize: token.fontSize,
16+
},
17+
},
18+
};
19+
});
20+
21+
export const Captcha: React.FC = () => {
22+
const intl = useIntl();
23+
const { styles } = useStyle();
24+
25+
return (
26+
<div className={styles.main}>
27+
<ProFormText
28+
fieldProps={{
29+
size: 'large',
30+
prefix: <MobileOutlined />,
31+
}}
32+
name="mobile"
33+
placeholder={intl.formatMessage({
34+
id: 'pages.login.phoneNumber.placeholder',
35+
defaultMessage: '手机号',
36+
})}
37+
rules={[
38+
{
39+
required: true,
40+
message: (
41+
<FormattedMessage
42+
id="pages.login.phoneNumber.required"
43+
defaultMessage="请输入手机号!"
44+
/>
45+
),
46+
},
47+
{
48+
pattern: /^1\d{10}$/,
49+
message: (
50+
<FormattedMessage
51+
id="pages.login.phoneNumber.invalid"
52+
defaultMessage="手机号格式错误!"
53+
/>
54+
),
55+
},
56+
]}
57+
/>
58+
<ProFormCaptcha
59+
fieldProps={{
60+
size: 'large',
61+
prefix: <LockOutlined />,
62+
}}
63+
captchaProps={{
64+
size: 'large',
65+
}}
66+
placeholder={intl.formatMessage({
67+
id: 'pages.login.captcha.placeholder',
68+
defaultMessage: '请输入验证码',
69+
})}
70+
captchaTextRender={(timing, count) => {
71+
if (timing) {
72+
return `${count} ${intl.formatMessage({
73+
id: 'pages.getCaptchaSecondText',
74+
defaultMessage: '获取验证码',
75+
})}`;
76+
}
77+
return intl.formatMessage({
78+
id: 'pages.login.phoneLogin.getVerificationCode',
79+
defaultMessage: '获取验证码',
80+
});
81+
}}
82+
name="captcha"
83+
rules={[
84+
{
85+
required: true,
86+
message: (
87+
<FormattedMessage id="pages.login.captcha.required" defaultMessage="请输入验证码!" />
88+
),
89+
},
90+
]}
91+
onGetCaptcha={async (phone) => {
92+
const result = await getFakeCaptcha({
93+
phone,
94+
});
95+
if (!result) {
96+
return;
97+
}
98+
message.success('获取验证码成功!验证码为:1234');
99+
}}
100+
/>
101+
</div>
102+
);
103+
};

src/pages/Login/Lang.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
import { SelectLang } from '@umijs/max';
3+
4+
export const Lang: React.FC<{ prefixCls: string }> = (props) => {
5+
return (
6+
<div className={`${props.prefixCls}-lang`} data-lang="">
7+
{SelectLang && <SelectLang />}
8+
</div>
9+
);
10+
};

src/pages/Login/Login.tsx

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React from 'react';
2+
import classnames from 'classnames';
3+
import { Spin, Tabs } from 'antd';
4+
import Footer from '@/components/Footer';
5+
import { LoginForm, ProCard, ProFormCheckbox } from '@ant-design/pro-components';
6+
import { FormattedMessage, Helmet, useIntl } from '@umijs/max';
7+
8+
import Settings from '../../../config/defaultSettings';
9+
10+
import useStyle from './style';
11+
import { Lang } from './Lang';
12+
import { Password } from './Password';
13+
import { Captcha } from './Captcha';
14+
import useLoginHook from './_hooks';
15+
import { ProviderIcons } from './ProviderIcons';
16+
17+
const prefixCls = 'iam-login';
18+
19+
const INTL = {
20+
TITLE: {
21+
id: 'menu.login',
22+
},
23+
LOGIN_CARD_SUB_TITLE: {
24+
id: 'pages.layouts.userLayout.title',
25+
},
26+
ACCOUNT_LOGIN: {
27+
id: 'pages.login.accountLogin.tab',
28+
},
29+
PHONE_LOGIN: {
30+
id: 'pages.login.phoneLogin.tab',
31+
},
32+
REMEMBER_ME: {
33+
id: 'pages.login.rememberMe',
34+
},
35+
FORGOT_PASSWORD: {
36+
id: 'pages.login.forgotPassword',
37+
},
38+
};
39+
40+
const Login: React.FC = () => {
41+
const intl = useIntl();
42+
const { styles } = useStyle({ prefix: prefixCls });
43+
const {
44+
states: { loginType, appConf, loadAppConfLoading },
45+
actions: { handleLogin, setLoginType, afterLoginSuccess },
46+
} = useLoginHook();
47+
48+
return (
49+
<div className={classnames(styles.main)}>
50+
<div className={classnames(`${prefixCls}-wrapper`)}>
51+
<Lang prefixCls={prefixCls} />
52+
<Helmet>
53+
<title>
54+
{intl.formatMessage(INTL.TITLE)} - {Settings.title}
55+
</title>
56+
</Helmet>
57+
<div className={`${prefixCls}-container`}>
58+
<ProCard className={`${prefixCls}-login-card`}>
59+
<LoginForm
60+
className={`${prefixCls}-login-form`}
61+
title="WeCoding"
62+
subTitle={intl.formatMessage(INTL.LOGIN_CARD_SUB_TITLE)}
63+
initialValues={{
64+
autoLogin: true,
65+
}}
66+
actions={[
67+
<Spin key="provider-icons" spinning={loadAppConfLoading}>
68+
<ProviderIcons appConf={appConf} afterLoginSuccess={afterLoginSuccess} />
69+
</Spin>,
70+
]}
71+
onFinish={async (values) => {
72+
await handleLogin(values as API.LoginParams);
73+
}}
74+
>
75+
<Tabs
76+
activeKey={loginType}
77+
onChange={setLoginType}
78+
centered
79+
items={[
80+
{
81+
key: 'account',
82+
label: intl.formatMessage(INTL.ACCOUNT_LOGIN),
83+
children: <Password />,
84+
},
85+
{
86+
key: 'mobile',
87+
label: intl.formatMessage(INTL.PHONE_LOGIN),
88+
children: <Captcha />,
89+
},
90+
]}
91+
/>
92+
<div style={{ marginBottom: 24 }}>
93+
<ProFormCheckbox noStyle name="autoLogin">
94+
<FormattedMessage {...INTL.REMEMBER_ME} />
95+
</ProFormCheckbox>
96+
<a style={{ float: 'right' }}>
97+
<FormattedMessage {...INTL.FORGOT_PASSWORD} />
98+
</a>
99+
</div>
100+
</LoginForm>
101+
</ProCard>
102+
</div>
103+
<Footer />
104+
</div>
105+
</div>
106+
);
107+
};
108+
109+
export default Login;

src/pages/Login/Password.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React from 'react';
2+
import { createStyles } from 'antd-style';
3+
import { ProFormText } from '@ant-design/pro-components';
4+
import { LockOutlined, UserOutlined } from '@ant-design/icons';
5+
import { useIntl, FormattedMessage } from '@umijs/max';
6+
7+
const useStyle = createStyles(({ token }) => {
8+
return {
9+
main: {
10+
['.icon']: {
11+
color: token.colorPrimary,
12+
fontSize: token.fontSize,
13+
},
14+
},
15+
};
16+
});
17+
18+
const INTL = {
19+
USERNAME_PLACEHOLDER: {
20+
id: 'pages.login.username.placeholder',
21+
},
22+
USERNAME_REQUIRED: {
23+
id: 'pages.login.username.required',
24+
},
25+
PASSWORD_PLACEHOLDER: {
26+
id: 'pages.login.password.placeholder',
27+
},
28+
PASSWORD_REQUIRED: {
29+
id: 'pages.login.password.required',
30+
},
31+
};
32+
33+
export const Password: React.FC = () => {
34+
const intl = useIntl();
35+
const { styles } = useStyle();
36+
37+
return (
38+
<div className={styles.main}>
39+
<ProFormText
40+
name="username"
41+
fieldProps={{
42+
size: 'large',
43+
prefix: <UserOutlined />,
44+
}}
45+
placeholder={intl.formatMessage(INTL.USERNAME_PLACEHOLDER)}
46+
rules={[
47+
{
48+
required: true,
49+
message: <FormattedMessage {...INTL.USERNAME_REQUIRED} />,
50+
},
51+
]}
52+
/>
53+
<ProFormText.Password
54+
name="password"
55+
fieldProps={{
56+
size: 'large',
57+
prefix: <LockOutlined />,
58+
}}
59+
placeholder={intl.formatMessage(INTL.PASSWORD_PLACEHOLDER)}
60+
rules={[
61+
{
62+
required: true,
63+
message: <FormattedMessage {...INTL.PASSWORD_REQUIRED} />,
64+
},
65+
]}
66+
/>
67+
</div>
68+
);
69+
};

0 commit comments

Comments
 (0)