Files
writer-work-flow/apps/web/lib/foreshadow/useChapterForeshadow.ts
Yaojia Wang b810a3fa3c feat(foreshadow): 写作台本章伏笔主动提醒(前端)
- 新增 chapterForeshadow 纯分类:按当前章分 可回收/待回收/已逾期(启用闲置的 isWindowApproaching)
- useChapterForeshadow hook(复用 GET /foreshadow,切章仅重分类不重拉)
- 右栏「本章参考」+ ContextDrawer 伏笔页新增「本章伏笔」实时清单,
  分色展示 + 跳看板链接 + 空态;标注『按已验收进度』
2026-07-12 17:53:00 +02:00

72 lines
2.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { api } from "@/lib/api/client";
import type { ForeshadowView } from "@/lib/api/types";
import {
classifyChapterForeshadow,
type ChapterForeshadowGroups,
} from "./chapterForeshadow";
// 写作台「本章伏笔」提醒的数据层拉本项目全量伏笔GET /foreshadow只读
// 再按当前章号本地分类。项目内一次拉取,切章仅重新分类(不重复请求)。
export type ChapterForeshadowStatus = "loading" | "ready" | "error";
export interface UseChapterForeshadow {
groups: ChapterForeshadowGroups;
status: ChapterForeshadowStatus;
reload: () => void;
}
const FORESHADOW_PATH = "/projects/{project_id}/foreshadow" as const;
const EMPTY_ITEMS: ForeshadowView[] = [];
export function useChapterForeshadow(
projectId: string,
chapterNo: number,
): UseChapterForeshadow {
const [items, setItems] = useState<ForeshadowView[]>(EMPTY_ITEMS);
const [status, setStatus] = useState<ChapterForeshadowStatus>("loading");
// reload 触发器:自增即重新拉取。
const [nonce, setNonce] = useState(0);
useEffect(() => {
let cancelled = false;
setStatus("loading");
void (async () => {
try {
const { data, error } = await api.GET(FORESHADOW_PATH, {
params: { path: { project_id: projectId } },
});
if (cancelled) return;
if (error || !data) {
setItems(EMPTY_ITEMS);
setStatus("error");
return;
}
setItems(data.foreshadow ?? EMPTY_ITEMS);
setStatus("ready");
} catch {
// 网络层失败(后端不可达/CORSopenapi-fetch 直接抛而非返回 error 信封。
if (cancelled) return;
setItems(EMPTY_ITEMS);
setStatus("error");
}
})();
return () => {
cancelled = true;
};
}, [projectId, nonce]);
// 切章不重拉,仅重新分类(伏笔窗口相对章号变化)。
const groups = useMemo(
() => classifyChapterForeshadow(items, chapterNo),
[items, chapterNo],
);
const reload = useCallback(() => setNonce((n) => n + 1), []);
return { groups, status, reload };
}