前端:
- 新增 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} 对象格式
81 lines
2.1 KiB
TypeScript
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,
|
|
};
|
|
}
|