feat: 认证相关

This commit is contained in:
2025-09-06 19:56:13 +08:00
parent 5a25f350c7
commit cd99485c9a
13 changed files with 148 additions and 45 deletions

View File

@@ -15,7 +15,8 @@
"axios": "^1.11.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^7.8.0"
"react-router": "^7.8.0",
"zustand": "^5.0.8"
},
"devDependencies": {
"@arco-plugins/unplugin-react": "2.0.0-beta.5",

View File

@@ -1,11 +1,14 @@
import ReactDOM from 'react-dom/client';
import App from '@pages/App';
import { BrowserRouter } from 'react-router';
import { useGlobalStore } from './stores/global';
const rootEl = document.getElementById('root');
if (rootEl) {
const root = ReactDOM.createRoot(rootEl);
useGlobalStore.getState().refreshUser();
root.render(
<BrowserRouter>
<App />

View File

@@ -1,17 +1,19 @@
import { Avatar, Layout, Menu } from '@arco-design/web-react';
import { Avatar, Dropdown, Layout, Menu } from '@arco-design/web-react';
import {
IconApps,
IconExport,
IconMenuFold,
IconMenuUnfold,
IconRobot,
IconUser,
} from '@arco-design/web-react/icon';
import { useState } from 'react';
import Logo from '@assets/images/logo.svg?react';
import { Link, Outlet } from 'react-router';
import { useGlobalStore } from '../../stores/global';
function Home() {
const [collapsed, setCollapsed] = useState(false);
const globalStore = useGlobalStore();
return (
<Layout className="h-screen w-full">
@@ -53,12 +55,32 @@ function Home() {
<Layout>
<Layout.Header className="h-14 border-b-gray-100 border-b-[1px]">
<div className="flex items-center justify-end px-4 h-full">
<Avatar>
<IconUser />
</Avatar>
<Dropdown
trigger={'click'}
droplist={
<Menu className="px-3">
<Menu.Item key="1">
<IconExport />
<span className="ml-2">退</span>
</Menu.Item>
</Menu>
}
>
<div className="p-2 rounded-xl cursor-pointer flex items-center hover:bg-[rgba(0,0,0,0.03)]">
<Avatar size={28}>
<img
alt="avatar"
src={globalStore.user?.avatar_url.replace('https', 'http')}
/>
</Avatar>
<span className="ml-2 font-semibold text-gray-500">
{globalStore.user?.username}
</span>
</div>
</Dropdown>
</div>
</Layout.Header>
<Layout.Content>
<Layout.Content className="overflow-y-auto bg-gray-100">
<Outlet />
</Layout.Content>
</Layout>

View File

@@ -61,7 +61,7 @@ function ProjectPage() {
};
return (
<div className="p-6 bg-gray-100 min-h-screen">
<div className="p-6">
<div className="mb-6 flex items-center justify-between">
<div>
<Typography.Title heading={2} className="!m-0 !text-gray-900">

View File

@@ -8,6 +8,24 @@ class Net {
timeout: 20000,
withCredentials: true,
});
this.applyInterceptors(this.instance);
}
private applyInterceptors(instance: Axios) {
instance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.log('error', error)
if (error.status === 401 && error.config.url !== '/api/auth/info') {
window.location.href = '/login';
return;
}
return Promise.reject(error);
},
);
}
async request<T>(config: AxiosRequestConfig): Promise<T> {

View File

@@ -0,0 +1,26 @@
import { net, type APIResponse } from '@shared';
import { create } from 'zustand';
interface User {
id: string;
username: string;
email: string;
avatar_url: string;
active: boolean;
}
interface GlobalStore {
user: User | null;
refreshUser: () => Promise<void>;
}
export const useGlobalStore = create<GlobalStore>((set) => ({
user: null,
async refreshUser() {
const { data } = await net.request<APIResponse<User>>({
method: 'GET',
url: '/api/auth/info',
});
set({ user: data });
},
}));