mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-09 18:23:49 +08:00
feat: 添加退出登录, 优化闪跳
This commit is contained in:
@@ -37,6 +37,10 @@ export enum ContentType {
|
||||
Text = "text/plain",
|
||||
}
|
||||
|
||||
|
||||
const whitePathnameList = ['/user/login', '/login'];
|
||||
const whiteApiList = ['/api/v1/user/profile', '/api/v1/admin/profile'];
|
||||
|
||||
const redirectToLogin = () => {
|
||||
const redirectAfterLogin = encodeURIComponent(location.href);
|
||||
const search = `redirect=${redirectAfterLogin}`;
|
||||
@@ -72,6 +76,9 @@ export class HttpClient<SecurityDataType = unknown> {
|
||||
},
|
||||
(err) => {
|
||||
if (err?.response?.status === 401) {
|
||||
if (whitePathnameList.includes(location.pathname)) {
|
||||
return;
|
||||
}
|
||||
Message.error('尚未登录')
|
||||
redirectToLogin();
|
||||
return
|
||||
|
||||
@@ -175,6 +175,51 @@ export const getAdminLoginHistory = (
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 管理员登出
|
||||
*
|
||||
* @tags Admin
|
||||
* @name PostAdminLogout
|
||||
* @summary 管理员登出
|
||||
* @request POST:/api/v1/admin/logout
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const postAdminLogout = (params: RequestParams = {}) =>
|
||||
request<WebResp>({
|
||||
path: `/api/v1/admin/logout`,
|
||||
method: "POST",
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 管理员信息
|
||||
*
|
||||
* @tags Admin
|
||||
* @name GetAdminProfile
|
||||
* @summary 管理员信息
|
||||
* @request GET:/api/v1/admin/profile
|
||||
* @response `200` `(WebResp & {
|
||||
data?: DomainAdminUser,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const getAdminProfile = (params: RequestParams = {}) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainAdminUser;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/admin/profile`,
|
||||
method: "GET",
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 获取系统设置
|
||||
*
|
||||
|
||||
@@ -125,7 +125,7 @@ export const getModelList = (params: RequestParams = {}) =>
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 报告
|
||||
* @description 报告,支持多种操作:accept(接受补全)、suggest(建议)、reject(拒绝补全并记录用户输入)、file_written(文件写入)
|
||||
*
|
||||
* @tags OpenAIV1
|
||||
* @name PostReport
|
||||
|
||||
@@ -189,6 +189,25 @@ export const getLoginHistory = (
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 用户登出
|
||||
*
|
||||
* @tags User
|
||||
* @name PostLogout
|
||||
* @summary 用户登出
|
||||
* @request POST:/api/v1/user/logout
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const postLogout = (params: RequestParams = {}) =>
|
||||
request<WebResp>({
|
||||
path: `/api/v1/user/logout`,
|
||||
method: "POST",
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 用户 OAuth 回调
|
||||
*
|
||||
|
||||
@@ -58,6 +58,9 @@ export enum ContentType {
|
||||
Text = "text/plain",
|
||||
}
|
||||
|
||||
const whitePathnameList = ["/user/login", "/login"];
|
||||
const whiteApiList = ["/api/v1/user/profile", "/api/v1/admin/profile"];
|
||||
|
||||
const redirectToLogin = () => {
|
||||
const redirectAfterLogin = encodeURIComponent(location.href);
|
||||
const search = `redirect=${redirectAfterLogin}`;
|
||||
@@ -101,6 +104,9 @@ export class HttpClient<SecurityDataType = unknown> {
|
||||
},
|
||||
(err) => {
|
||||
if (err?.response?.status === 401) {
|
||||
if (whitePathnameList.includes(location.pathname)) {
|
||||
return;
|
||||
}
|
||||
Message.error("尚未登录");
|
||||
redirectToLogin();
|
||||
return;
|
||||
|
||||
@@ -26,6 +26,7 @@ export enum ConstsReportAction {
|
||||
ReportActionAccept = "accept",
|
||||
ReportActionSuggest = "suggest",
|
||||
ReportActionFileWritten = "file_written",
|
||||
ReportActionReject = "reject",
|
||||
}
|
||||
|
||||
export enum ConstsModelType {
|
||||
@@ -367,8 +368,11 @@ export interface DomainLoginReq {
|
||||
password?: string;
|
||||
/** 会话Id插件登录时必填 */
|
||||
session_id?: string;
|
||||
/** 登录来源 plugin: 插件 browser: 浏览器; 默认为 plugin */
|
||||
source?: ConstsLoginSource;
|
||||
/**
|
||||
* 登录来源 plugin: 插件 browser: 浏览器; 默认为 plugin
|
||||
* @default "plugin"
|
||||
*/
|
||||
source: ConstsLoginSource;
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
}
|
||||
@@ -511,10 +515,16 @@ export interface DomainReportReq {
|
||||
action?: ConstsReportAction;
|
||||
/** 内容 */
|
||||
content?: string;
|
||||
/** 光标位置(用于reject action) */
|
||||
cursor_position?: number;
|
||||
/** task_id or resp_id */
|
||||
id?: string;
|
||||
/** 当前文件的原文(用于reject action) */
|
||||
source_code?: string;
|
||||
/** 工具 */
|
||||
tool?: string;
|
||||
/** 用户输入的新文本(用于reject action) */
|
||||
user_input?: string;
|
||||
}
|
||||
|
||||
export interface DomainSetting {
|
||||
@@ -1070,6 +1080,9 @@ export interface GetUserOauthSignupOrInParams {
|
||||
redirect_url?: string;
|
||||
/** 会话ID */
|
||||
session_id?: string;
|
||||
/** 登录来源 plugin: 插件 browser: 浏览器; 默认为 plugin */
|
||||
source?: "plugin" | "browser";
|
||||
/**
|
||||
* 登录来源 plugin: 插件 browser: 浏览器; 默认为 plugin
|
||||
* @default "plugin"
|
||||
*/
|
||||
source: "plugin" | "browser";
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
import { Button, IconButton, Stack } from '@mui/material';
|
||||
import { Icon, message } from '@c-x/ui';
|
||||
import { Button, IconButton, Stack, Tooltip } from '@mui/material';
|
||||
import { message } from '@c-x/ui';
|
||||
import { postLogout } from '@/api/User';
|
||||
import { postAdminLogout } from '@/api/Admin';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import { Box } from '@mui/material';
|
||||
import Bread from './Bread';
|
||||
|
||||
const Header = () => {
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const onLogout = async () => {
|
||||
if (pathname.startsWith('/user')) {
|
||||
await postLogout();
|
||||
} else {
|
||||
await postAdminLogout();
|
||||
}
|
||||
message.success('退出登录成功');
|
||||
navigate(pathname.startsWith('/user') ? '/user/login' : '/login');
|
||||
};
|
||||
return (
|
||||
<Stack
|
||||
direction={'row'}
|
||||
@@ -33,25 +43,22 @@ const Header = () => {
|
||||
>
|
||||
下载客户端
|
||||
</Button>
|
||||
<IconButton
|
||||
title='退出登录'
|
||||
size='small'
|
||||
sx={{
|
||||
bgcolor: '#fff',
|
||||
<Tooltip title='退出登录' arrow>
|
||||
<IconButton
|
||||
title='退出登录'
|
||||
size='small'
|
||||
sx={{
|
||||
bgcolor: '#fff',
|
||||
|
||||
'&:hover': {
|
||||
color: 'primary.main',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
message.success('退出登录成功');
|
||||
navigate(pathname.startsWith('/user/') ? '/user/login' : '/login', {
|
||||
replace: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<LogoutIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
'&:hover': {
|
||||
color: 'primary.main',
|
||||
},
|
||||
}}
|
||||
onClick={onLogout}
|
||||
>
|
||||
<LogoutIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
25
ui/src/context/index.tsx
Normal file
25
ui/src/context/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createContext } from 'react';
|
||||
import { DomainUser, DomainAdminUser } from '@/api/types';
|
||||
|
||||
export const AuthContext = createContext<
|
||||
[
|
||||
DomainUser | DomainAdminUser | null,
|
||||
{
|
||||
loading: boolean;
|
||||
setUser: (user: DomainUser | DomainAdminUser) => void;
|
||||
refreshUser: () => void;
|
||||
}
|
||||
]
|
||||
>([
|
||||
null,
|
||||
{
|
||||
setUser: () => {},
|
||||
loading: true,
|
||||
refreshUser: () => {},
|
||||
},
|
||||
]);
|
||||
|
||||
export const CommonContext = createContext<{
|
||||
contactModalOpen: boolean;
|
||||
setContactModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}>({ contactModalOpen: false, setContactModalOpen: () => {} });
|
||||
10
ui/src/hooks/context.ts
Normal file
10
ui/src/hooks/context.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { use } from 'react';
|
||||
import { AuthContext, CommonContext } from '@/context';
|
||||
|
||||
export const useAuthContext = () => {
|
||||
return use(AuthContext);
|
||||
};
|
||||
|
||||
export const useCommonContext = () => {
|
||||
return use(CommonContext);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { useState, useEffect } from 'react';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import '@/assets/fonts/font.css';
|
||||
@@ -6,22 +7,86 @@ import '@/assets/fonts/iconfont';
|
||||
import './index.css';
|
||||
import '@/assets/styles/markdown.css';
|
||||
import { ThemeProvider } from '@c-x/ui';
|
||||
import { getUserProfile } from '@/api/UserManage';
|
||||
import { getAdminProfile } from '@/api/Admin';
|
||||
import dayjs from 'dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
import { AuthContext } from './context';
|
||||
import { DomainUser, DomainAdminUser } from './api/types';
|
||||
import { lightTheme } from './theme';
|
||||
import router from './router';
|
||||
import { getRedirectUrl } from './utils';
|
||||
import { Loading } from '@c-x/ui';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const App = () => {
|
||||
const [user, setUser] = useState<DomainUser | DomainAdminUser | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const onGotoRedirect = (source: 'user' | 'admin') => {
|
||||
const redirectUrl = getRedirectUrl(source);
|
||||
window.location.href = redirectUrl.href;
|
||||
};
|
||||
|
||||
const getUser = () => {
|
||||
setLoading(true);
|
||||
if (location.pathname.startsWith('/user')) {
|
||||
return getUserProfile()
|
||||
.then((res) => {
|
||||
setUser(res);
|
||||
if (location.pathname.startsWith('/user/login')) {
|
||||
onGotoRedirect('user');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
return getAdminProfile()
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
setUser(res);
|
||||
if (location.pathname.startsWith('/login')) {
|
||||
onGotoRedirect('admin');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getUser();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<AuthContext.Provider
|
||||
value={[
|
||||
user,
|
||||
{
|
||||
loading,
|
||||
setUser,
|
||||
refreshUser: getUser,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<RouterProvider router={router} />
|
||||
</AuthContext.Provider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<RouterProvider router={router} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
root.render(<App />);
|
||||
|
||||
@@ -147,6 +147,7 @@ const AuthPage = () => {
|
||||
const loginResult = await postLogin({
|
||||
...data,
|
||||
session_id: sessionId,
|
||||
source: ConstsLoginSource.LoginSourcePlugin,
|
||||
});
|
||||
|
||||
if (!loginResult.redirect_url) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { useRequest } from 'ahooks';
|
||||
import { postRegister, getUserOauthSignupOrIn } from '@/api/User';
|
||||
import { getGetSetting } from '@/api/Admin';
|
||||
import { Icon } from '@c-x/ui';
|
||||
import { DomainSetting } from '@/api/types';
|
||||
import { ConstsLoginSource, DomainSetting } from '@/api/types';
|
||||
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import MenuBookIcon from '@mui/icons-material/MenuBook';
|
||||
@@ -140,6 +140,7 @@ const Invite = () => {
|
||||
platform,
|
||||
redirect_url: `${window.location.origin}/invite/${id}/2`,
|
||||
inviate_code: id,
|
||||
source: 'plugin',
|
||||
}).then((res) => {
|
||||
if (res.url) {
|
||||
window.location.href = res.url;
|
||||
|
||||
@@ -16,6 +16,7 @@ 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 Logo from '@/assets/images/logo.png';
|
||||
import { getRedirectUrl } from '@/utils';
|
||||
@@ -71,6 +72,7 @@ const LoginPage = () => {
|
||||
return postAdminLogin({
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
source: ConstsLoginSource.LoginSourceBrowser,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user