fix(web): 注入面板保存失败不再清空已加载实体并支持重试

This commit is contained in:
Yaojia Wang
2026-06-29 16:52:43 +02:00
parent ab3f9e17b1
commit 4afbbe0fe7
3 changed files with 166 additions and 40 deletions

View File

@@ -18,7 +18,12 @@ export interface UseInjection {
data: InjectionResponse | null;
loading: boolean;
saving: boolean;
error: string | null;
// 首次加载失败(满屏错误框)与每次保存失败(悬浮提示)独立追踪,互不覆盖。
loadError: string | null;
saveError: string | null;
clearSaveError: () => void;
reload: () => void;
retrySave: () => Promise<void>;
togglePin: (ref: InjectionEntityRef) => Promise<void>;
exclude: (ref: InjectionEntityRef) => Promise<void>;
restore: (ref: InjectionEntityRef) => Promise<void>;
@@ -35,14 +40,20 @@ export function useInjection(projectId: string, chapterNo: number): UseInjection
const [data, setData] = useState<InjectionResponse | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const [loadError, setLoadError] = useState<string | null>(null);
const [saveError, setSaveError] = useState<string | null>(null);
// 重载计数loadError「重试」自增 → 触发 effect 重新拉取(不需要把 loader 提到 effect 外)。
const [reloadNonce, setReloadNonce] = useState(0);
// 并发锁:保存在途时 ref 同步置位,拦下连点/快速切换造成的乱序覆盖state 异步不可靠)。
const savingRef = useRef(false);
// 记下最近一次提交的覆盖,供 saveError「重试」重放不重复构造 override
const lastOverrideRef = useRef<InjectionOverrideRequest | null>(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(null);
setLoadError(null);
setSaveError(null);
void (async () => {
try {
@@ -51,7 +62,7 @@ export function useInjection(projectId: string, chapterNo: number): UseInjection
});
if (cancelled) return;
if (err || !body) {
setError("注入信息暂不可用");
setLoadError("注入信息暂不可用");
setData(null);
} else {
setData(body);
@@ -59,7 +70,7 @@ export function useInjection(projectId: string, chapterNo: number): UseInjection
} catch {
// 网络层失败(后端不可达/CORSopenapi-fetch 直接抛而非返回 error 信封。
if (cancelled) return;
setError("注入信息暂不可用");
setLoadError("注入信息暂不可用");
setData(null);
} finally {
if (!cancelled) setLoading(false);
@@ -69,28 +80,30 @@ export function useInjection(projectId: string, chapterNo: number): UseInjection
return () => {
cancelled = true;
};
}, [projectId, chapterNo]);
}, [projectId, chapterNo, reloadNonce]);
const save = useCallback(
async (override: InjectionOverrideRequest) => {
// 已有保存在途 → 早返回(避免并发 PUT 互相覆盖、回放结果乱序)。
if (savingRef.current) return;
lastOverrideRef.current = override;
savingRef.current = true;
setSaving(true);
setError(null);
setSaveError(null);
try {
const { data: body, error: err } = await api.PUT(INJECTION_PATH, {
params: { path: { project_id: projectId, chapter_no: chapterNo } },
body: override,
});
if (err || !body) {
setError("保存注入设置失败,请重试");
// 保存失败:保留已加载的 data仅设保存态错误天然回滚 + 可重试)。
setSaveError("保存注入设置失败,请重试");
} else {
setData(body); // 以服务端确定结果为准(不变量 #6
}
} catch {
// 网络层失败:本地状态不动(天然回滚),给可读文案、不抛。
setError("保存注入设置失败,请重试");
setSaveError("保存注入设置失败,请重试");
} finally {
savingRef.current = false;
setSaving(false);
@@ -124,6 +137,26 @@ export function useInjection(projectId: string, chapterNo: number): UseInjection
(n: number) => mutate((o) => withRecentN(o, n))(),
[mutate],
);
const clearSaveError = useCallback(() => setSaveError(null), []);
const reload = useCallback(() => setReloadNonce((n) => n + 1), []);
const retrySave = useCallback(async () => {
const override = lastOverrideRef.current;
if (!override) return;
await save(override);
}, [save]);
return { data, loading, saving, error, togglePin, exclude, restore, setRecentN };
return {
data,
loading,
saving,
loadError,
saveError,
clearSaveError,
reload,
retrySave,
togglePin,
exclude,
restore,
setRecentN,
};
}