@tmagic/editor 1.8.0-beta.14 → 1.8.0-beta.16
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/index.js +2 -2
- package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +11 -8
- 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/props.js +1 -45
- package/dist/es/utils/type-match-rules.js +13 -6
- package/dist/style.css +4 -0
- package/dist/themes/magic-admin.css +4 -0
- package/dist/tmagic-editor.umd.cjs +107 -52
- package/package.json +7 -7
- package/src/components/ContentMenu.vue +34 -6
- package/src/fields/Code.vue +10 -2
- package/src/layouts/props-panel/FormPanel.vue +12 -8
- 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/props.ts +1 -98
- package/src/utils/type-match-rules.ts +12 -6
- package/types/index.d.ts +28 -66
|
@@ -5182,7 +5182,7 @@
|
|
|
5182
5182
|
}));
|
|
5183
5183
|
//#endregion
|
|
5184
5184
|
//#region packages/editor/src/utils/props.ts
|
|
5185
|
-
var arrayOptions, eqOptions, numberOptions, booleanOptions, getCondOpOptionsByFieldType, styleTabConfig, eventTabConfig, advancedTabConfig, displayTabConfig, fillConfig, removeStyleDisplayConfig
|
|
5185
|
+
var arrayOptions, eqOptions, numberOptions, booleanOptions, getCondOpOptionsByFieldType, styleTabConfig, eventTabConfig, advancedTabConfig, displayTabConfig, fillConfig, removeStyleDisplayConfig;
|
|
5186
5186
|
var init_props = __esmMin((() => {
|
|
5187
5187
|
arrayOptions = [{
|
|
5188
5188
|
text: "包含",
|
|
@@ -5479,21 +5479,6 @@
|
|
|
5479
5479
|
})
|
|
5480
5480
|
};
|
|
5481
5481
|
});
|
|
5482
|
-
validatePropsForm = ({ config, values, appContext = null, services, stage, extendState, debug, typeMatchValid }) => (0, _tmagic_form.validateForm)({
|
|
5483
|
-
config,
|
|
5484
|
-
debug,
|
|
5485
|
-
typeMatchValid,
|
|
5486
|
-
initValues: values,
|
|
5487
|
-
appContext: appContext ? {
|
|
5488
|
-
...appContext,
|
|
5489
|
-
provides: { services }
|
|
5490
|
-
} : null,
|
|
5491
|
-
extendState: async (state) => ({
|
|
5492
|
-
...await extendState?.(state) || {},
|
|
5493
|
-
stage,
|
|
5494
|
-
services
|
|
5495
|
-
})
|
|
5496
|
-
});
|
|
5497
5482
|
}));
|
|
5498
5483
|
//#endregion
|
|
5499
5484
|
//#region packages/editor/src/hooks/use-compare-form.ts
|
|
@@ -6605,6 +6590,22 @@
|
|
|
6605
6590
|
});
|
|
6606
6591
|
}
|
|
6607
6592
|
/**
|
|
6593
|
+
* 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
|
|
6594
|
+
*
|
|
6595
|
+
* 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
|
|
6596
|
+
* 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
|
|
6597
|
+
* 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
|
|
6598
|
+
*
|
|
6599
|
+
* 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
|
|
6600
|
+
* 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
|
|
6601
|
+
*
|
|
6602
|
+
* @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
|
|
6603
|
+
*/
|
|
6604
|
+
notifyPageStructureChange(change) {
|
|
6605
|
+
if (!change.add.length && !change.remove.length) return;
|
|
6606
|
+
this.emit("page-structure-change", change);
|
|
6607
|
+
}
|
|
6608
|
+
/**
|
|
6608
6609
|
* 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
|
|
6609
6610
|
*
|
|
6610
6611
|
* - 每个 UndoRedo 栈连同其游标、容量一并保存,恢复后可继续 undo/redo;
|
|
@@ -7701,6 +7702,11 @@
|
|
|
7701
7702
|
historySource,
|
|
7702
7703
|
doNotPushHistory
|
|
7703
7704
|
});
|
|
7705
|
+
const addedPages = newNodes.filter((node) => (0, _tmagic_utils.isPage)(node) || (0, _tmagic_utils.isPageFragment)(node));
|
|
7706
|
+
if (addedPages.length) history_default.notifyPageStructureChange({
|
|
7707
|
+
add: addedPages,
|
|
7708
|
+
remove: []
|
|
7709
|
+
});
|
|
7704
7710
|
return Array.isArray(addNode) ? newNodes : newNodes[0];
|
|
7705
7711
|
}
|
|
7706
7712
|
async doRemove(node, { doNotSelect = false, doNotSwitchPage = false } = {}) {
|
|
@@ -7804,6 +7810,11 @@
|
|
|
7804
7810
|
historySource,
|
|
7805
7811
|
doNotPushHistory
|
|
7806
7812
|
});
|
|
7813
|
+
const removedPages = nodes.filter((node) => (0, _tmagic_utils.isPage)(node) || (0, _tmagic_utils.isPageFragment)(node));
|
|
7814
|
+
if (removedPages.length) history_default.notifyPageStructureChange({
|
|
7815
|
+
add: [],
|
|
7816
|
+
remove: removedPages
|
|
7817
|
+
});
|
|
7807
7818
|
}
|
|
7808
7819
|
async doUpdate(config, { changeRecords = [], historySource } = {}) {
|
|
7809
7820
|
if (!this.get("root")) throw new Error("root为空");
|
|
@@ -8588,24 +8599,35 @@
|
|
|
8588
8599
|
const prevMap = new Map(prevPages.map((p) => [`${p.id}`, p]));
|
|
8589
8600
|
const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
|
|
8590
8601
|
const indexInItems = (root, id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
|
|
8602
|
+
const addedPages = [];
|
|
8603
|
+
const removedPages = [];
|
|
8591
8604
|
nextPages.forEach((nextPage) => {
|
|
8592
8605
|
const prevPage = prevMap.get(`${nextPage.id}`);
|
|
8593
|
-
if (!prevPage)
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8606
|
+
if (!prevPage) {
|
|
8607
|
+
this.pushPageDiffStep("add", nextPage, {
|
|
8608
|
+
newSchema: cloneDeep$1((0, vue.toRaw)(nextPage)),
|
|
8609
|
+
parentId: nextRoot.id,
|
|
8610
|
+
index: indexInItems(nextRoot, nextPage.id)
|
|
8611
|
+
}, source);
|
|
8612
|
+
addedPages.push(nextPage);
|
|
8613
|
+
} else if (!isEqual((0, vue.toRaw)(prevPage), (0, vue.toRaw)(nextPage))) this.pushPageDiffStep("update", nextPage, {
|
|
8599
8614
|
oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
|
|
8600
8615
|
newSchema: cloneDeep$1((0, vue.toRaw)(nextPage))
|
|
8601
8616
|
}, source);
|
|
8602
8617
|
});
|
|
8603
8618
|
prevPages.forEach((prevPage) => {
|
|
8604
|
-
if (!nextMap.has(`${prevPage.id}`))
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8619
|
+
if (!nextMap.has(`${prevPage.id}`)) {
|
|
8620
|
+
this.pushPageDiffStep("remove", prevPage, {
|
|
8621
|
+
oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
|
|
8622
|
+
parentId: preRoot.id,
|
|
8623
|
+
index: indexInItems(preRoot, prevPage.id)
|
|
8624
|
+
}, source);
|
|
8625
|
+
removedPages.push(prevPage);
|
|
8626
|
+
}
|
|
8627
|
+
});
|
|
8628
|
+
if (addedPages.length || removedPages.length) history_default.notifyPageStructureChange({
|
|
8629
|
+
add: addedPages,
|
|
8630
|
+
remove: removedPages
|
|
8609
8631
|
});
|
|
8610
8632
|
}
|
|
8611
8633
|
/**
|
|
@@ -9598,12 +9620,14 @@
|
|
|
9598
9620
|
if (options.skipNumberIndices && (0, _tmagic_utils.isNumber)(name)) continue;
|
|
9599
9621
|
if (!currentFields.length) return {
|
|
9600
9622
|
ok: false,
|
|
9601
|
-
fields: currentFields
|
|
9623
|
+
fields: currentFields,
|
|
9624
|
+
failedName: name
|
|
9602
9625
|
};
|
|
9603
9626
|
field = currentFields.find((item) => item.name === name);
|
|
9604
9627
|
if (!field) return {
|
|
9605
9628
|
ok: false,
|
|
9606
|
-
fields: currentFields
|
|
9629
|
+
fields: currentFields,
|
|
9630
|
+
failedName: name
|
|
9607
9631
|
};
|
|
9608
9632
|
currentFields = field.fields || [];
|
|
9609
9633
|
}
|
|
@@ -10768,7 +10792,7 @@
|
|
|
10768
10792
|
}
|
|
10769
10793
|
return String(value);
|
|
10770
10794
|
};
|
|
10771
|
-
var MAX_SUGGESTION_OPTIONS =
|
|
10795
|
+
var MAX_SUGGESTION_OPTIONS = 20;
|
|
10772
10796
|
/**
|
|
10773
10797
|
* 生成「请使用以下某一个值:xxx;xxx」形式的参考建议;无可选值时返回空字符串(不追加建议)。
|
|
10774
10798
|
*/
|
|
@@ -10929,10 +10953,10 @@
|
|
|
10929
10953
|
fieldNames = path.slice(1);
|
|
10930
10954
|
}
|
|
10931
10955
|
const ds = findDataSource(`${dsId}`);
|
|
10932
|
-
if (!ds) return defaultMessage(options.message,
|
|
10956
|
+
if (!ds) return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
|
|
10933
10957
|
if (!fieldNames.length) return;
|
|
10934
|
-
const { field, ok } = resolveFieldByPath(ds.fields, fieldNames);
|
|
10935
|
-
if (!ok) return defaultMessage(options.message,
|
|
10958
|
+
const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, fieldNames);
|
|
10959
|
+
if (!ok) return defaultMessage(options.message, `数据源字段(${failedName})不存在`, listSuggestion(fields.map((item) => item.name)));
|
|
10936
10960
|
const allowedTypes = options.dataSourceFieldType || ["any"];
|
|
10937
10961
|
if (!allowedTypes.length || allowedTypes.includes("any")) return;
|
|
10938
10962
|
const leafType = field?.type || "any";
|
|
@@ -11015,9 +11039,15 @@
|
|
|
11015
11039
|
if (config.value === "key") return true;
|
|
11016
11040
|
return `${value[0]}`.startsWith(_tmagic_utils.DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
|
|
11017
11041
|
};
|
|
11018
|
-
var validateDataSourceFieldSelect = (value, { message, props }) => {
|
|
11042
|
+
var validateDataSourceFieldSelect = (value, { mForm, message, props }) => {
|
|
11019
11043
|
const config = props.config || {};
|
|
11020
|
-
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return
|
|
11044
|
+
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return (0, _tmagic_form.validateTypeMatch)(value, mForm, {
|
|
11045
|
+
...props,
|
|
11046
|
+
config: {
|
|
11047
|
+
name: config.name,
|
|
11048
|
+
...config.fieldConfig
|
|
11049
|
+
}
|
|
11050
|
+
}, message);
|
|
11021
11051
|
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return defaultMessage(message, `${value}类型应为字符串数组`, dataSourceFieldPathSuggestion());
|
|
11022
11052
|
return validateDataSourceFieldPath(value, {
|
|
11023
11053
|
dataSourceId: config.dataSourceId,
|
|
@@ -14795,7 +14825,6 @@
|
|
|
14795
14825
|
//#endregion
|
|
14796
14826
|
//#region packages/editor/src/layouts/props-panel/FormPanel.vue?vue&type=script&setup=true&lang.ts
|
|
14797
14827
|
init_Icon();
|
|
14798
|
-
init_props();
|
|
14799
14828
|
init_CodeEditor();
|
|
14800
14829
|
var _hoisted_1$59 = { class: "m-editor-props-form-panel" };
|
|
14801
14830
|
var FormPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.defineComponent)({
|
|
@@ -14869,14 +14898,18 @@
|
|
|
14869
14898
|
return;
|
|
14870
14899
|
}
|
|
14871
14900
|
try {
|
|
14872
|
-
const error = await
|
|
14901
|
+
const error = await (0, _tmagic_form.validateForm)({
|
|
14873
14902
|
config: props.config,
|
|
14874
|
-
values: newValues,
|
|
14875
|
-
appContext: internalInstance?.appContext ?? null,
|
|
14876
|
-
services,
|
|
14877
|
-
stage: stage.value,
|
|
14878
14903
|
typeMatchValid: true,
|
|
14879
|
-
|
|
14904
|
+
initValues: newValues,
|
|
14905
|
+
appContext: internalInstance?.appContext ? {
|
|
14906
|
+
...internalInstance?.appContext,
|
|
14907
|
+
provides: { services }
|
|
14908
|
+
} : null,
|
|
14909
|
+
extendState: (state) => {
|
|
14910
|
+
if (configFormRef.value?.formState) return { ...configFormRef.value?.formState || {} };
|
|
14911
|
+
return props.extendState?.(state) || {};
|
|
14912
|
+
}
|
|
14880
14913
|
});
|
|
14881
14914
|
if (error) (0, _tmagic_design.tMagicMessage)({
|
|
14882
14915
|
type: "error",
|
|
@@ -15055,14 +15088,14 @@
|
|
|
15055
15088
|
...v,
|
|
15056
15089
|
style: {}
|
|
15057
15090
|
};
|
|
15058
|
-
if (v.style) {
|
|
15091
|
+
if (v.style) if (eventData) {
|
|
15059
15092
|
Object.entries(v.style).forEach(([key, value]) => {
|
|
15060
15093
|
if (value !== "" && newValue.style) newValue.style[key] = value;
|
|
15061
15094
|
});
|
|
15062
|
-
eventData
|
|
15095
|
+
eventData.changeRecords?.forEach((record) => {
|
|
15063
15096
|
if (record.propPath?.startsWith("style") && record.value === "") (0, _tmagic_utils.setValueByKeyPath)(record.propPath, record.value, newValue);
|
|
15064
15097
|
});
|
|
15065
|
-
}
|
|
15098
|
+
} else newValue.style = { ...v.style };
|
|
15066
15099
|
const historySource = eventData ? "props" : "code";
|
|
15067
15100
|
editorService.update(newValue, {
|
|
15068
15101
|
changeRecords: eventData?.changeRecords,
|
|
@@ -15700,15 +15733,29 @@
|
|
|
15700
15733
|
if (contains(target)) return;
|
|
15701
15734
|
hide();
|
|
15702
15735
|
};
|
|
15736
|
+
const fixPosition = () => {
|
|
15737
|
+
const menu = menuEl.value;
|
|
15738
|
+
if (!menu || !visible.value) return;
|
|
15739
|
+
const menuHeight = menu.clientHeight;
|
|
15740
|
+
const menuWidth = menu.clientWidth;
|
|
15741
|
+
let { top, left } = menuPosition.value;
|
|
15742
|
+
if (top + menuHeight > document.body.clientHeight) top = Math.max(0, document.body.clientHeight - menuHeight);
|
|
15743
|
+
if (left + menuWidth > document.body.clientWidth) left = Math.max(0, document.body.clientWidth - menuWidth);
|
|
15744
|
+
if (top !== menuPosition.value.top || left !== menuPosition.value.left) menuPosition.value = {
|
|
15745
|
+
top,
|
|
15746
|
+
left
|
|
15747
|
+
};
|
|
15748
|
+
};
|
|
15703
15749
|
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
15750
|
menuPosition.value = {
|
|
15708
|
-
top,
|
|
15751
|
+
top: e.clientY,
|
|
15709
15752
|
left: e.clientX
|
|
15710
15753
|
};
|
|
15754
|
+
fixPosition();
|
|
15711
15755
|
};
|
|
15756
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
15757
|
+
fixPosition();
|
|
15758
|
+
});
|
|
15712
15759
|
const show = (e) => {
|
|
15713
15760
|
visible.value = true;
|
|
15714
15761
|
(0, vue.nextTick)(() => {
|
|
@@ -15737,10 +15784,12 @@
|
|
|
15737
15784
|
emit("mouseenter");
|
|
15738
15785
|
};
|
|
15739
15786
|
(0, vue.onMounted)(() => {
|
|
15787
|
+
if (menuEl.value) resizeObserver.observe(menuEl.value);
|
|
15740
15788
|
if (props.isSubMenu) return;
|
|
15741
15789
|
globalThis.addEventListener("mousedown", outsideClickHideHandler, true);
|
|
15742
15790
|
});
|
|
15743
15791
|
(0, vue.onBeforeUnmount)(() => {
|
|
15792
|
+
resizeObserver.disconnect();
|
|
15744
15793
|
if (props.isSubMenu) return;
|
|
15745
15794
|
globalThis.removeEventListener("mousedown", outsideClickHideHandler, true);
|
|
15746
15795
|
});
|
|
@@ -25323,6 +25372,12 @@
|
|
|
25323
25372
|
},
|
|
25324
25373
|
emits: ["change"],
|
|
25325
25374
|
setup(__props, { emit: __emit }) {
|
|
25375
|
+
/**
|
|
25376
|
+
* 静默模式(submitForm/validateForm 隐藏挂载)下跳过 monaco 渲染:
|
|
25377
|
+
* 校验由 FormItem 针对 model 中的值完成,与编辑器实例无关;本组件挂载无值副作用
|
|
25378
|
+
* (save 仅由用户操作/编辑器内容变更触发),跳过可省去 monaco worker/model 的无谓实例化。
|
|
25379
|
+
*/
|
|
25380
|
+
const silentMode = (0, vue.inject)(_tmagic_form.FORM_SILENT_MODE_KEY, false);
|
|
25326
25381
|
const emit = __emit;
|
|
25327
25382
|
const props = __props;
|
|
25328
25383
|
/**
|
|
@@ -25340,7 +25395,8 @@
|
|
|
25340
25395
|
emit("change", v);
|
|
25341
25396
|
};
|
|
25342
25397
|
return (_ctx, _cache) => {
|
|
25343
|
-
return (0, vue.openBlock)(), (0, vue.createBlock)(CodeEditor_default, {
|
|
25398
|
+
return !(0, vue.unref)(silentMode) ? ((0, vue.openBlock)(), (0, vue.createBlock)(CodeEditor_default, {
|
|
25399
|
+
key: 0,
|
|
25344
25400
|
height: __props.config.height,
|
|
25345
25401
|
type: diffMode.value ? "diff" : void 0,
|
|
25346
25402
|
"init-values": diffMode.value ? (__props.lastValues || {})[__props.name] : __props.model[__props.name],
|
|
@@ -25364,7 +25420,7 @@
|
|
|
25364
25420
|
"autosize",
|
|
25365
25421
|
"parse",
|
|
25366
25422
|
"editor-custom-type"
|
|
25367
|
-
]);
|
|
25423
|
+
])) : (0, vue.createCommentVNode)("v-if", true);
|
|
25368
25424
|
};
|
|
25369
25425
|
}
|
|
25370
25426
|
});
|
|
@@ -25862,7 +25918,6 @@
|
|
|
25862
25918
|
exports.useServices = useServices;
|
|
25863
25919
|
exports.useStage = useStage;
|
|
25864
25920
|
exports.useWindowRect = useWindowRect;
|
|
25865
|
-
exports.validatePropsForm = validatePropsForm;
|
|
25866
25921
|
exports.warn = warn;
|
|
25867
25922
|
Object.keys(_tmagic_form).forEach(function(k) {
|
|
25868
25923
|
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-beta.
|
|
2
|
+
"version": "1.8.0-beta.16",
|
|
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/
|
|
63
|
-
"@tmagic/
|
|
64
|
-
"@tmagic/
|
|
65
|
-
"@tmagic/
|
|
61
|
+
"@tmagic/design": "1.8.0-beta.16",
|
|
62
|
+
"@tmagic/stage": "1.8.0-beta.16",
|
|
63
|
+
"@tmagic/form": "1.8.0-beta.16",
|
|
64
|
+
"@tmagic/utils": "1.8.0-beta.16",
|
|
65
|
+
"@tmagic/table": "1.8.0-beta.16"
|
|
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.16"
|
|
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
|
}>();
|
|
@@ -49,14 +49,13 @@ import { Document as DocumentIcon } from '@element-plus/icons-vue';
|
|
|
49
49
|
|
|
50
50
|
import { TMagicButton, tMagicMessage, TMagicScrollbar } from '@tmagic/design';
|
|
51
51
|
import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from '@tmagic/form';
|
|
52
|
-
import { MForm } from '@tmagic/form';
|
|
52
|
+
import { MForm, validateForm } from '@tmagic/form';
|
|
53
53
|
import { filterXSS } from '@tmagic/utils';
|
|
54
54
|
|
|
55
55
|
import MIcon from '@editor/components/Icon.vue';
|
|
56
56
|
import { ENABLE_PROPS_FORM_VALIDATE } from '@editor/editorProps';
|
|
57
57
|
import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
|
|
58
58
|
import { useServices } from '@editor/hooks/use-services';
|
|
59
|
-
import { validatePropsForm } from '@editor/utils/props';
|
|
60
59
|
|
|
61
60
|
import CodeEditor from '../CodeEditor.vue';
|
|
62
61
|
|
|
@@ -162,14 +161,19 @@ const saveCode = async (values: any) => {
|
|
|
162
161
|
// 做一次静默校验(不复用、也不污染页面上正在展示的表单),并将校验结果(错误信息)随提交
|
|
163
162
|
// 一并抛给上层记录,使源码保存的错误状态与表单编辑保持一致。
|
|
164
163
|
try {
|
|
165
|
-
const error = await
|
|
164
|
+
const error = await validateForm({
|
|
166
165
|
config: props.config,
|
|
167
|
-
values: newValues,
|
|
168
|
-
appContext: internalInstance?.appContext ?? null,
|
|
169
|
-
services,
|
|
170
|
-
stage: stage.value,
|
|
171
166
|
typeMatchValid: true,
|
|
172
|
-
|
|
167
|
+
initValues: newValues,
|
|
168
|
+
// 将当前组件实例的 provides(含 Editor 顶层的 services / codeOptions 等组件级 provide)
|
|
169
|
+
// 合入 appContext,使临时 MForm 中的编辑器字段组件(DataSourceInput 等)能正常 inject
|
|
170
|
+
appContext: internalInstance?.appContext ? { ...internalInstance?.appContext, provides: { services } } : null,
|
|
171
|
+
extendState: (state) => {
|
|
172
|
+
if (configFormRef.value?.formState) {
|
|
173
|
+
return { ...(configFormRef.value?.formState || {}) };
|
|
174
|
+
}
|
|
175
|
+
return props.extendState?.(state) || {};
|
|
176
|
+
},
|
|
173
177
|
});
|
|
174
178
|
|
|
175
179
|
if (error) {
|
|
@@ -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
|
}
|