mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-11-13 00:14:22 +08:00
123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
import type { DragEndEvent } from "@dnd-kit/core";
|
|
import type { OrderUpdatePayload } from "@/api";
|
|
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { arrayMove } from "@dnd-kit/sortable";
|
|
|
|
interface Result {
|
|
code: number;
|
|
msg?: string;
|
|
transportError?: boolean;
|
|
}
|
|
|
|
// Keep one complete, authoritative snapshot. Filtering is presentation-only.
|
|
export function useServerOrder<T extends { id: number }>(
|
|
fetchItems: () => Promise<T[]>,
|
|
saveOrder: (payload: OrderUpdatePayload) => Promise<Result>,
|
|
) {
|
|
const [items, setItems] = useState<T[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [loadError, setLoadError] = useState("");
|
|
const [saveWarning, setSaveWarning] = useState("");
|
|
const mounted = useRef(false);
|
|
const request = useRef(0);
|
|
const savingRef = useRef(false);
|
|
const snapshot = useRef(items);
|
|
|
|
snapshot.current = items;
|
|
const dragSnapshot = useRef<T[] | null>(null);
|
|
|
|
const reload = useCallback(async () => {
|
|
const version = ++request.current;
|
|
|
|
setLoading(true);
|
|
try {
|
|
const rows = await fetchItems();
|
|
|
|
if (!mounted.current || version !== request.current) return;
|
|
setItems(rows);
|
|
setLoadError("");
|
|
} catch {
|
|
if (!mounted.current || version !== request.current) return;
|
|
// Never expose a partial or unverified set as reorderable after failure.
|
|
setItems([]);
|
|
setLoadError("加载列表失败,请重试后再排序。");
|
|
} finally {
|
|
if (mounted.current && version === request.current) setLoading(false);
|
|
}
|
|
}, [fetchItems]);
|
|
|
|
useEffect(() => {
|
|
mounted.current = true;
|
|
void reload();
|
|
|
|
return () => {
|
|
mounted.current = false;
|
|
++request.current;
|
|
};
|
|
}, [reload]);
|
|
|
|
const onDragStart = () => {
|
|
dragSnapshot.current = snapshot.current;
|
|
};
|
|
const reorder = async ({ active, over }: DragEndEvent, disabled: boolean) => {
|
|
const before = dragSnapshot.current;
|
|
|
|
dragSnapshot.current = null;
|
|
if (
|
|
disabled ||
|
|
loading ||
|
|
savingRef.current ||
|
|
!over ||
|
|
active.id === over.id ||
|
|
before !== snapshot.current
|
|
)
|
|
return;
|
|
const from = items.findIndex((item) => item.id === active.id);
|
|
const to = items.findIndex((item) => item.id === over.id);
|
|
|
|
if (from < 0 || to < 0) return;
|
|
savingRef.current = true;
|
|
setSaving(true);
|
|
setSaveWarning("");
|
|
try {
|
|
const result = await saveOrder({
|
|
ids: arrayMove(items, from, to).map((item) => item.id),
|
|
expectedIds: items.map((item) => item.id),
|
|
});
|
|
|
|
if (!mounted.current) return;
|
|
if (result.code !== 0) {
|
|
setSaveWarning(
|
|
result.transportError
|
|
? "排序保存未确认,请检查重新加载后的服务器顺序再重试。"
|
|
: result.code === 409
|
|
? "列表已被其他操作更改,排序未保存,请按重新加载后的顺序重试。"
|
|
: `排序保存失败:${result.msg || "请重试"}。请检查重新加载后的服务器顺序。`,
|
|
);
|
|
}
|
|
} catch {
|
|
if (mounted.current)
|
|
setSaveWarning("排序保存未确认,请检查重新加载后的服务器顺序再重试。");
|
|
} finally {
|
|
if (mounted.current) {
|
|
await reload();
|
|
if (mounted.current) setSaving(false);
|
|
}
|
|
savingRef.current = false;
|
|
}
|
|
};
|
|
|
|
return {
|
|
items,
|
|
setItems,
|
|
loading,
|
|
saving,
|
|
error: [saveWarning, loadError].filter(Boolean).join(" "),
|
|
reload,
|
|
onDragStart,
|
|
reorder,
|
|
};
|
|
}
|