@tmagic/editor 1.8.0-beta.5 → 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.
Files changed (42) hide show
  1. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +4 -1
  2. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +8 -2
  3. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +25 -8
  4. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +6 -4
  5. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +25 -12
  6. package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +27 -5
  7. package/dist/es/index.js +2 -2
  8. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
  9. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +12 -4
  10. package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +16 -3
  11. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +23 -1
  12. package/dist/es/services/codeBlock.js +30 -19
  13. package/dist/es/services/dataSource.js +29 -21
  14. package/dist/es/services/editor.js +52 -33
  15. package/dist/es/services/history.js +10 -15
  16. package/dist/es/style.css +4 -1
  17. package/dist/es/utils/data-source/index.js +2 -0
  18. package/dist/es/utils/editor.js +68 -48
  19. package/dist/es/utils/history.js +42 -33
  20. package/dist/style.css +4 -1
  21. package/dist/tmagic-editor.umd.cjs +376 -206
  22. package/package.json +7 -7
  23. package/src/fields/CodeSelectCol.vue +7 -1
  24. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +16 -2
  25. package/src/fields/DataSourceFields.vue +37 -8
  26. package/src/fields/DataSourceMethodSelect.vue +9 -4
  27. package/src/fields/DataSourceMethods.vue +42 -21
  28. package/src/fields/StyleSetter/components/Border.vue +15 -6
  29. package/src/layouts/history-list/HistoryListPanel.vue +2 -2
  30. package/src/layouts/history-list/InitialRow.vue +3 -0
  31. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +30 -2
  32. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +26 -1
  33. package/src/services/codeBlock.ts +28 -22
  34. package/src/services/dataSource.ts +29 -24
  35. package/src/services/editor.ts +41 -37
  36. package/src/services/history.ts +14 -19
  37. package/src/theme/style-setter/border.scss +4 -1
  38. package/src/type.ts +16 -1
  39. package/src/utils/data-source/index.ts +2 -0
  40. package/src/utils/editor.ts +125 -59
  41. package/src/utils/history.ts +46 -36
  42. package/types/index.d.ts +87 -68
@@ -5112,46 +5112,29 @@
5112
5112
  };
5113
5113
  /**
5114
5114
  * 把单个「按 id 分栈」的历史栈(代码块 / 数据源)拆成若干 group:
5115
- * - 把"新增/删除"独立成组(语义上属于一次性事件,不应与 update 合并);
5116
- * - 连续 'update' 合并到同一组,组内 steps 顺序就是发生顺序。
5115
+ * 每条操作记录独立成组,不做相邻 update 合并(与页面历史的合并策略不同)。
5117
5116
  *
5118
5117
  * 代码块与数据源除 `kind` 外结构完全一致,统一由本方法处理;`kind` 决定返回的具体分组类型。
5119
5118
  */
