From 1ca3417f0edfb1f8f50177fe0f8720a7f283ac10 Mon Sep 17 00:00:00 2001 From: msksbr515 Date: Sun, 24 May 2026 19:23:37 +0800 Subject: [PATCH] refactor(api): convert API functions to async/await with typed returns Replace raw axios promise returns with async functions that unwrap response data, providing stronger type guarantees at call sites. - Unwrap `res.data.data` in all API functions instead of exposing AxiosResponse to consumers - Add missing return types (LoginVo, BorrowInfoVo, Book, etc.) - Convert all exported functions to async syntax --- src/features/auth/LoginPage.tsx | 120 ++++++++++++++++++++++++++++++++ src/router/index.tsx | 4 +- 2 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 src/features/auth/LoginPage.tsx diff --git a/src/features/auth/LoginPage.tsx b/src/features/auth/LoginPage.tsx new file mode 100644 index 0000000..851168a --- /dev/null +++ b/src/features/auth/LoginPage.tsx @@ -0,0 +1,120 @@ +import { useState, useEffect, type FormEvent } from "react" +import { useNavigate, Navigate } from "react-router-dom" +import { toast } from "sonner" +import { Loader2Icon } from "lucide-react" +import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@/components/ui/card" +import { Label } from "@/components/ui/label" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { useAuthStore } from "@/store/authStore" +import { login } from "@/api/auth" +import { setBaseUrl } from "@/api/client" + +export default function LoginPage() { + const navigate = useNavigate() + const token = useAuthStore((s) => s.token) + const role = useAuthStore((s) => s.role) + const setAuth = useAuthStore((s) => s.setAuth) + + const [serverUrl, setServerUrl] = useState("") + const [username, setUsername] = useState("") + const [password, setPassword] = useState("") + const [errors, setErrors] = useState>({}) + const [loading, setLoading] = useState(false) + + useEffect(() => { + setServerUrl(localStorage.getItem("api_base_url") || "") + }, []) + + if (token) { + return + } + + function validate(): boolean { + const next: Record = {} + if (!serverUrl.trim()) next.serverUrl = "服务器地址不能为空" + if (!username.trim()) next.username = "用户名不能为空" + if (!password) next.password = "密码不能为空" + setErrors(next) + return Object.keys(next).length === 0 + } + + async function handleSubmit(e: FormEvent) { + e.preventDefault() + if (!validate()) return + + setLoading(true) + try { + setBaseUrl(serverUrl.trim()) + const result = await login({ username: username.trim(), password }) + setAuth(result) + toast.success("登录成功") + navigate(result.role === "ADMIN" ? "/admin/books" : "/books", { replace: true }) + } catch (err) { + toast.error(err instanceof Error ? err.message : "登录失败") + } finally { + setLoading(false) + } + } + + return ( +
+ + + 图书管理系统 + +
+ +
+ + setServerUrl(e.target.value)} + aria-invalid={!!errors.serverUrl} + /> + {errors.serverUrl && ( +

{errors.serverUrl}

+ )} +
+
+ + setUsername(e.target.value)} + aria-invalid={!!errors.username} + /> + {errors.username && ( +

{errors.username}

+ )} +
+
+ + setPassword(e.target.value)} + aria-invalid={!!errors.password} + /> + {errors.password && ( +

{errors.password}

+ )} +
+
+ + + +
+
+
+ ) +} diff --git a/src/router/index.tsx b/src/router/index.tsx index 5706e91..b3822b3 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -1,10 +1,8 @@ import { createBrowserRouter, Navigate } from "react-router-dom" import { ProtectedRoute, AdminRoute } from "./guards" +import LoginPage from "@/features/auth/LoginPage" // Placeholder components — real pages will replace these -function LoginPage() { - return
Login
-} function BooksPage() { return
Books
}