fix(frontend): resolve all eslint errors to zero (B1)

This commit is contained in:
2026-09-15 11:39:21 +08:00
parent 29167d3cea
commit a427aa1c4a
9 changed files with 56 additions and 9 deletions
+3
View File
@@ -35,6 +35,9 @@ export default tseslint.config(
"jsx-a11y/click-events-have-key-events": "warn",
"jsx-a11y/no-static-element-interactions": "warn",
"jsx-a11y/no-noninteractive-element-interactions": "warn",
// 键盘焦点容器:CBZ/PDF 阅读器根 div 用 tabIndex={0} 接方向键翻页,
// 与上面三条同属 ② 改版范围(radix 原语自带焦点管理),本期随块降 warn
"jsx-a11y/no-noninteractive-tabindex": "warn",
},
},
{
+1
View File
@@ -40,6 +40,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
useEffect(() => {
if (token && meQ.isError) {
setToken(null); // 非 401 失败也要清持久化 token,避免与 React 状态分叉导致 RequireAuth 永久加载
// eslint-disable-next-line react-hooks/set-state-in-effect -- 对「me 查询失败」异步结果的清理反应,与持久化 token 需同帧回收;改渲染期重置/事件驱动会改变鉴权失败时序,保守保留
setTok(null);
}
}, [token, meQ.isError]);
+7 -1
View File
@@ -12,12 +12,18 @@ export function useAuthedImage(url: string | undefined): { src: string; failed:
});
const [nonce, setNonce] = useState(0);
const key = (url ?? "") + "#" + nonce;
// url/nonce 变化时先落回占位态:React 官方推荐的「渲染期按下发 key 重置本地 state」,
// 替代原先 effect 内的同步 setState(级联渲染),行为等价——加载中同样呈现 src="" 的占位
const [prevKey, setPrevKey] = useState(key);
if (prevKey !== key) {
setPrevKey(key);
setState({ key, src: "", failed: false });
}
useEffect(() => {
if (!url) return;
let dead = false;
const k = url + "#" + nonce;
setState({ key: k, src: "", failed: false });
fetchObjectUrl(url)
.then((src) => !dead && setState({ key: k, src, failed: false }))
.catch(() => !dead && setState({ key: k, src: "", failed: true }));
+1
View File
@@ -51,6 +51,7 @@ export default function Login() {
id="login-user"
value={u}
onChange={(e) => setU(e.target.value)}
// eslint-disable-next-line jsx-a11y/no-autofocus -- 登录页整页即表单,首字段自动聚焦是通行 UX;规则无按场景豁免选项,保留行为
autoFocus
autoComplete="username"
/>
+8 -1
View File
@@ -141,7 +141,13 @@ export default function Shelf() {
const [qRaw, setQRaw] = useState(serverQ);
const [confirmDel, setConfirmDel] = useState<Book | null>(null);
useEffect(() => setQRaw(serverQ), [serverQ]);
// URL 是筛选词的事实来源:回退/前进/程序化改参时把输入框拉回 URL 值。
// 用 React 官方推荐的「渲染期按下发值重置本地 state」替代原先 effect 内的同步 setState
const [prevServerQ, setPrevServerQ] = useState(serverQ);
if (prevServerQ !== serverQ) {
setPrevServerQ(serverQ);
setQRaw(serverQ);
}
useEffect(() => {
if (qRaw.trim() === serverQ) return;
const t = setTimeout(() => {
@@ -284,6 +290,7 @@ export default function Shelf() {
{continueRows.length > 0 && !searching && (
<section className="mb-8">
<h2 className="mb-3 text-sm font-medium text-muted-foreground">继续阅读</h2>
{/* eslint-disable-next-line jsx-a11y/no-redundant-roles -- Tailwind preflight 移除了 ul 的 list 样式,Safari/VoiceOver 因此丢列表语义;role="list" 是官方推荐的补偿写法,非冗余 */}
<ul className="flex gap-3 overflow-x-auto pb-2" role="list">
{continueRows.map((r, i) => {
const b = booksByKey.get(`${r.library_id}/${r.path}`);
+9 -2
View File
@@ -38,10 +38,17 @@ function PageImg({ url, width, onLoaded }: { url: string; width: number; onLoade
const [src, setSrc] = useState("");
const [failed, setFailed] = useState(false);
const [nonce, setNonce] = useState(0);
useEffect(() => {
let dead = false;
// url/nonce 变化时先落回占位态:React 官方推荐的「渲染期按下发 key 重置本地 state」,
// 替代原先 effect 内的同步 setState(级联渲染);换页/重试时的表现与原先一致
const key = url + "#" + nonce;
const [prevKey, setPrevKey] = useState(key);
if (prevKey !== key) {
setPrevKey(key);
setSrc("");
setFailed(false);
}
useEffect(() => {
let dead = false;
fetchObjectUrl(url)
.then((s) => !dead && setSrc(s))
.catch(() => !dead && setFailed(true));
+9 -2
View File
@@ -38,12 +38,19 @@ export default function EpubReader({ book, initialLocator, initialPercent }: Rea
const [err, setErr] = useState("");
const [nonce, setNonce] = useState(0);
const [ready, setReady] = useState(false);
// 重新加载(换书/重试)时先清错误与就绪态:React 官方推荐的「渲染期按下发 key 重置本地 state」,
// 替代原先 effect 内的同步 setState(级联渲染);首挂载 key 相等,重置为空操作,行为等价
const loadKey = (book.file_url ?? "") + "#" + nonce;
const [prevLoadKey, setPrevLoadKey] = useState(loadKey);
if (prevLoadKey !== loadKey) {
setPrevLoadKey(loadKey);
setErr("");
setReady(false);
}
useEffect(() => {
let dead = false;
let bookObj: EpubBookLike | null = null;
setErr("");
setReady(false);
(async () => {
const ePub = await loadEpub();
const buf = await apiRaw(book.file_url ?? "").then((r) => r.arrayBuffer());
+8 -1
View File
@@ -28,10 +28,17 @@ export default function PdfReader({ book, initialLocator, initialPercent }: Read
const [err, setErr] = useState("");
const [nonce, setNonce] = useState(0); // 仅加载重试
const [redraw, setRedraw] = useState(0); // 仅 resize 重绘(不重新下载文档)
// 重新加载(换书/重试)时先清错误:React 官方推荐的「渲染期按下发 key 重置本地 state」,
// 替代原先 effect 内的同步 setState(级联渲染);首挂载 key 相等,重置为空操作,行为等价
const loadKey = (book.file_url ?? "") + "#" + nonce;
const [prevLoadKey, setPrevLoadKey] = useState(loadKey);
if (prevLoadKey !== loadKey) {
setPrevLoadKey(loadKey);
setErr("");
}
useEffect(() => {
let dead = false;
setErr("");
(async () => {
const buf = await apiRaw(book.file_url ?? "").then((r) => r.arrayBuffer());
if (dead) return;
+10 -2
View File
@@ -19,10 +19,17 @@ function useFileText(url: string) {
const [text, setText] = useState<string | null>(null);
const [err, setErr] = useState("");
const [nonce, setNonce] = useState(0);
useEffect(() => {
let dead = false;
// url/nonce 变化时先落回加载态:React 官方推荐的「渲染期按下发 key 重置本地 state」,
// 替代原先 effect 内的同步 setState(级联渲染);重试时的表现与原先一致
const key = url + "#" + nonce;
const [prevKey, setPrevKey] = useState(key);
if (prevKey !== key) {
setPrevKey(key);
setText(null);
setErr("");
}
useEffect(() => {
let dead = false;
apiRaw(url)
.then((r) => r.arrayBuffer())
.then((b) => !dead && setText(decodeText(b)))
@@ -238,6 +245,7 @@ function TxtView({ book, initialLocator, initialPercent, chrome }: ReaderProps)
const rawCh = Number(initialLocator?.ch);
if (Number.isFinite(rawCh) && rawCh >= 0 && rawCh < chapters.length) {
targetCi.current = Math.floor(rawCh);
// eslint-disable-next-line react-hooks/set-state-in-effect -- 首章渲染后按缓存 locator 落位阅读进度;ci 与下方滚动定位 effect/refs 协作,改渲染期重置属高风险重构,保守保留
setCi(Math.floor(rawCh));
if (hasF) restoreFrac.current = f;
} else if (hasF) {