Files
course-agent-od/web/src/app/(auth)/login/page.tsx
T
pengxiao b8561e04c6 fix: source list scroll, citation targeting, and page UI polish
- Replace ScrollArea with native overflow-y-auto for reliable mouse wheel scrolling
- Scope source DOM IDs by message ID to fix cross-round citation jumps
- Improve profile, settings, forum, knowledge page layouts
- Update mobile navigation and chat interface

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 22:05:08 +08:00

193 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { useAuthStore } from "@/store/auth";
import { BookOpen, Eye, EyeOff, Loader2 } from "lucide-react";
import { FlickeringGrid } from "@/components/magicui/flickering-grid";
import { ThemeToggle } from "@/components/ui/theme-toggle";
const loginSchema = z.object({
username: z.string().min(1, "用户名不能为空"),
password: z.string().min(1, "密码不能为空"),
});
type LoginForm = z.infer<typeof loginSchema>;
export default function LoginPage() {
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const { login, error, clearError } = useAuthStore();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginForm>({
resolver: zodResolver(loginSchema),
});
const onSubmit = async (data: LoginForm) => {
setIsLoading(true);
clearError();
const success = await login(data);
if (success) {
// 登录成功后回到主页,让用户看到无缝过渡效果
router.push("/");
}
setIsLoading(false);
};
return (
<div className="min-h-screen bg-app relative overflow-hidden">
{/* 背景动画 */}
<FlickeringGrid
className="absolute inset-0 z-0 [mask-image:radial-gradient(800px_circle_at_center,white,transparent)]"
squareSize={4}
gridGap={4}
color="#60A5FA"
maxOpacity={0.1}
flickerChance={0.05}
/>
{/* 主题切换按钮 */}
<div className="absolute top-4 right-4 z-10">
<ThemeToggle />
</div>
<div className="relative z-10 flex items-center justify-center min-h-screen p-4">
<div className="w-full max-w-md">
{/* Logo和标题 */}
<div className="text-center mb-8">
<Link
href="/"
className="flex items-center justify-center space-x-3 mb-6 group cursor-pointer transition-opacity hover:opacity-80"
>
<div className="w-12 h-12 bg-primary rounded-xl flex items-center justify-center shadow-md group-hover:shadow-lg transition-shadow">
<BookOpen className="w-7 h-7 text-white" />
</div>
<span className="text-2xl font-bold">
</span>
</Link>
<p className="text-muted-foreground text-lg"></p>
</div>
{/* 登录表单 */}
<Card className="backdrop-blur-sm bg-card/80 border-border/50 shadow-xl">
<CardHeader>
<CardTitle className="text-2xl"></CardTitle>
<CardDescription>
访
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{/* 错误提示 */}
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* 用户名输入 */}
<div className="space-y-2">
<Label htmlFor="username"></Label>
<Input
id="username"
type="text"
placeholder="请输入用户名"
{...register("username")}
disabled={isLoading}
/>
{errors.username && (
<p className="text-sm text-red-600">{errors.username.message}</p>
)}
</div>
{/* 密码输入 */}
<div className="space-y-2">
<Label htmlFor="password"></Label>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="请输入密码"
{...register("password")}
disabled={isLoading}
/>
<button
type="button"
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
onClick={() => setShowPassword(!showPassword)}
disabled={isLoading}
>
{showPassword ? (
<EyeOff className="w-4 h-4" />
) : (
<Eye className="w-4 h-4" />
)}
</button>
</div>
{errors.password && (
<p className="text-sm text-red-600">{errors.password.message}</p>
)}
</div>
{/* 登录按钮 */}
<Button
type="submit"
className="w-full"
disabled={isLoading}
>
{isLoading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
...
</>
) : (
"登录"
)}
</Button>
</form>
{/* 注册链接 */}
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
{" "}
<Link
href="/register"
className="text-blue-600 hover:text-blue-800 font-medium"
>
</Link>
</p>
</div>
</CardContent>
</Card>
{/* 快速登录提示 */}
<div className="mt-6 text-center">
<p className="text-xs text-muted-foreground">
使
</p>
</div>
</div>
</div>
</div>
);
}