Ver Fonte

feat: login

banxia há 1 mês atrás
pai
commit
6f5c5d0407

+ 2 - 0
src/constants/index.ts

@@ -8,3 +8,5 @@ export const MESSAGE_STATUS = {
   HAS_READ: 1,
   UNREAD: 0,
 };
+
+export const STORAGE_UID = "uid";

+ 124 - 0
src/pages/login/login.scss

@@ -0,0 +1,124 @@
+.login-container {
+  height: calc(100vh - 2.5rem);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #f9fafb;
+  padding: 3rem 1rem;
+
+  @media (min-width: 640px) {
+    padding: 3rem 1.5rem;
+  }
+
+  @media (min-width: 1024px) {
+    padding: 3rem 2rem;
+  }
+}
+
+.login-content {
+  max-width: 28rem;
+  width: 100%;
+  margin-top: 2rem;
+  margin-bottom: 2rem;
+}
+
+.login-title {
+  margin-top: 1.5rem;
+  font-size: 1.875rem;
+  font-weight: 800;
+  color: #111827;
+  text-align: center;
+}
+
+.login-form {
+  margin-top: 2rem;
+  & > * + * {
+    margin-top: 1.5rem;
+  }
+}
+
+.form-group {
+  border-radius: 0.375rem;
+  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+  & > * + * {
+    margin-top: 1rem;
+  }
+}
+
+.form-label {
+  display: block;
+  font-size: 0.875rem;
+  font-weight: 500;
+  color: #374151;
+  margin-bottom: 0.25rem;
+}
+
+.form-input {
+  appearance: none;
+  position: relative;
+  display: block;
+  width: 100%;
+  padding: 0.5rem 0.75rem;
+  border: 1px solid #d1d5db;
+  border-radius: 0.375rem;
+  background-color: transparent;
+  color: #111827;
+  font-size: 0.875rem;
+  line-height: 1.25rem;
+
+  &::placeholder {
+    color: #6b7280;
+  }
+
+  &:focus {
+    outline: none;
+    border-color: #6366f1;
+    box-shadow: 0 0 0 1px #6366f1;
+  }
+}
+
+.remember-me {
+  display: flex;
+  align-items: center;
+
+  input {
+    height: 1rem;
+    width: 1rem;
+    color: #6366f1;
+    border-color: #d1d5db;
+    border-radius: 0.25rem;
+    &:focus {
+      border-color: #6366f1;
+      box-shadow: 0 0 0 1px #6366f1;
+    }
+  }
+
+  label {
+    margin-left: 0.5rem;
+    font-size: 0.875rem;
+    color: #111827;
+  }
+}
+
+.submit-button {
+  position: relative;
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  padding: 0.5rem 1rem;
+  border: 1px solid transparent;
+  font-size: 0.875rem;
+  font-weight: 500;
+  border-radius: 0.375rem;
+  color: white;
+  background-color: #6366f1;
+
+  &:hover {
+    background-color: #4f46e5;
+  }
+
+  &:focus {
+    outline: none;
+    box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.5);
+  }
+}

+ 154 - 0
src/pages/login/login.tsx