5120
5119
  var mergeStackSteps = (kind, id, list, cursor) => {
5121
- const groups = [];
5122
- let current = null;
5123
5120
  const currentIndex = cursor - 1;
5124
- list.forEach((step, index) => {
5125
- const { opType } = step;
5121
+ return list.map((step, index) => {
5126
5122
  const applied = index < cursor;
5127
5123
  const isCurrent = index === currentIndex;
5128
- if (opType === "update" && current?.opType === "update") {
5129
- current.steps.push({
5124
+ return {
5125
+ kind,
5126
+ id,
5127
+ opType: step.opType,
5128
+ steps: [{
5130
5129
  step,
5131
5130
  index,
5132
5131
  applied,
5133
5132
  isCurrent
5134
- });
5135
- current.applied = applied;
5136
- if (isCurrent) current.isCurrent = true;
5137
- } else {
5138
- current = {
5139
- kind,
5140
- id,
5141
- opType,
5142
- steps: [{
5143
- step,
5144
- index,
5145
- applied,
5146
- isCurrent
5147
- }],
5148
- applied,
5149
- isCurrent
5150
- };
5151
- groups.push(current);
5152
- }
5133
+ }],
5134
+ applied,
5135
+ isCurrent
5136
+ };
5153
5137
  });
5154
- return groups;
5155
5138
  };
5156
5139
  /**
5157
5140
  * 把页面栈拆成若干 group:
@@ -5232,22 +5215,45 @@
5232
5215
  return items?.length ? `${items.length} 个节点` : void 0;
5233
5216
  }
5234
5217
  };
5235
- /** 把 `Record<Id, UndoRedo>` 整体序列化为 `Record<Id, SerializedUndoRedo>`。 */
5218
+ /**
5219
+ * 把 `Record<Id, UndoRedo>` 整体序列化为 `Record<Id, SerializedUndoRedo>`。
5220
+ *
5221
+ * 序列化(深克隆)的同一趟里,只把每条 step 中可能含函数的 `diff` 用 serialize-javascript 序列化成字符串,
5222
+ * 其余字段(uuid / opType / timestamp / `modifiedNodeIds` Map 等)原样保留,交给 IndexedDB 结构化克隆。
5223
+ * 这样既能写入函数,又避免序列化整份快照的开销;读取时再由 {@link parseStacksStepDiff} 还原 diff。
5224
+ * 不含 `diff` 的元素(如通用栈)原样透传。
5225
+ */
5236
5226
  var serializeStacks = (stacks) => {
5237
5227
  const result = {};
5238
5228
  Object.entries(stacks).forEach(([id, undoRedo]) => {
5239
- if (undoRedo) result[id] = undoRedo.serialize();
5229
+ if (!undoRedo) return;
5230
+ const serialized = undoRedo.serialize();
5231
+ result[id] = {
5232
+ ...serialized,
5233
+ elementList: serialized.elementList.map((step) => step.diff === void 0 ? step : Object.assign({}, step, { diff: (0, serialize_javascript.default)(step.diff) }))
5234
+ };
5240
5235
  });
5241
5236
  return result;
5242
5237
  };
5243
5238
  /**
5244
5239
  * 把 `Record<Id, SerializedUndoRedo>` 整体还原为 `Record<Id, UndoRedo>`。
5245
5240
  * 还原时把每个栈的游标定位到最近一条已保存(`saved === true`)记录之后。
5241
+ *
5242
+ * 与 {@link serializeStacks} 相反:当传入 `parse`(parseDSL)时,把每条 step 中以字符串形式存储的 `diff`
5243
+ * 解析回真实对象(含函数);不含 `diff` 的元素(如通用栈)原样透传。
5246
5244
  */
5247
- var deserializeStacks = (stacks = {}) => {
5245
+ var deserializeStacks = (stacks = {}, parse) => {
5248
5246
  const result = {};
5249
5247
  Object.entries(stacks).forEach(([id, serialized]) => {
5250
- if (serialized) result[id] = UndoRedo.fromSerialized(serialized, { isSavedStep: (element) => element.saved === true });
5248
+ if (!serialized) return;
5249
+ const elementList = parse ? serialized.elementList.map((step) => {
5250
+ const { diff } = step;
5251
+ return typeof diff === "string" ? Object.assign({}, step, { diff: parse(`(${diff})`) }) : step;
5252
+ }) : serialized.elementList;
5253
+ result[id] = UndoRedo.fromSerialized({
5254
+ ...serialized,
5255
+ elementList
5256
+ }, { isSavedStep: (element) => element.saved === true });
5251
5257
  });
5252
5258
  return result;
5253
5259
  };
@@ -5265,6 +5271,8 @@
5265
5271
  var undoFloor = (undoRedo) => {
5266
5272
  return undoRedo.getElementList()[0]?.opType === "initial" ? 1 : 0;
5267
5273
  };
5274
+ /** 将单次 push 产生的 history uuid(或 null)转为 *AndGetHistoryId 返回用的 uuid 列表。 */
5275
+ var getLastPushedHistoryIds = (historyId) => historyId ? [historyId] : [];
5268
5276
  //#endregion
5269
5277
  //#region packages/editor/src/utils/indexed-db.ts
5270
5278
  /**
@@ -5356,7 +5364,7 @@
5356
5364
  var DEFAULT_DB_NAME = "tmagic-editor";
5357
5365
  var DEFAULT_STORE_NAME = "history";
5358
5366
  var DEFAULT_KEY = "default";
5359
- var PERSIST_VERSION = 1;
5367
+ var PERSIST_VERSION = 2;
5360
5368
  var History = class extends BaseService {
5361
5369
  state = (0, vue.reactive)({
5362
5370
  pageSteps: {},
@@ -5679,7 +5687,7 @@
5679
5687
  dataSourceState: serializeStacks(this.state.dataSourceState),
5680
5688
  savedAt: Date.now()
5681
5689
  };
5682
- await idbSet(this.resolveDbName(dbName, appId), storeName, key, (0, serialize_javascript.default)(snapshot));
5690
+ await idbSet(this.resolveDbName(dbName, appId), storeName, key, snapshot);
5683
5691
  this.emit("save-to-indexed-db", snapshot);
5684
5692
  return snapshot;
5685
5693
  }
@@ -5693,13 +5701,12 @@
5693
5701
  */
5694
5702
  async restoreFromIndexedDB(options = {}) {
5695
5703
  const { dbName = DEFAULT_DB_NAME, storeName = DEFAULT_STORE_NAME, key = DEFAULT_KEY, appId } = options;
5696
- const raw = await idbGet(this.resolveDbName(dbName, appId), storeName, key);
5697
- if (!raw) return null;
5698
- const snapshot = typeof raw === "string" ? getEditorConfig("parseDSL")(`(${raw})`) : raw;
5704
+ const snapshot = await idbGet(this.resolveDbName(dbName, appId), storeName, key);
5699
5705
  if (!snapshot) return null;
5700
- this.state.pageSteps = deserializeStacks(snapshot.pageSteps);
5701
- this.state.codeBlockState = deserializeStacks(snapshot.codeBlockState);
5702
- this.state.dataSourceState = deserializeStacks(snapshot.dataSourceState);
5706
+ const parseDSL = getEditorConfig("parseDSL");
5707
+ this.state.pageSteps = deserializeStacks(snapshot.pageSteps, parseDSL);
5708
+ this.state.codeBlockState = deserializeStacks(snapshot.codeBlockState, parseDSL);
5709
+ this.state.dataSourceState = deserializeStacks(snapshot.dataSourceState, parseDSL);
5703
5710
  this.state.pageId = snapshot.pageId;
5704
5711
  this.setCanUndoRedo();
5705
5712
  this.emit("restore-from-indexed-db", snapshot);
@@ -5739,11 +5746,8 @@
5739
5746
  return mergePageSteps(targetPageId, list, undoRedo.getCursor()).filter((group) => group.opType !== "initial");
5740
5747
  }
5741
5748
  /**
5742
- * 取出全部代码块的历史栈,按 codeBlockId 分组。
5743
- * 同一栈内相邻、同 opType 且作用于同一 id 的多步会被合并为一个 group:
5744
- * - 这正是"代码块/数据源各自按 id 分栈"的天然表现,再叠加"连续修改同目标的相邻步骤合并展示"。
5745
- * - 合并后 group 暴露子步骤数组,UI 可展开查看每一步的 changeRecords。
5746
- * - applied 字段:组内最后一步是否处于已应用段。
5749
+ * 取出全部代码块的历史栈,按 codeBlockId 分桶展示。
5750
+ * 同一栈内每条操作记录独立成组,不做相邻 update 合并。
5747
5751
  */
5748
5752
  getCodeBlockHistoryGroups() {
5749
5753
  const groups = [];
@@ -5840,7 +5844,7 @@
5840
5844
  return null;
5841
5845
  }
5842
5846
  /**
5843
- * 取出全部数据源的历史栈,按 dataSourceId 分组。同上。
5847
+ * 取出全部数据源的历史栈,按 dataSourceId 分桶展示。同上,每条操作独立成组。
5844
5848
  */
5845
5849
  getDataSourceHistoryGroups() {
5846
5850
  const groups = [];
@@ -6073,12 +6077,26 @@
6073
6077
  }
6074
6078
  return (Math.min(parentHeight, wrapperHeightDeal) - height) / 2;
6075
6079
  };
6080
+ var removeConflictPosition = (style, primary, secondary) => {
6081
+ const isInvalid = (value) => value === "" || value === 0 || value === void 0 || value === null;
6082
+ if (!(primary in style) || !(secondary in style)) return;
6083
+ const primaryValue = style[primary];
6084
+ const secondaryValue = style[secondary];
6085
+ const primaryInvalid = isInvalid(primaryValue);
6086
+ const secondaryInvalid = isInvalid(secondaryValue);
6087
+ if (primaryInvalid && !secondaryInvalid) delete style[primary];
6088
+ else if (secondaryInvalid && !primaryInvalid) delete style[secondary];
6089
+ else if (primaryInvalid && secondaryInvalid) if (secondaryValue === 0 && primaryValue !== 0) delete style[primary];
6090
+ else delete style[secondary];
6091
+ };
6076
6092
  var getInitPositionStyle = (style = {}, layout) => {
6077
6093
  if (layout === Layout.ABSOLUTE) {
6078
6094
  const newStyle = {
6079
6095
  ...style,
6080
6096
  position: "absolute"
6081
6097
  };
6098
+ removeConflictPosition(newStyle, "left", "right");
6099
+ removeConflictPosition(newStyle, "top", "bottom");
6082
6100
  if (typeof newStyle.left === "undefined" && typeof newStyle.right === "undefined") newStyle.left = 0;
6083
6101
  return newStyle;
6084
6102
  }
@@ -6103,58 +6121,63 @@
6103
6121
  }
6104
6122
  return node;
6105
6123
  };
6106
- var change2Fixed = (node, root) => {
6107
- const style = { ...node.style || {} };
6108
- const path = (0, _tmagic_utils.getNodePath)(node.id, root.items);
6109
- const offset = {
6110
- left: 0,
6111
- top: 0
6112
- };
6113
- if (!node.style?.right && (0, _tmagic_utils.isNumber)(node.style?.left || 0)) for (const value of path) {
6114
- if (value.style?.right || !(0, _tmagic_utils.isNumber)(value.style?.left || 0)) {
6115
- offset.left = 0;
6116
- break;
6117
- }
6118
- offset.left = offset.left + Number(value.style?.left || 0);
6124
+ var hasPositionValue = (style, key) => typeof style?.[key] !== "undefined" && style?.[key] !== "";
6125
+ /**
6126
+ * 沿节点路径累加(或抵消)某一方向上的定位偏移量
6127
+ * 当路径上的祖先使用了反方向定位(例如计算 left 时祖先用了 right)或非数值定位时,
6128
+ * 无法简单地通过求和换算坐标,此时返回 0 表示放弃补偿、保持原值
6129
+ * @param path 从根到目标节点的路径(含节点自身)
6130
+ * @param dir 需要累加的定位方向
6131
+ * @param opposite 反方向定位属性
6132
+ * @param sign change2Fixed 1(累加祖先偏移),Fixed2Other -1(抵消祖先偏移)
6133
+ * @param init 偏移量初始值
6134
+ */
6135
+ var accumulatePositionOffset = (path, dir, opposite, sign, init = 0) => {
6136
+ let offset = init;
6137
+ for (const value of path) {
6138
+ if (hasPositionValue(value.style, opposite) || !(0, _tmagic_utils.isNumber)(value.style?.[dir] || 0)) return 0;
6139
+ offset = offset + sign * Number(value.style?.[dir] || 0);
6119
6140
  }
6120
- if (!node.style?.bottom && (0, _tmagic_utils.isNumber)(node.style?.top || 0)) for (const value of path) {
6121
- if (value.style?.bottom || !(0, _tmagic_utils.isNumber)(value.style?.top || 0)) {
6122
- offset.top = 0;
6123
- break;
6124
- }
6125
- offset.top = offset.top + Number(value.style?.top || 0);
6141
+ return offset;
6142
+ };
6143
+ var change2Fixed = (node, path) => {
6144
+ const style = { ...node.style || {} };
6145
+ if (!hasPositionValue(node.style, "right") && (0, _tmagic_utils.isNumber)(node.style?.left || 0)) {
6146
+ const left = accumulatePositionOffset(path, "left", "right", 1);
6147
+ if (left) style.left = left;
6148
+ } else if (hasPositionValue(node.style, "right") && (0, _tmagic_utils.isNumber)(node.style?.right || 0)) {
6149
+ const right = accumulatePositionOffset(path, "right", "left", 1);
6150
+ if (right) style.right = right;
6151
+ }
6152
+ if (!hasPositionValue(node.style, "bottom") && (0, _tmagic_utils.isNumber)(node.style?.top || 0)) {
6153
+ const top = accumulatePositionOffset(path, "top", "bottom", 1);
6154
+ if (top) style.top = top;
6155
+ } else if (hasPositionValue(node.style, "bottom") && (0, _tmagic_utils.isNumber)(node.style?.bottom || 0)) {
6156
+ const bottom = accumulatePositionOffset(path, "bottom", "top", 1);
6157
+ if (bottom) style.bottom = bottom;
6126
6158
  }
6127
- if (offset.left) style.left = offset.left;
6128
- if (offset.top) style.top = offset.top;
6129
6159
  return style;
6130
6160
  };
6131
- var Fixed2Other = async (node, root, getLayout) => {
6132
- const path = (0, _tmagic_utils.getNodePath)(node.id, root.items);
6161
+ var Fixed2Other = async (node, path, getLayout) => {
6133
6162
  const cur = path.pop();
6134
6163
  const offset = {
6135
- left: cur?.style?.left || 0,
6136
- top: cur?.style?.top || 0
6164
+ left: 0,
6165
+ top: 0,
6166
+ right: 0,
6167
+ bottom: 0
6137
6168
  };
6138
- if (!node.style?.right && (0, _tmagic_utils.isNumber)(node.style?.left || 0)) for (const value of path) {
6139
- if (value.style?.right || !(0, _tmagic_utils.isNumber)(value.style?.left || 0)) {
6140
- offset.left = 0;
6141
- break;
6142
- }
6143
- offset.left = offset.left - Number(value.style?.left || 0);
6144
- }
6145
- if (!node.style?.bottom && (0, _tmagic_utils.isNumber)(node.style?.top || 0)) for (const value of path) {
6146
- if (value.style?.bottom || !(0, _tmagic_utils.isNumber)(value.style?.top || 0)) {
6147
- offset.top = 0;
6148
- break;
6149
- }
6150
- offset.top = offset.top - Number(value.style?.top || 0);
6151
- }
6169
+ if (!hasPositionValue(node.style, "right") && (0, _tmagic_utils.isNumber)(node.style?.left || 0)) offset.left = accumulatePositionOffset(path, "left", "right", -1, Number(cur?.style?.left || 0));
6170
+ else if (hasPositionValue(node.style, "right") && (0, _tmagic_utils.isNumber)(node.style?.right || 0)) offset.right = accumulatePositionOffset(path, "right", "left", -1, Number(cur?.style?.right || 0));
6171
+ if (!hasPositionValue(node.style, "bottom") && (0, _tmagic_utils.isNumber)(node.style?.top || 0)) offset.top = accumulatePositionOffset(path, "top", "bottom", -1, Number(cur?.style?.top || 0));
6172
+ else if (hasPositionValue(node.style, "bottom") && (0, _tmagic_utils.isNumber)(node.style?.bottom || 0)) offset.bottom = accumulatePositionOffset(path, "bottom", "top", -1, Number(cur?.style?.bottom || 0));
6152
6173
  const style = node.style || {};
6153
6174
  const parent = path.pop();
6154
6175
  if (!parent) return getRelativeStyle(style);
6155
6176
  if (await getLayout(parent) !== Layout.RELATIVE) {
6156
6177
  if (offset.left) style.left = offset.left;
6157
6178
  if (offset.top) style.top = offset.top;
6179
+ if (offset.right) style.right = offset.right;
6180
+ if (offset.bottom) style.bottom = offset.bottom;
6158
6181
  return {
6159
6182
  ...style,
6160
6183
  position: "absolute"
@@ -6186,11 +6209,11 @@
6186
6209
  };
6187
6210
  var fixNodePosition = (config, parent, stage) => {
6188
6211
  if (config.style?.position !== "absolute") return config.style;
6189
- return {
6190
- ...config.style || {},
6191
- top: getMiddleTop(config, parent, stage),
6192
- left: fixNodeLeft(config, parent, stage?.renderer?.contentWindow?.document)
6193
- };
6212
+ const style = { ...config.style || {} };
6213
+ const baseStyle = config.style || {};
6214
+ if ("left" in baseStyle && !("right" in baseStyle)) style.left = fixNodeLeft(config, parent, stage?.renderer?.contentWindow?.document);
6215
+ if ("top" in baseStyle && !("bottom" in baseStyle)) style.top = getMiddleTop(config, parent, stage);
6216
+ return style;
6194
6217
  };
6195
6218
  var serializeConfig = (config) => (0, serialize_javascript.default)(config, {
6196
6219
  space: 2,
@@ -6267,13 +6290,14 @@
6267
6290
  var resolveSelectedNode = (config, getNodeInfoFn, rootId) => {
6268
6291
  const id = typeof config === "string" || typeof config === "number" ? config : config.id;
6269
6292
  if (!id) throw new Error("没有ID,无法选中");
6270
- const { node, parent, page } = getNodeInfoFn(id);
6293
+ const { node, parent, page, path } = getNodeInfoFn(id);
6271
6294
  if (!node) throw new Error("获取不到组件信息");
6272
6295
  if (node.id === rootId) throw new Error("不能选根节点");
6273
6296
  return {
6274
6297
  node,
6275
6298
  parent,
6276
- page
6299
+ page,
6300
+ path
6277
6301
  };
6278
6302
  };
6279
6303
  /**
@@ -6285,11 +6309,11 @@
6285
6309
  * @param getLayoutFn 获取父节点布局方式的回调函数
6286
6310
  * @returns 处理后的节点配置(深拷贝)
6287
6311
  */
6288
- var toggleFixedPosition = async (dist, src, root, getLayoutFn) => {
6312
+ var toggleFixedPosition = async (dist, src, path, getLayoutFn) => {
6289
6313
  const newConfig = cloneDeep$1(dist);
6290
6314
  if (!(0, _tmagic_utils.isPop)(src) && newConfig.style?.position) {
6291
- if ((0, _tmagic_stage.isFixed)(newConfig.style) && !(0, _tmagic_stage.isFixed)(src.style || {})) newConfig.style = change2Fixed(newConfig, root);
6292
- else if (!(0, _tmagic_stage.isFixed)(newConfig.style) && (0, _tmagic_stage.isFixed)(src.style || {})) newConfig.style = await Fixed2Other(newConfig, root, getLayoutFn);
6315
+ if ((0, _tmagic_stage.isFixed)(newConfig.style) && !(0, _tmagic_stage.isFixed)(src.style || {})) newConfig.style = change2Fixed(newConfig, path);
6316
+ else if (!(0, _tmagic_stage.isFixed)(newConfig.style) && (0, _tmagic_stage.isFixed)(src.style || {})) newConfig.style = await Fixed2Other(newConfig, path, getLayoutFn);
6293
6317
  }
6294
6318
  return newConfig;
6295
6319
  };
@@ -6603,7 +6627,7 @@
6603
6627
  this.state.pageFragmentLength = 0;
6604
6628
  this.state.stageLoading = false;
6605
6629
  }
6606
- this.emit("root-change", value, preValue);
6630
+ this.emit("root-change", value, preValue, options);
6607
6631
  }
6608
6632
  }
6609
6633
  /**
@@ -6626,12 +6650,14 @@
6626
6650
  if (!root) return {
6627
6651
  node: null,
6628
6652
  parent: null,
6629
- page: null
6653
+ page: null,
6654
+ path: []
6630
6655
  };
6631
6656
  if (id === root.id) return {
6632
6657
  node: root,
6633
6658
  parent: null,
6634
- page: null
6659
+ page: null,
6660
+ path: []
6635
6661
  };
6636
6662
  const pageIdStr = `${this.get("page")?.id || ""}`;
6637
6663
  const currentPageNode = root.items?.find((item) => `${item.id}` === pageIdStr);
@@ -6951,13 +6977,12 @@
6951
6977
  this.emit("remove", nodes);
6952
6978
  }
6953
6979
  async doUpdate(config, { changeRecords = [], historySource } = {}) {
6954
- const root = this.get("root");
6955
- if (!root) throw new Error("root为空");
6980
+ if (!this.get("root")) throw new Error("root为空");
6956
6981
  if (!config?.id) throw new Error("没有配置或者配置缺少id值");
6957
6982
  const info = this.getNodeInfo(config.id, false);
6958
6983
  if (!info.node) throw new Error(`获取不到id为${config.id}的节点`);
6959
6984
  const node = (0, vue.toRaw)(info.node);
6960
- let newConfig = await toggleFixedPosition((0, vue.toRaw)(config), node, root, this.getLayout);
6985
+ let newConfig = await toggleFixedPosition((0, vue.toRaw)(config), node, info.path, this.getLayout);
6961
6986
  newConfig = mergeWith(cloneDeep$1(node), newConfig, editorNodeMergeCustomizer);
6962
6987
  if (!newConfig.type) throw new Error("配置缺少type值");
6963
6988
  if (newConfig.type === _tmagic_core.NodeType.ROOT) {
@@ -7332,46 +7357,61 @@
7332
7357
  }
7333
7358
  /**
7334
7359
  * 下列 *AndGetHistoryId 方法与对应的普通操作(add / remove / update ...)行为完全一致,
7335
- * 唯一区别是返回值为本次写入历史栈的历史记录 uuid{@link StepValue.uuid}),
7336
- * 而非节点 / 节点数组。可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
7360
+ * 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入历史栈的 uuid 列表({@link StepValue.uuid}),
7361
+ * 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
7337
7362
  *
7338
- * 当本次操作未写入历史(doNotPushHistory 为 true、或操作无实际变更 / 提前返回)时返回 null。
7363
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或操作无实际变更 / 提前返回)时 historyIds 为 `[]`。
7339
7364
  */
7340
- /** 等价于 {@link add},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7365
+ /** 等价于 {@link add},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7341
7366
  async addAndGetHistoryId(addNode, parent, options = {}) {
7342
7367
  this.lastPushedHistoryId = null;
7343
- await this.add(addNode, parent, options);
7344
- return this.lastPushedHistoryId;
7368
+ return {
7369
+ result: await this.add(addNode, parent, options),
7370
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7371
+ };
7345
7372
  }
7346
- /** 等价于 {@link remove},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7373
+ /** 等价于 {@link remove},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7347
7374
  async removeAndGetHistoryId(nodeOrNodeList, options = {}) {
7348
7375
  this.lastPushedHistoryId = null;
7349
7376
  await this.remove(nodeOrNodeList, options);
7350
- return this.lastPushedHistoryId;
7377
+ return {
7378
+ result: void 0,
7379
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7380
+ };
7351
7381
  }
7352
- /** 等价于 {@link update},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7382
+ /** 等价于 {@link update},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7353
7383
  async updateAndGetHistoryId(config, data = {}) {
7354
7384
  this.lastPushedHistoryId = null;
7355
- await this.update(config, data);
7356
- return this.lastPushedHistoryId;
7385
+ return {
7386
+ result: await this.update(config, data),
7387
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7388
+ };
7357
7389
  }
7358
- /** 等价于 {@link moveLayer},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7390
+ /** 等价于 {@link moveLayer},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7359
7391
  async moveLayerAndGetHistoryId(offset, options = {}) {
7360
7392
  this.lastPushedHistoryId = null;
7361
7393
  await this.moveLayer(offset, options);
7362
- return this.lastPushedHistoryId;
7394
+ return {
7395
+ result: void 0,
7396
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7397
+ };
7363
7398
  }
7364
- /** 等价于 {@link moveToContainer},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7399
+ /** 等价于 {@link moveToContainer},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7365
7400
  async moveToContainerAndGetHistoryId(config, targetId, options = {}) {
7366
7401
  this.lastPushedHistoryId = null;
7367
- await this.moveToContainer(config, targetId, options);
7368
- return this.lastPushedHistoryId;
7402
+ return {
7403
+ result: await this.moveToContainer(config, targetId, options),
7404
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7405
+ };
7369
7406
  }
7370
- /** 等价于 {@link dragTo},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
7407
+ /** 等价于 {@link dragTo},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
7371
7408
  async dragToAndGetHistoryId(config, targetParent, targetIndex, options = {}) {
7372
7409
  this.lastPushedHistoryId = null;
7373
7410
  await this.dragTo(config, targetParent, targetIndex, options);
7374
- return this.lastPushedHistoryId;
7411
+ return {
7412
+ result: void 0,
7413
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
7414
+ };
7375
7415
  }
7376
7416
  /**
7377
7417
  * 撤销当前操作
@@ -7480,17 +7520,19 @@
7480
7520
  return revertedStep;
7481
7521
  }
7482
7522
  /**
7483
- * 通过历史记录 uuid 回滚当前页面的某条历史步骤,语义与 {@link revertPageStep} 完全一致,
7484
- * 仅入参从 index 改为 uuid{@link StepValue.uuid})。uuid 不随栈内步骤增删而变化,
7485
- * 更适合业务侧持有引用后再回滚(埋点、跨端同步等场景)。
7523
+ * 通过历史记录 uuid 回滚当前页面的历史步骤,语义与 {@link revertPageStep} 完全一致,
7524
+ * 仅入参从 index 改为 uuid 列表({@link StepValue.uuid})。按数组顺序依次回滚,
7525
+ * 返回与入参同序的结果列表(某项失败时为 `null`)。
7486
7526
  *
7487
- * @param uuid 目标历史记录的 uuid,通常由 *AndGetHistoryId 方法返回
7488
- * @returns 反向后产生的新 step;找不到对应 uuid / 未应用 / 反向失败时返回 null
7527
+ * @param uuids 目标历史记录的 uuid 列表,通常由 *AndGetHistoryId 方法返回的 `historyIds`
7489
7528
  */
7490
- async revertPageStepById(uuid) {
7491
- const index = history_default.getPageStepIndexByUuid(uuid);
7492
- if (index < 0) return null;
7493
- return this.revertPageStep(index);
7529
+ async revertPageStepById(uuids) {
7530
+ const results = [];
7531
+ for (const uuid of uuids) {
7532
+ const index = history_default.getPageStepIndexByUuid(uuid);
7533
+ results.push(index < 0 ? null : await this.revertPageStep(index));
7534
+ }
7535
+ return results;
7494
7536
  }
7495
7537
  /**
7496
7538
  * 跳转当前页面历史栈到指定游标位置。
@@ -8472,6 +8514,7 @@
8472
8514
  items: [
8473
8515
  {
8474
8516
  title: "数据定义",
8517
+ status: "fields",
8475
8518
  items: [{
8476
8519
  name: "fields",
8477
8520
  type: "data-source-fields",
@@ -8480,6 +8523,7 @@
8480
8523
  },
8481
8524
  {
8482
8525
  title: "方法定义",
8526
+ status: "methods",
8483
8527
  items: [{
8484
8528
  name: "methods",
8485
8529
  type: "data-source-methods",
@@ -10293,7 +10337,7 @@
10293
10337
  class: "m-editor-history-list-item-saved",
10294
10338
  title: "该记录为最近一次保存的状态"
10295
10339
  };
10296
- var _hoisted_5$3 = {
10340
+ var _hoisted_5$4 = {
10297
10341
  key: 1,
10298
10342
  class: "m-editor-history-list-item-actions"
10299
10343
  };
@@ -10453,7 +10497,7 @@
10453
10497
  (0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["m-editor-history-list-item-op", `op-${__props.group.opType}`]) }, (0, vue.toDisplayString)((0, vue.unref)(opLabel)(__props.group.opType)), 3),
10454
10498
  (0, vue.createElementVNode)("span", _hoisted_3$11, (0, vue.toDisplayString)(__props.group.desc), 1),
10455
10499
  headSaved.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_4$8, "已保存")) : (0, vue.createCommentVNode)("v-if", true),
10456
- !merged.value && (headRevertable.value || headDiffable.value || canHeadGoto.value) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$3, [
10500
+ !merged.value && (headRevertable.value || headDiffable.value || canHeadGoto.value) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$4, [
10457
10501
  headRevertable.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10458
10502
  key: 0,
10459
10503
  class: "m-editor-history-list-item-revert",
@@ -10546,9 +10590,14 @@
10546
10590
  var _hoisted_2$22 = { class: "m-editor-history-list-item-desc" };
10547
10591
  var _hoisted_3$10 = {
10548
10592
  key: 0,
10593
+ class: "m-editor-history-list-item-saved",
10594
+ title: "该记录为最近一次保存的状态"
10595
+ };
10596
+ var _hoisted_4$7 = {
10597
+ key: 1,
10549
10598
  class: "m-editor-history-list-item-actions"
10550
10599
  };
10551
- var _hoisted_4$7 = ["title"];
10600
+ var _hoisted_5$3 = ["title"];
10552
10601
  var InitialRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
10553
10602
  name: "MEditorHistoryListInitialRow",
10554
10603
  __name: "InitialRow",
@@ -10573,6 +10622,8 @@
10573
10622
  */
10574
10623
  const props = __props;
10575
10624
  const desc = (0, vue.computed)(() => props.marker?.historyDescription || "未修改的初始状态");
10625
+ /** 基线(初始状态)是否为最近一次保存点:仅页面栈的 `initial` 基线 step 会被 markSaved 标记。 */
10626
+ const saved = (0, vue.computed)(() => Boolean(props.marker?.saved));
10576
10627
  const time = (0, vue.computed)(() => formatHistoryTime(props.marker?.timestamp));
10577
10628
  const timeTitle = (0, vue.computed)(() => formatHistoryFullTime(props.marker?.timestamp));
10578
10629
  const rowTitle = (0, vue.computed)(() => {
@@ -10598,16 +10649,17 @@
10598
10649
  }, "#0", -1)),
10599
10650
  _cache[1] || (_cache[1] = (0, vue.createElementVNode)("span", { class: "m-editor-history-list-item-op op-initial" }, "初始", -1)),
10600
10651
  (0, vue.createElementVNode)("span", _hoisted_2$22, (0, vue.toDisplayString)(desc.value), 1),
10601
- __props.gotoEnabled && !__props.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$10, [(0, vue.createElementVNode)("span", {
10652
+ saved.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$10, "已保存")) : (0, vue.createCommentVNode)("v-if", true),
10653
+ __props.gotoEnabled && !__props.isCurrent ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_4$7, [(0, vue.createElementVNode)("span", {
10602
10654
  class: "m-editor-history-list-item-goto",
10603
10655
  title: "回到该记录",
10604
10656
  onClick: (0, vue.withModifiers)(onClick, ["stop"])
10605
10657
  }, "回到")])) : (0, vue.createCommentVNode)("v-if", true),
10606
10658
  time.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
10607
- key: 1,
10659
+ key: 2,
10608
10660
  class: "m-editor-history-list-item-time",
10609
10661
  title: timeTitle.value
10610
- }, (0, vue.toDisplayString)(time.value), 9, _hoisted_4$7)) : (0, vue.createCommentVNode)("v-if", true)
10662
+ }, (0, vue.toDisplayString)(time.value), 9, _hoisted_5$3)) : (0, vue.createCommentVNode)("v-if", true)
10611
10663
  ], 10, _hoisted_1$60);
