mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-08 01:33:31 +08:00
@@ -45,9 +45,9 @@ const redirectToLogin = () => {
|
||||
const redirectAfterLogin = encodeURIComponent(location.href);
|
||||
const search = `redirect=${redirectAfterLogin}`;
|
||||
const pathname = location.pathname.startsWith('/user')
|
||||
? '/user/login'
|
||||
: '/login';
|
||||
window.location.href = `${pathname}?${search}`;
|
||||
? '/login'
|
||||
: '/login/admin';
|
||||
window.location.href = `${pathname}`;
|
||||
};
|
||||
|
||||
type ExtractDataProp<T> = T extends { data?: infer U } ? U : never
|
||||
|
||||
@@ -65,9 +65,9 @@ const redirectToLogin = () => {
|
||||
const redirectAfterLogin = encodeURIComponent(location.href);
|
||||
const search = `redirect=${redirectAfterLogin}`;
|
||||
const pathname = location.pathname.startsWith("/user")
|
||||
? "/user/login"
|
||||
: "/login";
|
||||
window.location.href = `${pathname}?${search}`;
|
||||
? "/login"
|
||||
: "/login/admin";
|
||||
window.location.href = `${pathname}`;
|
||||
};
|
||||
|
||||
type ExtractDataProp<T> = T extends { data?: infer U } ? U : never;
|
||||
|
||||
@@ -17,7 +17,7 @@ const Header = () => {
|
||||
await postAdminLogout();
|
||||
}
|
||||
message.success('退出登录成功');
|
||||
navigate(pathname.startsWith('/user') ? '/user/login' : '/login');
|
||||
navigate(pathname.startsWith('/user') ? '/login/user' : '/login/admin');
|
||||
};
|
||||
return (
|
||||
<Stack
|
||||
|
||||
@@ -107,18 +107,26 @@ const App = () => {
|
||||
|
||||
const getUser = () => {
|
||||
setLoading(true);
|
||||
if (location.pathname.startsWith('/user')) {
|
||||
if (
|
||||
location.pathname.startsWith('/user') ||
|
||||
location.pathname === '/login' ||
|
||||
location.pathname === '/login/user'
|
||||
) {
|
||||
return getUserProfile()
|
||||
.then((res) => {
|
||||
setUser(res);
|
||||
if (location.pathname.startsWith('/user/login')) {
|
||||
if (location.pathname.startsWith('/login')) {
|
||||
onGotoRedirect('user');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else if (!location.pathname.startsWith('/auth')) {
|
||||
} else if (
|
||||
!location.pathname.startsWith('/auth') ||
|
||||
!location.pathname.startsWith('/user') ||
|
||||
location.pathname === '/login/admin'
|
||||
) {
|
||||
return getAdminProfile()
|
||||
.then((res) => {
|
||||
setUser(res);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -6,20 +6,24 @@ import {
|
||||
Typography,
|
||||
Container,
|
||||
Paper,
|
||||
Alert,
|
||||
CircularProgress,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
IconButton,
|
||||
Stack,
|
||||
Divider,
|
||||
} from '@mui/material';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { postAdminLogin } from '@/api/Admin';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { ConstsLoginSource } from '@/api/types';
|
||||
import { Icon } from '@c-x/ui';
|
||||
import { ConstsLoginSource, DomainSetting } from '@/api/types';
|
||||
import { Icon, CusTabs } from '@c-x/ui';
|
||||
import Logo from '@/assets/images/logo.png';
|
||||
import { getRedirectUrl } from '@/utils';
|
||||
import { getGetSetting } from '@/api/Admin';
|
||||
import { postLogin, getUserOauthSignupOrIn } from '@/api/User';
|
||||
import { useRequest } from 'ahooks';
|
||||
|
||||
// @ts-ignore
|
||||
import { AestheticFluidBg } from '@/assets/jsm/AestheticFluidBg.module.js';
|
||||
@@ -57,10 +61,24 @@ interface LoginFormData {
|
||||
password: string;
|
||||
}
|
||||
|
||||
type TabType = 'user' | 'admin';
|
||||
|
||||
const LoginPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { tab: tabParam } = useParams();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [tab, setTab] = useState<TabType>((tabParam as TabType) || 'user');
|
||||
const { data: loginSetting = {} as DomainSetting } =
|
||||
useRequest(getGetSetting);
|
||||
const { custom_oauth = {}, dingtalk_oauth = {} } = loginSetting;
|
||||
|
||||
useEffect(() => {
|
||||
if (tabParam) {
|
||||
setTab(tabParam as TabType);
|
||||
}
|
||||
}, [tabParam]);
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -79,17 +97,80 @@ const LoginPage = () => {
|
||||
// 处理登录表单提交
|
||||
const onSubmit = async (data: LoginFormData) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await loginUser(data);
|
||||
const redirectUrl = getRedirectUrl();
|
||||
window.location.href = redirectUrl.href;
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '登录失败,请重试');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (tab === 'admin') {
|
||||
try {
|
||||
await loginUser(data);
|
||||
const redirectUrl = getRedirectUrl();
|
||||
window.location.href = redirectUrl.href;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (tab === 'user') {
|
||||
try {
|
||||
// 用户登录
|
||||
await postLogin({
|
||||
...data,
|
||||
source: ConstsLoginSource.LoginSourceBrowser,
|
||||
});
|
||||
const redirectUrl = getRedirectUrl('user');
|
||||
window.location.href = redirectUrl.href;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const oauthEnable = useMemo(() => {
|
||||
return (
|
||||
(loginSetting.custom_oauth?.enable ||
|
||||
loginSetting.dingtalk_oauth?.enable) &&
|
||||
tab === 'user'
|
||||
);
|
||||
}, [loginSetting, tab]);
|
||||
|
||||
const disablePasswordLogin = useMemo(() => {
|
||||
return loginSetting.disable_password_login && tab === 'user';
|
||||
}, [loginSetting, tab]);
|
||||
|
||||
const onOauthLogin = (platform: 'dingtalk' | 'custom') => {
|
||||
const redirectUrl = getRedirectUrl('user');
|
||||
getUserOauthSignupOrIn({
|
||||
platform,
|
||||
redirect_url: redirectUrl.href,
|
||||
source: ConstsLoginSource.LoginSourceBrowser,
|
||||
}).then((res) => {
|
||||
if (res.url) {
|
||||
window.location.href = res.url;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const oauthLogin = () => {
|
||||
return (
|
||||
<Stack justifyContent='center'>
|
||||
<Divider sx={{ my: 3, fontSize: 12, borderColor: 'divider' }}>
|
||||
使用其他方式登录
|
||||
</Divider>
|
||||
{dingtalk_oauth.enable && (
|
||||
<IconButton
|
||||
sx={{ alignSelf: 'center' }}
|
||||
onClick={() => onOauthLogin('dingtalk')}
|
||||
>
|
||||
<Icon type='icon-dingding' sx={{ fontSize: 30 }} />
|
||||
</IconButton>
|
||||
)}
|
||||
{custom_oauth.enable && (
|
||||
<IconButton
|
||||
sx={{ alignSelf: 'center' }}
|
||||
onClick={() => onOauthLogin('custom')}
|
||||
>
|
||||
<Icon type='icon-oauth' sx={{ fontSize: 30 }} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -124,131 +205,169 @@ const LoginPage = () => {
|
||||
>
|
||||
Monkey Code
|
||||
</Typography>
|
||||
<Typography variant='body1' color='text.secondary' paragraph>
|
||||
请输入您的账号和密码
|
||||
</Typography>
|
||||
</LogoContainer>
|
||||
|
||||
<Box component='form' onSubmit={handleSubmit(onSubmit)}>
|
||||
<Grid container spacing={5}>
|
||||
<Grid size={12}>
|
||||
<Controller
|
||||
name='username'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
rules={{
|
||||
required: '请输入用户名',
|
||||
minLength: {
|
||||
value: 2,
|
||||
message: '用户名至少需要2个字符',
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder='请输入用户名'
|
||||
variant='outlined'
|
||||
error={!!errors.username}
|
||||
helperText={errors.username?.message}
|
||||
disabled={loading}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<Icon
|
||||
type='icon-zhanghao'
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
mr: 1,
|
||||
fontSize: 18,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
<CusTabs
|
||||
list={[
|
||||
{ label: '普通账号', value: 'user' },
|
||||
{ label: '管理员账号', value: 'admin' },
|
||||
]}
|
||||
value={tab}
|
||||
onChange={(value: TabType) => {
|
||||
setTab(value);
|
||||
navigate(`/login/${value}?${searchParams.toString()}`);
|
||||
}}
|
||||
sx={{
|
||||
width: '100%',
|
||||
mb: 4,
|
||||
height: 40,
|
||||
border: 'none',
|
||||
padding: '4px',
|
||||
'.MuiTab-root': {
|
||||
width: '50%',
|
||||
height: 32,
|
||||
fontSize: 14,
|
||||
'&.Mui-selected': {
|
||||
color: 'text.primary',
|
||||
fontWeight: 500,
|
||||
},
|
||||
},
|
||||
'.MuiTabs-scroller': {
|
||||
height: 32,
|
||||
},
|
||||
'.MuiTabs-indicator': {
|
||||
borderRadius: '10px',
|
||||
height: 32,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
bgcolor: 'rgba(255, 255, 255, 0.2)',
|
||||
borderRadius: '10px',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Grid size={12}>
|
||||
<Controller
|
||||
name='password'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
rules={{
|
||||
required: '请输入密码',
|
||||
minLength: {
|
||||
value: 3,
|
||||
message: '密码至少需要3个字符',
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder='请输入密码'
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
variant='outlined'
|
||||
error={!!errors.password}
|
||||
helperText={errors.password?.message}
|
||||
disabled={loading}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<Icon
|
||||
type='icon-mima'
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
mr: 1,
|
||||
fontSize: 24,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
<IconButton
|
||||
aria-label='切换密码显示'
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
edge='end'
|
||||
disabled={loading}
|
||||
size='small'
|
||||
>
|
||||
{showPassword ? (
|
||||
<Icon
|
||||
type='icon-kejian'
|
||||
sx={{ fontSize: 20 }}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
type='icon-bukejian'
|
||||
sx={{ fontSize: 20 }}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
{!disablePasswordLogin && (
|
||||
<Box component='form' onSubmit={handleSubmit(onSubmit)}>
|
||||
<Grid container spacing={4}>
|
||||
<Grid size={12}>
|
||||
<Controller
|
||||
name='username'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
rules={{
|
||||
required: '请输入用户名',
|
||||
minLength: {
|
||||
value: 2,
|
||||
message: '用户名至少需要2个字符',
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder='请输入用户名'
|
||||
variant='outlined'
|
||||
error={!!errors.username}
|
||||
helperText={errors.username?.message}
|
||||
disabled={loading}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<Icon
|
||||
type='icon-zhanghao'
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
mr: 1,
|
||||
fontSize: 18,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid size={12}>
|
||||
<Button
|
||||
type='submit'
|
||||
fullWidth
|
||||
variant='contained'
|
||||
size='large'
|
||||
disabled={loading}
|
||||
sx={{ height: 48, textTransform: 'none' }}
|
||||
>
|
||||
{loading ? <CircularProgress size={18} /> : '登录'}
|
||||
</Button>
|
||||
<Grid size={12}>
|
||||
<Controller
|
||||
name='password'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
rules={{
|
||||
required: '请输入密码',
|
||||
minLength: {
|
||||
value: 3,
|
||||
message: '密码至少需要3个字符',
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder='请输入密码'
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
variant='outlined'
|
||||
error={!!errors.password}
|
||||
helperText={errors.password?.message}
|
||||
disabled={loading}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<Icon
|
||||
type='icon-mima'
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
mr: 1,
|
||||
fontSize: 24,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
<IconButton
|
||||
aria-label='切换密码显示'
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
edge='end'
|
||||
disabled={loading}
|
||||
size='small'
|
||||
>
|
||||
{showPassword ? (
|
||||
<Icon
|
||||
type='icon-kejian'
|
||||
sx={{ fontSize: 20 }}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
type='icon-bukejian'
|
||||
sx={{ fontSize: 20 }}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid size={12}>
|
||||
<Button
|
||||
type='submit'
|
||||
fullWidth
|
||||
variant='contained'
|
||||
size='large'
|
||||
disabled={loading}
|
||||
sx={{ height: 48, textTransform: 'none' }}
|
||||
>
|
||||
{loading ? <CircularProgress size={18} /> : '登录'}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{oauthEnable && oauthLogin()}
|
||||
</StyledPaper>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@@ -123,12 +123,12 @@ const routerConfig = [
|
||||
path: '/auth',
|
||||
element: <Auth />,
|
||||
},
|
||||
// {
|
||||
// path: '/user/login',
|
||||
// element: <UserLogin />,
|
||||
// },
|
||||
{
|
||||
path: '/user/login',
|
||||
element: <UserLogin />,
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
path: '/login/:tab?',
|
||||
element: <Login />,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user