0.3.2 UI 修复2
This commit is contained in:
@@ -281,12 +281,81 @@ export const move = mutation({
|
|||||||
.first();
|
.first();
|
||||||
if (!doc) throw new Error("页面不存在");
|
if (!doc) throw new Error("页面不存在");
|
||||||
if (doc.user_id !== userId) throw new Error("无权限");
|
if (doc.user_id !== userId) throw new Error("无权限");
|
||||||
|
|
||||||
|
// 说明:仅更新当前节点的 sort_order 会导致兄弟节点出现重复 sort_order,
|
||||||
|
// 前端会按 sort_order + created_at 排序,结果常常表现为“拖拽无效/拖完又回弹”。
|
||||||
|
// 这里统一对目标父节点(以及跨父移动时的原父节点)做重新编号,保证 sort_order 唯一且连续。
|
||||||
const ts = nowIso();
|
const ts = nowIso();
|
||||||
await ctx.db.patch(doc._id, {
|
|
||||||
parent_id: args.parentId,
|
const compareDocOrder = (a: any, b: any) => {
|
||||||
sort_order: args.sortOrder,
|
const orderA = typeof a.sort_order === "number" ? a.sort_order : Number.MAX_SAFE_INTEGER;
|
||||||
updated_at: ts,
|
const orderB = typeof b.sort_order === "number" ? b.sort_order : Number.MAX_SAFE_INTEGER;
|
||||||
});
|
if (orderA !== orderB) return orderA - orderB;
|
||||||
|
// created_at 为 ISO 字符串,按字典序比较即可。
|
||||||
|
return String(a.created_at ?? "").localeCompare(String(b.created_at ?? ""));
|
||||||
|
};
|
||||||
|
|
||||||
|
const clampIndex = (raw: number, max: number) => {
|
||||||
|
const n = Number.isFinite(raw) ? Math.floor(raw) : 0;
|
||||||
|
if (n < 0) return 0;
|
||||||
|
if (n > max) return max;
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchSiblings = async (parentId: string | null) => {
|
||||||
|
const siblings = await ctx.db
|
||||||
|
.query("documents")
|
||||||
|
.withIndex("by_workspace_parent", (q) => q.eq("workspace_id", doc.workspace_id).eq("parent_id", parentId))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return siblings
|
||||||
|
.filter((d) => d.user_id === userId)
|
||||||
|
.filter((d) => d.deleted_at == null)
|
||||||
|
.sort(compareDocOrder);
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyOrder = async (ordered: any[], parentId: string | null, movedId?: any) => {
|
||||||
|
for (let i = 0; i < ordered.length; i += 1) {
|
||||||
|
const item = ordered[i];
|
||||||
|
const nextSortOrder = i;
|
||||||
|
const nextParentId = parentId;
|
||||||
|
const patch: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
if ((item.parent_id ?? null) !== nextParentId) patch.parent_id = nextParentId;
|
||||||
|
if ((item.sort_order ?? null) !== nextSortOrder) patch.sort_order = nextSortOrder;
|
||||||
|
|
||||||
|
// 说明:只强制更新被移动节点的 updated_at,避免拖拽一次导致大量节点更新时间变化。
|
||||||
|
if (movedId && item._id === movedId) patch.updated_at = ts;
|
||||||
|
|
||||||
|
if (Object.keys(patch).length > 0) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await ctx.db.patch(item._id, patch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fromParentId = (doc.parent_id ?? null) as string | null;
|
||||||
|
const toParentId = args.parentId;
|
||||||
|
|
||||||
|
if (fromParentId === toParentId) {
|
||||||
|
const siblings = await fetchSiblings(toParentId);
|
||||||
|
const list = siblings.filter((d) => d.id !== doc.id);
|
||||||
|
const position = clampIndex(args.sortOrder, list.length);
|
||||||
|
list.splice(position, 0, doc);
|
||||||
|
await applyOrder(list, toParentId, doc._id);
|
||||||
|
return { ok: true, updated_at: ts };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先重排原父节点,确保移动后原列表连续。
|
||||||
|
const oldSiblings = (await fetchSiblings(fromParentId)).filter((d) => d.id !== doc.id);
|
||||||
|
await applyOrder(oldSiblings, fromParentId);
|
||||||
|
|
||||||
|
// 再重排目标父节点,把节点插入到目标位置。
|
||||||
|
const newSiblings = (await fetchSiblings(toParentId)).filter((d) => d.id !== doc.id);
|
||||||
|
const position = clampIndex(args.sortOrder, newSiblings.length);
|
||||||
|
newSiblings.splice(position, 0, doc);
|
||||||
|
await applyOrder(newSiblings, toParentId, doc._id);
|
||||||
|
|
||||||
return { ok: true, updated_at: ts };
|
return { ok: true, updated_at: ts };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user