|
|
@@ -588,30 +588,40 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
588
|
588
|
|
|
589
|
589
|
throw new Error(`${failedIndices.length} 个块保存失败`);
|
|
590
|
590
|
} else {
|
|
591
|
|
- // 全部保存成功
|
|
592
|
|
- const snapshot = JSON.stringify(blocks);
|
|
593
|
|
-
|
|
594
|
|
- // 更新成功保存的块的哈希值
|
|
595
|
|
- const newBlockHashes = new Map(get().blockHashes);
|
|
|
591
|
+ // 仅清理本次保存成功且期间没有再次修改的块,避免覆盖保存期间的新编辑。
|
|
|
592
|
+ const latestState = get();
|
|
|
593
|
+ const newDirtyBlocks = new Set(latestState.dirtyBlocks);
|
|
|
594
|
+ const newBlockHashes = new Map(latestState.blockHashes);
|
|
596
|
595
|
successIds.forEach(id => {
|
|
597
|
|
- const block = blocks.find(b => b.id === id);
|
|
598
|
|
- if (block) {
|
|
599
|
|
- newBlockHashes.set(id, computeBlockHash(block));
|
|
|
596
|
+ const savedBlock = blocks.find(block => block.id === id);
|
|
|
597
|
+ const latestBlock = latestState.blocks.find(block => block.id === id);
|
|
|
598
|
+ if (!savedBlock || !latestBlock) return;
|
|
|
599
|
+
|
|
|
600
|
+ const savedHash = computeBlockHash(savedBlock);
|
|
|
601
|
+ const latestHash = computeBlockHash(latestBlock);
|
|
|
602
|
+ if (savedHash === latestHash) {
|
|
|
603
|
+ newBlockHashes.set(id, savedHash);
|
|
|
604
|
+ newDirtyBlocks.delete(id);
|
|
600
|
605
|
}
|
|
601
|
606
|
});
|
|
602
|
|
-
|
|
603
|
|
- set({
|
|
|
607
|
+
|
|
|
608
|
+ const snapshots = newDirtyBlocks.size === 0
|
|
|
609
|
+ ? {
|
|
|
610
|
+ originalBlocksSnapshot: JSON.stringify(latestState.blocks),
|
|
|
611
|
+ lastSavedSnapshot: JSON.stringify(latestState.blocks),
|
|
|
612
|
+ }
|
|
|
613
|
+ : {};
|
|
|
614
|
+ set({
|
|
604
|
615
|
isSaving: false,
|
|
605
|
|
- hasModified: false,
|
|
606
|
|
- originalBlocksSnapshot: snapshot,
|
|
607
|
|
- lastSavedSnapshot: snapshot,
|
|
|
616
|
+ hasModified: newDirtyBlocks.size > 0,
|
|
608
|
617
|
failedBlocks: [],
|
|
609
|
|
- dirtyBlocks: new Set<string>(),
|
|
|
618
|
+ dirtyBlocks: newDirtyBlocks,
|
|
610
|
619
|
blockHashes: newBlockHashes,
|
|
611
|
620
|
currentSavePromise: null,
|
|
612
|
621
|
saveAbortController: null,
|
|
613
|
622
|
savingProgress: null,
|
|
614
|
623
|
lastSaveTime: Date.now(),
|
|
|
624
|
+ ...snapshots,
|
|
615
|
625
|
});
|
|
616
|
626
|
}
|
|
617
|
627
|
} catch (error: unknown) {
|
|
|
@@ -1024,6 +1034,8 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
1024
|
1034
|
if (!blockToDelete) {
|
|
1025
|
1035
|
return;
|
|
1026
|
1036
|
}
|
|
|
1037
|
+
|
|
|
1038
|
+ const wasDirty = dirtyBlocks.has(id);
|
|
1027
|
1039
|
|
|
1028
|
1040
|
// 先从本地状态删除(乐观更新)
|
|
1029
|
1041
|
const newBlocks = blocks.filter((block) => block.id !== id);
|
|
|
@@ -1065,25 +1077,13 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
1065
|
1077
|
|
|
1066
|
1078
|
// 3. 如果有块需要更新顺序,批量调用 PUT API 更新
|
|
1067
|
1079
|
if (blocksNeedUpdate.length > 0) {
|
|
1068
|
|
- // 并发调用 PUT API 更新所有受影响的块
|
|
1069
|
|
- const updatePromises = blocksNeedUpdate.map(({ block, newOrder }) => {
|
|
1070
|
|
- // 对于TOC块,只更新block_order,不传递其他字段
|
|
1071
|
|
- // 因为后端对TOC块有严格的验证限制
|
|
1072
|
|
- if (block.type === 'toc') {
|
|
1073
|
|
- return blockService.updateBlock(documentId, block.id, {
|
|
1074
|
|
- block_order: newOrder,
|
|
1075
|
|
- });
|
|
1076
|
|
- }
|
|
1077
|
|
-
|
|
1078
|
|
- // 非TOC块:传递完整的数据
|
|
1079
|
|
- return blockService.updateBlock(documentId, block.id, {
|
|
1080
|
|
- content: block.content,
|
|
1081
|
|
- style: block.style,
|
|
1082
|
|
- word_style: block.word_style,
|
|
1083
|
|
- metadata: block.metadata,
|
|
|
1080
|
+ // 并发调用 PUT API 更新所有受影响块的顺序。
|
|
|
1081
|
+ // 只发送顺序字段,避免用删除前的块快照覆盖并发编辑。
|
|
|
1082
|
+ const updatePromises = blocksNeedUpdate.map(({ block, newOrder }) =>
|
|
|
1083
|
+ blockService.updateBlock(documentId, block.id, {
|
|
1084
|
1084
|
block_order: newOrder,
|
|
1085
|
|
- });
|
|
1086
|
|
- });
|
|
|
1085
|
+ })
|
|
|
1086
|
+ );
|
|
1087
|
1087
|
|
|
1088
|
1088
|
await Promise.all(updatePromises);
|
|
1089
|
1089
|
|
|
|
@@ -1106,14 +1106,19 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
1106
|
1106
|
});
|
|
1107
|
1107
|
|
|
1108
|
1108
|
// 5. 更新本地状态,不标记为已修改(因为后端已经同步)
|
|
|
1109
|
+ const latestState = get();
|
|
1109
|
1110
|
set({
|
|
1110
|
1111
|
blocks: updatedBlocks,
|
|
1111
|
1112
|
blockHashes: updatedBlockHashes,
|
|
1112
|
|
- hasModified: false, // 所有操作都已同步到后端
|
|
|
1113
|
+ dirtyBlocks: latestState.dirtyBlocks,
|
|
|
1114
|
+ hasModified: latestState.dirtyBlocks.size > 0,
|
|
1113
|
1115
|
});
|
|
1114
|
1116
|
} else {
|
|
1115
|
1117
|
// 没有块需要更新顺序(可能删除的是最后一个块)
|
|
1116
|
|
- set({ hasModified: false }); // 标记为已同步
|
|
|
1118
|
+ const latestState = get();
|
|
|
1119
|
+ set({
|
|
|
1120
|
+ hasModified: latestState.dirtyBlocks.size > 0,
|
|
|
1121
|
+ });
|
|
1117
|
1122
|
}
|
|
1118
|
1123
|
} catch (error: unknown) {
|
|
1119
|
1124
|
// 删除或更新失败,回滚本地状态
|
|
|
@@ -1130,10 +1135,17 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
1130
|
1135
|
if (wasOriginalBlock) {
|
|
1131
|
1136
|
restoredBlockHashes.set(id, computeBlockHash(blockToDelete));
|
|
1132
|
1137
|
}
|
|
|
1138
|
+
|
|
|
1139
|
+ const restoredDirtyBlocks = new Set(get().dirtyBlocks);
|
|
|
1140
|
+ if (wasDirty) {
|
|
|
1141
|
+ restoredDirtyBlocks.add(id);
|
|
|
1142
|
+ }
|
|
1133
|
1143
|
|
|
1134
|
1144
|
set({
|
|
1135
|
1145
|
blocks: restoredBlocks,
|
|
1136
|
1146
|
blockHashes: restoredBlockHashes,
|
|
|
1147
|
+ dirtyBlocks: restoredDirtyBlocks,
|
|
|
1148
|
+ hasModified: restoredDirtyBlocks.size > 0,
|
|
1137
|
1149
|
});
|
|
1138
|
1150
|
}
|
|
1139
|
1151
|
|