feat: 管理端板块管理与旧版数据导入,补充部署运营文档

- 新增管理端板块管理页面与后端接口(admin_board)
- 新增旧版数据导入:legacyimport 服务、导入面板、importusers 命令行工具
- 聊天用户卡片、板块图标等 UI 组件与界面优化
- 补充 about/公告/1Panel 部署等文档
- gitignore 排除 dist/ 构建产物与 .agents/ 本地工具目录
This commit is contained in:
2026-09-24 03:37:44 +08:00
parent c1f6a6636b
commit 3e55b5d230
53 changed files with 5078 additions and 409 deletions

View File

@@ -38,6 +38,14 @@ export default function LoginPage() {
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [retryAfter, setRetryAfter] = useState(0);
// 登录限流倒计时:窗口期内禁用提交,避免无意义请求与提示条消失重现的闪烁
useEffect(() => {
if (retryAfter <= 0) return;
const t = setTimeout(() => setRetryAfter((s) => s - 1), 1000);
return () => clearTimeout(t);
}, [retryAfter]);
// 已登录用户访问登录页:直接回落到安全的跳转目标
useEffect(() => {
@@ -52,7 +60,7 @@ export default function LoginPage() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
// 提交不清空 error:提示条消失又重现会造成表单跳动(闪烁),输入时再清除
setLoading(true);
try {
const res = await apiLogin(username, password);
@@ -63,6 +71,9 @@ export default function LoginPage() {
router.refresh();
} else {
setError(res.error || "登录失败");
if (typeof res.retry_after === "number" && res.retry_after > 0) {
setRetryAfter(res.retry_after);
}
}
} finally {
setLoading(false);
@@ -96,7 +107,10 @@ export default function LoginPage() {
id="login-username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
onChange={(e) => {
setUsername(e.target.value);
setError("");
}}
required
autoFocus
autoComplete="username"
@@ -110,15 +124,18 @@ export default function LoginPage() {
id="login-password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
onChange={(e) => {
setPassword(e.target.value);
setError("");
}}
required
autoComplete="current-password"
className="field"
placeholder="输入密码"
/>
</div>
<button type="submit" disabled={loading} className="btn btn-primary w-full justify-center mt-2">
{loading ? "登录中..." : "登录"}
<button type="submit" disabled={loading || retryAfter > 0} className="btn btn-primary w-full justify-center mt-2">
{loading ? "登录中..." : retryAfter > 0 ? `${retryAfter} 秒后可重试` : "登录"}
</button>
</form>