@tmagic/editor 1.8.0-beta.14 → 1.8.0-beta.15
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/ContentMenu.vue_vue_type_script_setup_true_lang.js +20 -4
- package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +11 -3
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/services/editor.js +32 -11
- package/dist/es/services/history.js +16 -0
- package/dist/es/style.css +4 -0
- package/dist/es/utils/data-source/index.js +4 -2
- package/dist/es/utils/type-match-rules.js +12 -5
- package/dist/style.css +4 -0
- package/dist/themes/magic-admin.css +4 -0
- package/dist/tmagic-editor.umd.cjs +95 -27
- package/package.json +7 -7
- package/src/components/ContentMenu.vue +34 -6
- package/src/fields/Code.vue +10 -2
- package/src/layouts/props-panel/PropsPanel.vue +19 -11
- package/src/services/editor.ts +22 -0
- package/src/services/history.ts +22 -1
- package/src/type.ts +6 -0
- package/src/utils/data-source/index.ts +3 -3
- package/src/utils/type-match-rules.ts +11 -5
- package/types/index.d.ts +26 -0
|
@@ -66,15 +66,29 @@ var ContentMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
|
|
|
66
66
|
if (contains(target)) return;
|
|
67
67
|
hide();
|
|
68
68
|
};
|
|
69
|
+
const fixPosition = () => {
|
|
70
|
+
const menu = menuEl.value;
|
|
71
|
+
if (!menu || !visible.value) return;
|
|
72
|
+
const menuHeight = menu.clientHeight;
|
|
73
|
+
const menuWidth = menu.clientWidth;
|
|
74
|
+
let { top, left } = menuPosition.value;
|
|
75
|
+
if (top + menuHeight > document.body.clientHeight) top = Math.max(0, document.body.clientHeight - menuHeight);
|
|
76
|
+
if (left + menuWidth > document.body.clientWidth) left = Math.max(0, document.body.clientWidth - menuWidth);
|
|
77
|
+
if (top !== menuPosition.value.top || left !== menuPosition.value.left) menuPosition.value = {
|
|
78
|
+
top,
|
|
79
|
+
left
|
|
80
|
+
};
|
|
81
|
+
};
|
|
69
82
|
const setPosition = (e) => {
|
|
70
|
-
const menuHeight = menuEl.value?.clientHeight || 0;
|
|
71
|
-
let top = e.clientY;
|
|
72
|
-
if (menuHeight + e.clientY > document.body.clientHeight) top = document.body.clientHeight - menuHeight;
|
|
73
83
|
menuPosition.value = {
|
|
74
|
-
top,
|
|
84
|
+
top: e.clientY,
|
|
75
85
|
left: e.clientX
|
|
76
86
|
};
|
|
87
|
+
fixPosition();
|
|
77
88
|
};
|
|
89
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
90
|
+
fixPosition();
|
|
91
|
+
});
|
|
78
92
|
const show = (e) => {
|
|
79
93
|
visible.value = true;
|
|
80
94
|
nextTick(() => {
|
|
@@ -103,10 +117,12 @@ var ContentMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
|
|
|
103
117
|
emit("mouseenter");
|
|
104
118
|
};
|
|
105
119
|
onMounted(() => {
|
|
120
|
+
if (menuEl.value) resizeObserver.observe(menuEl.value);
|
|
106
121
|
if (props.isSubMenu) return;
|
|
107
122
|
globalThis.addEventListener("mousedown", outsideClickHideHandler, true);
|
|
108
123
|
});
|
|
109
124
|
onBeforeUnmount(() => {
|
|
125
|
+
resizeObserver.disconnect();
|
|
110
126
|
if (props.isSubMenu) return;
|
|
111
127
|
globalThis.removeEventListener("mousedown", outsideClickHideHandler, true);
|
|
112
128
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import CodeEditor_default from "../layouts/CodeEditor.js";
|
|
2
|
-
import {
|
|
2
|
+
import { FORM_SILENT_MODE_KEY } from "@tmagic/form";
|
|
3
|
+
import { computed, createBlock, createCommentVNode, defineComponent, inject, openBlock, unref } from "vue";
|
|
3
4
|
//#region packages/editor/src/fields/Code.vue?vue&type=script&setup=true&lang.ts
|
|
4
5
|
var Code_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
5
6
|
name: "MFieldsVsCode",
|
|
@@ -24,6 +25,12 @@ var Code_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
24
25
|
},
|
|
25
26
|
emits: ["change"],
|
|
26
27
|
setup(__props, { emit: __emit }) {
|
|
28
|
+
/**
|
|
29
|
+
* 静默模式(submitForm/validateForm 隐藏挂载)下跳过 monaco 渲染:
|
|
30
|
+
* 校验由 FormItem 针对 model 中的值完成,与编辑器实例无关;本组件挂载无值副作用
|
|
31
|
+
* (save 仅由用户操作/编辑器内容变更触发),跳过可省去 monaco worker/model 的无谓实例化。
|
|
32
|
+
*/
|
|
33
|
+
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
|
27
34
|
const emit = __emit;
|
|
28
35
|
const props = __props;
|
|
29
36
|
/**
|
|
@@ -41,7 +48,8 @@ var Code_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
41
48
|
emit("change", v);
|
|
42
49
|
};
|
|
43
50
|
return (_ctx, _cache) => {
|
|
44
|
-
return openBlock(), createBlock(CodeEditor_default, {
|
|
51
|
+
return !unref(silentMode) ? (openBlock(), createBlock(CodeEditor_default, {
|
|
52
|
+
key: 0,
|
|
45
53
|
height: __props.config.height,
|
|
46
54
|
type: diffMode.value ? "diff" : void 0,
|
|
47
55
|
"init-values": diffMode.value ? (__props.lastValues || {})[__props.name] : __props.model[__props.name],
|
|
@@ -65,7 +73,7 @@ var Code_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
65
73
|
"autosize",
|
|
66
74
|
"parse",
|
|
67
75
|
"editor-custom-type"
|
|
68
|
-
]);
|
|
76
|
+
])) : createCommentVNode("v-if", true);
|
|
69
77
|
};
|
|
70
78
|
}
|
|
71
79
|
});
|
|
@@ -72,14 +72,14 @@ var PropsPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
72
72
|
...v,
|
|
73
73
|
style: {}
|
|
74
74
|
};
|
|
75
|
-
if (v.style) {
|
|
75
|
+
if (v.style) if (eventData) {
|
|
76
76
|
Object.entries(v.style).forEach(([key, value]) => {
|
|
77
77
|
if (value !== "" && newValue.style) newValue.style[key] = value;
|
|
78
78
|
});
|
|
79
|
-
eventData
|
|
79
|
+
eventData.changeRecords?.forEach((record) => {
|
|
80
80
|
if (record.propPath?.startsWith("style") && record.value === "") setValueByKeyPath(record.propPath, record.value, newValue);
|
|
81
81
|
});
|
|
82
|
-
}
|
|
82
|
+
} else newValue.style = { ...v.style };
|
|
83
83
|
const historySource = eventData ? "props" : "code";
|
|
84
84
|
editorService.update(newValue, {
|
|
85
85
|
changeRecords: eventData?.changeRecords,
|
|
@@ -351,6 +351,11 @@ var Editor = class extends BaseService {
|
|
|
351
351
|
historySource,
|
|
352
352
|
doNotPushHistory
|
|
353
353
|
});
|
|
354
|
+
const addedPages = newNodes.filter((node) => isPage(node) || isPageFragment(node));
|
|
355
|
+
if (addedPages.length) history_default.notifyPageStructureChange({
|
|
356
|
+
add: addedPages,
|
|
357
|
+
remove: []
|
|
358
|
+
});
|
|
354
359
|
return Array.isArray(addNode) ? newNodes : newNodes[0];
|
|
355
360
|
}
|
|
356
361
|
async doRemove(node, { doNotSelect = false, doNotSwitchPage = false } = {}) {
|
|
@@ -454,6 +459,11 @@ var Editor = class extends BaseService {
|
|
|
454
459
|
historySource,
|
|
455
460
|
doNotPushHistory
|
|
456
461
|
});
|
|
462
|
+
const removedPages = nodes.filter((node) => isPage(node) || isPageFragment(node));
|
|
463
|
+
if (removedPages.length) history_default.notifyPageStructureChange({
|
|
464
|
+
add: [],
|
|
465
|
+
remove: removedPages
|
|
466
|
+
});
|
|
457
467
|
}
|
|
458
468
|
async doUpdate(config, { changeRecords = [], historySource } = {}) {
|
|
459
469
|
if (!this.get("root")) throw new Error("root为空");
|
|
@@ -1238,24 +1248,35 @@ var Editor = class extends BaseService {
|
|
|
1238
1248
|
const prevMap = new Map(prevPages.map((p) => [`${p.id}`, p]));
|
|
1239
1249
|
const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
|
|
1240
1250
|
const indexInItems = (root, id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
|
|
1251
|
+
const addedPages = [];
|
|
1252
|
+
const removedPages = [];
|
|
1241
1253
|
nextPages.forEach((nextPage) => {
|
|
1242
1254
|
const prevPage = prevMap.get(`${nextPage.id}`);
|
|
1243
|
-
if (!prevPage)
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1255
|
+
if (!prevPage) {
|
|
1256
|
+
this.pushPageDiffStep("add", nextPage, {
|
|
1257
|
+
newSchema: cloneDeep$1(toRaw(nextPage)),
|
|
1258
|
+
parentId: nextRoot.id,
|
|
1259
|
+
index: indexInItems(nextRoot, nextPage.id)
|
|
1260
|
+
}, source);
|
|
1261
|
+
addedPages.push(nextPage);
|
|
1262
|
+
} else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) this.pushPageDiffStep("update", nextPage, {
|
|
1249
1263
|
oldSchema: cloneDeep$1(toRaw(prevPage)),
|
|
1250
1264
|
newSchema: cloneDeep$1(toRaw(nextPage))
|
|
1251
1265
|
}, source);
|
|
1252
1266
|
});
|
|
1253
1267
|
prevPages.forEach((prevPage) => {
|
|
1254
|
-
if (!nextMap.has(`${prevPage.id}`))
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1268
|
+
if (!nextMap.has(`${prevPage.id}`)) {
|
|
1269
|
+
this.pushPageDiffStep("remove", prevPage, {
|
|
1270
|
+
oldSchema: cloneDeep$1(toRaw(prevPage)),
|
|
1271
|
+
parentId: preRoot.id,
|
|
1272
|
+
index: indexInItems(preRoot, prevPage.id)
|
|
1273
|
+
}, source);
|
|
1274
|
+
removedPages.push(prevPage);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
if (addedPages.length || removedPages.length) history_default.notifyPageStructureChange({
|
|
1278
|
+
add: addedPages,
|
|
1279
|
+
remove: removedPages
|
|
1259
1280
|
});
|
|
1260
1281
|
}
|
|
1261
1282
|
/**
|
|
@@ -244,6 +244,22 @@ var History = class extends BaseService {
|
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
246
|
/**
|
|
247
|
+
* 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
|
|
248
|
+
*
|
|
249
|
+
* 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
|
|
250
|
+
* 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
|
|
251
|
+
* 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
|
|
252
|
+
*
|
|
253
|
+
* 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
|
|
254
|
+
* 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
|
|
255
|
+
*
|
|
256
|
+
* @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
|
|
257
|
+
*/
|
|
258
|
+
notifyPageStructureChange(change) {
|
|
259
|
+
if (!change.add.length && !change.remove.length) return;
|
|
260
|
+
this.emit("page-structure-change", change);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
247
263
|
* 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
|
|
248
264
|
*
|
|
249
265
|
* - 每个 UndoRedo 栈连同其游标、容量一并保存,恢复后可继续 undo/redo;
|
package/dist/es/style.css
CHANGED
|
@@ -199,12 +199,14 @@ var resolveFieldByPath = (fields, fieldNames, options = {}) => {
|
|
|
199
199
|
if (options.skipNumberIndices && isNumber(name)) continue;
|
|
200
200
|
if (!currentFields.length) return {
|
|
201
201
|
ok: false,
|
|
202
|
-
fields: currentFields
|
|
202
|
+
fields: currentFields,
|
|
203
|
+
failedName: name
|
|
203
204
|
};
|
|
204
205
|
field = currentFields.find((item) => item.name === name);
|
|
205
206
|
if (!field) return {
|
|
206
207
|
ok: false,
|
|
207
|
-
fields: currentFields
|
|
208
|
+
fields: currentFields,
|
|
209
|
+
failedName: name
|
|
208
210
|
};
|
|
209
211
|
currentFields = field.fields || [];
|
|
210
212
|
}
|
|
@@ -3,6 +3,7 @@ import editor_default from "../services/editor.js";
|
|
|
3
3
|
import { getFieldType, resolveFieldByPath } from "./data-source/index.js";
|
|
4
4
|
import codeBlock_default from "../services/codeBlock.js";
|
|
5
5
|
import dataSource_default from "../services/dataSource.js";
|
|
6
|
+
import { validateTypeMatch } from "@tmagic/form";
|
|
6
7
|
import { appendValidateSuggestion } from "@tmagic/design";
|
|
7
8
|
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, DATA_SOURCE_SET_DATA_METHOD_NAME, dataSourceTemplateRegExp, getKeysArray, removeDataSourceFieldPrefix } from "@tmagic/utils";
|
|
8
9
|
import { NodeType } from "@tmagic/core";
|
|
@@ -181,10 +182,10 @@ var validateDataSourceFieldPath = (path, options = {}) => {
|
|
|
181
182
|
fieldNames = path.slice(1);
|
|
182
183
|
}
|
|
183
184
|
const ds = findDataSource(`${dsId}`);
|
|
184
|
-
if (!ds) return defaultMessage(options.message,
|
|
185
|
+
if (!ds) return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
|
|
185
186
|
if (!fieldNames.length) return;
|
|
186
|
-
const { field, ok } = resolveFieldByPath(ds.fields, fieldNames);
|
|
187
|
-
if (!ok) return defaultMessage(options.message,
|
|
187
|
+
const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, fieldNames);
|
|
188
|
+
if (!ok) return defaultMessage(options.message, `数据源字段(${failedName})不存在`, listSuggestion(fields.map((item) => item.name)));
|
|
188
189
|
const allowedTypes = options.dataSourceFieldType || ["any"];
|
|
189
190
|
if (!allowedTypes.length || allowedTypes.includes("any")) return;
|
|
190
191
|
const leafType = field?.type || "any";
|
|
@@ -267,9 +268,15 @@ var isDataSourceFieldPathValue = (value, config) => {
|
|
|
267
268
|
if (config.value === "key") return true;
|
|
268
269
|
return `${value[0]}`.startsWith(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
|
|
269
270
|
};
|
|
270
|
-
var validateDataSourceFieldSelect = (value, { message, props }) => {
|
|
271
|
+
var validateDataSourceFieldSelect = (value, { mForm, message, props }) => {
|
|
271
272
|
const config = props.config || {};
|
|
272
|
-
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return
|
|
273
|
+
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return validateTypeMatch(value, mForm, {
|
|
274
|
+
...props,
|
|
275
|
+
config: {
|
|
276
|
+
name: config.name,
|
|
277
|
+
...config.fieldConfig
|
|
278
|
+
}
|
|
279
|
+
}, message);
|
|
273
280
|
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return defaultMessage(message, `${value}类型应为字符串数组`, dataSourceFieldPathSuggestion());
|
|
274
281
|
return validateDataSourceFieldPath(value, {
|
|
275
282
|
dataSourceId: config.dataSourceId,
|
package/dist/style.css
CHANGED
|
@@ -6605,6 +6605,22 @@
|
|
|
6605
6605
|
});
|
|
6606
6606
|
}
|
|
6607
6607
|
/**
|
|
6608
|
+
* 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
|
|
6609
|
+
*
|
|
6610
|
+
* 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
|
|
6611
|
+
* 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
|
|
6612
|
+
* 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
|
|
6613
|
+
*
|
|
6614
|
+
* 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
|
|
6615
|
+
* 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
|
|
6616
|
+
*
|
|
6617
|
+
* @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
|
|
6618
|
+
*/
|
|
6619
|
+
notifyPageStructureChange(change) {
|
|
6620
|
+
if (!change.add.length && !change.remove.length) return;
|
|
6621
|
+
this.emit("page-structure-change", change);
|
|
6622
|
+
}
|
|
6623
|
+
/**
|
|
6608
6624
|
* 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
|
|
6609
6625
|
*
|
|
6610
6626
|
* - 每个 UndoRedo 栈连同其游标、容量一并保存,恢复后可继续 undo/redo;
|
|
@@ -7701,6 +7717,11 @@
|
|
|
7701
7717
|
historySource,
|
|
7702
7718
|
doNotPushHistory
|
|
7703
7719
|
});
|
|
7720
|
+
const addedPages = newNodes.filter((node) => (0, _tmagic_utils.isPage)(node) || (0, _tmagic_utils.isPageFragment)(node));
|
|
7721
|
+
if (addedPages.length) history_default.notifyPageStructureChange({
|
|
7722
|
+
add: addedPages,
|
|
7723
|
+
remove: []
|
|
7724
|
+
});
|
|
7704
7725
|
return Array.isArray(addNode) ? newNodes : newNodes[0];
|
|
7705
7726
|
}
|
|
7706
7727
|
async doRemove(node, { doNotSelect = false, doNotSwitchPage = false } = {}) {
|
|
@@ -7804,6 +7825,11 @@
|
|
|
7804
7825
|
historySource,
|
|
7805
7826
|
doNotPushHistory
|
|
7806
7827
|
});
|
|
7828
|
+
const removedPages = nodes.filter((node) => (0, _tmagic_utils.isPage)(node) || (0, _tmagic_utils.isPageFragment)(node));
|
|
7829
|
+
if (removedPages.length) history_default.notifyPageStructureChange({
|
|
7830
|
+
add: [],
|
|
7831
|
+
remove: removedPages
|
|
7832
|
+
});
|
|
7807
7833
|
}
|
|
7808
7834
|
async doUpdate(config, { changeRecords = [], historySource } = {}) {
|
|
7809
7835
|
if (!this.get("root")) throw new Error("root为空");
|
|
@@ -8588,24 +8614,35 @@
|
|
|
8588
8614
|
const prevMap = new Map(prevPages.map((p) => [`${p.id}`, p]));
|
|
8589
8615
|
const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
|
|
8590
8616
|
const indexInItems = (root, id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
|
|
8617
|
+
const addedPages = [];
|
|
8618
|
+
const removedPages = [];
|
|
8591
8619
|
nextPages.forEach((nextPage) => {
|
|
8592
8620
|
const prevPage = prevMap.get(`${nextPage.id}`);
|
|
8593
|
-
if (!prevPage)
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8621
|
+
if (!prevPage) {
|
|
8622
|
+
this.pushPageDiffStep("add", nextPage, {
|
|
8623
|
+
newSchema: cloneDeep$1((0, vue.toRaw)(nextPage)),
|
|
8624
|
+
parentId: nextRoot.id,
|
|
8625
|
+
index: indexInItems(nextRoot, nextPage.id)
|
|
8626
|
+
}, source);
|
|
8627
|
+
addedPages.push(nextPage);
|
|
8628
|
+
} else if (!isEqual((0, vue.toRaw)(prevPage), (0, vue.toRaw)(nextPage))) this.pushPageDiffStep("update", nextPage, {
|
|
8599
8629
|
oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
|
|
8600
8630
|
newSchema: cloneDeep$1((0, vue.toRaw)(nextPage))
|
|
8601
8631
|
}, source);
|
|
8602
8632
|
});
|
|
8603
8633
|
prevPages.forEach((prevPage) => {
|
|
8604
|
-
if (!nextMap.has(`${prevPage.id}`))
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8634
|
+
if (!nextMap.has(`${prevPage.id}`)) {
|
|
8635
|
+
this.pushPageDiffStep("remove", prevPage, {
|
|
8636
|
+
oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
|
|
8637
|
+
parentId: preRoot.id,
|
|
8638
|
+
index: indexInItems(preRoot, prevPage.id)
|
|
8639
|
+
}, source);
|
|
8640
|
+
removedPages.push(prevPage);
|
|
8641
|
+
}
|
|
8642
|
+
});
|
|
8643
|
+
if (addedPages.length || removedPages.length) history_default.notifyPageStructureChange({
|
|
8644
|
+
add: addedPages,
|
|
8645
|
+
remove: removedPages
|
|
8609
8646
|
});
|
|
8610
8647
|
}
|
|
8611
8648
|
/**
|
|
@@ -9598,12 +9635,14 @@
|
|
|
9598
9635
|
if (options.skipNumberIndices && (0, _tmagic_utils.isNumber)(name)) continue;
|
|
9599
9636
|
if (!currentFields.length) return {
|
|
9600
9637
|
ok: false,
|
|
9601
|
-
fields: currentFields
|
|
9638
|
+
fields: currentFields,
|
|
9639
|
+
failedName: name
|
|
9602
9640
|
};
|
|
9603
9641
|
field = currentFields.find((item) => item.name === name);
|
|
9604
9642
|
if (!field) return {
|
|
9605
9643
|
ok: false,
|
|
9606
|
-
fields: currentFields
|
|
9644
|
+
fields: currentFields,
|
|
9645
|
+
failedName: name
|
|
9607
9646
|
};
|
|
9608
9647
|
currentFields = field.fields || [];
|
|
9609
9648
|
}
|
|
@@ -10929,10 +10968,10 @@
|
|
|
10929
10968
|
fieldNames = path.slice(1);
|
|
10930
10969
|
}
|
|
10931
10970
|
const ds = findDataSource(`${dsId}`);
|
|
10932
|
-
if (!ds) return defaultMessage(options.message,
|
|
10971
|
+
if (!ds) return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
|
|
10933
10972
|
if (!fieldNames.length) return;
|
|
10934
|
-
const { field, ok } = resolveFieldByPath(ds.fields, fieldNames);
|
|
10935
|
-
if (!ok) return defaultMessage(options.message,
|
|
10973
|
+
const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, fieldNames);
|
|
10974
|
+
if (!ok) return defaultMessage(options.message, `数据源字段(${failedName})不存在`, listSuggestion(fields.map((item) => item.name)));
|
|
10936
10975
|
const allowedTypes = options.dataSourceFieldType || ["any"];
|
|
10937
10976
|
if (!allowedTypes.length || allowedTypes.includes("any")) return;
|
|
10938
10977
|
const leafType = field?.type || "any";
|
|
@@ -11015,9 +11054,15 @@
|
|
|
11015
11054
|
if (config.value === "key") return true;
|
|
11016
11055
|
return `${value[0]}`.startsWith(_tmagic_utils.DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
|
|
11017
11056
|
};
|
|
11018
|
-
var validateDataSourceFieldSelect = (value, { message, props }) => {
|
|
11057
|
+
var validateDataSourceFieldSelect = (value, { mForm, message, props }) => {
|
|
11019
11058
|
const config = props.config || {};
|
|
11020
|
-
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return
|
|
11059
|
+
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return (0, _tmagic_form.validateTypeMatch)(value, mForm, {
|
|
11060
|
+
...props,
|
|
11061
|
+
config: {
|
|
11062
|
+
name: config.name,
|
|
11063
|
+
...config.fieldConfig
|
|
11064
|
+
}
|
|
11065
|
+
}, message);
|
|
11021
11066
|
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return defaultMessage(message, `${value}类型应为字符串数组`, dataSourceFieldPathSuggestion());
|
|
11022
11067
|
return validateDataSourceFieldPath(value, {
|
|
11023
11068
|
dataSourceId: config.dataSourceId,
|
|
@@ -15055,14 +15100,14 @@
|
|
|
15055
15100
|
...v,
|
|
15056
15101
|
style: {}
|
|
15057
15102
|
};
|
|
15058
|
-
if (v.style) {
|
|
15103
|
+
if (v.style) if (eventData) {
|
|
15059
15104
|
Object.entries(v.style).forEach(([key, value]) => {
|
|
15060
15105
|
if (value !== "" && newValue.style) newValue.style[key] = value;
|
|
15061
15106
|
});
|
|
15062
|
-
eventData
|
|
15107
|
+
eventData.changeRecords?.forEach((record) => {
|
|
15063
15108
|
if (record.propPath?.startsWith("style") && record.value === "") (0, _tmagic_utils.setValueByKeyPath)(record.propPath, record.value, newValue);
|
|
15064
15109
|
});
|
|
15065
|
-
}
|
|
15110
|
+
} else newValue.style = { ...v.style };
|
|
15066
15111
|
const historySource = eventData ? "props" : "code";
|
|
15067
15112
|
editorService.update(newValue, {
|
|
15068
15113
|
changeRecords: eventData?.changeRecords,
|
|
@@ -15700,15 +15745,29 @@
|
|
|
15700
15745
|
if (contains(target)) return;
|
|
15701
15746
|
hide();
|
|
15702
15747
|
};
|
|
15748
|
+
const fixPosition = () => {
|
|
15749
|
+
const menu = menuEl.value;
|
|
15750
|
+
if (!menu || !visible.value) return;
|
|
15751
|
+
const menuHeight = menu.clientHeight;
|
|
15752
|
+
const menuWidth = menu.clientWidth;
|
|
15753
|
+
let { top, left } = menuPosition.value;
|
|
15754
|
+
if (top + menuHeight > document.body.clientHeight) top = Math.max(0, document.body.clientHeight - menuHeight);
|
|
15755
|
+
if (left + menuWidth > document.body.clientWidth) left = Math.max(0, document.body.clientWidth - menuWidth);
|
|
15756
|
+
if (top !== menuPosition.value.top || left !== menuPosition.value.left) menuPosition.value = {
|
|
15757
|
+
top,
|
|
15758
|
+
left
|
|
15759
|
+
};
|
|
15760
|
+
};
|
|
15703
15761
|
const setPosition = (e) => {
|
|
15704
|
-
const menuHeight = menuEl.value?.clientHeight || 0;
|
|
15705
|
-
let top = e.clientY;
|
|
15706
|
-
if (menuHeight + e.clientY > document.body.clientHeight) top = document.body.clientHeight - menuHeight;
|
|
15707
15762
|
menuPosition.value = {
|
|
15708
|
-
top,
|
|
15763
|
+
top: e.clientY,
|
|
15709
15764
|
left: e.clientX
|
|
15710
15765
|
};
|
|
15766
|
+
fixPosition();
|
|
15711
15767
|
};
|
|
15768
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
15769
|
+
fixPosition();
|
|
15770
|
+
});
|
|
15712
15771
|
const show = (e) => {
|
|
15713
15772
|
visible.value = true;
|
|
15714
15773
|
(0, vue.nextTick)(() => {
|
|
@@ -15737,10 +15796,12 @@
|
|
|
15737
15796
|
emit("mouseenter");
|
|
15738
15797
|
};
|
|
15739
15798
|
(0, vue.onMounted)(() => {
|
|
15799
|
+
if (menuEl.value) resizeObserver.observe(menuEl.value);
|
|
15740
15800
|
if (props.isSubMenu) return;
|
|
15741
15801
|
globalThis.addEventListener("mousedown", outsideClickHideHandler, true);
|
|
15742
15802
|
});
|
|
15743
15803
|
(0, vue.onBeforeUnmount)(() => {
|
|
15804
|
+
resizeObserver.disconnect();
|
|
15744
15805
|
if (props.isSubMenu) return;
|
|
15745
15806
|
globalThis.removeEventListener("mousedown", outsideClickHideHandler, true);
|
|
15746
15807
|
});
|
|
@@ -25323,6 +25384,12 @@
|
|
|
25323
25384
|
},
|
|
25324
25385
|
emits: ["change"],
|
|
25325
25386
|
setup(__props, { emit: __emit }) {
|
|
25387
|
+
/**
|
|
25388
|
+
* 静默模式(submitForm/validateForm 隐藏挂载)下跳过 monaco 渲染:
|
|
25389
|
+
* 校验由 FormItem 针对 model 中的值完成,与编辑器实例无关;本组件挂载无值副作用
|
|
25390
|
+
* (save 仅由用户操作/编辑器内容变更触发),跳过可省去 monaco worker/model 的无谓实例化。
|
|
25391
|
+
*/
|
|
25392
|
+
const silentMode = (0, vue.inject)(_tmagic_form.FORM_SILENT_MODE_KEY, false);
|
|
25326
25393
|
const emit = __emit;
|
|
25327
25394
|
const props = __props;
|
|
25328
25395
|
/**
|
|
@@ -25340,7 +25407,8 @@
|
|
|
25340
25407
|
emit("change", v);
|
|
25341
25408
|
};
|
|
25342
25409
|
return (_ctx, _cache) => {
|
|
25343
|
-
return (0, vue.openBlock)(), (0, vue.createBlock)(CodeEditor_default, {
|
|
25410
|
+
return !(0, vue.unref)(silentMode) ? ((0, vue.openBlock)(), (0, vue.createBlock)(CodeEditor_default, {
|
|
25411
|
+
key: 0,
|
|
25344
25412
|
height: __props.config.height,
|
|
25345
25413
|
type: diffMode.value ? "diff" : void 0,
|
|
25346
25414
|
"init-values": diffMode.value ? (__props.lastValues || {})[__props.name] : __props.model[__props.name],
|
|
@@ -25364,7 +25432,7 @@
|
|
|
25364
25432
|
"autosize",
|
|
25365
25433
|
"parse",
|
|
25366
25434
|
"editor-custom-type"
|
|
25367
|
-
]);
|
|
25435
|
+
])) : (0, vue.createCommentVNode)("v-if", true);
|
|
25368
25436
|
};
|
|
25369
25437
|
}
|
|
25370
25438
|
});
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-beta.
|
|
2
|
+
"version": "1.8.0-beta.15",
|
|
3
3
|
"name": "@tmagic/editor",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"moveable": "^0.53.0",
|
|
59
59
|
"serialize-javascript": "^7.0.0",
|
|
60
60
|
"sortablejs": "^1.15.6",
|
|
61
|
-
"@tmagic/design": "1.8.0-beta.
|
|
62
|
-
"@tmagic/form": "1.8.0-beta.
|
|
63
|
-
"@tmagic/
|
|
64
|
-
"@tmagic/
|
|
65
|
-
"@tmagic/
|
|
61
|
+
"@tmagic/design": "1.8.0-beta.15",
|
|
62
|
+
"@tmagic/form": "1.8.0-beta.15",
|
|
63
|
+
"@tmagic/table": "1.8.0-beta.15",
|
|
64
|
+
"@tmagic/utils": "1.8.0-beta.15",
|
|
65
|
+
"@tmagic/stage": "1.8.0-beta.15"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/events": "^3.0.3",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"type-fest": "^5.2.0",
|
|
77
77
|
"typescript": "^6.0.3",
|
|
78
78
|
"vue": "^3.5.40",
|
|
79
|
-
"@tmagic/core": "1.8.0-beta.
|
|
79
|
+
"@tmagic/core": "1.8.0-beta.15"
|
|
80
80
|
},
|
|
81
81
|
"peerDependenciesMeta": {
|
|
82
82
|
"typescript": {
|
|
@@ -128,20 +128,42 @@ const outsideClickHideHandler = (e: MouseEvent) => {
|
|
|
128
128
|
hide();
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
// 根据菜单实际尺寸修正位置,避免超出可视范围
|
|
132
|
+
const fixPosition = () => {
|
|
133
|
+
const menu = menuEl.value;
|
|
134
|
+
if (!menu || !visible.value) return;
|
|
135
|
+
|
|
136
|
+
const menuHeight = menu.clientHeight;
|
|
137
|
+
const menuWidth = menu.clientWidth;
|
|
138
|
+
|
|
139
|
+
let { top, left } = menuPosition.value;
|
|
140
|
+
|
|
141
|
+
if (top + menuHeight > document.body.clientHeight) {
|
|
142
|
+
top = Math.max(0, document.body.clientHeight - menuHeight);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (left + menuWidth > document.body.clientWidth) {
|
|
146
|
+
left = Math.max(0, document.body.clientWidth - menuWidth);
|
|
147
|
+
}
|
|
133
148
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
top = document.body.clientHeight - menuHeight;
|
|
149
|
+
if (top !== menuPosition.value.top || left !== menuPosition.value.left) {
|
|
150
|
+
menuPosition.value = { top, left };
|
|
137
151
|
}
|
|
152
|
+
};
|
|
138
153
|
|
|
154
|
+
const setPosition = (e: { clientY: number; clientX: number }) => {
|
|
139
155
|
menuPosition.value = {
|
|
140
|
-
top,
|
|
156
|
+
top: e.clientY,
|
|
141
157
|
left: e.clientX,
|
|
142
158
|
};
|
|
159
|
+
fixPosition();
|
|
143
160
|
};
|
|
144
161
|
|
|
162
|
+
// 菜单大小动态变化(如菜单项更新)后重新修正位置
|
|
163
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
164
|
+
fixPosition();
|
|
165
|
+
});
|
|
166
|
+
|
|
145
167
|
const show = (e?: { clientY: number; clientX: number }) => {
|
|
146
168
|
visible.value = true;
|
|
147
169
|
|
|
@@ -186,12 +208,18 @@ const mouseenterHandler = () => {
|
|
|
186
208
|
};
|
|
187
209
|
|
|
188
210
|
onMounted(() => {
|
|
211
|
+
if (menuEl.value) {
|
|
212
|
+
resizeObserver.observe(menuEl.value);
|
|
213
|
+
}
|
|
214
|
+
|
|
189
215
|
if (props.isSubMenu) return;
|
|
190
216
|
|
|
191
217
|
globalThis.addEventListener('mousedown', outsideClickHideHandler, true);
|
|
192
218
|
});
|
|
193
219
|
|
|
194
220
|
onBeforeUnmount(() => {
|
|
221
|
+
resizeObserver.disconnect();
|
|
222
|
+
|
|
195
223
|
if (props.isSubMenu) return;
|
|
196
224
|
|
|
197
225
|
globalThis.removeEventListener('mousedown', outsideClickHideHandler, true);
|
package/src/fields/Code.vue
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<MagicCodeEditor
|
|
3
|
+
v-if="!silentMode"
|
|
3
4
|
:height="config.height"
|
|
4
5
|
:type="diffMode ? 'diff' : undefined"
|
|
5
6
|
:init-values="diffMode ? (lastValues || {})[name] : model[name]"
|
|
@@ -17,9 +18,9 @@
|
|
|
17
18
|
</template>
|
|
18
19
|
|
|
19
20
|
<script lang="ts" setup>
|
|
20
|
-
import { computed } from 'vue';
|
|
21
|
+
import { computed, inject } from 'vue';
|
|
21
22
|
|
|
22
|
-
import type
|
|
23
|
+
import { type CodeConfig, type FieldProps, FORM_SILENT_MODE_KEY } from '@tmagic/form';
|
|
23
24
|
|
|
24
25
|
import MagicCodeEditor from '@editor/layouts/CodeEditor.vue';
|
|
25
26
|
|
|
@@ -27,6 +28,13 @@ defineOptions({
|
|
|
27
28
|
name: 'MFieldsVsCode',
|
|
28
29
|
});
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* 静默模式(submitForm/validateForm 隐藏挂载)下跳过 monaco 渲染:
|
|
33
|
+
* 校验由 FormItem 针对 model 中的值完成,与编辑器实例无关;本组件挂载无值副作用
|
|
34
|
+
* (save 仅由用户操作/编辑器内容变更触发),跳过可省去 monaco worker/model 的无谓实例化。
|
|
35
|
+
*/
|
|
36
|
+
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
|
37
|
+
|
|
30
38
|
const emit = defineEmits<{
|
|
31
39
|
change: [value: string | any];
|
|
32
40
|
}>();
|
|
@@ -164,17 +164,25 @@ const submit = async (
|
|
|
164
164
|
};
|
|
165
165
|
|
|
166
166
|
if (v.style) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
167
|
+
// 空字符串样式值表示「清除该样式」,需保留才能在 doUpdate 的 mergeWith 中覆盖旧值。
|
|
168
|
+
if (eventData) {
|
|
169
|
+
// 表单编辑:先过滤掉空字符串(避免表单默认空值污染 DSL),
|
|
170
|
+
// 再按 changeRecords 恢复被主动清空的字段。
|
|
171
|
+
Object.entries(v.style).forEach(([key, value]) => {
|
|
172
|
+
if (value !== '' && newValue.style) {
|
|
173
|
+
newValue.style[key] = value;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
eventData.changeRecords?.forEach((record) => {
|
|
178
|
+
if (record.propPath?.startsWith('style') && record.value === '') {
|
|
179
|
+
setValueByKeyPath(record.propPath, record.value, newValue);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
} else {
|
|
183
|
+
// 源码编辑器保存(无 eventData):style 原样保留,其中的空字符串视为用户主动清除该样式。
|
|
184
|
+
newValue.style = { ...v.style };
|
|
185
|
+
}
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
// 区分操作途径:表单字段编辑(MForm @change)会带上 eventData(含 changeRecords);
|
package/src/services/editor.ts
CHANGED
|
@@ -554,6 +554,12 @@ class Editor extends BaseService {
|
|
|
554
554
|
doNotPushHistory,
|
|
555
555
|
});
|
|
556
556
|
|
|
557
|
+
// 页面 / 页面片新增不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
|
|
558
|
+
const addedPages = newNodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
|
|
559
|
+
if (addedPages.length) {
|
|
560
|
+
historyService.notifyPageStructureChange({ add: addedPages, remove: [] });
|
|
561
|
+
}
|
|
562
|
+
|
|
557
563
|
return Array.isArray(addNode) ? newNodes : newNodes[0];
|
|
558
564
|
}
|
|
559
565
|
|
|
@@ -695,6 +701,12 @@ class Editor extends BaseService {
|
|
|
695
701
|
|
|
696
702
|
this.emit('remove', nodes);
|
|
697
703
|
this.emit('change', { type: 'remove', data: changeItems, historySource, doNotPushHistory });
|
|
704
|
+
|
|
705
|
+
// 页面 / 页面片删除不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
|
|
706
|
+
const removedPages = nodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
|
|
707
|
+
if (removedPages.length) {
|
|
708
|
+
historyService.notifyPageStructureChange({ add: [], remove: removedPages });
|
|
709
|
+
}
|
|
698
710
|
}
|
|
699
711
|
|
|
700
712
|
public async doUpdate(
|
|
@@ -1776,6 +1788,10 @@ class Editor extends BaseService {
|
|
|
1776
1788
|
const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
|
|
1777
1789
|
const indexInItems = (root: MApp, id: Id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
|
|
1778
1790
|
|
|
1791
|
+
// 收集本次整体替换中增删的页面,循环结束后合并为一次结构变更通知(避免逐页派发多个事件)
|
|
1792
|
+
const addedPages: (MPage | MPageFragment)[] = [];
|
|
1793
|
+
const removedPages: (MPage | MPageFragment)[] = [];
|
|
1794
|
+
|
|
1779
1795
|
nextPages.forEach((nextPage) => {
|
|
1780
1796
|
const prevPage = prevMap.get(`${nextPage.id}`);
|
|
1781
1797
|
if (!prevPage) {
|
|
@@ -1785,6 +1801,7 @@ class Editor extends BaseService {
|
|
|
1785
1801
|
{ newSchema: cloneDeep(toRaw(nextPage)), parentId: nextRoot.id, index: indexInItems(nextRoot, nextPage.id) },
|
|
1786
1802
|
source,
|
|
1787
1803
|
);
|
|
1804
|
+
addedPages.push(nextPage);
|
|
1788
1805
|
} else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) {
|
|
1789
1806
|
this.pushPageDiffStep(
|
|
1790
1807
|
'update',
|
|
@@ -1803,8 +1820,13 @@ class Editor extends BaseService {
|
|
|
1803
1820
|
{ oldSchema: cloneDeep(toRaw(prevPage)), parentId: preRoot.id, index: indexInItems(preRoot, prevPage.id) },
|
|
1804
1821
|
source,
|
|
1805
1822
|
);
|
|
1823
|
+
removedPages.push(prevPage);
|
|
1806
1824
|
}
|
|
1807
1825
|
});
|
|
1826
|
+
|
|
1827
|
+
if (addedPages.length || removedPages.length) {
|
|
1828
|
+
historyService.notifyPageStructureChange({ add: addedPages, remove: removedPages });
|
|
1829
|
+
}
|
|
1808
1830
|
}
|
|
1809
1831
|
|
|
1810
1832
|
/**
|
package/src/services/history.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { reactive } from 'vue';
|
|
20
20
|
import type { Writable } from 'type-fest';
|
|
21
21
|
|
|
22
|
-
import type { Id } from '@tmagic/core';
|
|
22
|
+
import type { Id, MPage, MPageFragment } from '@tmagic/core';
|
|
23
23
|
import { guid } from '@tmagic/utils';
|
|
24
24
|
|
|
25
25
|
import type {
|
|
@@ -353,6 +353,27 @@ class History extends BaseService {
|
|
|
353
353
|
this.emit('mark-saved', { kind: stepType, id });
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
+
/**
|
|
357
|
+
* 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
|
|
358
|
+
*
|
|
359
|
+
* 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
|
|
360
|
+
* 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
|
|
361
|
+
* 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
|
|
362
|
+
*
|
|
363
|
+
* 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
|
|
364
|
+
* 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
|
|
365
|
+
*
|
|
366
|
+
* @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
|
|
367
|
+
*/
|
|
368
|
+
public notifyPageStructureChange(change: {
|
|
369
|
+
add: (MPage | MPageFragment)[];
|
|
370
|
+
remove: (MPage | MPageFragment)[];
|
|
371
|
+
}): void {
|
|
372
|
+
// 增删均为空时不派发,避免无意义通知
|
|
373
|
+
if (!change.add.length && !change.remove.length) return;
|
|
374
|
+
this.emit('page-structure-change', change);
|
|
375
|
+
}
|
|
376
|
+
|
|
356
377
|
/**
|
|
357
378
|
* 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
|
|
358
379
|
*
|
package/src/type.ts
CHANGED
|
@@ -1385,6 +1385,12 @@ export interface HistoryEvents {
|
|
|
1385
1385
|
'mark-saved': [{ kind: HistoryStepType; id?: Id }];
|
|
1386
1386
|
clear: [{ id: Id; stepType: HistoryStepType }];
|
|
1387
1387
|
'marker-change': [{ id: Id; marker: StepValue; stepType: HistoryStepType }];
|
|
1388
|
+
/**
|
|
1389
|
+
* 页面 / 页面片结构变更(新增 / 删除)时派发,见 {@link HistoryService.notifyPageStructureChange}。
|
|
1390
|
+
* 一次操作(add / remove / setRoot 整体替换)涉及多个页面时合并为**一个**事件;
|
|
1391
|
+
* `add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
|
|
1392
|
+
*/
|
|
1393
|
+
'page-structure-change': [change: { add: (MPage | MPageFragment)[]; remove: (MPage | MPageFragment)[] }];
|
|
1388
1394
|
}
|
|
1389
1395
|
|
|
1390
1396
|
export const canUsePluginMethods = {
|
|
@@ -243,7 +243,7 @@ export const resolveFieldByPath = (
|
|
|
243
243
|
fields: DataSchema[] | undefined,
|
|
244
244
|
fieldNames: string[],
|
|
245
245
|
options: { skipNumberIndices?: boolean } = {},
|
|
246
|
-
): { ok: boolean; field?: DataSchema; fields: DataSchema[] } => {
|
|
246
|
+
): { ok: boolean; field?: DataSchema; fields: DataSchema[]; failedName?: string } => {
|
|
247
247
|
let currentFields = fields || [];
|
|
248
248
|
let field: DataSchema | undefined;
|
|
249
249
|
|
|
@@ -252,11 +252,11 @@ export const resolveFieldByPath = (
|
|
|
252
252
|
continue;
|
|
253
253
|
}
|
|
254
254
|
if (!currentFields.length) {
|
|
255
|
-
return { ok: false, fields: currentFields };
|
|
255
|
+
return { ok: false, fields: currentFields, failedName: name };
|
|
256
256
|
}
|
|
257
257
|
field = currentFields.find((item) => item.name === name);
|
|
258
258
|
if (!field) {
|
|
259
|
-
return { ok: false, fields: currentFields };
|
|
259
|
+
return { ok: false, fields: currentFields, failedName: name };
|
|
260
260
|
}
|
|
261
261
|
currentFields = field.fields || [];
|
|
262
262
|
}
|
|
@@ -20,6 +20,7 @@ import type { DataSourceFieldType, DataSourceSchema, Id } from '@tmagic/core';
|
|
|
20
20
|
import { NodeType } from '@tmagic/core';
|
|
21
21
|
import { appendValidateSuggestion } from '@tmagic/design';
|
|
22
22
|
import type { TypeMatchValidateContext, TypeMatchValidator } from '@tmagic/form';
|
|
23
|
+
import { validateTypeMatch } from '@tmagic/form';
|
|
23
24
|
import {
|
|
24
25
|
DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
|
|
25
26
|
DATA_SOURCE_SET_DATA_METHOD_NAME,
|
|
@@ -261,16 +262,20 @@ const validateDataSourceFieldPath = (
|
|
|
261
262
|
|
|
262
263
|
const ds = findDataSource(`${dsId}`);
|
|
263
264
|
if (!ds) {
|
|
264
|
-
return defaultMessage(options.message,
|
|
265
|
+
return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
|
|
265
266
|
}
|
|
266
267
|
|
|
267
268
|
if (!fieldNames.length) {
|
|
268
269
|
return undefined;
|
|
269
270
|
}
|
|
270
271
|
|
|
271
|
-
const { field, ok } = resolveFieldByPath(ds.fields, fieldNames);
|
|
272
|
+
const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, fieldNames);
|
|
272
273
|
if (!ok) {
|
|
273
|
-
return defaultMessage(
|
|
274
|
+
return defaultMessage(
|
|
275
|
+
options.message,
|
|
276
|
+
`数据源字段(${failedName})不存在`,
|
|
277
|
+
listSuggestion(fields.map((item) => item.name)),
|
|
278
|
+
);
|
|
274
279
|
}
|
|
275
280
|
|
|
276
281
|
const allowedTypes = options.dataSourceFieldType || ['any'];
|
|
@@ -471,11 +476,12 @@ const isDataSourceFieldPathValue = (value: any, config: any): value is string[]
|
|
|
471
476
|
return `${value[0]}`.startsWith(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
|
|
472
477
|
};
|
|
473
478
|
|
|
474
|
-
const validateDataSourceFieldSelect: TypeMatchValidator = (value, { message, props }) => {
|
|
479
|
+
const validateDataSourceFieldSelect: TypeMatchValidator = (value, { mForm, message, props }) => {
|
|
475
480
|
const config = props.config || {};
|
|
476
481
|
|
|
477
482
|
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) {
|
|
478
|
-
|
|
483
|
+
// 值不是数据源字段路径时,按 fieldConfig 的类型校验(与表单项自身 typeMatch 行为一致)
|
|
484
|
+
return validateTypeMatch(value, mForm, { ...props, config: { name: config.name, ...config.fieldConfig } }, message);
|
|
479
485
|
}
|
|
480
486
|
|
|
481
487
|
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
package/types/index.d.ts
CHANGED
|
@@ -1214,6 +1214,22 @@ declare class History extends BaseService {
|
|
|
1214
1214
|
* @param id 目标栈 id;缺省表示标记全部类型 / 全部栈
|
|
1215
1215
|
*/
|
|
1216
1216
|
markSaved(stepType: HistoryStepType, id?: Id): void;
|
|
1217
|
+
/**
|
|
1218
|
+
* 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
|
|
1219
|
+
*
|
|
1220
|
+
* 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
|
|
1221
|
+
* 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
|
|
1222
|
+
* 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
|
|
1223
|
+
*
|
|
1224
|
+
* 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
|
|
1225
|
+
* 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
|
|
1226
|
+
*
|
|
1227
|
+
* @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
|
|
1228
|
+
*/
|
|
1229
|
+
notifyPageStructureChange(change: {
|
|
1230
|
+
add: (MPage | MPageFragment)[];
|
|
1231
|
+
remove: (MPage | MPageFragment)[];
|
|
1232
|
+
}): void;
|
|
1217
1233
|
/**
|
|
1218
1234
|
* 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
|
|
1219
1235
|
*
|
|
@@ -2694,6 +2710,15 @@ interface HistoryEvents {
|
|
|
2694
2710
|
marker: StepValue;
|
|
2695
2711
|
stepType: HistoryStepType;
|
|
2696
2712
|
}];
|
|
2713
|
+
/**
|
|
2714
|
+
* 页面 / 页面片结构变更(新增 / 删除)时派发,见 {@link HistoryService.notifyPageStructureChange}。
|
|
2715
|
+
* 一次操作(add / remove / setRoot 整体替换)涉及多个页面时合并为**一个**事件;
|
|
2716
|
+
* `add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
|
|
2717
|
+
*/
|
|
2718
|
+
'page-structure-change': [change: {
|
|
2719
|
+
add: (MPage | MPageFragment)[];
|
|
2720
|
+
remove: (MPage | MPageFragment)[];
|
|
2721
|
+
}];
|
|
2697
2722
|
}
|
|
2698
2723
|
declare const canUsePluginMethods: {
|
|
2699
2724
|
async: readonly ["getLayout", "highlight", "select", "multiSelect", "doAdd", "add", "doRemove", "remove", "doUpdate", "update", "sort", "copy", "paste", "doPaste", "doAlignCenter", "alignCenter", "moveLayer", "moveToContainer", "dragTo", "undo", "redo", "move"];
|
|
@@ -3370,6 +3395,7 @@ declare const resolveFieldByPath: (fields: DataSchema[] | undefined, fieldNames:
|
|
|
3370
3395
|
ok: boolean;
|
|
3371
3396
|
field?: DataSchema;
|
|
3372
3397
|
fields: DataSchema[];
|
|
3398
|
+
failedName?: string;
|
|
3373
3399
|
};
|
|
3374
3400
|
declare const getFieldType: (ds: DataSourceSchema | undefined, fieldNames: string[]) => "" | DataSourceFieldType;
|
|
3375
3401
|
//#endregion
|