Ver Fonte

add login page

周玉环 há 2 semanas atrás
pai
commit
8e0e5a8021

+ 5 - 0
src/App.tsx

@@ -8,6 +8,11 @@ function AppRouter() {
   const { menus, loading } = useAuth();
 
   const router = useMemo(() => {
+    // 如果在登录页,创建只包含登录页的路由
+    if (window.location.pathname.includes("/login")) {
+      return createAppRouter([]);
+    }
+    
     if (menus.length === 0) {
       return null;
     }

+ 6 - 0
src/contexts/AuthContext.tsx

@@ -35,6 +35,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
   };
 
   useEffect(() => {
+    // 如果在登录页,跳过权限加载
+    if (window.location.pathname.includes("/login")) {
+      setLoading(false);
+      return;
+    }
+    
     loadAuthData();
   }, []);
 

+ 3 - 3
src/http/Axios.ts

@@ -180,7 +180,7 @@ export class VAxios {
 
     const opt: RequestOptions = Object.assign({}, requestOptions, options);
 
-    const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {};
+    const { beforeRequestHook, requestCatchHook, transformResponseHook } = transform || {};
 
     if (beforeRequestHook && typeof beforeRequestHook === "function") {
       conf = beforeRequestHook(conf, opt);
@@ -194,9 +194,9 @@ export class VAxios {
       this.axiosInstance
         .request<any, AxiosResponse<Result>>(conf)
         .then((res: AxiosResponse<Result>) => {
-          if (transformRequestHook && typeof transformRequestHook === "function") {
+          if (transformResponseHook && typeof transformResponseHook === "function") {
             try {
-              const ret = transformRequestHook(res, opt);
+              const ret = transformResponseHook(res, opt);
               resolve(ret);
             } catch (err) {
               reject(err || new Error("request error!"));

+ 2 - 2
src/http/axiosTransform.ts

@@ -18,9 +18,9 @@ export abstract class AxiosTransform {
   beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig;
 
   /**
-   * 请求成功处理
+   * 响应数据转换处理
    */
-  transformRequestHook?: (res: AxiosResponse<Result>, options: RequestOptions) => any;
+  transformResponseHook?: (res: AxiosResponse<Result>, options: RequestOptions) => any;
 
   /**
    * 请求失败处理

+ 60 - 17
src/http/index.ts

@@ -19,11 +19,13 @@ import { getCurrentTenantId } from "../utils/tenant";
  */
 const transform: AxiosTransform = {
   /**
-   * 处理请求数据
+   * 响应数据转换处理
    */
-  transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
+  transformResponseHook: (
+    res: AxiosResponse<Result>,
+    options: RequestOptions
+  ) => {
     const { isTransformResponse, isReturnNativeResponse } = options;
-
     // 返回原生响应头
     if (isReturnNativeResponse) {
       return res;
@@ -43,7 +45,8 @@ const transform: AxiosTransform = {
     const { code, result, message: msg, success } = data;
 
     // 成功
-    const hasSuccess = data && Reflect.has(data, "code") && (code === 200 || code === 0);
+    const hasSuccess =
+      data && Reflect.has(data, "code") && (code === 200 || code === 0);
     if (hasSuccess) {
       if (success && msg && options.successMessageMode === "success") {
         message.success(msg);
@@ -53,14 +56,14 @@ const transform: AxiosTransform = {
 
     // 错误处理
     let errorMsg = msg || "请求失败";
-
     // 根据不同的 code 处理
     switch (code) {
       case 401:
         errorMsg = msg || "登录已过期";
         // 跳转登录页
+        localStorage.removeItem("token");
         setTimeout(() => {
-          window.location.href = "/login";
+          window.location.href = "/spaces/login";
         }, 1500);
         break;
       default:
@@ -83,7 +86,14 @@ const transform: AxiosTransform = {
    * 请求之前处理 config
    */
   beforeRequestHook: (config, options) => {
-    const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
+    const {
+      apiUrl,
+      joinPrefix,
+      joinParamsToUrl,
+      formatDate,
+      joinTime = true,
+      urlPrefix,
+    } = options;
 
     // 添加前缀
     if (joinPrefix && urlPrefix && !config.url?.startsWith("http")) {
@@ -102,7 +112,10 @@ const transform: AxiosTransform = {
     if (config.method?.toUpperCase() === "GET") {
       if (typeof params === "object") {
         // GET 请求加上时间戳参数,避免从缓存中拿数据
-        config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
+        config.params = Object.assign(
+          params || {},
+          joinTimestamp(joinTime, false)
+        );
       } else {
         // 兼容 restful 风格
         config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
@@ -111,7 +124,11 @@ const transform: AxiosTransform = {
     } else {
       if (typeof params === "object") {
         formatDate && formatRequestDate(params);
-        if (Reflect.has(config, "data") && config.data && Object.keys(config.data).length > 0) {
+        if (
+          Reflect.has(config, "data") &&
+          config.data &&
+          Object.keys(config.data).length > 0
+        ) {
           config.data = data;
           config.params = params;
         } else {
@@ -120,7 +137,10 @@ const transform: AxiosTransform = {
           config.params = undefined;
         }
         if (joinParamsToUrl) {
-          config.url = setObjToUrlParams(config.url as string, Object.assign({}, config.params, config.data));
+          config.url = setObjToUrlParams(
+            config.url as string,
+            Object.assign({}, config.params, config.data)
+          );
         }
       } else {
         // 兼容 restful 风格
@@ -150,10 +170,11 @@ const transform: AxiosTransform = {
     config.headers["X-Tenant-ID"] = String(tenantId);
 
     // 如果需要 token,这里可以添加
-    const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IuWGheWuueS4reW_g-euoeeQhuWRmCIsImV4cCI6MTc2NDk2NzgwNn0.mF7iqqQ5sGsHbb5aPSAKyMcVmidPkbWGQoXLYEbdnSI';
+    const token =
+      "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IuWGheWuueS4reW_g-euoeeQhuWRmCIsImV4cCI6MTc2NTA0MjczOX0.xymZCEWejOAfdncfJRdgL27ejI7_7nE2TzT9XX7soXk";
     if (token && options.requestOptions?.withToken !== false) {
       config.headers.Authorization = token;
-      config.headers['X-Access-Token'] = token;
+      config.headers["X-Access-Token"] = token;
     }
 
     return config;
@@ -162,16 +183,26 @@ const transform: AxiosTransform = {
   /**
    * 响应拦截器处理
    */
-  responseInterceptors: (res: AxiosResponse<any>) => {
-    return res;
-  },
+  // responseInterceptors: (res: AxiosResponse<any>) => {
+  //   // 可以在这里统一处理响应数据
+  //   // 例如:记录日志、刷新 token 等
+    
+  //   // 检查响应头中的新 token
+  //   const newToken = res.headers["x-access-token"] || res.headers["authorization"];
+  //   if (newToken) {
+  //     // 更新 token(可以存储到 localStorage 或状态管理中)
+  //     console.log("[HTTP] New token received:", newToken);
+  //   }
+
+  //   return res;
+  // },
 
   /**
    * 响应错误处理
    */
   responseInterceptorsCatch: (error: any) => {
     const { response, code, message: msg, config } = error || {};
-    const errorMessageMode = config?.requestOptions?.errorMessageMode || "none";
+    const errorMessageMode = config?.requestOptions?.errorMessageMode || "message";
     const responseMsg: string = response?.data?.message ?? "";
     const err: string = error?.toString?.() ?? "";
     let errMessage = "";
@@ -196,7 +227,19 @@ const transform: AxiosTransform = {
       throw new Error(error as any);
     }
 
-    checkStatus(error?.response?.status, responseMsg, errorMessageMode);
+    // 处理 HTTP 状态码错误
+    if (response?.status) {
+      checkStatus(response.status, responseMsg, errorMessageMode);
+      
+      // 401 未授权,跳转登录页
+      if (response.status === 401) {
+        // 清除 token
+        localStorage.removeItem("token");
+        // 跳转登录页
+        window.location.href = "/spaces/login";
+      }
+    }
+
     return Promise.reject(error);
   },
 };

+ 6 - 1
src/layouts/Header/index.tsx

@@ -64,6 +64,11 @@ export default function Header() {
   // 跳转到权限管理
   const toPermissionPage = () => {
     debugger;
+  };
+
+  // 退出登录
+  const loginOut = () => {
+    navigate('/login')
   }
 
   if (loading) {
@@ -216,7 +221,7 @@ export default function Header() {
               rel="noopener noreferrer"
             />
             <Navbar.MenuDivider />
-            <Navbar.MenuItem title="退出登录" />
+            <Navbar.MenuItem title="退出登录" onClick={loginOut} />
           </Navbar.Account>
         }
         aria={{

+ 56 - 0
src/pages/login/index.module.css

@@ -0,0 +1,56 @@
+.container {
+  min-height: 100vh;
+  background-color: #f7f9fa;
+  padding: 30px 20px;
+}
+
+.loginBox {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+}
+
+.header {
+  text-align: center;
+  margin-bottom: 20px;
+}
+
+.title {
+  font-size: 48px;
+  font-weight: 400;
+  color: #1a1a1a;
+  margin-bottom: 5px;
+  margin-top: 0;
+}
+
+.subtitle {
+  font-size: 20px;
+  color: #8091a5;
+}
+
+.formContainer {
+  width: 520px;
+  background: white;
+  border-radius: 8px;
+  padding: 40px 30px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+
+.socialButton {
+  flex: 1;
+}
+
+.footer {
+  flex-wrap: wrap;
+}
+
+.footer button {
+  color: #0066ff;
+  font-size: 14px;
+}
+
+.footer button:hover {
+  text-decoration: underline;
+}

+ 161 - 0
src/pages/login/index.tsx

@@ -0,0 +1,161 @@
+import { useState } from "react";
+import {
+  Box,
+  Flex,
+  TextInput,
+  Button,
+  FormControl,
+  Checkbox,
+  Note,
+} from "@contentful/f36-components";
+import { useNavigate } from "react-router-dom";
+import styles from "./index.module.css";
+
+export default function LoginPage() {
+  const [account, setAccount] = useState("");
+  const [password, setPassword] = useState("");
+  const [rememberMe, setRememberMe] = useState(false);
+  const [loading, setLoading] = useState(false);
+  const [errorMessage, setErrorMessage] = useState("");
+  const navigate = useNavigate();
+
+  const handleLogin = async (e: React.FormEvent) => {
+    e.preventDefault();
+
+    // 清空之前的错误
+    setErrorMessage("");
+
+    // 验证表单 - 只检查必填
+    if (!account.trim() || !password.trim()) {
+      setErrorMessage("账号或密码不能为空");
+      return;
+    }
+
+    setLoading(true);
+
+    try {
+      // TODO: 实现登录逻辑
+      console.log("Login:", { account, password, rememberMe });
+
+      // 模拟登录失败
+      // setErrorMessage("账号或密码错误");
+
+      // 模拟登录成功后跳转
+      setTimeout(() => {
+        navigate("/content-model");
+      }, 1000);
+    } catch (error) {
+      console.error("登录失败:", error);
+      setErrorMessage("登录失败,请稍后重试");
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <Flex
+      justifyContent="center"
+      alignItems="center"
+      className={styles.container}
+    >
+      <Box className={styles.loginBox}>
+        {/* 标题 */}
+        <Box className={styles.header}>
+          <h1 className={styles.title}>欢迎回来</h1>
+          <h2 className={styles.subtitle}>登录你的账号</h2>
+        </Box>
+
+        {/* 登录表单 */}
+        <Box className={styles.formContainer}>
+          <form onSubmit={handleLogin}>
+            <Flex flexDirection="column" gap="spacingL">
+              {/* 错误提示 */}
+              {errorMessage && (
+                <Note variant="warning">
+                  {errorMessage}
+                </Note>
+              )}
+
+              {/* account */}
+              <FormControl>
+                <FormControl.Label>账号</FormControl.Label>
+                <TextInput
+                  //   type="account"
+                  value={account}
+                  onChange={(e) => {
+                    setAccount(e.target.value);
+                    // 清除错误提示
+                    if (errorMessage) {
+                      setErrorMessage("");
+                    }
+                  }}
+                  placeholder="请输入你的账号"
+                />
+              </FormControl>
+
+              {/* Password */}
+              <FormControl>
+                <FormControl.Label>密码</FormControl.Label>
+                <TextInput
+                  type="password"
+                  value={password}
+                  onChange={(e) => {
+                    setPassword(e.target.value);
+                    // 清除错误提示
+                    if (errorMessage) {
+                      setErrorMessage("");
+                    }
+                  }}
+                  placeholder="请输入你的密码"
+                />
+              </FormControl>
+
+              {/* Remember me */}
+              <Checkbox
+                isChecked={rememberMe}
+                onChange={() => setRememberMe(!rememberMe)}
+              >
+                Remember me
+              </Checkbox>
+
+              {/* Login Button */}
+              <Button
+                variant="primary"
+                size="large"
+                type="submit"
+                isFullWidth
+                isLoading={loading}
+              >
+                Log in
+              </Button>
+            </Flex>
+          </form>
+        </Box>
+
+        {/* Footer Links */}
+        {/* <Flex
+          justifyContent="center"
+          gap="spacingS"
+          marginTop="spacingL"
+          className={styles.footer}
+        >
+          <Button variant="transparent" size="small">
+            Log in via SSO
+          </Button>
+          <Text>|</Text>
+          <Button variant="transparent" size="small">
+            Reset password
+          </Button>
+          <Text>|</Text>
+          <Button variant="transparent" size="small">
+            Resend confirmation
+          </Button>
+          <Text>|</Text>
+          <Button variant="transparent" size="small">
+            Unlock account
+          </Button>
+        </Flex> */}
+      </Box>
+    </Flex>
+  );
+}

+ 6 - 0
src/router/index.tsx

@@ -5,6 +5,8 @@
 import { lazy, Suspense } from "react";
 import { createBrowserRouter, Navigate, type RouteObject } from "react-router-dom";
 import MainLayout from "../layouts/MainLayout/index";
+import LoginPage from "../pages/login/index";
+
 import type { JeecgMenu } from "../types/menu";
 
 // 默认加载组件
@@ -171,6 +173,10 @@ export function generateRoutes(menus: JeecgMenu[]): RouteObject[] {
         ...routes,
       ],
     },
+    {
+      path: "/login",
+      element: <LoginPage />,
+    },
   ];
 }
 

+ 9 - 9
vite.config.ts

@@ -27,15 +27,15 @@ export default defineConfig(() => {
         "/sohoyw-som": {
           target: "http://52.83.132.95:8080",
           changeOrigin: true,
-          configure: (proxy) => {
-            proxy.on("proxyReq", (proxyReq) => {
-              // 添加模拟登录的 Cookie
-              proxyReq.setHeader(
-                "Authorization",
-                "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzY0NTMxMjM3fQ.15IfYua8M_cSE4i3kT86fanqO3OTG39fl2EJYXSQ1z8"
-              );
-            });
-          },
+          // configure: (proxy) => {
+          //   proxy.on("proxyReq", (proxyReq) => {
+          //     // 添加模拟登录的 Cookie
+          //     proxyReq.setHeader(
+          //       "Authorization",
+          //       "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzY0NTMxMjM3fQ.15IfYua8M_cSE4i3kT86fanqO3OTG39fl2EJYXSQ1z8"
+          //     );
+          //   });
+          // },
         },
       },
     },