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
This commit is contained in:
2026-05-24 19:23:37 +08:00
parent ffc1f34331
commit 1ca3417f0e
2 changed files with 121 additions and 3 deletions
+120
View File
@@ -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<Record<string, string>>({})
const [loading, setLoading] = useState(false)
useEffect(() => {
setServerUrl(localStorage.getItem("api_base_url") || "")
}, [])
if (token) {
return <Navigate to={role === "ADMIN" ? "/admin/books" : "/books"} replace />
}
function validate(): boolean {
const next: Record<string, string> = {}
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 (
<div className="flex min-h-screen items-center justify-center px-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-center text-xl"></CardTitle>
</CardHeader>
<form onSubmit={handleSubmit}>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="serverUrl"></Label>
<Input
id="serverUrl"
type="url"
placeholder="http://localhost:8080"
value={serverUrl}
onChange={(e) => setServerUrl(e.target.value)}
aria-invalid={!!errors.serverUrl}
/>
{errors.serverUrl && (
<p className="text-sm text-destructive">{errors.serverUrl}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="username"></Label>
<Input
id="username"
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
aria-invalid={!!errors.username}
/>
{errors.username && (
<p className="text-sm text-destructive">{errors.username}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password"></Label>
<Input
id="password"
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
aria-invalid={!!errors.password}
/>
{errors.password && (
<p className="text-sm text-destructive">{errors.password}</p>
)}
</div>
</CardContent>
<CardFooter>
<Button type="submit" className="w-full" disabled={loading}>
{loading && <Loader2Icon className="animate-spin" />}
{loading ? "登录中..." : "登录"}
</Button>
</CardFooter>
</form>
</Card>
</div>
)
}
+1 -3
View File
@@ -1,10 +1,8 @@
import { createBrowserRouter, Navigate } from "react-router-dom" import { createBrowserRouter, Navigate } from "react-router-dom"
import { ProtectedRoute, AdminRoute } from "./guards" import { ProtectedRoute, AdminRoute } from "./guards"
import LoginPage from "@/features/auth/LoginPage"
// Placeholder components — real pages will replace these // Placeholder components — real pages will replace these
function LoginPage() {
return <div>Login</div>
}
function BooksPage() { function BooksPage() {
return <div>Books</div> return <div>Books</div>
} }