10612
10664
  };
10613
10665
  }
@@ -11299,8 +11351,8 @@
11299
11351
  /**
11300
11352
  * 历史记录面板:在顶部 NavMenu 上点击图标打开 popover,分三个 tab:
11301
11353
  * - 页面:当前活动页面的历史栈,连续修改同一节点的多步会被合并成一组
11302
- * - 数据源:以 dataSource.id 分组,每组内部相邻的连续 update 自动合并
11303
- * - 代码块:同上,按 codeBlock.id 分组并合并相邻 update
11354
+ * - 数据源:以 dataSource.id 分桶,每条操作记录独立展示
11355
+ * - 代码块:同上,按 codeBlock.id 分桶,每条操作记录独立展示
11304
11356
  *
11305
11357
  * 数据通过 historyService 暴露的聚合 API 读取,UI 仅用于只读展示,
11306
11358
  * 同时支持点击任意一条记录跳转至该状态:
@@ -13644,7 +13696,9 @@
13644
13696
  props: /* @__PURE__ */ (0, vue.mergeModels)({
13645
13697
  title: {},
13646
13698
  values: {},
13647
- disabled: { type: Boolean }
13699
+ disabled: { type: Boolean },
13700
+ editMethodName: {},
13701
+ editFieldPath: {}
13648
13702
  }, {
13649
13703
  "visible": {
13650
13704
  type: Boolean,
@@ -13669,9 +13723,20 @@
13669
13723
  const dataSourceConfig = (0, vue.ref)([]);
13670
13724
  const { height: editorHeight } = useEditorContentHeight();
13671
13725
  const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, (0, vue.inject)("parentFloating", (0, vue.ref)(null)));
13726
+ /** 供「方法定义」tab 内的字段消费,用于打开数据源详情后自动打开指定方法 */
13727
+ (0, vue.provide)("editingDataSourceMethodName", (0, vue.computed)(() => props.editMethodName));
13728
+ /** 供「数据定义」tab 内的字段消费,用于打开数据源详情后自动打开指定字段 */
13729
+ (0, vue.provide)("editingDataSourceFieldPath", (0, vue.computed)(() => props.editFieldPath || []));
13672
13730
  (0, vue.watchEffect)(() => {
13673
13731
  initValues.value = props.values;
13674
- dataSourceConfig.value = dataSourceService.getFormConfig(initValues.value.type);
13732
+ const config = dataSourceService.getFormConfig(initValues.value.type);
13733
+ let activeTab = "";
13734
+ if (props.editMethodName) activeTab = "methods";
13735
+ else if (props.editFieldPath?.length) activeTab = "fields";
13736
+ dataSourceConfig.value = activeTab ? config.map((item) => item.type === "tab" ? {
13737
+ ...item,
13738
+ active: activeTab
13739
+ } : item) : config;
13675
13740
  });
13676
13741
  const submitHandler = (values, data) => {
13677
13742
  emit("submit", values, data);
@@ -13960,7 +14025,13 @@
13960
14025
  const eventBus = (0, vue.inject)("eventBus");
13961
14026
  const { dataSourceService } = useServices();
13962
14027
  const { editDialog, dataSourceValues, dialogTitle, editable, editHandler, submitDataSourceHandler } = useDataSourceEdit(dataSourceService);
14028
+ /** 打开数据源详情时需要直接定位并打开的方法名,为空则正常展示「数据定义」tab */
14029
+ const editMethodName = (0, vue.ref)("");
14030
+ /** 打开数据源详情时需要直接定位并打开的字段路径,为空则不自动打开字段配置 */
14031
+ const editFieldPath = (0, vue.ref)([]);
13963
14032
  const editDialogCloseHandler = () => {
14033
+ editMethodName.value = "";
14034
+ editFieldPath.value = [];
13964
14035
  if (dataSourceListRef.value) for (const [, status] of dataSourceListRef.value.nodeStatusMap.entries()) status.selected = false;
13965
14036
  };
13966
14037
  (0, vue.watch)(dataSourceValues, (dataSourceValues) => {
@@ -13998,6 +14069,18 @@
13998
14069
  dataSourceListRef.value?.filter(val);
13999
14070
  };
14000
14071
  eventBus?.on("edit-data-source", (id) => {
14072
+ editMethodName.value = "";
14073
+ editFieldPath.value = [];
14074
+ editHandler(id);
14075
+ });
14076
+ eventBus?.on("edit-data-source-method", (id, methodName) => {
14077
+ editMethodName.value = methodName;
14078
+ editFieldPath.value = [];
14079
+ editHandler(id);
14080
+ });
14081
+ eventBus?.on("edit-data-source-field", (id, fieldPath) => {
14082
+ editMethodName.value = "";
14083
+ editFieldPath.value = fieldPath;
14001
14084
  editHandler(id);
14002
14085
  });
14003
14086
  eventBus?.on("remove-data-source", (id) => {
@@ -14046,12 +14129,16 @@
14046
14129
  disabled: !(0, vue.unref)(editable),
14047
14130
  values: (0, vue.unref)(dataSourceValues),
14048
14131
  title: (0, vue.unref)(dialogTitle),
14132
+ "edit-method-name": editMethodName.value,
14133
+ "edit-field-path": editFieldPath.value,
14049
14134
  onSubmit: (0, vue.unref)(submitDataSourceHandler),
14050
14135
  onClose: editDialogCloseHandler
14051
14136
  }, null, 8, [
14052
14137
  "disabled",
14053
14138
  "values",
14054
14139
  "title",
14140
+ "edit-method-name",
14141
+ "edit-field-path",
14055
14142
  "onSubmit"
14056
14143
  ]),
14057
14144
  ((0, vue.openBlock)(), (0, vue.createBlock)(vue.Teleport, { to: "body" }, [menuData.value.length ? ((0, vue.openBlock)(), (0, vue.createBlock)(ContentMenu_default, {
@@ -16436,31 +16523,40 @@
16436
16523
  }
16437
16524
  /**
16438
16525
  * 下列 *AndGetHistoryId 方法与对应的写入方法行为完全一致,
16439
- * 唯一区别是返回值为本次写入历史栈的历史记录 uuid({@link CodeBlockStepValue.uuid}),
16526
+ * 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入的历史 uuid 列表,
16440
16527
  * 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
16441
16528
  *
16442
- * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时:单条写入返回 null,批量删除返回空数组。
16529
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时 historyIds 为 `[]`。
16443
16530
  */
16444
- /** 等价于 {@link setCodeDslById},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
16531
+ /** 等价于 {@link setCodeDslById},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
16445
16532
  async setCodeDslByIdAndGetHistoryId(id, codeConfig, options = {}) {
16446
16533
  this.lastPushedHistoryId = null;
16447
16534
  await this.setCodeDslById(id, codeConfig, options);
16448
- return this.lastPushedHistoryId;
16535
+ return {
16536
+ result: void 0,
16537
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
16538
+ };
16449
16539
  }
16450
- /** 等价于 {@link setCodeDslByIdSync},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
16540
+ /** 等价于 {@link setCodeDslByIdSync},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
16451
16541
  setCodeDslByIdSyncAndGetHistoryId(id, codeConfig, force = true, options = {}) {
16452
16542
  this.lastPushedHistoryId = null;
16453
16543
  this.setCodeDslByIdSync(id, codeConfig, force, options);
16454
- return this.lastPushedHistoryId;
16544
+ return {
16545
+ result: void 0,
16546
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
16547
+ };
16455
16548
  }
16456
16549
  /**
16457
- * 等价于 {@link deleteCodeDslByIds},但返回本次写入的全部历史记录 uuid(按删除顺序)。
16458
- * 一次删除多个代码块会产生多条历史记录,因此返回数组;未写入任何历史时返回空数组。
16550
+ * 等价于 {@link deleteCodeDslByIds},并额外返回本次写入的全部历史记录 uuid(按删除顺序)。
16551
+ * 一次删除多个代码块会产生多条历史记录;未写入任何历史时 historyIds 为 `[]`。
16459
16552
  */
16460
16553
  async deleteCodeDslByIdsAndGetHistoryId(codeIds, options = {}) {
16461
16554
  this.lastDeletedHistoryIds = [];
16462
16555
  await this.deleteCodeDslByIds(codeIds, options);
16463
- return [...this.lastDeletedHistoryIds];
16556
+ return {
16557
+ result: void 0,
16558
+ historyIds: [...this.lastDeletedHistoryIds]
16559
+ };
16464
16560
  }
16465
16561
  setParamsColConfig(config) {
16466
16562
  this.state.paramsColConfig = config;
@@ -16543,17 +16639,19 @@
16543
16639
  return await this.applyRevertStep(entry.step, description);
16544
16640
  }
16545
16641
  /**
16546
- * 通过历史记录 uuid 回滚某条代码块历史步骤,语义同 {@link revert},
16547
- * 仅无需调用方再传 codeBlockId 与 index:内部会按 uuid({@link CodeBlockStepValue.uuid})
16548
- * 在全部代码块栈中定位对应步骤后再回滚。
16642
+ * 通过历史记录 uuid 回滚代码块历史步骤,语义同 {@link revert},
16643
+ * 仅无需调用方再传 codeBlockId 与 index:内部会按 uuid 在全部代码块栈中定位对应步骤后再回滚。
16644
+ * 按数组顺序依次回滚,返回与入参同序的结果列表(某项失败时为 `null`)。
16549
16645
  *
16550
- * @param uuid 目标历史记录的 uuid,通常由 {@link setCodeDslByIdAndGetHistoryId} 等方法返回
16551
- * @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
16646
+ * @param uuids 目标历史记录的 uuid 列表,通常由 {@link setCodeDslByIdAndGetHistoryId} 等方法返回的 `historyIds`
16552
16647
  */
16553
- async revertById(uuid) {
16554
- const location = history_default.findCodeBlockStepLocationByUuid(uuid);
16555
- if (!location) return null;
16556
- return await this.revert(location.id, location.index);
16648
+ async revertById(uuids) {
16649
+ const results = [];
16650
+ for (const uuid of uuids) {
16651
+ const location = history_default.findCodeBlockStepLocationByUuid(uuid);
16652
+ results.push(location ? await this.revert(location.id, location.index) : null);
16653
+ }
16654
+ return results;
16557
16655
  }
16558
16656
  /**
16559
16657
  * 生成代码块唯一id
@@ -16883,28 +16981,35 @@
16883
16981
  }
16884
16982
  /**
16885
16983
  * 下列 *AndGetHistoryId 方法与对应的 add / update / remove 行为完全一致,
16886
- * 唯一区别是返回值为本次写入历史栈的历史记录 uuid{@link DataSourceStepValue.uuid}),
16887
- * 而非数据源配置。可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
16984
+ * 返回值在 {@link DslOpWithHistoryIdsResult} 中同时包含原操作结果与本次写入历史栈的 uuid 列表({@link DataSourceStepValue.uuid}),
16985
+ * 可用于精确引用 / 定位该条历史记录(埋点、revert、跨端同步等)。
16888
16986
  *
16889
- * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时返回 null。
16987
+ * 当本次操作未写入历史(doNotPushHistory 为 true、或无对应记录)时 historyIds 为 `[]`。
16890
16988
  */
16891
- /** 等价于 {@link add},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
16989
+ /** 等价于 {@link add},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
16892
16990
  addAndGetHistoryId(config, options = {}) {
16893
16991
  this.lastPushedHistoryId = null;
16894
- this.add(config, options);
16895
- return this.lastPushedHistoryId;
16992
+ return {
16993
+ result: this.add(config, options),
16994
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
16995
+ };
16896
16996
  }
16897
- /** 等价于 {@link update},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
16997
+ /** 等价于 {@link update},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
16898
16998
  updateAndGetHistoryId(config, options = {}) {
16899
16999
  this.lastPushedHistoryId = null;
16900
- this.update(config, options);
16901
- return this.lastPushedHistoryId;
17000
+ return {
17001
+ result: this.update(config, options),
17002
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
17003
+ };
16902
17004
  }
16903
- /** 等价于 {@link remove},但返回本次写入历史记录的 uuid(未入栈时返回 null)。 */
17005
+ /** 等价于 {@link remove},并额外返回本次写入历史记录的 uuid 列表(未入栈时 historyIds 为 `[]`)。 */
16904
17006
  removeAndGetHistoryId(id, options = {}) {
16905
17007
  this.lastPushedHistoryId = null;
16906
17008
  this.remove(id, options);
16907
- return this.lastPushedHistoryId;
17009
+ return {
17010
+ result: void 0,
17011
+ historyIds: getLastPushedHistoryIds(this.lastPushedHistoryId)
17012
+ };
16908
17013
  }
16909
17014
  /**
16910
17015
  * 撤销指定数据源的最近一次变更。
@@ -16980,17 +17085,18 @@
16980
17085
  return this.applyRevertStep(entry.step, description);
16981
17086
  }
16982
17087
  /**
16983
- * 通过历史记录 uuid 回滚某条数据源历史步骤,语义同 {@link revert},
16984
- * 仅无需调用方再传 dataSourceId 与 index:内部会按 uuid({@link DataSourceStepValue.uuid})
16985
- * 在全部数据源栈中定位对应步骤后再回滚。
17088
+ * 通过历史记录 uuid 回滚数据源历史步骤,语义同 {@link revert},
17089
+ * 仅无需调用方再传 dataSourceId 与 index:内部会按 uuid 在全部数据源栈中定位对应步骤后再回滚。
17090
+ * 按数组顺序依次回滚,返回与入参同序的结果列表(某项失败时为 `null`)。
16986
17091
  *
16987
- * @param uuid 目标历史记录的 uuid,通常由 {@link addAndGetHistoryId} 等方法返回
16988
- * @returns 反向后产生的新 step;找不到对应 uuid / 未应用时返回 null
17092
+ * @param uuids 目标历史记录的 uuid 列表,通常由 {@link addAndGetHistoryId} 等方法返回的 `historyIds`
16989
17093
  */
16990
- revertById(uuid) {
16991
- const location = history_default.findDataSourceStepLocationByUuid(uuid);
16992
- if (!location) return null;
16993
- return this.revert(location.id, location.index);
17094
+ revertById(uuids) {
17095
+ return uuids.map((uuid) => {
17096
+ const location = history_default.findDataSourceStepLocationByUuid(uuid);
17097
+ if (!location) return null;
17098
+ return this.revert(location.id, location.index);
17099
+ });
16994
17100
  }
16995
17101
  createId() {
16996
17102
  return `ds_${(0, _tmagic_utils.guid)()}`;
@@ -18630,7 +18736,8 @@
18630
18736
  */
18631
18737
  const isCompareMode = (0, vue.computed)(() => Boolean(props.isCompare && props.lastValues));
18632
18738
  const notEditable = (0, vue.computed)(() => (0, _tmagic_form.filterFunction)(mForm, props.config.notEditable, props));
18633
- const hasCodeBlockSidePanel = (0, vue.computed)(() => (uiService.get("sideBarItems") || []).find((item) => item.$key === SideItemKey.CODE_BLOCK));
18739
+ const codeBlockSidePanel = (0, vue.computed)(() => (uiService.get("sideBarItems") || []).find((item) => item.$key === SideItemKey.CODE_BLOCK));
18740
+ const hasCodeBlockSidePanel = (0, vue.computed)(() => codeBlockSidePanel.value);
18634
18741
  /**
18635
18742
  * 根据代码块id获取代码块参数配置
18636
18743
  * @param codeId 代码块ID
@@ -18685,6 +18792,8 @@
18685
18792
  emit("change", props.model[props.name], eventData);
18686
18793
  };
18687
18794
  const editCode = (id) => {
18795
+ const sideBarItem = codeBlockSidePanel.value;
18796
+ if (sideBarItem) uiService.set("sideBarActiveTabName", sideBarItem.text || sideBarItem.$key || SideItemKey.CODE_BLOCK);
18688
18797
  eventBus?.emit("edit-code", id);
18689
18798
  };
18690
18799
  return (_ctx, _cache) => {
@@ -18817,6 +18926,15 @@
18817
18926
  calcBoxPosition();
18818
18927
  addDialogVisible.value = true;
18819
18928
  };
18929
+ const editField = (row, index) => {
18930
+ fieldValues.value = {
18931
+ ...row,
18932
+ index
18933
+ };
18934
+ fieldTitle.value = `编辑${row.title}`;
18935
+ calcBoxPosition();
18936
+ addDialogVisible.value = true;
18937
+ };
18820
18938
  const fieldChange = ({ index, ...value }, data) => {
18821
18939
  addDialogVisible.value = false;
18822
18940
  if (index > -1) emit("change", value, {
@@ -18868,13 +18986,7 @@
18868
18986
  actions: [{
18869
18987
  text: "编辑",
18870
18988
  handler: (row, index) => {
18871
- fieldValues.value = {
18872
- ...row,
18873
- index
18874
- };
18875
- fieldTitle.value = `编辑${row.title}`;
18876
- calcBoxPosition();
18877
- addDialogVisible.value = true;
18989
+ editField(row, index);
18878
18990
  }
18879
18991
  }, {
18880
18992
  text: "删除",
@@ -19061,6 +19173,20 @@
19061
19173
  const addFromJsonDialogVisible = (0, vue.useModel)(__props, "visible1");
19062
19174
  const { height: editorHeight } = useEditorContentHeight();
19063
19175
  const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, (0, vue.inject)("parentFloating", (0, vue.ref)(null)));
19176
+ /**
19177
+ * 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的字段路径(字段名数组)。
19178
+ * 当前层消费 path[0],并把剩余路径下发给嵌套字段,实现逐层打开。
19179
+ */
19180
+ const editingFieldPath = (0, vue.inject)("editingDataSourceFieldPath", (0, vue.computed)(() => []));
19181
+ (0, vue.provide)("editingDataSourceFieldPath", (0, vue.computed)(() => editingFieldPath.value.slice(1)));
19182
+ (0, vue.onMounted)(() => {
19183
+ const path = editingFieldPath.value;
19184
+ if (!path.length) return;
19185
+ const fields = props.model[props.name] || [];
19186
+ const index = fields.findIndex((field) => field.name === path[0]);
19187
+ if (index === -1) return;
19188
+ editField(fields[index], index);
19189
+ });
19064
19190
  return (_ctx, _cache) => {
19065
19191
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$36, [
19066
19192
  (0, vue.createVNode)((0, vue.unref)(_tmagic_table.MagicTable), {
@@ -19442,6 +19568,19 @@
19442
19568
  const codeConfig = (0, vue.ref)();
19443
19569
  const codeBlockEditorRef = (0, vue.useTemplateRef)("codeBlockEditor");
19444
19570
  let editIndex = -1;
19571
+ const editMethod = (method, index) => {
19572
+ let codeContent = "({ params, dataSource, app }) => {\n // place your code here\n}";
19573
+ if (method.content) if (typeof method.content !== "string") codeContent = method.content.toString();
19574
+ else codeContent = method.content;
19575
+ codeConfig.value = {
19576
+ ...cloneDeep$1(method),
19577
+ content: codeContent
19578
+ };
19579
+ editIndex = index;
19580
+ (0, vue.nextTick)(() => {
19581
+ codeBlockEditorRef.value?.show();
19582
+ });
19583
+ };
19445
19584
  const methodColumns = [
19446
19585
  {
19447
19586
  label: "名称",
@@ -19466,17 +19605,7 @@
19466
19605
  actions: [{
19467
19606
  text: "编辑",
19468
19607
  handler: (method, index) => {
19469
- let codeContent = "({ params, dataSource, app }) => {\n // place your code here\n}";
19470
- if (method.content) if (typeof method.content !== "string") codeContent = method.content.toString();
19471
- else codeContent = method.content;
19472
- codeConfig.value = {
19473
- ...cloneDeep$1(method),
19474
- content: codeContent
19475
- };
19476
- editIndex = index;
19477
- (0, vue.nextTick)(() => {
19478
- codeBlockEditorRef.value?.show();
19479
- });
19608
+ editMethod(method, index);
19480
19609
  }
19481
19610
  }, {
19482
19611
  text: "删除",
@@ -19524,6 +19653,16 @@
19524
19653
  codeConfig.value = void 0;
19525
19654
  codeBlockEditorRef.value?.hide();
19526
19655
  };
19656
+ /** 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的方法名 */
19657
+ const editingMethodName = (0, vue.inject)("editingDataSourceMethodName", (0, vue.computed)(() => ""));
19658
+ (0, vue.onMounted)(() => {
19659
+ const methodName = editingMethodName.value;
19660
+ if (!methodName) return;
19661
+ const methods = props.model[props.name] || [];
19662
+ const index = methods.findIndex((method) => method.name === methodName);
19663
+ if (index === -1) return;
19664
+ editMethod(methods[index], index);
19665
+ });
19527
19666
  return (_ctx, _cache) => {
19528
19667
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$34, [
19529
19668
  (0, vue.createVNode)((0, vue.unref)(_tmagic_table.MagicTable), {
@@ -19951,7 +20090,7 @@
19951
20090
  const eventBus = (0, vue.inject)("eventBus");
19952
20091
  const emit = __emit;
19953
20092
  const props = __props;
19954
- const hasDataSourceSidePanel = (0, vue.computed)(() => (uiService.get("sideBarItems") || []).find((item) => item.$key === SideItemKey.DATA_SOURCE));
20093
+ const dataSourceSidePanel = (0, vue.computed)(() => (uiService.get("sideBarItems") || []).find((item) => item.$key === SideItemKey.DATA_SOURCE));
19955
20094
  const notEditable = (0, vue.computed)(() => (0, _tmagic_form.filterFunction)(mForm, props.config.notEditable, props));
19956
20095
  /** 对比模式下隐藏查看/编辑操作按钮,仅保留只读展示。 */
19957
20096
  const isCompare = (0, vue.computed)(() => Boolean(mForm?.isCompare));
@@ -20039,9 +20178,11 @@
20039
20178
  emit("change", props.model[props.name], eventData);
20040
20179
  };
20041
20180
  const editCodeHandler = () => {
20042
- const [id] = props.model[props.name];
20181
+ const [id, methodName] = props.model[props.name];
20043
20182
  if (!dataSourceService.getDataSourceById(id)) return;
20044
- eventBus?.emit("edit-data-source", id);
20183
+ const sideBarItem = dataSourceSidePanel.value;
20184
+ if (sideBarItem) uiService.set("sideBarActiveTabName", sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
20185
+ eventBus?.emit("edit-data-source-method", id, methodName);
20045
20186
  };
20046
20187
  return (_ctx, _cache) => {
20047
20188
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$31, [(0, vue.createElementVNode)("div", _hoisted_2$6, [(0, vue.createVNode)((0, vue.unref)(_tmagic_form.MCascader), {
@@ -20060,7 +20201,7 @@
20060
20201
  "size",
20061
20202
  "disabled",
20062
20203
  "prop"
20063
- ]), __props.model[__props.name] && isCustomMethod.value && hasDataSourceSidePanel.value && !isCompare.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
20204
+ ]), __props.model[__props.name] && isCustomMethod.value && dataSourceSidePanel.value && !isCompare.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
20064
20205
  key: 0,
20065
20206
  content: notEditable.value ? "查看" : "编辑"
20066
20207
  }, {
@@ -20176,9 +20317,15 @@
20176
20317
  modelValue.value = v;
20177
20318
  emit("change", v);
20178
20319
  };
20179
- const hasDataSourceSidePanel = (0, vue.computed)(() => uiService.get("sideBarItems").find((item) => item.$key === SideItemKey.DATA_SOURCE));
20320
+ const dataSourceSidePanel = (0, vue.computed)(() => uiService.get("sideBarItems").find((item) => item.$key === SideItemKey.DATA_SOURCE));
20321
+ const hasDataSourceSidePanel = (0, vue.computed)(() => dataSourceSidePanel.value);
20180
20322
  const editHandler = (id) => {
20181
- eventBus?.emit("edit-data-source", (0, _tmagic_utils.removeDataSourceFieldPrefix)(id));
20323
+ const sideBarItem = dataSourceSidePanel.value;
20324
+ if (sideBarItem) uiService.set("sideBarActiveTabName", sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
20325
+ const dataSourceId = (0, _tmagic_utils.removeDataSourceFieldPrefix)(id);
20326
+ const fieldPath = selectFieldsId.value;
20327
+ if (fieldPath.length) eventBus?.emit("edit-data-source-field", dataSourceId, [...fieldPath]);
20328
+ else eventBus?.emit("edit-data-source", dataSourceId);
20182
20329
  };
20183
20330
  return (_ctx, _cache) => {
20184
20331
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$30, [__props.dataSourceId ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicCascader), {
@@ -22397,6 +22544,13 @@
22397
22544
  ] }));
22398
22545
  const selectDirection = (d) => direction.value = d || "";
22399
22546
  const emit = __emit;
22547
+ const props = __props;
22548
+ const hasValue = (value) => value !== void 0 && value !== null && value !== "";
22549
+ const isConfigured = (d) => [
22550
+ "Width",
22551
+ "Color",
22552
+ "Style"
22553
+ ].some((key) => hasValue(props.model?.[`border${d}${key}`]));
22400
22554
  const change = (value, eventData) => {
22401
22555
  eventData.changeRecords?.forEach((record) => {
22402
22556
  emit("change", record.value, { modifyKey: record.propPath });
@@ -22406,25 +22560,40 @@
22406
22560
  return (_ctx, _cache) => {
22407
22561
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$1, [(0, vue.createElementVNode)("div", _hoisted_2, [
22408
22562
  (0, vue.createElementVNode)("div", _hoisted_3, [(0, vue.createElementVNode)("div", {
22409
- class: (0, vue.normalizeClass)(["border-icon border-icon-top", { active: direction.value === "Top" }]),
22563
+ class: (0, vue.normalizeClass)(["border-icon border-icon-top", {
22564
+ active: direction.value === "Top",
22565
+ configured: isConfigured("Top")
22566
+ }]),
22410
22567
  onClick: _cache[0] || (_cache[0] = ($event) => selectDirection("Top"))
22411
22568
  }, null, 2)]),
22412
22569
  (0, vue.createElementVNode)("div", _hoisted_4, [
22413
22570
  (0, vue.createElementVNode)("div", {
22414
- class: (0, vue.normalizeClass)(["border-icon border-icon-left", { active: direction.value === "Left" }]),
22571
+ class: (0, vue.normalizeClass)(["border-icon border-icon-left", {
22572
+ active: direction.value === "Left",
22573
+ configured: isConfigured("Left")
22574
+ }]),
22415
22575
  onClick: _cache[1] || (_cache[1] = ($event) => selectDirection("Left"))
22416
22576
  }, null, 2),
22417
22577
  (0, vue.createElementVNode)("div", {
22418
- class: (0, vue.normalizeClass)(["border-icon", { active: direction.value === "" }]),
22578
+ class: (0, vue.normalizeClass)(["border-icon", {
22579
+ active: direction.value === "",
22580
+ configured: isConfigured("")
22581
+ }]),
22419
22582
  onClick: _cache[2] || (_cache[2] = ($event) => selectDirection())
22420
22583
  }, null, 2),
22421
22584
  (0, vue.createElementVNode)("div", {
22422
- class: (0, vue.normalizeClass)(["border-icon border-icon-right", { active: direction.value === "Right" }]),
22585
+ class: (0, vue.normalizeClass)(["border-icon border-icon-right", {
22586
+ active: direction.value === "Right",
22587
+ configured: isConfigured("Right")
22588
+ }]),
22423
22589
  onClick: _cache[3] || (_cache[3] = ($event) => selectDirection("Right"))
22424
22590
  }, null, 2)
22425
22591
  ]),
22426
22592
  (0, vue.createElementVNode)("div", _hoisted_5, [(0, vue.createElementVNode)("div", {
22427
- class: (0, vue.normalizeClass)(["border-icon border-icon-bottom", { active: direction.value === "Bottom" }]),
22593
+ class: (0, vue.normalizeClass)(["border-icon border-icon-bottom", {
22594
+ active: direction.value === "Bottom",
22595
+ configured: isConfigured("Bottom")
22596
+ }]),
22428
22597
  onClick: _cache[4] || (_cache[4] = ($event) => selectDirection("Bottom"))
22429
22598
  }, null, 2)])
22430
22599
  ]), (0, vue.createElementVNode)("div", _hoisted_6, [(0, vue.createVNode)((0, vue.unref)(_tmagic_form.MContainer), {
@@ -23138,6 +23307,7 @@
23138
23307
  exports.getFormValue = getFormValue;
23139
23308
  exports.getGuideLineFromCache = getGuideLineFromCache;
23140
23309
  exports.getInitPositionStyle = getInitPositionStyle;
23310
+ exports.getLastPushedHistoryIds = getLastPushedHistoryIds;
23141
23311
  exports.getNodeIndex = getNodeIndex;
23142
23312
  exports.getOrCreateStack = getOrCreateStack;
23143
23313
  exports.getPageFragmentList = getPageFragmentList;