fix(web): 注入面板保存失败不再清空已加载实体并支持重试
This commit is contained in:
@@ -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 {
|
||||
// 网络层失败(后端不可达/CORS):openapi-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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user