@tmagic/editor 1.8.0 → 1.8.2
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/Editor.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/editorProps.js +3 -0
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +4 -7
- package/dist/es/fields/configs/displayConds.js +3 -6
- package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +8 -6
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/utils/data-source/index.js +40 -6
- package/dist/es/utils/type-match-rules.js +15 -7
- package/dist/tmagic-editor-headless.umd.cjs +55 -16
- package/dist/tmagic-editor.umd.cjs +72 -32
- package/package.json +7 -7
- package/src/Editor.vue +6 -1
- package/src/editorProps.ts +5 -2
- package/src/fields/DataSourceFieldSelect/Index.vue +11 -11
- package/src/fields/configs/displayConds.ts +9 -9
- package/src/layouts/props-panel/FormPanel.vue +8 -5
- package/src/layouts/props-panel/PropsPanel.vue +7 -2
- package/src/utils/data-source/index.ts +62 -10
- package/src/utils/type-match-rules.ts +45 -9
- package/types/index.d.ts +10 -2
|
@@ -136,7 +136,7 @@ var Editor_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
|
|
|
136
136
|
provide("codeOptions", props.codeOptions);
|
|
137
137
|
provide("stageOptions", stageOptions);
|
|
138
138
|
/** 是否启用「属性配置表单校验」联动能力,供 PropsPanel / FormPanel 判断校验失败时是否仍更新节点并记录错误 */
|
|
139
|
-
provide(ENABLE_PROPS_FORM_VALIDATE, props.enablePropsFormValidate ?? false);
|
|
139
|
+
provide(ENABLE_PROPS_FORM_VALIDATE, computed(() => props.enablePropsFormValidate ?? false));
|
|
140
140
|
/**
|
|
141
141
|
* 编辑器注入给整棵表单树的业务上下文(`services` / `stage`),属性面板、对比表单、
|
|
142
142
|
* 侧边栏里的嵌套 MForm 都通过 inject 自动继承。
|
package/dist/es/editorProps.js
CHANGED
|
@@ -4,6 +4,9 @@ import { getIdFromEl } from "@tmagic/utils";
|
|
|
4
4
|
/**
|
|
5
5
|
* 「属性配置表单校验」联动能力的 provide/inject 注入键。
|
|
6
6
|
* 使用 Symbol 避免与其它字符串键冲突,供 PropsPanel / FormPanel 注入判断校验失败时是否仍更新节点并记录错误。
|
|
7
|
+
*
|
|
8
|
+
* 注入值是 ComputedRef:Editor 用 `computed(() => props.enablePropsFormValidate ?? false)` 提供,
|
|
9
|
+
* 保证外部动态切换该 prop 时子孙组件能同步拿到最新值;子孙侧用 `.value` 读取。
|
|
7
10
|
*/
|
|
8
11
|
var ENABLE_PROPS_FORM_VALIDATE = Symbol("enablePropsFormValidate");
|
|
9
12
|
var defaultEditorProps = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useServices } from "../../hooks/use-services.js";
|
|
2
|
+
import { resolveFieldByPath } from "../../utils/data-source/index.js";
|
|
2
3
|
import Icon_default from "../../components/Icon.js";
|
|
3
4
|
import DatasourceIcon_default from "../../icons/DatasourceIcon.js";
|
|
4
5
|
import FieldSelect_default from "./FieldSelect.js";
|
|
@@ -86,15 +87,11 @@ var Index_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComp
|
|
|
86
87
|
emit("change", value, eventData);
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
|
-
|
|
90
|
-
let field;
|
|
91
|
-
(keys || []).forEach((key) => {
|
|
92
|
-
field = fields.find((f) => f.name === key);
|
|
93
|
-
fields = field?.fields || [];
|
|
94
|
-
});
|
|
90
|
+
const { field, ok, untypedArrayElement } = resolveFieldByPath(dataSource.fields, keys, { allowArrayIndex: true });
|
|
95
91
|
const dataSourceFieldType = props.config.dataSourceFieldType || ["any"];
|
|
96
92
|
if (!dataSourceFieldType.length) dataSourceFieldType.push("any");
|
|
97
|
-
|
|
93
|
+
const typeMatches = ok && !!field?.type && (dataSourceFieldType.includes("any") || !untypedArrayElement && (field.type === "any" || dataSourceFieldType.includes(field.type)));
|
|
94
|
+
if (!keys.length || typeMatches) emit("change", value, eventData);
|
|
98
95
|
else {
|
|
99
96
|
tMagicMessage.error(`请选择类型为${dataSourceFieldType.join("或")}的字段`);
|
|
100
97
|
emit("change", [dsId], eventData);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCascaderOptionsFromFields, getFieldType } from "../../utils/data-source/index.js";
|
|
1
|
+
import { getCascaderOptionsFromFields, getFieldType, resolveFieldByPath } from "../../utils/data-source/index.js";
|
|
2
2
|
import dataSource_default from "../../services/dataSource.js";
|
|
3
3
|
import { stickyAddButton } from "./stickyAddButton.js";
|
|
4
4
|
import { removeDataSourceFieldPrefix } from "@tmagic/utils";
|
|
@@ -34,11 +34,8 @@ var createDisplayCondsConfig = (config, name, parentFields) => {
|
|
|
34
34
|
options: () => {
|
|
35
35
|
const { ds, fieldNames } = resolveFieldPath(parentFields);
|
|
36
36
|
if (!ds) return [];
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
fields = fields.find((f) => f.name === key)?.fields || [];
|
|
40
|
-
});
|
|
41
|
-
return getCascaderOptionsFromFields(fields, [
|
|
37
|
+
const resolved = resolveFieldByPath(ds.fields, fieldNames, { allowArrayIndex: true });
|
|
38
|
+
return getCascaderOptionsFromFields(resolved.ok ? resolved.fields : [], [
|
|
42
39
|
"string",
|
|
43
40
|
"number",
|
|
44
41
|
"boolean",
|
|
@@ -32,7 +32,7 @@ var FormPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
32
32
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
33
33
|
const props = __props;
|
|
34
34
|
const emit = __emit;
|
|
35
|
-
const enablePropsFormValidate = inject(ENABLE_PROPS_FORM_VALIDATE, false);
|
|
35
|
+
const enablePropsFormValidate = inject(ENABLE_PROPS_FORM_VALIDATE, computed(() => false));
|
|
36
36
|
const services = useServices();
|
|
37
37
|
const { uiService } = services;
|
|
38
38
|
const codeOptions = inject("codeOptions", {});
|
|
@@ -53,7 +53,7 @@ var FormPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
53
53
|
const values = await configFormRef.value?.submitForm();
|
|
54
54
|
emit("submit", values, eventData);
|
|
55
55
|
} catch (e) {
|
|
56
|
-
if (enablePropsFormValidate) emit("submit", v, eventData, e);
|
|
56
|
+
if (enablePropsFormValidate.value) emit("submit", v, eventData, e);
|
|
57
57
|
else emit("submit-error", e);
|
|
58
58
|
}
|
|
59
59
|
};
|
|
@@ -70,7 +70,7 @@ var FormPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
70
70
|
const formatValidateErrorHtml = (error) => error.split(/<br\s*\/?>/i).map((segment) => filterXSS(segment).replaceAll("\n", "<br>")).join("<br>");
|
|
71
71
|
const saveCode = async (values) => {
|
|
72
72
|
const newValues = props.codeValueKey ? { [props.codeValueKey]: values } : values;
|
|
73
|
-
if (!enablePropsFormValidate) {
|
|
73
|
+
if (!enablePropsFormValidate.value) {
|
|
74
74
|
emit("submit", newValues);
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
@@ -109,9 +109,9 @@ var FormPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
109
109
|
size: propsPanelSize.value,
|
|
110
110
|
"init-values": __props.values,
|
|
111
111
|
config: __props.config,
|
|
112
|
-
"type-match-valid":
|
|
112
|
+
"type-match-valid": unref(enablePropsFormValidate),
|
|
113
113
|
context: unref(formContext),
|
|
114
|
-
"validate-on-init":
|
|
114
|
+
"validate-on-init": unref(enablePropsFormValidate),
|
|
115
115
|
onChange: submit,
|
|
116
116
|
onError: errorHandler
|
|
117
117
|
}, null, 8, [
|
|
@@ -122,7 +122,9 @@ var FormPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
122
122
|
"size",
|
|
123
123
|
"init-values",
|
|
124
124
|
"config",
|
|
125
|
-
"
|
|
125
|
+
"type-match-valid",
|
|
126
|
+
"context",
|
|
127
|
+
"validate-on-init"
|
|
126
128
|
])]),
|
|
127
129
|
_: 1
|
|
128
130
|
}),
|
|
@@ -30,7 +30,7 @@ var PropsPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
30
30
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
31
31
|
const emit = __emit;
|
|
32
32
|
const { editorService, uiService, propsService, storageService } = useServices();
|
|
33
|
-
const enablePropsFormValidate = inject(ENABLE_PROPS_FORM_VALIDATE, false);
|
|
33
|
+
const enablePropsFormValidate = inject(ENABLE_PROPS_FORM_VALIDATE, computed(() => false));
|
|
34
34
|
const values = ref({});
|
|
35
35
|
const curFormConfig = ref([]);
|
|
36
36
|
const node = computed(() => editorService.get("node"));
|
|
@@ -93,7 +93,7 @@ var PropsPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
93
93
|
changeRecords: eventData?.changeRecords,
|
|
94
94
|
historySource,
|
|
95
95
|
replace,
|
|
96
|
-
...enablePropsFormValidate && error ? { invalidInfo: {
|
|
96
|
+
...enablePropsFormValidate.value && error ? { invalidInfo: {
|
|
97
97
|
id: newValue.id,
|
|
98
98
|
source,
|
|
99
99
|
error: error?.message
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import base_default from "./formConfigs/base.js";
|
|
2
2
|
import http_default from "./formConfigs/http.js";
|
|
3
|
-
import { dataSourceTemplateRegExp, getKeysArray, isNumber } from "@tmagic/utils";
|
|
3
|
+
import { dataSourceTemplateRegExp, getKeysArray, isArrayIndex, isNumber } from "@tmagic/utils";
|
|
4
4
|
//#region packages/editor/src/utils/data-source/index.ts
|
|
5
5
|
var dataSourceFormConfig = {
|
|
6
6
|
type: "tab",
|
|
@@ -192,25 +192,59 @@ var getCascaderOptionsFromFields = (fields = [], dataSourceFieldType = ["any"])
|
|
|
192
192
|
/**
|
|
193
193
|
* 按字段名路径下钻 DataSchema。
|
|
194
194
|
* @param skipNumberIndices 为 true 时跳过数字段(模板路径中的数组下标,如 arr[0].x)
|
|
195
|
+
* @param allowArrayIndex 为 true 时识别数组下标。`number` 只能是下标;数字字符串先按当前层字段名匹配,没有同名字段时才当中下标
|
|
195
196
|
*/
|
|
196
197
|
var resolveFieldByPath = (fields, fieldNames, options = {}) => {
|
|
197
198
|
let currentFields = fields || [];
|
|
198
199
|
let field;
|
|
200
|
+
/** 刚消费了数组下标,路径若在此结束则当前值是数组元素 */
|
|
201
|
+
let atArrayElement = false;
|
|
202
|
+
let arrayIndexName = "";
|
|
199
203
|
for (const name of fieldNames) {
|
|
200
204
|
if (options.skipNumberIndices && isNumber(name)) continue;
|
|
205
|
+
const segment = `${name}`;
|
|
206
|
+
if (options.allowArrayIndex && isArrayIndex(name) && field?.type === "array" && !atArrayElement) {
|
|
207
|
+
if (!(typeof name === "string" ? currentFields.find((item) => item.name === segment) : void 0)) {
|
|
208
|
+
atArrayElement = true;
|
|
209
|
+
arrayIndexName = segment;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (options.allowArrayIndex && typeof name === "number") return {
|
|
214
|
+
ok: false,
|
|
215
|
+
fields: currentFields,
|
|
216
|
+
failedName: segment,
|
|
217
|
+
invalidArrayIndex: true
|
|
218
|
+
};
|
|
219
|
+
atArrayElement = false;
|
|
201
220
|
if (!currentFields.length) return {
|
|
202
221
|
ok: false,
|
|
203
222
|
fields: currentFields,
|
|
204
|
-
failedName:
|
|
223
|
+
failedName: segment
|
|
205
224
|
};
|
|
206
|
-
field = currentFields.find((item) => item.name ===
|
|
225
|
+
field = currentFields.find((item) => item.name === segment);
|
|
207
226
|
if (!field) return {
|
|
208
227
|
ok: false,
|
|
209
228
|
fields: currentFields,
|
|
210
|
-
failedName:
|
|
229
|
+
failedName: segment
|
|
211
230
|
};
|
|
212
231
|
currentFields = field.fields || [];
|
|
213
232
|
}
|
|
233
|
+
if (atArrayElement && field?.type === "array") {
|
|
234
|
+
const elementFields = field.fields || [];
|
|
235
|
+
const untypedArrayElement = elementFields.length === 0;
|
|
236
|
+
return {
|
|
237
|
+
ok: true,
|
|
238
|
+
arrayElement: true,
|
|
239
|
+
untypedArrayElement,
|
|
240
|
+
field: {
|
|
241
|
+
name: arrayIndexName,
|
|
242
|
+
type: untypedArrayElement ? "any" : "object",
|
|
243
|
+
fields: elementFields
|
|
244
|
+
},
|
|
245
|
+
fields: elementFields
|
|
246
|
+
};
|
|
247
|
+
}
|
|
214
248
|
return {
|
|
215
249
|
field,
|
|
216
250
|
ok: true,
|
|
@@ -218,8 +252,8 @@ var resolveFieldByPath = (fields, fieldNames, options = {}) => {
|
|
|
218
252
|
};
|
|
219
253
|
};
|
|
220
254
|
var getFieldType = (ds, fieldNames) => {
|
|
221
|
-
const { ok, field } = resolveFieldByPath(ds?.fields, fieldNames);
|
|
222
|
-
if (!ok) return "";
|
|
255
|
+
const { ok, field, untypedArrayElement } = resolveFieldByPath(ds?.fields, fieldNames, { allowArrayIndex: true });
|
|
256
|
+
if (!ok || untypedArrayElement) return "";
|
|
223
257
|
return field?.type || "";
|
|
224
258
|
};
|
|
225
259
|
//#endregion
|
|
@@ -3,7 +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 { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, DATA_SOURCE_SET_DATA_METHOD_NAME, dataSourceTemplateRegExp, getKeysArray, removeDataSourceFieldPrefix } from "@tmagic/utils";
|
|
6
|
+
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, DATA_SOURCE_SET_DATA_METHOD_NAME, dataSourceTemplateRegExp, getKeysArray, isArrayIndex, removeDataSourceFieldPrefix } from "@tmagic/utils";
|
|
7
7
|
import { NodeType } from "@tmagic/core";
|
|
8
8
|
import { appendValidateSuggestion } from "@tmagic/design/headless";
|
|
9
9
|
import { MAX_SUGGESTION_OPTIONS, optionSuggestion, stringifyExampleValue, validateTypeMatch } from "@tmagic/form/headless";
|
|
@@ -157,14 +157,18 @@ var validateDataSourceFieldPath = (path, options = {}) => {
|
|
|
157
157
|
const ds = findDataSource(`${dsId}`);
|
|
158
158
|
if (!ds) return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
|
|
159
159
|
if (!path.length) return;
|
|
160
|
-
const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, path);
|
|
161
|
-
if (!ok)
|
|
160
|
+
const { field, ok, fields, failedName, invalidArrayIndex, arrayElement, untypedArrayElement } = resolveFieldByPath(ds.fields, path, { allowArrayIndex: true });
|
|
161
|
+
if (!ok) {
|
|
162
|
+
if (invalidArrayIndex) return defaultMessage(options.message, `数组下标(${failedName})只能接在数组字段后面`);
|
|
163
|
+
return defaultMessage(options.message, `数据源字段(${failedName})不存在`, listSuggestion(fields.map((item) => item.name)));
|
|
164
|
+
}
|
|
162
165
|
const allowedTypes = options.dataSourceFieldType || ["any"];
|
|
163
166
|
if (!allowedTypes.length || allowedTypes.includes("any")) return;
|
|
167
|
+
if (untypedArrayElement) return defaultMessage(options.message, `数组下标(${field?.name ?? path[path.length - 1]})的元素类型未定义,请选择具体字段`);
|
|
164
168
|
const leafType = field?.type || "any";
|
|
165
169
|
if (leafType !== "any" && !allowedTypes.includes(leafType)) {
|
|
166
170
|
const fieldName = field?.name || path[path.length - 1];
|
|
167
|
-
return defaultMessage(options.message, `请选择类型为${allowedTypes.join("或")}的字段,字段(${fieldName})的类型为${leafType}`);
|
|
171
|
+
return defaultMessage(options.message, arrayElement ? `请选择类型为${allowedTypes.join("或")}的字段,数组元素的类型为${leafType}` : `请选择类型为${allowedTypes.join("或")}的字段,字段(${fieldName})的类型为${leafType}`);
|
|
168
172
|
}
|
|
169
173
|
};
|
|
170
174
|
var validateDataSourceMethodTuple = (value, message) => {
|
|
@@ -238,8 +242,10 @@ var validateDataSourceInput = (value, { message }) => {
|
|
|
238
242
|
return validateDataSourceTemplateBindings(value, message);
|
|
239
243
|
};
|
|
240
244
|
var validateDataSourceMethodSelect = (value, { message }) => validateDataSourceMethodTuple(value, message);
|
|
245
|
+
/** 字段名,或非负整数下标(number)。数字字符串本身是 string,走字段名分支。 */
|
|
246
|
+
var isDataSourceFieldPathSegment = (item) => typeof item === "string" || typeof item === "number" && isArrayIndex(item);
|
|
241
247
|
var isDataSourceFieldPathValue = (value, config) => {
|
|
242
|
-
if (!Array.isArray(value) || !value.length || value.some((item) =>
|
|
248
|
+
if (!Array.isArray(value) || !value.length || value.some((item) => !isDataSourceFieldPathSegment(item))) return false;
|
|
243
249
|
if (config.dataSourceId) return true;
|
|
244
250
|
if (config.value === "key") return true;
|
|
245
251
|
return `${value[0]}`.startsWith(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
|
|
@@ -260,11 +266,13 @@ var validateDataSourceFieldSelect = (value, context, options) => {
|
|
|
260
266
|
}
|
|
261
267
|
}, message);
|
|
262
268
|
}
|
|
263
|
-
if (!Array.isArray(value) || value.some((item) =>
|
|
269
|
+
if (!Array.isArray(value) || value.some((item) => !isDataSourceFieldPathSegment(item))) return defaultMessage(message, `${value}类型应为字符串数组,数组字段后可接数字下标`, dataSourceFieldPathSuggestion());
|
|
264
270
|
let dataSourceId = config.dataSourceId ? `${config.dataSourceId}` : void 0;
|
|
265
271
|
let fieldNames = value;
|
|
266
272
|
if (!dataSourceId) {
|
|
267
|
-
|
|
273
|
+
const dsId = value[0];
|
|
274
|
+
if (value.length && typeof dsId !== "string") return defaultMessage(message, `${value}类型应为字符串数组,数组字段后可接数字下标`, dataSourceFieldPathSuggestion());
|
|
275
|
+
dataSourceId = value.length ? removeDataSourceFieldPrefix(dsId) : void 0;
|
|
268
276
|
fieldNames = value.slice(1);
|
|
269
277
|
}
|
|
270
278
|
return validateDataSourceFieldPath(fieldNames, {
|
|
@@ -8023,25 +8023,59 @@
|
|
|
8023
8023
|
/**
|
|
8024
8024
|
* 按字段名路径下钻 DataSchema。
|
|
8025
8025
|
* @param skipNumberIndices 为 true 时跳过数字段(模板路径中的数组下标,如 arr[0].x)
|
|
8026
|
+
* @param allowArrayIndex 为 true 时识别数组下标。`number` 只能是下标;数字字符串先按当前层字段名匹配,没有同名字段时才当中下标
|
|
8026
8027
|
*/
|
|
8027
8028
|
var resolveFieldByPath = (fields, fieldNames, options = {}) => {
|
|
8028
8029
|
let currentFields = fields || [];
|
|
8029
8030
|
let field;
|
|
8031
|
+
/** 刚消费了数组下标,路径若在此结束则当前值是数组元素 */
|
|
8032
|
+
let atArrayElement = false;
|
|
8033
|
+
let arrayIndexName = "";
|
|
8030
8034
|
for (const name of fieldNames) {
|
|
8031
8035
|
if (options.skipNumberIndices && (0, _tmagic_utils.isNumber)(name)) continue;
|
|
8036
|
+
const segment = `${name}`;
|
|
8037
|
+
if (options.allowArrayIndex && (0, _tmagic_utils.isArrayIndex)(name) && field?.type === "array" && !atArrayElement) {
|
|
8038
|
+
if (!(typeof name === "string" ? currentFields.find((item) => item.name === segment) : void 0)) {
|
|
8039
|
+
atArrayElement = true;
|
|
8040
|
+
arrayIndexName = segment;
|
|
8041
|
+
continue;
|
|
8042
|
+
}
|
|
8043
|
+
}
|
|
8044
|
+
if (options.allowArrayIndex && typeof name === "number") return {
|
|
8045
|
+
ok: false,
|
|
8046
|
+
fields: currentFields,
|
|
8047
|
+
failedName: segment,
|
|
8048
|
+
invalidArrayIndex: true
|
|
8049
|
+
};
|
|
8050
|
+
atArrayElement = false;
|
|
8032
8051
|
if (!currentFields.length) return {
|
|
8033
8052
|
ok: false,
|
|
8034
8053
|
fields: currentFields,
|
|
8035
|
-
failedName:
|
|
8054
|
+
failedName: segment
|
|
8036
8055
|
};
|
|
8037
|
-
field = currentFields.find((item) => item.name ===
|
|
8056
|
+
field = currentFields.find((item) => item.name === segment);
|
|
8038
8057
|
if (!field) return {
|
|
8039
8058
|
ok: false,
|
|
8040
8059
|
fields: currentFields,
|
|
8041
|
-
failedName:
|
|
8060
|
+
failedName: segment
|
|
8042
8061
|
};
|
|
8043
8062
|
currentFields = field.fields || [];
|
|
8044
8063
|
}
|
|
8064
|
+
if (atArrayElement && field?.type === "array") {
|
|
8065
|
+
const elementFields = field.fields || [];
|
|
8066
|
+
const untypedArrayElement = elementFields.length === 0;
|
|
8067
|
+
return {
|
|
8068
|
+
ok: true,
|
|
8069
|
+
arrayElement: true,
|
|
8070
|
+
untypedArrayElement,
|
|
8071
|
+
field: {
|
|
8072
|
+
name: arrayIndexName,
|
|
8073
|
+
type: untypedArrayElement ? "any" : "object",
|
|
8074
|
+
fields: elementFields
|
|
8075
|
+
},
|
|
8076
|
+
fields: elementFields
|
|
8077
|
+
};
|
|
8078
|
+
}
|
|
8045
8079
|
return {
|
|
8046
8080
|
field,
|
|
8047
8081
|
ok: true,
|
|
@@ -8049,8 +8083,8 @@
|
|
|
8049
8083
|
};
|
|
8050
8084
|
};
|
|
8051
8085
|
var getFieldType = (ds, fieldNames) => {
|
|
8052
|
-
const { ok, field } = resolveFieldByPath(ds?.fields, fieldNames);
|
|
8053
|
-
if (!ok) return "";
|
|
8086
|
+
const { ok, field, untypedArrayElement } = resolveFieldByPath(ds?.fields, fieldNames, { allowArrayIndex: true });
|
|
8087
|
+
if (!ok || untypedArrayElement) return "";
|
|
8054
8088
|
return field?.type || "";
|
|
8055
8089
|
};
|
|
8056
8090
|
//#endregion
|
|
@@ -8628,14 +8662,18 @@
|
|
|
8628
8662
|
const ds = findDataSource(`${dsId}`);
|
|
8629
8663
|
if (!ds) return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
|
|
8630
8664
|
if (!path.length) return;
|
|
8631
|
-
const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, path);
|
|
8632
|
-
if (!ok)
|
|
8665
|
+
const { field, ok, fields, failedName, invalidArrayIndex, arrayElement, untypedArrayElement } = resolveFieldByPath(ds.fields, path, { allowArrayIndex: true });
|
|
8666
|
+
if (!ok) {
|
|
8667
|
+
if (invalidArrayIndex) return defaultMessage(options.message, `数组下标(${failedName})只能接在数组字段后面`);
|
|
8668
|
+
return defaultMessage(options.message, `数据源字段(${failedName})不存在`, listSuggestion(fields.map((item) => item.name)));
|
|
8669
|
+
}
|
|
8633
8670
|
const allowedTypes = options.dataSourceFieldType || ["any"];
|
|
8634
8671
|
if (!allowedTypes.length || allowedTypes.includes("any")) return;
|
|
8672
|
+
if (untypedArrayElement) return defaultMessage(options.message, `数组下标(${field?.name ?? path[path.length - 1]})的元素类型未定义,请选择具体字段`);
|
|
8635
8673
|
const leafType = field?.type || "any";
|
|
8636
8674
|
if (leafType !== "any" && !allowedTypes.includes(leafType)) {
|
|
8637
8675
|
const fieldName = field?.name || path[path.length - 1];
|
|
8638
|
-
return defaultMessage(options.message, `请选择类型为${allowedTypes.join("或")}的字段,字段(${fieldName})的类型为${leafType}`);
|
|
8676
|
+
return defaultMessage(options.message, arrayElement ? `请选择类型为${allowedTypes.join("或")}的字段,数组元素的类型为${leafType}` : `请选择类型为${allowedTypes.join("或")}的字段,字段(${fieldName})的类型为${leafType}`);
|
|
8639
8677
|
}
|
|
8640
8678
|
};
|
|
8641
8679
|
var validateDataSourceMethodTuple = (value, message) => {
|
|
@@ -8708,8 +8746,10 @@
|
|
|
8708
8746
|
return validateDataSourceTemplateBindings(value, message);
|
|
8709
8747
|
};
|
|
8710
8748
|
var validateDataSourceMethodSelect = (value, { message }) => validateDataSourceMethodTuple(value, message);
|
|
8749
|
+
/** 字段名,或非负整数下标(number)。数字字符串本身是 string,走字段名分支。 */
|
|
8750
|
+
var isDataSourceFieldPathSegment = (item) => typeof item === "string" || typeof item === "number" && (0, _tmagic_utils.isArrayIndex)(item);
|
|
8711
8751
|
var isDataSourceFieldPathValue = (value, config) => {
|
|
8712
|
-
if (!Array.isArray(value) || !value.length || value.some((item) =>
|
|
8752
|
+
if (!Array.isArray(value) || !value.length || value.some((item) => !isDataSourceFieldPathSegment(item))) return false;
|
|
8713
8753
|
if (config.dataSourceId) return true;
|
|
8714
8754
|
if (config.value === "key") return true;
|
|
8715
8755
|
return `${value[0]}`.startsWith(_tmagic_utils.DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
|
|
@@ -8730,11 +8770,13 @@
|
|
|
8730
8770
|
}
|
|
8731
8771
|
}, message);
|
|
8732
8772
|
}
|
|
8733
|
-
if (!Array.isArray(value) || value.some((item) =>
|
|
8773
|
+
if (!Array.isArray(value) || value.some((item) => !isDataSourceFieldPathSegment(item))) return defaultMessage(message, `${value}类型应为字符串数组,数组字段后可接数字下标`, dataSourceFieldPathSuggestion());
|
|
8734
8774
|
let dataSourceId = config.dataSourceId ? `${config.dataSourceId}` : void 0;
|
|
8735
8775
|
let fieldNames = value;
|
|
8736
8776
|
if (!dataSourceId) {
|
|
8737
|
-
|
|
8777
|
+
const dsId = value[0];
|
|
8778
|
+
if (value.length && typeof dsId !== "string") return defaultMessage(message, `${value}类型应为字符串数组,数组字段后可接数字下标`, dataSourceFieldPathSuggestion());
|
|
8779
|
+
dataSourceId = value.length ? (0, _tmagic_utils.removeDataSourceFieldPrefix)(dsId) : void 0;
|
|
8738
8780
|
fieldNames = value.slice(1);
|
|
8739
8781
|
}
|
|
8740
8782
|
return validateDataSourceFieldPath(fieldNames, {
|
|
@@ -8967,11 +9009,8 @@
|
|
|
8967
9009
|
options: () => {
|
|
8968
9010
|
const { ds, fieldNames } = resolveFieldPath(parentFields);
|
|
8969
9011
|
if (!ds) return [];
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
fields = fields.find((f) => f.name === key)?.fields || [];
|
|
8973
|
-
});
|
|
8974
|
-
return getCascaderOptionsFromFields(fields, [
|
|
9012
|
+
const resolved = resolveFieldByPath(ds.fields, fieldNames, { allowArrayIndex: true });
|
|
9013
|
+
return getCascaderOptionsFromFields(resolved.ok ? resolved.fields : [], [
|
|
8975
9014
|
"string",
|
|
8976
9015
|
"number",
|
|
8977
9016
|
"boolean",
|
|
@@ -9630,25 +9630,59 @@
|
|
|
9630
9630
|
/**
|
|
9631
9631
|
* 按字段名路径下钻 DataSchema。
|
|
9632
9632
|
* @param skipNumberIndices 为 true 时跳过数字段(模板路径中的数组下标,如 arr[0].x)
|
|
9633
|
+
* @param allowArrayIndex 为 true 时识别数组下标。`number` 只能是下标;数字字符串先按当前层字段名匹配,没有同名字段时才当中下标
|
|
9633
9634
|
*/
|
|
9634
9635
|
var resolveFieldByPath = (fields, fieldNames, options = {}) => {
|
|
9635
9636
|
let currentFields = fields || [];
|
|
9636
9637
|
let field;
|
|
9638
|
+
/** 刚消费了数组下标,路径若在此结束则当前值是数组元素 */
|
|
9639
|
+
let atArrayElement = false;
|
|
9640
|
+
let arrayIndexName = "";
|
|
9637
9641
|
for (const name of fieldNames) {
|
|
9638
9642
|
if (options.skipNumberIndices && (0, _tmagic_utils.isNumber)(name)) continue;
|
|
9643
|
+
const segment = `${name}`;
|
|
9644
|
+
if (options.allowArrayIndex && (0, _tmagic_utils.isArrayIndex)(name) && field?.type === "array" && !atArrayElement) {
|
|
9645
|
+
if (!(typeof name === "string" ? currentFields.find((item) => item.name === segment) : void 0)) {
|
|
9646
|
+
atArrayElement = true;
|
|
9647
|
+
arrayIndexName = segment;
|
|
9648
|
+
continue;
|
|
9649
|
+
}
|
|
9650
|
+
}
|
|
9651
|
+
if (options.allowArrayIndex && typeof name === "number") return {
|
|
9652
|
+
ok: false,
|
|
9653
|
+
fields: currentFields,
|
|
9654
|
+
failedName: segment,
|
|
9655
|
+
invalidArrayIndex: true
|
|
9656
|
+
};
|
|
9657
|
+
atArrayElement = false;
|
|
9639
9658
|
if (!currentFields.length) return {
|
|
9640
9659
|
ok: false,
|
|
9641
9660
|
fields: currentFields,
|
|
9642
|
-
failedName:
|
|
9661
|
+
failedName: segment
|
|
9643
9662
|
};
|
|
9644
|
-
field = currentFields.find((item) => item.name ===
|
|
9663
|
+
field = currentFields.find((item) => item.name === segment);
|
|
9645
9664
|
if (!field) return {
|
|
9646
9665
|
ok: false,
|
|
9647
9666
|
fields: currentFields,
|
|
9648
|
-
failedName:
|
|
9667
|
+
failedName: segment
|
|
9649
9668
|
};
|
|
9650
9669
|
currentFields = field.fields || [];
|
|
9651
9670
|
}
|
|
9671
|
+
if (atArrayElement && field?.type === "array") {
|
|
9672
|
+
const elementFields = field.fields || [];
|
|
9673
|
+
const untypedArrayElement = elementFields.length === 0;
|
|
9674
|
+
return {
|
|
9675
|
+
ok: true,
|
|
9676
|
+
arrayElement: true,
|
|
9677
|
+
untypedArrayElement,
|
|
9678
|
+
field: {
|
|
9679
|
+
name: arrayIndexName,
|
|
9680
|
+
type: untypedArrayElement ? "any" : "object",
|
|
9681
|
+
fields: elementFields
|
|
9682
|
+
},
|
|
9683
|
+
fields: elementFields
|
|
9684
|
+
};
|
|
9685
|
+
}
|
|
9652
9686
|
return {
|
|
9653
9687
|
field,
|
|
9654
9688
|
ok: true,
|
|
@@ -9656,8 +9690,8 @@
|
|
|
9656
9690
|
};
|
|
9657
9691
|
};
|
|
9658
9692
|
var getFieldType = (ds, fieldNames) => {
|
|
9659
|
-
const { ok, field } = resolveFieldByPath(ds?.fields, fieldNames);
|
|
9660
|
-
if (!ok) return "";
|
|
9693
|
+
const { ok, field, untypedArrayElement } = resolveFieldByPath(ds?.fields, fieldNames, { allowArrayIndex: true });
|
|
9694
|
+
if (!ok || untypedArrayElement) return "";
|
|
9661
9695
|
return field?.type || "";
|
|
9662
9696
|
};
|
|
9663
9697
|
//#endregion
|
|
@@ -10993,14 +11027,18 @@
|
|
|
10993
11027
|
const ds = findDataSource(`${dsId}`);
|
|
10994
11028
|
if (!ds) return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
|
|
10995
11029
|
if (!path.length) return;
|
|
10996
|
-
const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, path);
|
|
10997
|
-
if (!ok)
|
|
11030
|
+
const { field, ok, fields, failedName, invalidArrayIndex, arrayElement, untypedArrayElement } = resolveFieldByPath(ds.fields, path, { allowArrayIndex: true });
|
|
11031
|
+
if (!ok) {
|
|
11032
|
+
if (invalidArrayIndex) return defaultMessage(options.message, `数组下标(${failedName})只能接在数组字段后面`);
|
|
11033
|
+
return defaultMessage(options.message, `数据源字段(${failedName})不存在`, listSuggestion(fields.map((item) => item.name)));
|
|
11034
|
+
}
|
|
10998
11035
|
const allowedTypes = options.dataSourceFieldType || ["any"];
|
|
10999
11036
|
if (!allowedTypes.length || allowedTypes.includes("any")) return;
|
|
11037
|
+
if (untypedArrayElement) return defaultMessage(options.message, `数组下标(${field?.name ?? path[path.length - 1]})的元素类型未定义,请选择具体字段`);
|
|
11000
11038
|
const leafType = field?.type || "any";
|
|
11001
11039
|
if (leafType !== "any" && !allowedTypes.includes(leafType)) {
|
|
11002
11040
|
const fieldName = field?.name || path[path.length - 1];
|
|
11003
|
-
return defaultMessage(options.message, `请选择类型为${allowedTypes.join("或")}的字段,字段(${fieldName})的类型为${leafType}`);
|
|
11041
|
+
return defaultMessage(options.message, arrayElement ? `请选择类型为${allowedTypes.join("或")}的字段,数组元素的类型为${leafType}` : `请选择类型为${allowedTypes.join("或")}的字段,字段(${fieldName})的类型为${leafType}`);
|
|
11004
11042
|
}
|
|
11005
11043
|
};
|
|
11006
11044
|
var validateDataSourceMethodTuple = (value, message) => {
|
|
@@ -11073,8 +11111,10 @@
|
|
|
11073
11111
|
return validateDataSourceTemplateBindings(value, message);
|
|
11074
11112
|
};
|
|
11075
11113
|
var validateDataSourceMethodSelect = (value, { message }) => validateDataSourceMethodTuple(value, message);
|
|
11114
|
+
/** 字段名,或非负整数下标(number)。数字字符串本身是 string,走字段名分支。 */
|
|
11115
|
+
var isDataSourceFieldPathSegment = (item) => typeof item === "string" || typeof item === "number" && (0, _tmagic_utils.isArrayIndex)(item);
|
|
11076
11116
|
var isDataSourceFieldPathValue = (value, config) => {
|
|
11077
|
-
if (!Array.isArray(value) || !value.length || value.some((item) =>
|
|
11117
|
+
if (!Array.isArray(value) || !value.length || value.some((item) => !isDataSourceFieldPathSegment(item))) return false;
|
|
11078
11118
|
if (config.dataSourceId) return true;
|
|
11079
11119
|
if (config.value === "key") return true;
|
|
11080
11120
|
return `${value[0]}`.startsWith(_tmagic_utils.DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
|
|
@@ -11095,11 +11135,13 @@
|
|
|
11095
11135
|
}
|
|
11096
11136
|
}, message);
|
|
11097
11137
|
}
|
|
11098
|
-
if (!Array.isArray(value) || value.some((item) =>
|
|
11138
|
+
if (!Array.isArray(value) || value.some((item) => !isDataSourceFieldPathSegment(item))) return defaultMessage(message, `${value}类型应为字符串数组,数组字段后可接数字下标`, dataSourceFieldPathSuggestion());
|
|
11099
11139
|
let dataSourceId = config.dataSourceId ? `${config.dataSourceId}` : void 0;
|
|
11100
11140
|
let fieldNames = value;
|
|
11101
11141
|
if (!dataSourceId) {
|
|
11102
|
-
|
|
11142
|
+
const dsId = value[0];
|
|
11143
|
+
if (value.length && typeof dsId !== "string") return defaultMessage(message, `${value}类型应为字符串数组,数组字段后可接数字下标`, dataSourceFieldPathSuggestion());
|
|
11144
|
+
dataSourceId = value.length ? (0, _tmagic_utils.removeDataSourceFieldPrefix)(dsId) : void 0;
|
|
11103
11145
|
fieldNames = value.slice(1);
|
|
11104
11146
|
}
|
|
11105
11147
|
return validateDataSourceFieldPath(fieldNames, {
|
|
@@ -14827,6 +14869,9 @@
|
|
|
14827
14869
|
/**
|
|
14828
14870
|
* 「属性配置表单校验」联动能力的 provide/inject 注入键。
|
|
14829
14871
|
* 使用 Symbol 避免与其它字符串键冲突,供 PropsPanel / FormPanel 注入判断校验失败时是否仍更新节点并记录错误。
|
|
14872
|
+
*
|
|
14873
|
+
* 注入值是 ComputedRef:Editor 用 `computed(() => props.enablePropsFormValidate ?? false)` 提供,
|
|
14874
|
+
* 保证外部动态切换该 prop 时子孙组件能同步拿到最新值;子孙侧用 `.value` 读取。
|
|
14830
14875
|
*/
|
|
14831
14876
|
var ENABLE_PROPS_FORM_VALIDATE = Symbol("enablePropsFormValidate");
|
|
14832
14877
|
var defaultEditorProps = {
|
|
@@ -14889,7 +14934,7 @@
|
|
|
14889
14934
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
14890
14935
|
const props = __props;
|
|
14891
14936
|
const emit = __emit;
|
|
14892
|
-
const enablePropsFormValidate = (0, vue.inject)(ENABLE_PROPS_FORM_VALIDATE, false);
|
|
14937
|
+
const enablePropsFormValidate = (0, vue.inject)(ENABLE_PROPS_FORM_VALIDATE, (0, vue.computed)(() => false));
|
|
14893
14938
|
const services = useServices();
|
|
14894
14939
|
const { uiService } = services;
|
|
14895
14940
|
const codeOptions = (0, vue.inject)("codeOptions", {});
|
|
@@ -14910,7 +14955,7 @@
|
|
|
14910
14955
|
const values = await configFormRef.value?.submitForm();
|
|
14911
14956
|
emit("submit", values, eventData);
|
|
14912
14957
|
} catch (e) {
|
|
14913
|
-
if (enablePropsFormValidate) emit("submit", v, eventData, e);
|
|
14958
|
+
if (enablePropsFormValidate.value) emit("submit", v, eventData, e);
|
|
14914
14959
|
else emit("submit-error", e);
|
|
14915
14960
|
}
|
|
14916
14961
|
};
|
|
@@ -14927,7 +14972,7 @@
|
|
|
14927
14972
|
const formatValidateErrorHtml = (error) => error.split(/<br\s*\/?>/i).map((segment) => (0, _tmagic_utils.filterXSS)(segment).replaceAll("\n", "<br>")).join("<br>");
|
|
14928
14973
|
const saveCode = async (values) => {
|
|
14929
14974
|
const newValues = props.codeValueKey ? { [props.codeValueKey]: values } : values;
|
|
14930
|
-
if (!enablePropsFormValidate) {
|
|
14975
|
+
if (!enablePropsFormValidate.value) {
|
|
14931
14976
|
emit("submit", newValues);
|
|
14932
14977
|
return;
|
|
14933
14978
|
}
|
|
@@ -14966,9 +15011,9 @@
|
|
|
14966
15011
|
size: propsPanelSize.value,
|
|
14967
15012
|
"init-values": __props.values,
|
|
14968
15013
|
config: __props.config,
|
|
14969
|
-
"type-match-valid":
|
|
15014
|
+
"type-match-valid": (0, vue.unref)(enablePropsFormValidate),
|
|
14970
15015
|
context: (0, vue.unref)(formContext),
|
|
14971
|
-
"validate-on-init":
|
|
15016
|
+
"validate-on-init": (0, vue.unref)(enablePropsFormValidate),
|
|
14972
15017
|
onChange: submit,
|
|
14973
15018
|
onError: errorHandler
|
|
14974
15019
|
}, null, 8, [
|
|
@@ -14979,7 +15024,9 @@
|
|
|
14979
15024
|
"size",
|
|
14980
15025
|
"init-values",
|
|
14981
15026
|
"config",
|
|
14982
|
-
"
|
|
15027
|
+
"type-match-valid",
|
|
15028
|
+
"context",
|
|
15029
|
+
"validate-on-init"
|
|
14983
15030
|
])]),
|
|
14984
15031
|
_: 1
|
|
14985
15032
|
}),
|
|
@@ -15073,7 +15120,7 @@
|
|
|
15073
15120
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
15074
15121
|
const emit = __emit;
|
|
15075
15122
|
const { editorService, uiService, propsService, storageService } = useServices();
|
|
15076
|
-
const enablePropsFormValidate = (0, vue.inject)(ENABLE_PROPS_FORM_VALIDATE, false);
|
|
15123
|
+
const enablePropsFormValidate = (0, vue.inject)(ENABLE_PROPS_FORM_VALIDATE, (0, vue.computed)(() => false));
|
|
15077
15124
|
const values = (0, vue.ref)({});
|
|
15078
15125
|
const curFormConfig = (0, vue.ref)([]);
|
|
15079
15126
|
const node = (0, vue.computed)(() => editorService.get("node"));
|
|
@@ -15136,7 +15183,7 @@
|
|
|
15136
15183
|
changeRecords: eventData?.changeRecords,
|
|
15137
15184
|
historySource,
|
|
15138
15185
|
replace,
|
|
15139
|
-
...enablePropsFormValidate && error ? { invalidInfo: {
|
|
15186
|
+
...enablePropsFormValidate.value && error ? { invalidInfo: {
|
|
15140
15187
|
id: newValue.id,
|
|
15141
15188
|
source,
|
|
15142
15189
|
error: error?.message
|
|
@@ -20619,7 +20666,7 @@
|
|
|
20619
20666
|
(0, vue.provide)("codeOptions", props.codeOptions);
|
|
20620
20667
|
(0, vue.provide)("stageOptions", stageOptions);
|
|
20621
20668
|
/** 是否启用「属性配置表单校验」联动能力,供 PropsPanel / FormPanel 判断校验失败时是否仍更新节点并记录错误 */
|
|
20622
|
-
(0, vue.provide)(ENABLE_PROPS_FORM_VALIDATE, props.enablePropsFormValidate ?? false);
|
|
20669
|
+
(0, vue.provide)(ENABLE_PROPS_FORM_VALIDATE, (0, vue.computed)(() => props.enablePropsFormValidate ?? false));
|
|
20623
20670
|
/**
|
|
20624
20671
|
* 编辑器注入给整棵表单树的业务上下文(`services` / `stage`),属性面板、对比表单、
|
|
20625
20672
|
* 侧边栏里的嵌套 MForm 都通过 inject 自动继承。
|
|
@@ -22885,15 +22932,11 @@
|
|
|
22885
22932
|
emit("change", value, eventData);
|
|
22886
22933
|
return;
|
|
22887
22934
|
}
|
|
22888
|
-
|
|
22889
|
-
let field;
|
|
22890
|
-
(keys || []).forEach((key) => {
|
|
22891
|
-
field = fields.find((f) => f.name === key);
|
|
22892
|
-
fields = field?.fields || [];
|
|
22893
|
-
});
|
|
22935
|
+
const { field, ok, untypedArrayElement } = resolveFieldByPath(dataSource.fields, keys, { allowArrayIndex: true });
|
|
22894
22936
|
const dataSourceFieldType = props.config.dataSourceFieldType || ["any"];
|
|
22895
22937
|
if (!dataSourceFieldType.length) dataSourceFieldType.push("any");
|
|
22896
|
-
|
|
22938
|
+
const typeMatches = ok && !!field?.type && (dataSourceFieldType.includes("any") || !untypedArrayElement && (field.type === "any" || dataSourceFieldType.includes(field.type)));
|
|
22939
|
+
if (!keys.length || typeMatches) emit("change", value, eventData);
|
|
22897
22940
|
else {
|
|
22898
22941
|
_tmagic_design.tMagicMessage.error(`请选择类型为${dataSourceFieldType.join("或")}的字段`);
|
|
22899
22942
|
emit("change", [dsId], eventData);
|
|
@@ -23650,11 +23693,8 @@
|
|
|
23650
23693
|
options: () => {
|
|
23651
23694
|
const { ds, fieldNames } = resolveFieldPath(parentFields);
|
|
23652
23695
|
if (!ds) return [];
|
|
23653
|
-
|
|
23654
|
-
|
|
23655
|
-
fields = fields.find((f) => f.name === key)?.fields || [];
|
|
23656
|
-
});
|
|
23657
|
-
return getCascaderOptionsFromFields(fields, [
|
|
23696
|
+
const resolved = resolveFieldByPath(ds.fields, fieldNames, { allowArrayIndex: true });
|
|
23697
|
+
return getCascaderOptionsFromFields(resolved.ok ? resolved.fields : [], [
|
|
23658
23698
|
"string",
|
|
23659
23699
|
"number",
|
|
23660
23700
|
"boolean",
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.
|
|
2
|
+
"version": "1.8.2",
|
|
3
3
|
"name": "@tmagic/editor",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
"moveable": "^0.53.0",
|
|
64
64
|
"serialize-javascript": "^7.1.1",
|
|
65
65
|
"sortablejs": "^1.15.7",
|
|
66
|
-
"@tmagic/design": "1.8.
|
|
67
|
-
"@tmagic/form": "1.8.
|
|
68
|
-
"@tmagic/stage": "1.8.
|
|
69
|
-
"@tmagic/
|
|
70
|
-
"@tmagic/
|
|
66
|
+
"@tmagic/design": "1.8.2",
|
|
67
|
+
"@tmagic/form": "1.8.2",
|
|
68
|
+
"@tmagic/stage": "1.8.2",
|
|
69
|
+
"@tmagic/table": "1.8.2",
|
|
70
|
+
"@tmagic/utils": "1.8.2"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@types/events": "^3.0.3",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"type-fest": "^5.2.0",
|
|
82
82
|
"typescript": "^6.0.3",
|
|
83
83
|
"vue": "^3.5.43",
|
|
84
|
-
"@tmagic/core": "1.8.
|
|
84
|
+
"@tmagic/core": "1.8.2"
|
|
85
85
|
},
|
|
86
86
|
"peerDependenciesMeta": {
|
|
87
87
|
"typescript": {
|
package/src/Editor.vue
CHANGED
|
@@ -236,7 +236,12 @@ provide('services', services);
|
|
|
236
236
|
provide('codeOptions', props.codeOptions);
|
|
237
237
|
provide('stageOptions', stageOptions);
|
|
238
238
|
/** 是否启用「属性配置表单校验」联动能力,供 PropsPanel / FormPanel 判断校验失败时是否仍更新节点并记录错误 */
|
|
239
|
-
|
|
239
|
+
// 同 isLargeStageContainer:用 computed 包一层,否则传下去的是 provide 那一刻的值快照,
|
|
240
|
+
// props.enablePropsFormValidate 后续变化不会同步到子孙。
|
|
241
|
+
provide(
|
|
242
|
+
ENABLE_PROPS_FORM_VALIDATE,
|
|
243
|
+
computed(() => props.enablePropsFormValidate ?? false),
|
|
244
|
+
);
|
|
240
245
|
/**
|
|
241
246
|
* 编辑器注入给整棵表单树的业务上下文(`services` / `stage`),属性面板、对比表单、
|
|
242
247
|
* 侧边栏里的嵌套 MForm 都通过 inject 自动继承。
|
package/src/editorProps.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InjectionKey } from 'vue';
|
|
1
|
+
import type { ComputedRef, InjectionKey } from 'vue';
|
|
2
2
|
|
|
3
3
|
import type { DataSourceSchema, EventOption, Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/core';
|
|
4
4
|
import type { FormConfig } from '@tmagic/form';
|
|
@@ -31,8 +31,11 @@ import type {
|
|
|
31
31
|
/**
|
|
32
32
|
* 「属性配置表单校验」联动能力的 provide/inject 注入键。
|
|
33
33
|
* 使用 Symbol 避免与其它字符串键冲突,供 PropsPanel / FormPanel 注入判断校验失败时是否仍更新节点并记录错误。
|
|
34
|
+
*
|
|
35
|
+
* 注入值是 ComputedRef:Editor 用 `computed(() => props.enablePropsFormValidate ?? false)` 提供,
|
|
36
|
+
* 保证外部动态切换该 prop 时子孙组件能同步拿到最新值;子孙侧用 `.value` 读取。
|
|
34
37
|
*/
|
|
35
|
-
export const ENABLE_PROPS_FORM_VALIDATE: InjectionKey<boolean
|
|
38
|
+
export const ENABLE_PROPS_FORM_VALIDATE: InjectionKey<ComputedRef<boolean>> = Symbol('enablePropsFormValidate');
|
|
36
39
|
|
|
37
40
|
export interface EditorProps {
|
|
38
41
|
/** 是否是大屏模拟器容器,大屏容器时,左侧属性面板会扩展为两列(正常3列),颜色表单不扩展两列等 */
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
<script setup lang="ts">
|
|
52
52
|
import { computed, inject, ref, resolveComponent, watch } from 'vue';
|
|
53
53
|
|
|
54
|
-
import { DataSchema } from '@tmagic/core';
|
|
55
54
|
import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
|
|
56
55
|
import {
|
|
57
56
|
type ContainerChangeEventData,
|
|
@@ -64,6 +63,7 @@ import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, removeDataSourceFieldPrefix } f
|
|
|
64
63
|
|
|
65
64
|
import MIcon from '@editor/components/Icon.vue';
|
|
66
65
|
import { useServices } from '@editor/hooks/use-services';
|
|
66
|
+
import { resolveFieldByPath } from '@editor/utils/data-source';
|
|
67
67
|
|
|
68
68
|
import dataSourceIcon from '../../icons/DatasourceIcon.vue';
|
|
69
69
|
|
|
@@ -175,11 +175,8 @@ const onChangeHandler = (value: string[], eventData?: ContainerChangeEventData)
|
|
|
175
175
|
return;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
(keys || []).forEach((key) => {
|
|
181
|
-
field = fields.find((f) => f.name === key);
|
|
182
|
-
fields = field?.fields || [];
|
|
178
|
+
const { field, ok, untypedArrayElement } = resolveFieldByPath(dataSource.fields, keys, {
|
|
179
|
+
allowArrayIndex: true,
|
|
183
180
|
});
|
|
184
181
|
|
|
185
182
|
const dataSourceFieldType = props.config.dataSourceFieldType || ['any'];
|
|
@@ -187,11 +184,14 @@ const onChangeHandler = (value: string[], eventData?: ContainerChangeEventData)
|
|
|
187
184
|
dataSourceFieldType.push('any');
|
|
188
185
|
}
|
|
189
186
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
// 未声明元素结构的下标不是显式 any,限定了具体类型时不放行
|
|
188
|
+
const typeMatches =
|
|
189
|
+
ok &&
|
|
190
|
+
!!field?.type &&
|
|
191
|
+
(dataSourceFieldType.includes('any') ||
|
|
192
|
+
(!untypedArrayElement && (field.type === 'any' || dataSourceFieldType.includes(field.type))));
|
|
193
|
+
|
|
194
|
+
if (!keys.length || typeMatches) {
|
|
195
195
|
emit('change', value, eventData);
|
|
196
196
|
} else {
|
|
197
197
|
tMagicMessage.error(`请选择类型为${dataSourceFieldType.join('或')}的字段`);
|
|
@@ -20,7 +20,7 @@ import type { DisplayCondsConfig, FormState, GroupListConfig } from '@tmagic/for
|
|
|
20
20
|
import { removeDataSourceFieldPrefix } from '@tmagic/utils';
|
|
21
21
|
|
|
22
22
|
import dataSourceService from '@editor/services/dataSource';
|
|
23
|
-
import { getCascaderOptionsFromFields, getFieldType } from '@editor/utils/data-source';
|
|
23
|
+
import { getCascaderOptionsFromFields, getFieldType, resolveFieldByPath } from '@editor/utils/data-source';
|
|
24
24
|
|
|
25
25
|
import { stickyAddButton } from './stickyAddButton';
|
|
26
26
|
|
|
@@ -38,7 +38,7 @@ export const createDisplayCondsConfig = (
|
|
|
38
38
|
name: string,
|
|
39
39
|
parentFields: string[],
|
|
40
40
|
): GroupListConfig => {
|
|
41
|
-
const resolveFieldPath = (path: string
|
|
41
|
+
const resolveFieldPath = (path: Array<string | number>) => {
|
|
42
42
|
const [id, ...fieldNames] = path;
|
|
43
43
|
const ds = id ? dataSourceService.getDataSourceById(removeDataSourceFieldPrefix(`${id}`)) : undefined;
|
|
44
44
|
return { ds, fieldNames };
|
|
@@ -69,13 +69,13 @@ export const createDisplayCondsConfig = (
|
|
|
69
69
|
return [];
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
const resolved = resolveFieldByPath(ds.fields, fieldNames, { allowArrayIndex: true });
|
|
73
|
+
return getCascaderOptionsFromFields(resolved.ok ? resolved.fields : [], [
|
|
74
|
+
'string',
|
|
75
|
+
'number',
|
|
76
|
+
'boolean',
|
|
77
|
+
'any',
|
|
78
|
+
]);
|
|
79
79
|
},
|
|
80
80
|
name: 'field',
|
|
81
81
|
value: 'key',
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
:size="propsPanelSize"
|
|
13
13
|
:init-values="values"
|
|
14
14
|
:config="config"
|
|
15
|
-
:type-match-valid="
|
|
15
|
+
:type-match-valid="enablePropsFormValidate"
|
|
16
16
|
:context="formContext"
|
|
17
|
-
:validate-on-init="
|
|
17
|
+
:validate-on-init="enablePropsFormValidate"
|
|
18
18
|
@change="submit"
|
|
19
19
|
@error="errorHandler"
|
|
20
20
|
></MForm>
|
|
@@ -86,7 +86,10 @@ const emit = defineEmits<{
|
|
|
86
86
|
unmounted: [];
|
|
87
87
|
}>();
|
|
88
88
|
|
|
89
|
-
const enablePropsFormValidate = inject(
|
|
89
|
+
const enablePropsFormValidate = inject(
|
|
90
|
+
ENABLE_PROPS_FORM_VALIDATE,
|
|
91
|
+
computed(() => false),
|
|
92
|
+
);
|
|
90
93
|
|
|
91
94
|
const services = useServices();
|
|
92
95
|
const { uiService } = services;
|
|
@@ -116,7 +119,7 @@ const submit = async (v: FormValue, eventData: ContainerChangeEventData) => {
|
|
|
116
119
|
// 校验成功:正常更新节点(第三个参数 error 为空,表示清除该来源的错误记录)
|
|
117
120
|
emit('submit', values, eventData);
|
|
118
121
|
} catch (e: any) {
|
|
119
|
-
if (enablePropsFormValidate) {
|
|
122
|
+
if (enablePropsFormValidate.value) {
|
|
120
123
|
// 启用校验联动:校验失败时仍以当前表单值更新节点,并把错误信息一并抛给上层记录
|
|
121
124
|
emit('submit', v, eventData, e);
|
|
122
125
|
} else {
|
|
@@ -146,7 +149,7 @@ const formatValidateErrorHtml = (error: string): string =>
|
|
|
146
149
|
const saveCode = async (values: any) => {
|
|
147
150
|
const newValues = props.codeValueKey ? { [props.codeValueKey]: values } : values;
|
|
148
151
|
|
|
149
|
-
if (!enablePropsFormValidate) {
|
|
152
|
+
if (!enablePropsFormValidate.value) {
|
|
150
153
|
// 未启用校验联动:保持原行为,直接提交源码保存的值
|
|
151
154
|
emit('submit', newValues);
|
|
152
155
|
return;
|
|
@@ -93,7 +93,10 @@ const emit = defineEmits<{
|
|
|
93
93
|
|
|
94
94
|
const { editorService, uiService, propsService, storageService } = useServices();
|
|
95
95
|
|
|
96
|
-
const enablePropsFormValidate = inject(
|
|
96
|
+
const enablePropsFormValidate = inject(
|
|
97
|
+
ENABLE_PROPS_FORM_VALIDATE,
|
|
98
|
+
computed(() => false),
|
|
99
|
+
);
|
|
97
100
|
|
|
98
101
|
const values = ref<FormValue>({});
|
|
99
102
|
// ts类型应该是FormConfig, 但是打包时会出错,所以暂时用any
|
|
@@ -205,7 +208,9 @@ const submit = async (
|
|
|
205
208
|
replace,
|
|
206
209
|
// 启用校验联动时,仅校验失败(error 存在)才把错误信息随更新传入 editorService 记录;
|
|
207
210
|
// 其余情况(含表单校验成功、CodeEditor 源码保存)不携带 invalidInfo,由 editorService 在执行 update 时统一清除该节点错误。
|
|
208
|
-
...(enablePropsFormValidate && error
|
|
211
|
+
...(enablePropsFormValidate.value && error
|
|
212
|
+
? { invalidInfo: { id: newValue.id, source, error: error?.message } }
|
|
213
|
+
: {}),
|
|
209
214
|
});
|
|
210
215
|
} catch (e: any) {
|
|
211
216
|
emit('submit-error', e);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DataSchema, DataSourceFieldType, DataSourceSchema } from '@tmagic/core';
|
|
2
2
|
import { type CascaderOption, type FormConfig, type TabConfig } from '@tmagic/form';
|
|
3
|
-
import { dataSourceTemplateRegExp, getKeysArray, isNumber } from '@tmagic/utils';
|
|
3
|
+
import { dataSourceTemplateRegExp, getKeysArray, isArrayIndex, isNumber } from '@tmagic/utils';
|
|
4
4
|
|
|
5
5
|
import BaseFormConfig from './formConfigs/base';
|
|
6
6
|
import HttpFormConfig from './formConfigs/http';
|
|
@@ -239,34 +239,86 @@ export const getCascaderOptionsFromFields = (
|
|
|
239
239
|
/**
|
|
240
240
|
* 按字段名路径下钻 DataSchema。
|
|
241
241
|
* @param skipNumberIndices 为 true 时跳过数字段(模板路径中的数组下标,如 arr[0].x)
|
|
242
|
+
* @param allowArrayIndex 为 true 时识别数组下标。`number` 只能是下标;数字字符串先按当前层字段名匹配,没有同名字段时才当中下标
|
|
242
243
|
*/
|
|
243
244
|
export const resolveFieldByPath = (
|
|
244
245
|
fields: DataSchema[] | undefined,
|
|
245
|
-
fieldNames: string
|
|
246
|
-
options: { skipNumberIndices?: boolean } = {},
|
|
247
|
-
): {
|
|
246
|
+
fieldNames: Array<string | number>,
|
|
247
|
+
options: { skipNumberIndices?: boolean; allowArrayIndex?: boolean } = {},
|
|
248
|
+
): {
|
|
249
|
+
ok: boolean;
|
|
250
|
+
field?: DataSchema;
|
|
251
|
+
fields: DataSchema[];
|
|
252
|
+
failedName?: string;
|
|
253
|
+
/** number 下标没有紧跟在数组字段后面 */
|
|
254
|
+
invalidArrayIndex?: boolean;
|
|
255
|
+
/** 路径停在数组下标上,当前值是数组元素 */
|
|
256
|
+
arrayElement?: boolean;
|
|
257
|
+
/** 数组没有子字段,元素类型未知 */
|
|
258
|
+
untypedArrayElement?: boolean;
|
|
259
|
+
} => {
|
|
248
260
|
let currentFields = fields || [];
|
|
249
261
|
let field: DataSchema | undefined;
|
|
262
|
+
/** 刚消费了数组下标,路径若在此结束则当前值是数组元素 */
|
|
263
|
+
let atArrayElement = false;
|
|
264
|
+
let arrayIndexName = '';
|
|
250
265
|
|
|
251
266
|
for (const name of fieldNames) {
|
|
252
267
|
if (options.skipNumberIndices && isNumber(name)) {
|
|
253
268
|
continue;
|
|
254
269
|
}
|
|
270
|
+
|
|
271
|
+
const segment = `${name}`;
|
|
272
|
+
// number 不能当字段名。数字字符串仅在当前层没有同名字段时才当中下标,避免盖住名为 "0" 的子字段。
|
|
273
|
+
if (options.allowArrayIndex && isArrayIndex(name) && field?.type === 'array' && !atArrayElement) {
|
|
274
|
+
const namedField = typeof name === 'string' ? currentFields.find((item) => item.name === segment) : undefined;
|
|
275
|
+
if (!namedField) {
|
|
276
|
+
atArrayElement = true;
|
|
277
|
+
arrayIndexName = segment;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (options.allowArrayIndex && typeof name === 'number') {
|
|
283
|
+
return { ok: false, fields: currentFields, failedName: segment, invalidArrayIndex: true };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
atArrayElement = false;
|
|
287
|
+
|
|
255
288
|
if (!currentFields.length) {
|
|
256
|
-
return { ok: false, fields: currentFields, failedName:
|
|
289
|
+
return { ok: false, fields: currentFields, failedName: segment };
|
|
257
290
|
}
|
|
258
|
-
field = currentFields.find((item) => item.name ===
|
|
291
|
+
field = currentFields.find((item) => item.name === segment);
|
|
259
292
|
if (!field) {
|
|
260
|
-
return { ok: false, fields: currentFields, failedName:
|
|
293
|
+
return { ok: false, fields: currentFields, failedName: segment };
|
|
261
294
|
}
|
|
262
295
|
currentFields = field.fields || [];
|
|
263
296
|
}
|
|
264
297
|
|
|
298
|
+
if (atArrayElement && field?.type === 'array') {
|
|
299
|
+
const elementFields = field.fields || [];
|
|
300
|
+
const untypedArrayElement = elementFields.length === 0;
|
|
301
|
+
return {
|
|
302
|
+
ok: true,
|
|
303
|
+
arrayElement: true,
|
|
304
|
+
untypedArrayElement,
|
|
305
|
+
field: {
|
|
306
|
+
name: arrayIndexName,
|
|
307
|
+
// 有子字段时元素是这些字段组成的对象;没有子字段时类型未知,不能当成显式 any
|
|
308
|
+
type: untypedArrayElement ? 'any' : 'object',
|
|
309
|
+
fields: elementFields,
|
|
310
|
+
},
|
|
311
|
+
fields: elementFields,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
265
315
|
return { field, ok: true, fields: currentFields };
|
|
266
316
|
};
|
|
267
317
|
|
|
268
|
-
export const getFieldType = (ds: DataSourceSchema | undefined, fieldNames: string
|
|
269
|
-
const { ok, field } = resolveFieldByPath(ds?.fields, fieldNames
|
|
270
|
-
|
|
318
|
+
export const getFieldType = (ds: DataSourceSchema | undefined, fieldNames: Array<string | number>) => {
|
|
319
|
+
const { ok, field, untypedArrayElement } = resolveFieldByPath(ds?.fields, fieldNames, {
|
|
320
|
+
allowArrayIndex: true,
|
|
321
|
+
});
|
|
322
|
+
if (!ok || untypedArrayElement) return '';
|
|
271
323
|
return field?.type || '';
|
|
272
324
|
};
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
DATA_SOURCE_SET_DATA_METHOD_NAME,
|
|
32
32
|
dataSourceTemplateRegExp,
|
|
33
33
|
getKeysArray,
|
|
34
|
+
isArrayIndex,
|
|
34
35
|
removeDataSourceFieldPrefix,
|
|
35
36
|
} from '@tmagic/utils';
|
|
36
37
|
|
|
@@ -211,7 +212,7 @@ const displayCondSuggestion = (): string => {
|
|
|
211
212
|
};
|
|
212
213
|
|
|
213
214
|
const validateDataSourceFieldPath = (
|
|
214
|
-
path: string
|
|
215
|
+
path: Array<string | number>,
|
|
215
216
|
options: {
|
|
216
217
|
dataSourceId?: string;
|
|
217
218
|
dataSourceFieldType?: DataSourceFieldType[];
|
|
@@ -239,8 +240,17 @@ const validateDataSourceFieldPath = (
|
|
|
239
240
|
return undefined;
|
|
240
241
|
}
|
|
241
242
|
|
|
242
|
-
const { field, ok, fields, failedName } = resolveFieldByPath(
|
|
243
|
+
const { field, ok, fields, failedName, invalidArrayIndex, arrayElement, untypedArrayElement } = resolveFieldByPath(
|
|
244
|
+
ds.fields,
|
|
245
|
+
path,
|
|
246
|
+
{
|
|
247
|
+
allowArrayIndex: true,
|
|
248
|
+
},
|
|
249
|
+
);
|
|
243
250
|
if (!ok) {
|
|
251
|
+
if (invalidArrayIndex) {
|
|
252
|
+
return defaultMessage(options.message, `数组下标(${failedName})只能接在数组字段后面`);
|
|
253
|
+
}
|
|
244
254
|
return defaultMessage(
|
|
245
255
|
options.message,
|
|
246
256
|
`数据源字段(${failedName})不存在`,
|
|
@@ -253,6 +263,13 @@ const validateDataSourceFieldPath = (
|
|
|
253
263
|
return undefined;
|
|
254
264
|
}
|
|
255
265
|
|
|
266
|
+
if (untypedArrayElement) {
|
|
267
|
+
return defaultMessage(
|
|
268
|
+
options.message,
|
|
269
|
+
`数组下标(${field?.name ?? path[path.length - 1]})的元素类型未定义,请选择具体字段`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
256
273
|
const leafType = field?.type || 'any';
|
|
257
274
|
if (leafType !== 'any' && !allowedTypes.includes(leafType)) {
|
|
258
275
|
const fieldName = field?.name || path[path.length - 1];
|
|
@@ -260,7 +277,9 @@ const validateDataSourceFieldPath = (
|
|
|
260
277
|
// 文案已点明当前字段类型与要求类型,无需再列同级可选字段
|
|
261
278
|
return defaultMessage(
|
|
262
279
|
options.message,
|
|
263
|
-
|
|
280
|
+
arrayElement
|
|
281
|
+
? `请选择类型为${allowedTypes.join('或')}的字段,数组元素的类型为${leafType}`
|
|
282
|
+
: `请选择类型为${allowedTypes.join('或')}的字段,字段(${fieldName})的类型为${leafType}`,
|
|
264
283
|
);
|
|
265
284
|
}
|
|
266
285
|
};
|
|
@@ -437,8 +456,12 @@ const validateDataSourceInput: TypeMatchValidator = (value, { message }) => {
|
|
|
437
456
|
const validateDataSourceMethodSelect: TypeMatchValidator = (value, { message }) =>
|
|
438
457
|
validateDataSourceMethodTuple(value, message);
|
|
439
458
|
|
|
440
|
-
|
|
441
|
-
|
|
459
|
+
/** 字段名,或非负整数下标(number)。数字字符串本身是 string,走字段名分支。 */
|
|
460
|
+
const isDataSourceFieldPathSegment = (item: unknown): item is string | number =>
|
|
461
|
+
typeof item === 'string' || (typeof item === 'number' && isArrayIndex(item));
|
|
462
|
+
|
|
463
|
+
const isDataSourceFieldPathValue = (value: any, config: any): value is Array<string | number> => {
|
|
464
|
+
if (!Array.isArray(value) || !value.length || value.some((item) => !isDataSourceFieldPathSegment(item))) {
|
|
442
465
|
return false;
|
|
443
466
|
}
|
|
444
467
|
if (config.dataSourceId) {
|
|
@@ -488,16 +511,29 @@ export const validateDataSourceFieldSelect = (
|
|
|
488
511
|
}
|
|
489
512
|
}
|
|
490
513
|
|
|
491
|
-
if (!Array.isArray(value) || value.some((item) =>
|
|
492
|
-
return defaultMessage(
|
|
514
|
+
if (!Array.isArray(value) || value.some((item) => !isDataSourceFieldPathSegment(item))) {
|
|
515
|
+
return defaultMessage(
|
|
516
|
+
message,
|
|
517
|
+
`${value}类型应为字符串数组,数组字段后可接数字下标`,
|
|
518
|
+
dataSourceFieldPathSuggestion(),
|
|
519
|
+
);
|
|
493
520
|
}
|
|
494
521
|
|
|
495
522
|
// 未指定 dataSourceId 时,路径首项为数据源 id,其余为字段名。
|
|
496
523
|
// value 模式的首项带 ds-field:: 前缀、key 模式不带,removeDataSourceFieldPrefix 对后者是幂等的,无需分支。
|
|
524
|
+
// 数组下标只出现在字段段,数据源 id 仍必须是字符串。
|
|
497
525
|
let dataSourceId = config.dataSourceId ? `${config.dataSourceId}` : undefined;
|
|
498
|
-
let fieldNames = value;
|
|
526
|
+
let fieldNames: Array<string | number> = value;
|
|
499
527
|
if (!dataSourceId) {
|
|
500
|
-
|
|
528
|
+
const dsId = value[0];
|
|
529
|
+
if (value.length && typeof dsId !== 'string') {
|
|
530
|
+
return defaultMessage(
|
|
531
|
+
message,
|
|
532
|
+
`${value}类型应为字符串数组,数组字段后可接数字下标`,
|
|
533
|
+
dataSourceFieldPathSuggestion(),
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
dataSourceId = value.length ? removeDataSourceFieldPrefix(dsId) : undefined;
|
|
501
537
|
fieldNames = value.slice(1);
|
|
502
538
|
}
|
|
503
539
|
|
package/types/index.d.ts
CHANGED
|
@@ -3261,16 +3261,24 @@ export declare const getCascaderOptionsFromFields: (fields?: DataSchema[], dataS
|
|
|
3261
3261
|
/**
|
|
3262
3262
|
* 按字段名路径下钻 DataSchema。
|
|
3263
3263
|
* @param skipNumberIndices 为 true 时跳过数字段(模板路径中的数组下标,如 arr[0].x)
|
|
3264
|
+
* @param allowArrayIndex 为 true 时识别数组下标。`number` 只能是下标;数字字符串先按当前层字段名匹配,没有同名字段时才当中下标
|
|
3264
3265
|
*/
|
|
3265
|
-
export declare const resolveFieldByPath: (fields: DataSchema[] | undefined, fieldNames: string
|
|
3266
|
+
export declare const resolveFieldByPath: (fields: DataSchema[] | undefined, fieldNames: Array<string | number>, options?: {
|
|
3266
3267
|
skipNumberIndices?: boolean;
|
|
3268
|
+
allowArrayIndex?: boolean;
|
|
3267
3269
|
}) => {
|
|
3268
3270
|
ok: boolean;
|
|
3269
3271
|
field?: DataSchema;
|
|
3270
3272
|
fields: DataSchema[];
|
|
3271
3273
|
failedName?: string;
|
|
3274
|
+
/** number 下标没有紧跟在数组字段后面 */
|
|
3275
|
+
invalidArrayIndex?: boolean;
|
|
3276
|
+
/** 路径停在数组下标上,当前值是数组元素 */
|
|
3277
|
+
arrayElement?: boolean;
|
|
3278
|
+
/** 数组没有子字段,元素类型未知 */
|
|
3279
|
+
untypedArrayElement?: boolean;
|
|
3272
3280
|
};
|
|
3273
|
-
export declare const getFieldType: (ds: DataSourceSchema | undefined, fieldNames: string
|
|
3281
|
+
export declare const getFieldType: (ds: DataSourceSchema | undefined, fieldNames: Array<string | number>) => "" | DataSourceFieldType;
|
|
3274
3282
|
//#endregion
|
|
3275
3283
|
//#region temp/packages/editor/src/utils/dep/idle-task.d.ts
|
|
3276
3284
|
export interface IdleTaskEvents {
|