feat: login
This commit is contained in:
@@ -4,10 +4,10 @@ import { UserVO } from "./services/login/types";
|
||||
* @see https://umijs.org/docs/max/access#access
|
||||
* */
|
||||
export default function access(
|
||||
initialState: { currentUser?: UserVO } | undefined
|
||||
initialState: { currentUser?: { user: UserVO } } | undefined
|
||||
) {
|
||||
const { currentUser } = initialState ?? {};
|
||||
return {
|
||||
canAdmin: currentUser && currentUser.username === "admin",
|
||||
canAdmin: currentUser && currentUser.user.username === "admin",
|
||||
};
|
||||
}
|
||||
|
||||
30
src/app.tsx
30
src/app.tsx
@@ -16,6 +16,7 @@ import type { UserVO, TokenType } from "@/services/login/types";
|
||||
import defaultSettings from "../config/defaultSettings";
|
||||
import { errorConfig } from "./requestErrorConfig";
|
||||
import "@ant-design/v5-patch-for-react-19";
|
||||
import { getAccessToken, getRefreshToken, getTenantId } from "./utils/auth";
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const isDevOrTest = isDev || process.env.CI;
|
||||
@@ -33,6 +34,10 @@ export async function getInitialState(): Promise<{
|
||||
const fetchUserInfo = async () => {
|
||||
// history.push(loginPath);
|
||||
try {
|
||||
const token = getAccessToken();
|
||||
if (!token) {
|
||||
throw new Error("No token found");
|
||||
}
|
||||
const msg = await getInfo();
|
||||
return msg.data;
|
||||
} catch (_error) {
|
||||
@@ -84,6 +89,7 @@ export const layout: RunTimeLayoutConfig = ({
|
||||
onPageChange: () => {
|
||||
const { location } = history;
|
||||
// 如果没有登录,重定向到 login
|
||||
console.log(initialState);
|
||||
if (!initialState?.currentUser && location.pathname !== loginPath) {
|
||||
history.push(loginPath);
|
||||
}
|
||||
@@ -153,4 +159,28 @@ export const layout: RunTimeLayoutConfig = ({
|
||||
export const request: RequestConfig = {
|
||||
baseURL: isDev ? "" : "https://proapi.azurewebsites.net",
|
||||
...errorConfig,
|
||||
// 添加请求拦截器
|
||||
requestInterceptors: [
|
||||
(url, options) => {
|
||||
// 为所有请求添加 API 前缀
|
||||
if (url && !url.startsWith(process.env.API_PREFIX || "/admin-api")) {
|
||||
url = (process.env.API_PREFIX || "/admin-api") + url;
|
||||
}
|
||||
// 获取存储在本地的 token 和 tenantId
|
||||
const token = getAccessToken();
|
||||
const tenantId = getTenantId(); // 默认租户ID为1
|
||||
console.log(tenantId);
|
||||
// 设置统一的请求头
|
||||
const headers: Record<string, string> = {
|
||||
...options.headers,
|
||||
Accept: "*",
|
||||
"tenant-id": tenantId,
|
||||
};
|
||||
// 如果有token,则添加Authorization头
|
||||
if (token) {
|
||||
headers["Authorization"] = `Bearer ${getAccessToken()}`;
|
||||
}
|
||||
return { url, options: { ...options, headers } };
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getTenantIdByName, login } from "@/services/login";
|
||||
import {
|
||||
AlipayOutlined,
|
||||
LockOutlined,
|
||||
@@ -13,10 +14,12 @@ import {
|
||||
ProFormCheckbox,
|
||||
ProFormText,
|
||||
} from "@ant-design/pro-components";
|
||||
import { Button, Divider, Space, Tabs, message, theme } from "antd";
|
||||
import { history, useModel } from "@umijs/max";
|
||||
import { Button, Divider, Space, Tabs, theme, message } from "antd";
|
||||
import type { CSSProperties } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import * as authUtil from "@/utils/auth";
|
||||
import { flushSync } from "react-dom";
|
||||
type LoginType = "phone" | "account";
|
||||
|
||||
const iconStyles: CSSProperties = {
|
||||
@@ -27,8 +30,62 @@ const iconStyles: CSSProperties = {
|
||||
};
|
||||
|
||||
const Page = () => {
|
||||
const [loginType, setLoginType] = useState<LoginType>("phone");
|
||||
const [loginType, setLoginType] = useState<LoginType>("account");
|
||||
const { token } = theme.useToken();
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
const { initialState, setInitialState } = useModel("@@initialState");
|
||||
// 获取租户 ID
|
||||
const getTenantId = async (name: string) => {
|
||||
const res = await getTenantIdByName({ name });
|
||||
authUtil.setTenantId(res.data);
|
||||
};
|
||||
|
||||
const fetchUserInfo = async () => {
|
||||
const userInfo = await initialState?.fetchUserInfo?.();
|
||||
if (userInfo) {
|
||||
flushSync(() => {
|
||||
setInitialState((s) => ({
|
||||
...s,
|
||||
currentUser: userInfo,
|
||||
}));
|
||||
});
|
||||
}
|
||||
};
|
||||
// 添加登录处理函数
|
||||
const handleSubmit = async (values: any) => {
|
||||
try {
|
||||
// 根据登录类型处理不同的参数
|
||||
const params = {
|
||||
tenantName: "芋道源码",
|
||||
...values,
|
||||
};
|
||||
|
||||
await getTenantId(params.tenantName);
|
||||
if (params.rememberMe) {
|
||||
authUtil.setLoginForm(params);
|
||||
} else {
|
||||
authUtil.removeLoginForm();
|
||||
}
|
||||
|
||||
// 调用登录接口
|
||||
const res = await login(params);
|
||||
if (res.code === 0) {
|
||||
// 登录成功
|
||||
messageApi.success("登录成功!");
|
||||
|
||||
// 跳转到首页
|
||||
authUtil.setToken(res.data);
|
||||
await fetchUserInfo();
|
||||
const urlParams = new URL(window.location.href).searchParams;
|
||||
window.location.href = urlParams.get("redirect") || "/";
|
||||
} else {
|
||||
// 登录失败
|
||||
messageApi.error(res.msg || "登录失败,请重试!");
|
||||
}
|
||||
} catch (error) {
|
||||
messageApi.error("登录失败,请检查网络或稍后重试!");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -36,40 +93,42 @@ const Page = () => {
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
{contextHolder}
|
||||
<LoginFormPage
|
||||
backgroundImageUrl="https://mdn.alipayobjects.com/huamei_gcee1x/afts/img/A*y0ZTS6WLwvgAAAAAAAAAAAAADml6AQ/fmt.webp"
|
||||
logo="https://github.githubassets.com/favicons/favicon.png"
|
||||
logo="/logo.svg"
|
||||
backgroundVideoUrl="https://gw.alipayobjects.com/v/huamei_gcee1x/afts/video/jXRBRK_VAwoAAAAAAAAAAAAAK4eUAQBr"
|
||||
title="Github"
|
||||
// title="BY"
|
||||
containerStyle={{
|
||||
backgroundColor: "rgba(0, 0, 0,0.65)",
|
||||
backgroundColor: "rgb(0 0 0 / 51%)",
|
||||
backdropFilter: "blur(4px)",
|
||||
}}
|
||||
subTitle="全球最大的代码托管平台"
|
||||
activityConfig={{
|
||||
style: {
|
||||
boxShadow: "0px 0px 8px rgba(0, 0, 0, 0.2)",
|
||||
color: token.colorTextHeading,
|
||||
borderRadius: 8,
|
||||
backgroundColor: "rgba(255,255,255,0.25)",
|
||||
backdropFilter: "blur(4px)",
|
||||
},
|
||||
title: "活动标题,可配置图片",
|
||||
subTitle: "活动介绍说明文字",
|
||||
action: (
|
||||
<Button
|
||||
size="large"
|
||||
style={{
|
||||
borderRadius: 20,
|
||||
background: token.colorBgElevated,
|
||||
color: token.colorPrimary,
|
||||
width: 120,
|
||||
}}
|
||||
>
|
||||
去看看
|
||||
</Button>
|
||||
),
|
||||
}}
|
||||
onFinish={handleSubmit}
|
||||
subTitle="百业到家-管理平台"
|
||||
// activityConfig={{
|
||||
// style: {
|
||||
// boxShadow: "0px 0px 8px rgba(0, 0, 0, 0.2)",
|
||||
// color: token.colorTextHeading,
|
||||
// borderRadius: 8,
|
||||
// backgroundColor: "rgba(255,255,255,0.25)",
|
||||
// backdropFilter: "blur(4px)",
|
||||
// },
|
||||
// title: "活动标题,可配置图片",
|
||||
// subTitle: "活动介绍说明文字",
|
||||
// action: (
|
||||
// <Button
|
||||
// size="large"
|
||||
// style={{
|
||||
// borderRadius: 20,
|
||||
// background: token.colorBgElevated,
|
||||
// color: token.colorPrimary,
|
||||
// width: 120,
|
||||
// }}
|
||||
// >
|
||||
// 去看看
|
||||
// </Button>
|
||||
// ),
|
||||
// }}
|
||||
actions={
|
||||
<div
|
||||
style={{
|
||||
@@ -100,6 +159,7 @@ const Page = () => {
|
||||
height: 40,
|
||||
width: 40,
|
||||
border: "1px solid " + token.colorPrimaryBorder,
|
||||
background: token.colorBgContainer,
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
>
|
||||
@@ -114,6 +174,7 @@ const Page = () => {
|
||||
height: 40,
|
||||
width: 40,
|
||||
border: "1px solid " + token.colorPrimaryBorder,
|
||||
background: token.colorBgContainer,
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
>
|
||||
@@ -127,6 +188,7 @@ const Page = () => {
|
||||
flexDirection: "column",
|
||||
height: 40,
|
||||
width: 40,
|
||||
background: token.colorBgContainer,
|
||||
border: "1px solid " + token.colorPrimaryBorder,
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
@@ -159,6 +221,10 @@ const Page = () => {
|
||||
className={"prefixIcon"}
|
||||
/>
|
||||
),
|
||||
style: {
|
||||
backgroundColor: "rgb(0 0 0 / 77%)",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
placeholder={"用户名: admin or user"}
|
||||
rules={[
|
||||
@@ -180,6 +246,10 @@ const Page = () => {
|
||||
className={"prefixIcon"}
|
||||
/>
|
||||
),
|
||||
style: {
|
||||
backgroundColor: "rgb(0 0 0 / 77%)",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
placeholder={"密码: ant.design"}
|
||||
rules={[
|
||||
@@ -204,6 +274,10 @@ const Page = () => {
|
||||
className={"prefixIcon"}
|
||||
/>
|
||||
),
|
||||
style: {
|
||||
backgroundColor: "rgb(0 0 0 / 77%)",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
name="mobile"
|
||||
placeholder={"手机号"}
|
||||
@@ -258,8 +332,8 @@ const Page = () => {
|
||||
marginBlockEnd: 24,
|
||||
}}
|
||||
>
|
||||
<ProFormCheckbox noStyle name="autoLogin">
|
||||
自动登录
|
||||
<ProFormCheckbox noStyle name="rememberMe">
|
||||
记住我
|
||||
</ProFormCheckbox>
|
||||
<a
|
||||
style={{
|
||||
|
||||
@@ -19,7 +19,7 @@ export async function login(
|
||||
body: API.UserLoginVO,
|
||||
options?: { [key: string]: any }
|
||||
) {
|
||||
return request<API.TokenType>("/system/auth/login", {
|
||||
return request<IResponse<API.TokenType>>("/system/auth/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
2
src/types/global.d.ts
vendored
2
src/types/global.d.ts
vendored
@@ -53,7 +53,7 @@ declare global {
|
||||
}
|
||||
|
||||
interface IResponse<T> {
|
||||
code: string;
|
||||
code: number;
|
||||
data: T;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,8 @@ export const getLoginForm = () => {
|
||||
};
|
||||
|
||||
export const setLoginForm = (loginForm: LoginFormType) => {
|
||||
loginForm.password = encrypt(loginForm.password) as string;
|
||||
let password = loginForm.password;
|
||||
password = encrypt(loginForm.password) as string;
|
||||
wsCache.set(CACHE_KEY.LoginForm, loginForm, { exp: 30 * 24 * 60 * 60 });
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user