@tmagic/editor 1.8.0-beta.4 → 1.8.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/components/ToolButton.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +25 -8
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +6 -4
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +25 -12
- package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +27 -5
- package/dist/es/index.js +6 -3
- package/dist/es/initService.js +1 -1
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +19 -55
- package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +22 -39
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +149 -103
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +32 -8
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +335 -213
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +36 -7
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +47 -60
- package/dist/es/layouts/history-list/composables.js +73 -32
- package/dist/es/layouts/page-bar/PageBar.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +16 -3
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +23 -1
- package/dist/es/services/codeBlock.js +96 -37
- package/dist/es/services/dataSource.js +70 -26
- package/dist/es/services/editor.js +266 -104
- package/dist/es/services/history.js +271 -212
- package/dist/es/services/ui.js +3 -0
- package/dist/es/style.css +53 -7
- package/dist/es/utils/data-source/index.js +2 -0
- package/dist/es/utils/editor.js +103 -49
- package/dist/es/utils/history.js +232 -0
- package/dist/es/utils/indexed-db.js +86 -0
- package/dist/es/utils/undo-redo.js +60 -1
- package/dist/style.css +53 -7
- package/dist/tmagic-editor.umd.cjs +2064 -1000
- package/package.json +7 -7
- package/src/components/CompareForm.vue +3 -1
- package/src/components/ToolButton.vue +2 -2
- package/src/fields/CodeSelectCol.vue +7 -1
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +16 -2
- package/src/fields/DataSourceFields.vue +37 -8
- package/src/fields/DataSourceMethodSelect.vue +9 -4
- package/src/fields/DataSourceMethods.vue +42 -21
- package/src/fields/StyleSetter/components/Border.vue +15 -6
- package/src/index.ts +1 -0
- package/src/initService.ts +1 -1
- package/src/layouts/Framework.vue +1 -1
- package/src/layouts/history-list/Bucket.vue +34 -71
- package/src/layouts/history-list/BucketTab.vue +46 -54
- package/src/layouts/history-list/GroupRow.vue +146 -111
- package/src/layouts/history-list/HistoryDiffDialog.vue +94 -68
- package/src/layouts/history-list/HistoryListPanel.vue +298 -115
- package/src/layouts/history-list/InitialRow.vue +28 -9
- package/src/layouts/history-list/PageTab.vue +57 -51
- package/src/layouts/history-list/composables.ts +157 -36
- package/src/layouts/page-bar/PageBar.vue +4 -2
- package/src/layouts/sidebar/Sidebar.vue +6 -1
- package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +30 -2
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +26 -1
- package/src/services/codeBlock.ts +113 -37
- package/src/services/dataSource.ts +94 -40
- package/src/services/editor.ts +376 -136
- package/src/services/history.ts +306 -209
- package/src/services/ui.ts +7 -0
- package/src/theme/history-list-panel.scss +72 -5
- package/src/theme/page-bar.scss +0 -4
- package/src/theme/style-setter/border.scss +4 -1
- package/src/type.ts +183 -64
- package/src/utils/data-source/index.ts +2 -0
- package/src/utils/editor.ts +166 -60
- package/src/utils/history.ts +308 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/indexed-db.ts +122 -0
- package/src/utils/undo-redo.ts +88 -0
- package/types/index.d.ts +813 -302
|
@@ -32,12 +32,14 @@ import type {
|
|
|
32
32
|
AsyncHookPlugin,
|
|
33
33
|
CodeBlockStepValue,
|
|
34
34
|
CodeState,
|
|
35
|
+
DslOpWithHistoryIdsResult,
|
|
35
36
|
HistoryOpOptions,
|
|
36
37
|
HistoryOpOptionsWithChangeRecords,
|
|
37
38
|
} from '@editor/type';
|
|
38
39
|
import { CODE_DRAFT_STORAGE_KEY } from '@editor/type';
|
|
39
40
|
import { getEditorConfig } from '@editor/utils/config';
|
|
40
41
|
import { COPY_CODE_STORAGE_KEY } from '@editor/utils/editor';
|
|
42
|
+
import { describeRevertStep, getLastPushedHistoryIds } from '@editor/utils/history';
|
|
41
43
|
|
|
42
44
|
import BaseService from './BaseService';
|
|
43
45
|
|
|
@@ -48,18 +50,6 @@ const canUsePluginMethods = {
|
|
|
48
50
|
|
|
49
51
|
type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
|
|
50
52
|
|
|
51
|
-
/**
|
|
52
|
-
* 「回滚」生成的新 step 简短描述。仅 service 层使用。
|
|
53
|
-
*/
|
|
54
|
-
const describeRevertCodeBlockStep = (step: CodeBlockStepValue): string => {
|
|
55
|
-
const { oldContent, newContent, changeRecords, id } = step;
|
|
56
|
-
if (oldContent === null && newContent) return `撤回新增 ${newContent.name || newContent.id || id}`;
|
|
57
|
-
if (oldContent && newContent === null) return `还原已删除的 ${oldContent.name || oldContent.id || id}`;
|
|
58
|
-
const name = newContent?.name || oldContent?.name || `${id}`;
|
|
59
|
-
const propPath = changeRecords?.[0]?.propPath;
|
|
60
|
-
return propPath ? `还原 ${name} · ${propPath}` : `还原 ${name}`;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
53
|
class CodeBlock extends BaseService {
|
|
64
54
|
private state = reactive<CodeState>({
|
|
65
55
|
codeDsl: null,
|
|
@@ -69,6 +59,17 @@ class CodeBlock extends BaseService {
|
|
|
69
59
|
paramsColConfig: undefined,
|
|
70
60
|
});
|
|
71
61
|
|
|
62
|
+
/**
|
|
63
|
+
* 最近一次写入历史栈的代码块历史记录 uuid(单条写入场景:新增 / 更新)。
|
|
64
|
+
* 供 setCodeDslById(Sync)AndGetHistoryId 取回,普通方法不读取它。
|
|
65
|
+
*/
|
|
66
|
+
private lastPushedHistoryId: string | null = null;
|
|
67
|
+
/**
|
|
68
|
+
* deleteCodeDslByIds 一次删除多个代码块时,按写入顺序收集的历史记录 uuid 列表。
|
|
69
|
+
* 在 deleteCodeDslByIds 入口处重置,供 deleteCodeDslByIdsAndGetHistoryId 取回。
|
|
70
|
+
*/
|
|
71
|
+
private lastDeletedHistoryIds: string[] = [];
|
|
72
|
+
|
|
72
73
|
constructor() {
|
|
73
74
|
super([
|
|
74
75
|
...canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })),
|
|
@@ -187,13 +188,14 @@ class CodeBlock extends BaseService {
|
|
|
187
188
|
const newContent = cloneDeep(codeDsl[id]);
|
|
188
189
|
|
|
189
190
|
if (!doNotPushHistory) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
191
|
+
this.lastPushedHistoryId =
|
|
192
|
+
historyService.pushCodeBlock(id, {
|
|
193
|
+
oldContent,
|
|
194
|
+
newContent,
|
|
195
|
+
changeRecords,
|
|
196
|
+
historyDescription,
|
|
197
|
+
source: historySource,
|
|
198
|
+
})?.uuid ?? null;
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
this.emit('addOrUpdate', id, codeDsl[id]);
|
|
@@ -295,6 +297,8 @@ class CodeBlock extends BaseService {
|
|
|
295
297
|
|
|
296
298
|
if (!currentDsl) return;
|
|
297
299
|
|
|
300
|
+
this.lastDeletedHistoryIds = [];
|
|
301
|
+
|
|
298
302
|
codeIds.forEach((id) => {
|
|
299
303
|
// 历史记录:删除前快照内容;不存在的 id 直接跳过历史推入
|
|
300
304
|
const oldContent: CodeBlockContent | null = currentDsl[id] ? cloneDeep(currentDsl[id]) : null;
|
|
@@ -302,13 +306,65 @@ class CodeBlock extends BaseService {
|
|
|
302
306
|
delete currentDsl[id];
|
|
303
307
|
|
|
304
308
|
if (oldContent && !doNotPushHistory) {
|
|
305
|
-
historyService.pushCodeBlock(id, {
|
|
309
|
+
const uuid = historyService.pushCodeBlock(id, {
|
|
310
|
+
oldContent,
|
|
311
|
+
newContent: null,
|
|
312
|
+
historyDescription,
|
|
313
|
+
source: historySource,
|
|
314
|
+
})?.uuid;
|
|
315
|
+
if (uuid) this.lastDeletedHistoryIds.push(uuid);
|
|
306
316
|
}
|
|
307
317
|
|
|
308
318
|
this.emit('remove', id);
|
|
309
319
|
});
|
|
310
320
|
}
|
|
311
321
|
|
|
322
|
+
// #region AndGetHistoryId
|
|
323
|
+
/**
|
|
324
|
+
* 下列 *AndGetHistoryId 方法与对应的写入方法行为完全一致,
|
|
325
|
+
* 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入的历史 uuid 列表,
|
|
326
|
+
* 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
|
|
327
|
+
*
|
|
328
|
+
* 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时 historyIds 为 `[]`。
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
/** 等价于 {@link setCodeDslById},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
332
|
+
public async setCodeDslByIdAndGetHistoryId(
|
|
333
|
+
id: Id,
|
|
334
|
+
codeConfig: Partial<CodeBlockContent>,
|
|
335
|
+
options: HistoryOpOptionsWithChangeRecords = {},
|
|
336
|
+
): Promise<DslOpWithHistoryIdsResult<void>> {
|
|
337
|
+
this.lastPushedHistoryId = null;
|
|
338
|
+
await this.setCodeDslById(id, codeConfig, options);
|
|
339
|
+
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** 等价于 {@link setCodeDslByIdSync},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
343
|
+
public setCodeDslByIdSyncAndGetHistoryId(
|
|
344
|
+
id: Id,
|
|
345
|
+
codeConfig: Partial<CodeBlockContent>,
|
|
346
|
+
force = true,
|
|
347
|
+
options: HistoryOpOptionsWithChangeRecords = {},
|
|
348
|
+
): DslOpWithHistoryIdsResult<void> {
|
|
349
|
+
this.lastPushedHistoryId = null;
|
|
350
|
+
this.setCodeDslByIdSync(id, codeConfig, force, options);
|
|
351
|
+
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 等价于 {@link deleteCodeDslByIds},并额外返回本次写入的全部历史记录 uuid(按删除顺序)。
|
|
356
|
+
* 一次删除多个代码块会产生多条历史记录;未写入任何历史时 historyIds 为 `[]`。
|
|
357
|
+
*/
|
|
358
|
+
public async deleteCodeDslByIdsAndGetHistoryId(
|
|
359
|
+
codeIds: Id[],
|
|
360
|
+
options: HistoryOpOptions = {},
|
|
361
|
+
): Promise<DslOpWithHistoryIdsResult<void>> {
|
|
362
|
+
this.lastDeletedHistoryIds = [];
|
|
363
|
+
await this.deleteCodeDslByIds(codeIds, options);
|
|
364
|
+
return { result: undefined, historyIds: [...this.lastDeletedHistoryIds] };
|
|
365
|
+
}
|
|
366
|
+
// #endregion AndGetHistoryId
|
|
367
|
+
|
|
312
368
|
public setParamsColConfig(config: TableColumnConfig): void {
|
|
313
369
|
this.state.paramsColConfig = config;
|
|
314
370
|
}
|
|
@@ -395,11 +451,29 @@ class CodeBlock extends BaseService {
|
|
|
395
451
|
const entry = list[index];
|
|
396
452
|
if (!entry?.applied) return null;
|
|
397
453
|
// 更新类步骤(前后 content 都存在)必须带 changeRecords 才支持回滚,否则只能整内容替换,会冲掉后续无关变更。
|
|
398
|
-
|
|
399
|
-
|
|
454
|
+
const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
|
|
455
|
+
|
|
456
|
+
if (oldSchema && newSchema && !changeRecords?.length) return null;
|
|
457
|
+
const description = `回滚 #${index + 1}: ${describeRevertStep<CodeBlockContent>(entry.step.id, entry.step.diff?.[0], (s) => s.name)}`;
|
|
400
458
|
return await this.applyRevertStep(entry.step, description);
|
|
401
459
|
}
|
|
402
460
|
|
|
461
|
+
/**
|
|
462
|
+
* 通过历史记录 uuid 回滚代码块历史步骤,语义同 {@link revert},
|
|
463
|
+
* 仅无需调用方再传 codeBlockId 与 index:内部会按 uuid 在全部代码块栈中定位对应步骤后再回滚。
|
|
464
|
+
* 按数组顺序依次回滚,返回与入参同序的结果列表(某项失败时为 `null`)。
|
|
465
|
+
*
|
|
466
|
+
* @param uuids 目标历史记录的 uuid 列表,通常由 {@link setCodeDslByIdAndGetHistoryId} 等方法返回的 `historyIds`
|
|
467
|
+
*/
|
|
468
|
+
public async revertById(uuids: string[]): Promise<(CodeBlockStepValue | null)[]> {
|
|
469
|
+
const results: (CodeBlockStepValue | null)[] = [];
|
|
470
|
+
for (const uuid of uuids) {
|
|
471
|
+
const location = historyService.findCodeBlockStepLocationByUuid(uuid);
|
|
472
|
+
results.push(location ? await this.revert(location.id, location.index) : null);
|
|
473
|
+
}
|
|
474
|
+
return results;
|
|
475
|
+
}
|
|
476
|
+
|
|
403
477
|
/**
|
|
404
478
|
* 生成代码块唯一id
|
|
405
479
|
* @returns {Id} 代码块唯一id
|
|
@@ -490,21 +564,22 @@ class CodeBlock extends BaseService {
|
|
|
490
564
|
step: CodeBlockStepValue,
|
|
491
565
|
historyDescription: string,
|
|
492
566
|
): Promise<CodeBlockStepValue | null> {
|
|
493
|
-
const { id
|
|
567
|
+
const { id } = step;
|
|
568
|
+
const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
|
|
494
569
|
|
|
495
570
|
// 原本是新增 → revert 即删除
|
|
496
|
-
if (
|
|
571
|
+
if (!oldSchema && newSchema) {
|
|
497
572
|
await this.deleteCodeDslByIds([id], { historyDescription, historySource: 'rollback' });
|
|
498
573
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
499
574
|
}
|
|
500
575
|
|
|
501
576
|
// 原本是删除 → revert 即写回
|
|
502
|
-
if (
|
|
503
|
-
this.setCodeDslByIdSync(id, cloneDeep(
|
|
577
|
+
if (oldSchema && !newSchema) {
|
|
578
|
+
this.setCodeDslByIdSync(id, cloneDeep(oldSchema), true, { historyDescription, historySource: 'rollback' });
|
|
504
579
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
505
580
|
}
|
|
506
581
|
|
|
507
|
-
if (!
|
|
582
|
+
if (!oldSchema || !newSchema) return null;
|
|
508
583
|
|
|
509
584
|
// 原本是更新 → 把 oldContent 写回;优先按 changeRecords 局部 patch
|
|
510
585
|
if (changeRecords?.length) {
|
|
@@ -517,10 +592,10 @@ class CodeBlock extends BaseService {
|
|
|
517
592
|
fallbackToFullReplace = true;
|
|
518
593
|
break;
|
|
519
594
|
}
|
|
520
|
-
const value = cloneDeep(getValueByKeyPath(record.propPath,
|
|
595
|
+
const value = cloneDeep(getValueByKeyPath(record.propPath, oldSchema));
|
|
521
596
|
setValueByKeyPath(record.propPath, value, patched);
|
|
522
597
|
}
|
|
523
|
-
this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep(
|
|
598
|
+
this.setCodeDslByIdSync(id, fallbackToFullReplace ? cloneDeep(oldSchema) : patched, true, {
|
|
524
599
|
changeRecords,
|
|
525
600
|
historyDescription,
|
|
526
601
|
historySource: 'rollback',
|
|
@@ -528,7 +603,7 @@ class CodeBlock extends BaseService {
|
|
|
528
603
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
529
604
|
}
|
|
530
605
|
|
|
531
|
-
this.setCodeDslByIdSync(id, cloneDeep(
|
|
606
|
+
this.setCodeDslByIdSync(id, cloneDeep(oldSchema), true, { historyDescription, historySource: 'rollback' });
|
|
532
607
|
return historyService.getCodeBlockStepList(id).slice(-1)[0]?.step ?? null;
|
|
533
608
|
}
|
|
534
609
|
|
|
@@ -547,31 +622,32 @@ class CodeBlock extends BaseService {
|
|
|
547
622
|
* @param reverse true=撤销,false=重做
|
|
548
623
|
*/
|
|
549
624
|
private async applyHistoryStep(step: CodeBlockStepValue, reverse: boolean): Promise<void> {
|
|
550
|
-
const { id
|
|
625
|
+
const { id } = step;
|
|
626
|
+
const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
|
|
551
627
|
|
|
552
628
|
// 新增 / 删除:直接 set 或 delete,不走 patch 逻辑
|
|
553
|
-
if (
|
|
629
|
+
if (!oldSchema && newSchema) {
|
|
554
630
|
if (reverse) {
|
|
555
631
|
await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
|
|
556
632
|
} else {
|
|
557
|
-
this.setCodeDslByIdSync(id, cloneDeep(
|
|
633
|
+
this.setCodeDslByIdSync(id, cloneDeep(newSchema), true, { doNotPushHistory: true });
|
|
558
634
|
}
|
|
559
635
|
return;
|
|
560
636
|
}
|
|
561
637
|
|
|
562
|
-
if (
|
|
638
|
+
if (oldSchema && !newSchema) {
|
|
563
639
|
if (reverse) {
|
|
564
|
-
this.setCodeDslByIdSync(id, cloneDeep(
|
|
640
|
+
this.setCodeDslByIdSync(id, cloneDeep(oldSchema), true, { doNotPushHistory: true });
|
|
565
641
|
} else {
|
|
566
642
|
await this.deleteCodeDslByIds([id], { doNotPushHistory: true });
|
|
567
643
|
}
|
|
568
644
|
return;
|
|
569
645
|
}
|
|
570
646
|
|
|
571
|
-
if (!
|
|
647
|
+
if (!oldSchema || !newSchema) return;
|
|
572
648
|
|
|
573
649
|
// 更新场景:优先按 changeRecords 局部 patch;缺省退化为整内容替换
|
|
574
|
-
const sourceForValues = reverse ?
|
|
650
|
+
const sourceForValues = reverse ? oldSchema : newSchema;
|
|
575
651
|
|
|
576
652
|
if (changeRecords?.length) {
|
|
577
653
|
const current = this.getCodeContentById(id);
|
|
@@ -13,12 +13,14 @@ import storageService, { Protocol } from '@editor/services/storage';
|
|
|
13
13
|
import type {
|
|
14
14
|
DataSourceStepValue,
|
|
15
15
|
DatasourceTypeOption,
|
|
16
|
+
DslOpWithHistoryIdsResult,
|
|
16
17
|
HistoryOpOptions,
|
|
17
18
|
HistoryOpOptionsWithChangeRecords,
|
|
18
19
|
SyncHookPlugin,
|
|
19
20
|
} from '@editor/type';
|
|
20
21
|
import { getFormConfig, getFormValue } from '@editor/utils/data-source';
|
|
21
22
|
import { COPY_DS_STORAGE_KEY } from '@editor/utils/editor';
|
|
23
|
+
import { describeRevertStep, getLastPushedHistoryIds } from '@editor/utils/history';
|
|
22
24
|
|
|
23
25
|
import BaseService from './BaseService';
|
|
24
26
|
|
|
@@ -54,19 +56,6 @@ const canUsePluginMethods = {
|
|
|
54
56
|
|
|
55
57
|
type SyncMethodName = Writable<(typeof canUsePluginMethods)['sync']>;
|
|
56
58
|
|
|
57
|
-
/**
|
|
58
|
-
* 「回滚」生成的新 step 简短描述。
|
|
59
|
-
* 仅在 service 层使用,避免依赖 UI 层 composables。
|
|
60
|
-
*/
|
|
61
|
-
const describeRevertDataSourceStep = (step: DataSourceStepValue): string => {
|
|
62
|
-
const { oldSchema, newSchema, changeRecords, id } = step;
|
|
63
|
-
if (oldSchema === null && newSchema) return `撤回新增 ${newSchema.title || newSchema.id || id}`;
|
|
64
|
-
if (oldSchema && newSchema === null) return `还原已删除的 ${oldSchema.title || oldSchema.id || id}`;
|
|
65
|
-
const title = newSchema?.title || oldSchema?.title || `${id}`;
|
|
66
|
-
const propPath = changeRecords?.[0]?.propPath;
|
|
67
|
-
return propPath ? `还原 ${title} · ${propPath}` : `还原 ${title}`;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
59
|
class DataSource extends BaseService {
|
|
71
60
|
private state = reactive<State>({
|
|
72
61
|
datasourceTypeList: [],
|
|
@@ -78,6 +67,13 @@ class DataSource extends BaseService {
|
|
|
78
67
|
methods: {},
|
|
79
68
|
});
|
|
80
69
|
|
|
70
|
+
/**
|
|
71
|
+
* 最近一次写入历史栈的数据源历史记录 uuid。
|
|
72
|
+
* 供 *AndGetHistoryId 系列方法在调用 add / update / remove 后取回本次产生的历史记录 id;
|
|
73
|
+
* 普通方法不读取它,调用前由 *AndGetHistoryId 重置为 null。
|
|
74
|
+
*/
|
|
75
|
+
private lastPushedHistoryId: string | null = null;
|
|
76
|
+
|
|
81
77
|
constructor() {
|
|
82
78
|
super(canUsePluginMethods.sync.map((methodName) => ({ name: methodName, isAsync: false })));
|
|
83
79
|
}
|
|
@@ -141,12 +137,13 @@ class DataSource extends BaseService {
|
|
|
141
137
|
this.get('dataSources').push(newConfig);
|
|
142
138
|
|
|
143
139
|
if (!doNotPushHistory) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
140
|
+
this.lastPushedHistoryId =
|
|
141
|
+
historyService.pushDataSource(newConfig.id, {
|
|
142
|
+
oldSchema: null,
|
|
143
|
+
newSchema: newConfig,
|
|
144
|
+
historyDescription,
|
|
145
|
+
source: historySource,
|
|
146
|
+
})?.uuid ?? null;
|
|
150
147
|
}
|
|
151
148
|
|
|
152
149
|
this.emit('add', newConfig);
|
|
@@ -181,13 +178,14 @@ class DataSource extends BaseService {
|
|
|
181
178
|
dataSources[index] = newConfig;
|
|
182
179
|
|
|
183
180
|
if (!doNotPushHistory) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
181
|
+
this.lastPushedHistoryId =
|
|
182
|
+
historyService.pushDataSource(newConfig.id, {
|
|
183
|
+
oldSchema: oldConfig ? cloneDeep(oldConfig) : null,
|
|
184
|
+
newSchema: newConfig,
|
|
185
|
+
changeRecords,
|
|
186
|
+
historyDescription,
|
|
187
|
+
source: historySource,
|
|
188
|
+
})?.uuid ?? null;
|
|
191
189
|
}
|
|
192
190
|
|
|
193
191
|
this.emit('update', newConfig, {
|
|
@@ -212,17 +210,55 @@ class DataSource extends BaseService {
|
|
|
212
210
|
dataSources.splice(index, 1);
|
|
213
211
|
|
|
214
212
|
if (oldConfig && !doNotPushHistory) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
213
|
+
this.lastPushedHistoryId =
|
|
214
|
+
historyService.pushDataSource(id, {
|
|
215
|
+
oldSchema: cloneDeep(oldConfig),
|
|
216
|
+
newSchema: null,
|
|
217
|
+
historyDescription,
|
|
218
|
+
source: historySource,
|
|
219
|
+
})?.uuid ?? null;
|
|
221
220
|
}
|
|
222
221
|
|
|
223
222
|
this.emit('remove', id);
|
|
224
223
|
}
|
|
225
224
|
|
|
225
|
+
// #region AndGetHistoryId
|
|
226
|
+
/**
|
|
227
|
+
* 下列 *AndGetHistoryId 方法与对应的 add / update / remove 行为完全一致,
|
|
228
|
+
* 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入历史栈的 uuid 列表({@link DataSourceStepValue.uuid}),
|
|
229
|
+
* 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
|
|
230
|
+
*
|
|
231
|
+
* 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时 historyIds 为 `[]`。
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
/** 等价于 {@link add},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
235
|
+
public addAndGetHistoryId(
|
|
236
|
+
config: DataSourceSchema,
|
|
237
|
+
options: HistoryOpOptions = {},
|
|
238
|
+
): DslOpWithHistoryIdsResult<DataSourceSchema> {
|
|
239
|
+
this.lastPushedHistoryId = null;
|
|
240
|
+
const result = this.add(config, options);
|
|
241
|
+
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** 等价于 {@link update},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
245
|
+
public updateAndGetHistoryId(
|
|
246
|
+
config: DataSourceSchema,
|
|
247
|
+
options: HistoryOpOptionsWithChangeRecords = {},
|
|
248
|
+
): DslOpWithHistoryIdsResult<DataSourceSchema> {
|
|
249
|
+
this.lastPushedHistoryId = null;
|
|
250
|
+
const result = this.update(config, options);
|
|
251
|
+
return { result, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** 等价于 {@link remove},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
|
|
255
|
+
public removeAndGetHistoryId(id: string, options: HistoryOpOptions = {}): DslOpWithHistoryIdsResult<void> {
|
|
256
|
+
this.lastPushedHistoryId = null;
|
|
257
|
+
this.remove(id, options);
|
|
258
|
+
return { result: undefined, historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId) };
|
|
259
|
+
}
|
|
260
|
+
// #endregion AndGetHistoryId
|
|
261
|
+
|
|
226
262
|
/**
|
|
227
263
|
* 撤销指定数据源的最近一次变更。
|
|
228
264
|
*
|
|
@@ -298,11 +334,27 @@ class DataSource extends BaseService {
|
|
|
298
334
|
const entry = list[index];
|
|
299
335
|
if (!entry?.applied) return null;
|
|
300
336
|
// 更新类步骤(前后 schema 都存在)必须带 changeRecords 才支持回滚,否则只能整 schema 替换,会冲掉后续无关变更。
|
|
301
|
-
|
|
302
|
-
|
|
337
|
+
const { oldSchema, newSchema, changeRecords } = entry.step.diff?.[0] ?? {};
|
|
338
|
+
if (oldSchema && newSchema && !changeRecords?.length) return null;
|
|
339
|
+
const description = `回滚 #${index + 1}: ${describeRevertStep<DataSourceSchema>(entry.step.id, entry.step.diff?.[0], (s) => s.title)}`;
|
|
303
340
|
return this.applyRevertStep(entry.step, description);
|
|
304
341
|
}
|
|
305
342
|
|
|
343
|
+
/**
|
|
344
|
+
* 通过历史记录 uuid 回滚数据源历史步骤,语义同 {@link revert},
|
|
345
|
+
* 仅无需调用方再传 dataSourceId 与 index:内部会按 uuid 在全部数据源栈中定位对应步骤后再回滚。
|
|
346
|
+
* 按数组顺序依次回滚,返回与入参同序的结果列表(某项失败时为 `null`)。
|
|
347
|
+
*
|
|
348
|
+
* @param uuids 目标历史记录的 uuid 列表,通常由 {@link addAndGetHistoryId} 等方法返回的 `historyIds`
|
|
349
|
+
*/
|
|
350
|
+
public revertById(uuids: string[]): (DataSourceStepValue | null)[] {
|
|
351
|
+
return uuids.map((uuid) => {
|
|
352
|
+
const location = historyService.findDataSourceStepLocationByUuid(uuid);
|
|
353
|
+
if (!location) return null;
|
|
354
|
+
return this.revert(location.id, location.index);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
306
358
|
public createId(): string {
|
|
307
359
|
return `ds_${guid()}`;
|
|
308
360
|
}
|
|
@@ -383,16 +435,17 @@ class DataSource extends BaseService {
|
|
|
383
435
|
* 同构,差异仅在于走对应的公共 add / update / remove 而不是带 doNotPushHistory 的版本。
|
|
384
436
|
*/
|
|
385
437
|
private applyRevertStep(step: DataSourceStepValue, historyDescription: string): DataSourceStepValue | null {
|
|
386
|
-
const { id
|
|
438
|
+
const { id } = step;
|
|
439
|
+
const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
|
|
387
440
|
|
|
388
441
|
// 原本是新增 → revert 即删除
|
|
389
|
-
if (oldSchema
|
|
442
|
+
if (!oldSchema && newSchema) {
|
|
390
443
|
this.remove(`${id}`, { historyDescription, historySource: 'rollback' });
|
|
391
444
|
return historyService.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
392
445
|
}
|
|
393
446
|
|
|
394
447
|
// 原本是删除 → revert 即重新加回
|
|
395
|
-
if (oldSchema && newSchema
|
|
448
|
+
if (oldSchema && !newSchema) {
|
|
396
449
|
this.add(cloneDeep(oldSchema), { historyDescription, historySource: 'rollback' });
|
|
397
450
|
return historyService.getDataSourceStepList(id).slice(-1)[0]?.step ?? null;
|
|
398
451
|
}
|
|
@@ -440,10 +493,11 @@ class DataSource extends BaseService {
|
|
|
440
493
|
* @param reverse true=撤销,false=重做
|
|
441
494
|
*/
|
|
442
495
|
private applyHistoryStep(step: DataSourceStepValue, reverse: boolean): void {
|
|
443
|
-
const { id
|
|
496
|
+
const { id } = step;
|
|
497
|
+
const { oldSchema, newSchema, changeRecords } = step.diff?.[0] ?? {};
|
|
444
498
|
|
|
445
499
|
// 新增 / 删除:直接 add 或 remove,不走 patch 逻辑
|
|
446
|
-
if (oldSchema
|
|
500
|
+
if (!oldSchema && newSchema) {
|
|
447
501
|
if (reverse) {
|
|
448
502
|
this.remove(`${id}`, { doNotPushHistory: true });
|
|
449
503
|
} else {
|
|
@@ -452,7 +506,7 @@ class DataSource extends BaseService {
|
|
|
452
506
|
return;
|
|
453
507
|
}
|
|
454
508
|
|
|
455
|
-
if (oldSchema && newSchema
|
|
509
|
+
if (oldSchema && !newSchema) {
|
|
456
510
|
if (reverse) {
|
|
457
511
|
this.add(cloneDeep(oldSchema), { doNotPushHistory: true });
|
|
458
512
|
} else {
|