@@ -0,0 +1,154 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import axios from "axios";
+import { message } from "antd";
+import "./login.scss";
+import { STORAGE_UID } from "@/constants";
+import storage from "@/utils/storage";
+
+// 加密函数(简单示例,实际项目应使用更安全的加密方式)
+const encryptData = (data: string) => {
+  return btoa(encodeURIComponent(data));
+};
+
+const decryptData = (data: string) => {
+  return decodeURIComponent(atob(data));
+};
+
+export default function LoginPage() {
+  const [account, setAccount] = useState("");
+  const [password, setPassword] = useState("");
+  const [remember, setRemember] = useState(false);
+  const [loading, setLoading] = useState(false);
+  const navigate = useNavigate();
+
+  // 初始化时检查记住的账号密码
+  useEffect(() => {
+    const rememberFlag = localStorage.getItem("remember_me") === "true";
+    if (rememberFlag) {
+      try {
+        const encryptedAccount = localStorage.getItem("remembered_account");
+        const encryptedPassword = localStorage.getItem("remembered_password");
+        const encryptedData = localStorage.getItem("user_data");
+
+        if (encryptedAccount && encryptedPassword) {
+          setAccount(decryptData(encryptedAccount));
+          setPassword(decryptData(encryptedPassword));
+          setRemember(true);
+        }
+
+        if (encryptedData) {
+          // 如果需要可以直接还原完整用户数据
+          const userData = JSON.parse(decryptData(encryptedData));
+          console.log("已存储的用户数据:", userData);
+        }
+      } catch (error) {
+        console.error("解密存储数据失败:", error);
+        clearRememberedData();
+      }
+    }
+  }, []);
+
+  const clearRememberedData = () => {
+    localStorage.removeItem("remembered_account");
+    localStorage.removeItem("remembered_password");
+    localStorage.removeItem("user_data");
+    localStorage.removeItem("remember_me");
+  };
+
+  const handleSubmit = async (e: React.FormEvent) => {
+    e.preventDefault();
+
+    if (!account || !password) {
+      message.warning("请输入账号和密码");
+      return;
+    }
+
+    setLoading(true);
+
+    try {
+      const response = await axios.post("/userLogin/api/login", {
+        name: account,
+        password: password,
+      });
+
+      const responseData = response.data;
+
+      if (responseData.code === 200) {
+        storage.setItem(STORAGE_UID, JSON.stringify(responseData.data.id));
+
+        // 2. 处理记住密码功能
+        if (remember) {
+          localStorage.setItem("remembered_account", encryptData(account));
+          localStorage.setItem("remembered_password", encryptData(password));
+          localStorage.setItem("remember_me", "true");
+        } else {
+          clearRememberedData();
+        }
+
+        message.success("登录成功");
+        navigate("/");
+      } else {
+        throw new Error(responseData.message || "登录失败");
+      }
+    } catch (error) {
+      console.error("登录错误:", error);
+      message.error(error.response?.data?.message || "登录失败");
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <div className="login-container">
+      <div className="login-content">
+        <h2 className="login-title">欢迎登录</h2>
+
+        <form className="login-form">
+          <div className="form-group">
+            <div>
+              <label className="form-label">账号</label>
+              <input
+                type="text"
+                className="form-input"
+                value={account}
+                onChange={(e) => setAccount(e.target.value)}
+                autoComplete="username"
+              />
+            </div>
+
+            <div>
+              <label className="form-label">密码</label>
+              <input
+                type="password"
+                className="form-input"
+                value={password}
+                onChange={(e) => setPassword(e.target.value)}
+                autoComplete={remember ? "current-password" : "off"}
+              />
+            </div>
+          </div>
+
+          <div className="remember-me">
+            <input
+              id="remember"
+              type="checkbox"
+              checked={remember}
+              onChange={(e) => setRemember(e.target.checked)}
+            />
+            <label htmlFor="remember">记住密码</label>
+          </div>
+
+          <button
+            type="button"
+            className="submit-button"
+            onClick={(e) => handleSubmit(e)}
+            disabled={loading}
+          >
+            {loading ? "登录中..." : "登录"}
+          </button>
+        </form>
+      </div>
+    </div>
+  );
+}

+ 29 - 7
src/router/beforeEnter.tsx

@@ -2,6 +2,7 @@ import { useLocation, useNavigate, useRoutes } from "react-router-dom";
 import { useState, useEffect } from "react";
 import storage from "@/utils/storage";
 import LayoutHeader from "@/layouts/Header";
+import { STORAGE_UID } from "@/constants";
 
 const BeforeEnter = ({ routers }) => {
   //1.在路由数组中找当前页面路由的对应路由项
@@ -28,12 +29,23 @@ const BeforeEnter = ({ routers }) => {
       navigate("/404");
       return;
     }
-    const toekn = storage.getItem("sky");
-    // if (!toekn && findRoute.path != "/login") {
-    //   navigate("/login");
-    // }
-    if (findRoute.mete) {
-      document.title = findRoute.mete.title;
+
+    const uid = storage.getItem(STORAGE_UID);
+
+    // 重定向到登录页
+    if (!uid && findRoute.path !== "/login") {
+      navigate("/login");
+      return;
+    }
+
+    // 已登录但访问登录页
+    if (uid && findRoute.path === "/login") {
+      navigate("/");
+      return;
+    }
+
+    if (findRoute.meta) {
+      document.title = findRoute.meta.title;
     }
   };
 
@@ -45,10 +57,20 @@ const BeforeEnter = ({ routers }) => {
     //路由守卫判断
     judgeRouter(location, navigate);
   }, [navigate, location]);
+
+  const { pathname } = location;
+  const findRoute = fineRouter(routers, pathname);
+  const uid = storage.getItem(STORAGE_UID);
+
+  // 如果是登录页或未登录,不显示Header
+  const showHeader = uid && findRoute?.path !== "/login";
+
   return (
     <div className="h-full">
       <LayoutHeader />
-      <div className="h-[calc(100vh-2.5rem)]">{router}</div>
+      <div className={showHeader ? "h-[calc(100vh-2.5rem)]" : "h-full"}>
+        {router}
+      </div>
     </div>
   );
 };

+ 10 - 2
src/router/index.tsx

@@ -6,17 +6,25 @@ import Loading from "@/pages/loading/loading";
 // 用懒加载实现优化
 const HomePage = lazy(() => import("@/pages/home/home"));
 const Error404 = lazy(() => import("@/pages/404/404"));
+const LoginPage = lazy(() => import("@/pages/login/login"));
 
 // 实现懒加载的用Suspense包裹 定义函数
 const LazyLoad = (children: ReactNode): ReactNode => {
-  return <Suspense fallback={Loading()}>{children}</Suspense>;
+  return <Suspense fallback={<Loading />}>{children}</Suspense>;
 };
 
 export const routers = [
+  {
+    path: "/login",
+    element: LazyLoad(<LoginPage />),
+    meta: {
+      title: "登录",
+    },
+  },
   {
     path: "/",
     element: <MainPage />,
-    mete: {
+    meta: {
       title: "首页",
     },
     children: [

+ 5 - 0
vite.config.ts

@@ -151,6 +151,11 @@ export default defineConfig(({ command }) => {
           changeOrigin: true,
           rewrite: (path) => path.replace(/^\/notification/, ""),
         },
+        "/userLogin": {
+          target: "http://182.44.10.206:8000",
+          changeOrigin: true,
+          rewrite: (path) => path.replace(/^\/userLogin/, ""),
+        },
       },
     },
     resolve: {