简化登录页并新增记住密码
This commit is contained in:
@@ -478,3 +478,26 @@
|
||||
- 风险与影响:
|
||||
- 影响范围限定在前端管理页交互层;后端接口仍保持兼容,可继续返回权限相关字段但前端不再暴露配置入口。
|
||||
- 若后续需彻底下线该能力(含后端字段/持久化),需单独评估接口契约与历史数据兼容。
|
||||
|
||||
## Work Log - 登录页面还原最简洁样式并保留记住密码(2026-05-01)
|
||||
|
||||
- 背景:
|
||||
- Issue `FL-144` 要求将登录页还原为最简洁样式,保留“登录、记住密码”功能,并将标题改为“防雷计算”。
|
||||
|
||||
- 本次改动(最小闭环):
|
||||
- 文件:`web/src/app/page.tsx`
|
||||
- 去除登录页注册模式相关状态与 UI(`mode/register/username/切换按钮`),仅保留登录流程。
|
||||
- 页面主标题改为 `防雷计算`。
|
||||
- 视觉样式收敛为简洁白底 + 居中卡片布局,移除装饰性图标块、渐变、复杂文案。
|
||||
- 新增“记住密码”复选框:
|
||||
- 勾选后登录成功时将用户 ID 与密码写入 `localStorage`;
|
||||
- 未勾选时清理本地缓存;
|
||||
- 页面加载时若已记住则自动回填账号密码并默认勾选。
|
||||
|
||||
- 验证:
|
||||
- 代码路径自检:登录仍走 `useAuth().login` 既有链路,未改动鉴权接口与路由跳转逻辑。
|
||||
- 按任务约束未执行编译/安装依赖。
|
||||
|
||||
- 风险与影响:
|
||||
- 影响范围:仅前端登录页 `web/src/app/page.tsx`。
|
||||
- 风险:记住密码当前使用浏览器 `localStorage` 明文存储,存在本机安全暴露风险(符合需求但需知悉)。
|
||||
|
||||
+54
-86
@@ -1,23 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { IdcardOutlined, LockOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { IdcardOutlined, LockOutlined } from "@ant-design/icons";
|
||||
import { ChangeEvent, FormEvent, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Alert, Button, Input, Space, Typography } from "antd";
|
||||
import { Alert, Button, Checkbox, Input, Space, Typography } from "antd";
|
||||
|
||||
import { useAuth } from "@/components/auth-provider";
|
||||
import { Card } from "@/components/ui-antd";
|
||||
|
||||
type Mode = "login" | "register";
|
||||
const LOGIN_REMEMBER_KEY = "login.remember";
|
||||
const LOGIN_USER_ID_KEY = "login.user_id";
|
||||
const LOGIN_PASSWORD_KEY = "login.password";
|
||||
|
||||
export default function Home() {
|
||||
const router = useRouter();
|
||||
const { user, initializing, login, register } = useAuth();
|
||||
const { user, initializing, login } = useAuth();
|
||||
|
||||
const [mode, setMode] = useState<Mode>("login");
|
||||
const [userId, setUserId] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [rememberPassword, setRememberPassword] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
@@ -27,8 +28,20 @@ export default function Home() {
|
||||
}
|
||||
}, [initializing, router, user]);
|
||||
|
||||
const formTitle = mode === "login" ? "登录你的工作台" : "创建你的工作台";
|
||||
const submitLabel = mode === "login" ? "登录" : "创建账号";
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const remembered = window.localStorage.getItem(LOGIN_REMEMBER_KEY) === "1";
|
||||
if (!remembered) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRememberPassword(true);
|
||||
setUserId(window.localStorage.getItem(LOGIN_USER_ID_KEY) ?? "");
|
||||
setPassword(window.localStorage.getItem(LOGIN_PASSWORD_KEY) ?? "");
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
@@ -36,11 +49,21 @@ export default function Home() {
|
||||
setBusy(true);
|
||||
|
||||
try {
|
||||
if (mode === "login") {
|
||||
await login(userId.trim(), password);
|
||||
const normalizedUserId = userId.trim();
|
||||
await login(normalizedUserId, password);
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
if (rememberPassword) {
|
||||
window.localStorage.setItem(LOGIN_REMEMBER_KEY, "1");
|
||||
window.localStorage.setItem(LOGIN_USER_ID_KEY, normalizedUserId);
|
||||
window.localStorage.setItem(LOGIN_PASSWORD_KEY, password);
|
||||
} else {
|
||||
await register(`${username.trim() || userId.trim()}@example.local`, username.trim(), password);
|
||||
window.localStorage.removeItem(LOGIN_REMEMBER_KEY);
|
||||
window.localStorage.removeItem(LOGIN_USER_ID_KEY);
|
||||
window.localStorage.removeItem(LOGIN_PASSWORD_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
setPassword("");
|
||||
} catch (submitError) {
|
||||
const message = submitError instanceof Error ? submitError.message : "未知错误";
|
||||
@@ -67,108 +90,53 @@ export default function Home() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-[#eef2f8] px-4 py-6 sm:px-8 sm:py-10">
|
||||
<section className="mx-auto flex w-full max-w-[760px] items-center justify-center rounded-[24px] bg-[#f3f4f6] p-4 shadow-[0_20px_48px_rgba(15,23,42,0.12)] sm:p-8">
|
||||
<Card className="w-full border-0 shadow-none" styles={{ body: { padding: "24px 16px" } }}>
|
||||
<Space direction="vertical" size={22} className="w-full">
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="grid h-[60px] w-[60px] place-items-center rounded-[16px] bg-[linear-gradient(160deg,#0ccbf0_0%,#3571ff_54%,#7156f8_100%)] text-[32px] font-bold text-white shadow-[0_10px_20px_rgba(53,113,255,0.32)]">
|
||||
D
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Title level={2} className="!mb-0 !text-[#0f1b36]">
|
||||
开发智能平台
|
||||
</Typography.Title>
|
||||
<Typography.Text type="secondary">Development Intelligence Platform</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Typography.Title level={2} className="!mb-0 !text-center !text-[#0f1b36]">
|
||||
{formTitle}
|
||||
<main className="flex min-h-screen items-center justify-center bg-white px-4 py-8">
|
||||
<Card className="w-full max-w-[360px]">
|
||||
<Space direction="vertical" size={20} className="w-full">
|
||||
<Typography.Title level={3} className="!mb-0 !text-center">
|
||||
防雷计算
|
||||
</Typography.Title>
|
||||
|
||||
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||
<div>
|
||||
<Typography.Text strong className="mb-2 block tracking-wide text-[#1e293b]">
|
||||
用户 ID
|
||||
</Typography.Text>
|
||||
<Input
|
||||
size="large"
|
||||
value={userId}
|
||||
prefix={<IdcardOutlined className="text-slate-400" />}
|
||||
placeholder="请输入用户ID"
|
||||
prefix={<IdcardOutlined />}
|
||||
placeholder="用户 ID"
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => setUserId(event.currentTarget.value)}
|
||||
className="h-[48px] rounded-[10px]"
|
||||
autoComplete="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{mode === "register" && (
|
||||
<div>
|
||||
<Typography.Text strong className="mb-2 block tracking-wide text-[#1e293b]">
|
||||
用户名
|
||||
</Typography.Text>
|
||||
<Input
|
||||
size="large"
|
||||
value={username}
|
||||
prefix={<UserOutlined className="text-slate-400" />}
|
||||
placeholder="请输入用户名"
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => setUsername(event.currentTarget.value)}
|
||||
className="h-[48px] rounded-[10px]"
|
||||
minLength={3}
|
||||
maxLength={64}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Typography.Text strong className="mb-2 block tracking-wide text-[#1e293b]">
|
||||
密码
|
||||
</Typography.Text>
|
||||
<Input.Password
|
||||
size="large"
|
||||
value={password}
|
||||
prefix={<LockOutlined className="text-slate-400" />}
|
||||
placeholder={mode === "login" ? "请输入密码" : "请输入密码(至少 8 位)"}
|
||||
prefix={<LockOutlined />}
|
||||
placeholder="密码"
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => setPassword(event.currentTarget.value)}
|
||||
className="h-[48px] rounded-[10px]"
|
||||
autoComplete={mode === "login" ? "current-password" : "new-password"}
|
||||
minLength={mode === "login" ? 1 : 8}
|
||||
autoComplete="current-password"
|
||||
minLength={1}
|
||||
maxLength={128}
|
||||
required
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Checkbox
|
||||
checked={rememberPassword}
|
||||
onChange={(event) => setRememberPassword(event.target.checked)}
|
||||
>
|
||||
记住密码
|
||||
</Checkbox>
|
||||
</div>
|
||||
|
||||
<Button block size="large" type="primary" htmlType="submit" loading={busy}>
|
||||
{submitLabel}
|
||||
</Button>
|
||||
|
||||
{mode === "login" && (
|
||||
<div className="flex justify-end">
|
||||
<Button type="link" onClick={() => setError("请联系管理员重置密码。")}>
|
||||
忘记密码?
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
block
|
||||
type="link"
|
||||
onClick={() => {
|
||||
setError("");
|
||||
setMode((current) => (current === "login" ? "register" : "login"));
|
||||
}}
|
||||
>
|
||||
{mode === "login" ? "创建新项目?" : "返回登录"}
|
||||
登录
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
{error && <Alert showIcon type="error" message={error} />}
|
||||
</Space>
|
||||
</Card>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user