diff --git a/src/main/index.ts b/src/main/index.ts index 77fda1c..9291957 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -127,7 +127,9 @@ function registerIpc(): void { for (const flow of createExampleFlows(url)) saveFlow(flow); return listFlows(); }); - ipcMain.handle("flows:get", (_event, id: string) => getFlow(id)); + ipcMain.handle("flows:get", (_event, id: string) => { + return getFlow(id); + }); ipcMain.handle("flows:save", (_event, flow: FlowDefinition, opts?: FlowSaveOptions): FlowSaveResult => { if (!opts?.force && opts?.expectedUpdatedAt) { let disk: FlowDefinition | null = null; diff --git a/src/renderer/src/pages/EditorPage.tsx b/src/renderer/src/pages/EditorPage.tsx index d1373f6..f0bf626 100644 --- a/src/renderer/src/pages/EditorPage.tsx +++ b/src/renderer/src/pages/EditorPage.tsx @@ -65,7 +65,9 @@ export function EditorPage({ catalog, onToast, onBack }: EditorPageProps) { setSaving(true); try { const result = await window.rpa.flows.save(current, { - expectedUpdatedAt: current.meta?.updatedAt || undefined + // 用「磁盘基准版本」做乐观锁,不能用 current.meta.updatedAt—— + // 每次本地编辑都会拨快它,会导致每次保存都误判磁盘冲突 + expectedUpdatedAt: useEditorStore.getState().baseUpdatedAt || undefined }); if (result.conflict) { if (opts?.interactive) { diff --git a/src/renderer/src/store/editorStore.ts b/src/renderer/src/store/editorStore.ts index 1c2bf21..5e0e736 100644 --- a/src/renderer/src/store/editorStore.ts +++ b/src/renderer/src/store/editorStore.ts @@ -57,6 +57,10 @@ function uniqueOutputVar(base: string, nodes: FlowNode[]): string { interface EditorState { flow: FlowDefinition | null; + /** 乐观锁基准:最近一次从磁盘加载/保存成功返回的 meta.updatedAt。 + * 保存时以此为 expectedUpdatedAt;不能直接用 flow.meta.updatedAt, + * 因为 commit() 每次编辑都会拨快它,会导致每次保存都误判磁盘冲突。 */ + baseUpdatedAt: string | null; catalog: Catalog | null; selectedNodeId: string | null; dirty: boolean; @@ -104,6 +108,7 @@ export const useEditorStore = create((set, get) => { return { flow: null, + baseUpdatedAt: null, catalog: null, selectedNodeId: null, dirty: false, @@ -116,7 +121,14 @@ export const useEditorStore = create((set, get) => { // 切换流程时清掉上一次的运行面板状态,避免显示旧流程的运行结果 const runState = useRunStore.getState(); if (!runState.running) runState.clear(); - set({ flow, selectedNodeId: null, dirty: false, past: [], future: [] }); + set({ + flow, + selectedNodeId: null, + dirty: false, + past: [], + future: [], + baseUpdatedAt: flow.meta?.updatedAt ?? null + }); }, selectNode: (id) => set({ selectedNodeId: id }), @@ -340,9 +352,11 @@ export const useEditorStore = create((set, get) => { }, markSaved: () => set({ dirty: false }), - syncSavedFlow: (flow) => set({ flow, dirty: false }), + syncSavedFlow: (flow) => + set({ flow, dirty: false, baseUpdatedAt: flow.meta?.updatedAt ?? null }), - reset: () => set({ flow: null, selectedNodeId: null, dirty: false, past: [], future: [] }) + reset: () => + set({ flow: null, baseUpdatedAt: null, selectedNodeId: null, dirty: false, past: [], future: [] }) }; });