AstroResearch/dashboard/src/hooks/useAuth.ts
Asfmq 26d3b2fc02 feat: PWA 移动端适配、响应式三档布局与书签认证
前端:
  - 新增 PWA manifest、透传 Service Worker、iOS 全屏 meta 标签
  - 响应式断点细化为手机(<768px)/平板(<1024px)/桌面三档
  - ReaderToolbar 操作按钮在窄屏折叠为下拉菜单,按钮文案响应式精简
  - BilingualViewer 手机端强制单列堆叠,100dvh + safe-area-inset-top 适配
  - Bookmarklet 去硬编码 localhost,动态注入 origin + bookmarklet_key 完成认证
  - 修复 useAutoScroll 改用容器级 scrollTo(scrollHeight)
  - PaperCard 操作区 flex-wrap / SearchPanel 移动端垂直布局

  后端:
  - 登录返回 bookmarklet_key(SHA-1 哈希),auth_middleware 对书签导入路径额外放行 Bearer token
  - CORS 白名单扩展到全部私有网段,SameSite cookie 改为 Lax 兼容局域网访问
  - 新增 pdf_inline_middleware,PDF 文件强制内联显示防移动端下载
  - 修复 meta.rs datetime 列别名缺失 / 登录错误兼容 {error} 对象格式
2026-07-01 18:57:21 +08:00

81 lines
2.1 KiB
TypeScript

// dashboard/src/hooks/useAuth.ts
import { useState, useEffect } from 'react';
import axios from 'axios';
export function useAuth() {
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
const [password, setPassword] = useState('');
const [loginError, setLoginError] = useState<string | null>(null);
const [loggingIn, setLoggingIn] = useState(false);
// 全局启用 Axios 跨域凭证
useEffect(() => {
axios.defaults.withCredentials = true;
}, []);
// 校验登录状态
useEffect(() => {
axios.get('/api/auth/check')
.then(() => {
setIsAuthenticated(true);
})
.catch(() => {
setIsAuthenticated(false);
});
}, []);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
if (!password.trim()) {
setLoginError('请输入访问密码!');
return;
}
setLoggingIn(true);
setLoginError(null);
try {
const res = await axios.post('/api/auth/login', { password });
if (res.data && res.data.bookmarklet_key) {
localStorage.setItem('bookmarklet_key', res.data.bookmarklet_key);
}
setIsAuthenticated(true);
setLoginError(null);
} catch (err: unknown) {
console.error('登录校验失败:', err);
const axiosError = err as { response?: { data?: string | { error?: string } } };
let errMsg = '密码错误,请重试。';
if (axiosError.response?.data) {
const data = axiosError.response.data;
if (typeof data === 'string') {
errMsg = data;
} else if (typeof data === 'object' && data !== null && 'error' in data) {
errMsg = data.error || errMsg;
}
}
setLoginError(errMsg);
} finally {
setLoggingIn(false);
}
};
const handleLogout = async () => {
try {
await axios.post('/api/auth/logout');
} catch (e) {
console.error('登出失败:', e);
}
localStorage.removeItem('bookmarklet_key');
setIsAuthenticated(false);
setPassword('');
};
return {
isAuthenticated,
password,
setPassword,
loginError,
loggingIn,
handleLogin,
handleLogout,
};
}