@tmagic/form 1.8.0-manmanyu.8 → 1.8.0
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/Form.vue_vue_type_script_setup_true_lang.js +88 -107
- package/dist/es/FormBox.vue_vue_type_script_setup_true_lang.js +21 -5
- package/dist/es/FormDialog.vue_vue_type_script_setup_true_lang.js +21 -5
- package/dist/es/FormDrawer.vue_vue_type_script_setup_true_lang.js +46 -12
- package/dist/es/containers/Col.vue_vue_type_script_setup_true_lang.js +6 -2
- package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +44 -47
- package/dist/es/containers/Fieldset.vue_vue_type_script_setup_true_lang.js +7 -1
- package/dist/es/containers/FlexLayout.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/containers/FormLabel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/containers/GroupList.vue_vue_type_script_setup_true_lang.js +59 -22
- package/dist/es/containers/GroupListItem.vue_vue_type_script_setup_true_lang.js +118 -100
- package/dist/es/containers/Panel.vue_vue_type_script_setup_true_lang.js +8 -3
- package/dist/es/containers/Row.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/containers/Step.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/containers/Tabs.vue_vue_type_script_setup_true_lang.js +8 -1
- package/dist/es/containers/table/ActionsColumn.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/containers/table/SortColumn.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +14 -10
- package/dist/es/containers/table/useTableColumns.js +10 -15
- package/dist/es/containers/table-group-list/TableGroupList.vue_vue_type_script_setup_true_lang.js +72 -79
- package/dist/es/containers/table-group-list/useAdd.js +26 -16
- package/dist/es/containers/table-group-list/useScrollLastItemIntoView.js +72 -0
- package/dist/es/fields/Cascader.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/fields/Checkbox.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/CheckboxGroup/Index.js +5 -0
- package/dist/es/fields/{CheckboxGroup.vue_vue_type_script_setup_true_lang.js → CheckboxGroup/Index.vue_vue_type_script_setup_true_lang.js} +6 -7
- package/dist/es/fields/CheckboxGroup/effect.js +8 -0
- package/dist/es/fields/ColorPicker.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/Date/Index.js +5 -0
- package/dist/es/fields/{Date.vue_vue_type_script_setup_true_lang.js → Date/Index.vue_vue_type_script_setup_true_lang.js} +5 -7
- package/dist/es/fields/Date/effect.js +10 -0
- package/dist/es/fields/DateTime/Index.js +5 -0
- package/dist/es/fields/{DateTime.vue_vue_type_script_setup_true_lang.js → DateTime/Index.vue_vue_type_script_setup_true_lang.js} +5 -9
- package/dist/es/fields/DateTime/effect.js +15 -0
- package/dist/es/fields/Daterange.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/fields/Display/Index.js +5 -0
- package/dist/es/fields/{Display.vue_vue_type_script_setup_true_lang.js → Display/Index.vue_vue_type_script_setup_true_lang.js} +6 -7
- package/dist/es/fields/Display/effect.js +8 -0
- package/dist/es/fields/DynamicField/Index.js +5 -0
- package/dist/es/fields/{DynamicField.vue_vue_type_script_setup_true_lang.js → DynamicField/Index.vue_vue_type_script_setup_true_lang.js} +11 -15
- package/dist/es/fields/DynamicField/effect.js +40 -0
- package/dist/es/fields/Hidden.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/Link.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/es/fields/Number.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/fields/NumberRange/Index.js +5 -0
- package/dist/es/fields/{NumberRange.vue_vue_type_script_setup_true_lang.js → NumberRange/Index.vue_vue_type_script_setup_true_lang.js} +5 -6
- package/dist/es/fields/NumberRange/effect.js +8 -0
- package/dist/es/fields/RadioGroup.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/Select.vue_vue_type_script_setup_true_lang.js +14 -6
- package/dist/es/fields/Switch.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/Text.vue_vue_type_script_setup_true_lang.js +13 -10
- package/dist/es/fields/Textarea.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/Time.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/Timerange.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/headless.js +13 -0
- package/dist/es/index.js +19 -12
- package/dist/es/plugin.js +57 -39
- package/dist/es/schema.js +11 -2
- package/dist/es/style.css +123 -3
- package/dist/es/submitForm.js +270 -72
- package/dist/es/utils/builtInFields.js +49 -0
- package/dist/es/utils/collectFields.js +407 -0
- package/dist/es/utils/config.js +4 -8
- package/dist/es/utils/fieldInnerConfig.js +42 -0
- package/dist/es/utils/fieldValueEffects.js +80 -0
- package/dist/es/utils/form.js +154 -19
- package/dist/es/utils/formStateProxy.js +111 -0
- package/dist/es/utils/registerField.js +158 -0
- package/dist/es/utils/submitHeadless.js +54 -0
- package/dist/es/utils/tableGroupList.js +104 -0
- package/dist/es/utils/typeMatch.js +532 -0
- package/dist/es/utils/validateError.js +57 -0
- package/dist/es/utils/validateValues.js +115 -0
- package/dist/style.css +123 -3
- package/dist/themes/magic-admin.css +576 -14
- package/dist/tmagic-form-headless.umd.cjs +4515 -0
- package/dist/tmagic-form.umd.cjs +3444 -983
- package/package.json +16 -10
- package/src/Form.vue +113 -129
- package/src/FormBox.vue +16 -4
- package/src/FormDialog.vue +16 -5
- package/src/FormDrawer.vue +41 -13
- package/src/containers/Col.vue +3 -0
- package/src/containers/Container.vue +40 -45
- package/src/containers/Fieldset.vue +5 -0
- package/src/containers/FlexLayout.vue +2 -0
- package/src/containers/GroupList.vue +64 -13
- package/src/containers/GroupListItem.vue +88 -81
- package/src/containers/Panel.vue +3 -0
- package/src/containers/Row.vue +2 -0
- package/src/containers/Step.vue +2 -0
- package/src/containers/Tabs.vue +13 -0
- package/src/containers/table/ActionsColumn.vue +1 -1
- package/src/containers/table/Table.vue +5 -3
- package/src/containers/table/usePagination.ts +1 -1
- package/src/containers/table/useSortable.ts +1 -1
- package/src/containers/table/useTableColumns.ts +15 -22
- package/src/containers/table-group-list/TableGroupList.vue +46 -72
- package/src/containers/table-group-list/useAdd.ts +40 -25
- package/src/containers/table-group-list/useScrollLastItemIntoView.ts +94 -0
- package/src/fields/{CheckboxGroup.vue → CheckboxGroup/Index.vue} +3 -8
- package/src/fields/CheckboxGroup/effect.ts +27 -0
- package/src/fields/{Date.vue → Date/Index.vue} +2 -5
- package/src/fields/Date/effect.ts +29 -0
- package/src/fields/{DateTime.vue → DateTime/Index.vue} +2 -16
- package/src/fields/DateTime/effect.ts +36 -0
- package/src/fields/{Display.vue → Display/Index.vue} +3 -7
- package/src/fields/Display/effect.ts +28 -0
- package/src/fields/{DynamicField.vue → DynamicField/Index.vue} +11 -11
- package/src/fields/DynamicField/effect.ts +74 -0
- package/src/fields/Link.vue +3 -4
- package/src/fields/Number.vue +2 -1
- package/src/fields/{NumberRange.vue → NumberRange/Index.vue} +2 -6
- package/src/fields/NumberRange/effect.ts +27 -0
- package/src/fields/RadioGroup.vue +13 -1
- package/src/fields/Select.vue +13 -4
- package/src/headless.ts +78 -0
- package/src/index.ts +46 -9
- package/src/plugin.ts +68 -37
- package/src/schema.ts +13 -2
- package/src/submitForm.ts +410 -131
- package/src/theme/fieldset.scss +1 -0
- package/src/theme/form-box.scss +5 -0
- package/src/theme/form-dialog.scss +1 -1
- package/src/theme/group-list.scss +143 -2
- package/src/theme/table.scss +11 -1
- package/src/theme/themes/magic-admin/index.scss +27 -16
- package/src/utils/builtInFields.ts +69 -0
- package/src/utils/collectFields.ts +561 -0
- package/src/utils/config.ts +7 -15
- package/src/utils/fieldInnerConfig.ts +127 -0
- package/src/utils/fieldValueEffects.ts +140 -0
- package/src/utils/form.ts +230 -30
- package/src/utils/formStateProxy.ts +149 -0
- package/src/utils/registerField.ts +308 -0
- package/src/utils/submitHeadless.ts +206 -0
- package/src/utils/tableGroupList.ts +147 -0
- package/src/utils/typeMatch.ts +948 -0
- package/src/utils/validateError.ts +92 -0
- package/src/utils/validateValues.ts +177 -0
- package/types/headless.d.ts +682 -0
- package/types/index.d.ts +900 -180
- package/dist/es/_virtual/_rolldown/runtime.js +0 -27
- package/dist/es/fields/CheckboxGroup.js +0 -5
- package/dist/es/fields/Date.js +0 -5
- package/dist/es/fields/DateTime.js +0 -5
- package/dist/es/fields/Display.js +0 -5
- package/dist/es/fields/DynamicField.js +0 -5
- package/dist/es/fields/NumberRange.js +0 -5
package/dist/es/utils/form.js
CHANGED
|
@@ -1,10 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getConfig } from "./config.js";
|
|
2
|
+
import { createTypeMatchValidator } from "./typeMatch.js";
|
|
3
|
+
import { createFormStateProxy, mergeFormContexts } from "./formStateProxy.js";
|
|
4
|
+
import { readonly, unref } from "vue";
|
|
2
5
|
import { cloneDeep } from "lodash-es";
|
|
3
6
|
import { getValueByKeyPath } from "@tmagic/utils";
|
|
4
7
|
import dayjs from "dayjs";
|
|
5
|
-
import utc from "dayjs/plugin/utc";
|
|
8
|
+
import utc from "dayjs/plugin/utc.js";
|
|
9
|
+
import { getDesignConfig } from "@tmagic/design/headless";
|
|
6
10
|
//#region packages/form/src/utils/form.ts
|
|
7
|
-
var
|
|
11
|
+
var isTDesignAdapter = () => getDesignConfig("adapterType") === "tdesign-vue-next";
|
|
12
|
+
/**
|
|
13
|
+
* 将 async-validator(Element Plus)风格的 validator 适配到当前 UI 库。
|
|
14
|
+
* TDesign 调用签名为 `(val) => boolean | CustomValidateObj | Promise`,无 callback。
|
|
15
|
+
*/
|
|
16
|
+
var adaptFormValidator = (validator) => {
|
|
17
|
+
return (arg1, arg2, arg3, arg4, arg5) => {
|
|
18
|
+
if (!isTDesignAdapter()) return validator(arg1, arg2, arg3, arg4, arg5);
|
|
19
|
+
const value = arg1;
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
let settled = false;
|
|
22
|
+
const callback = (error) => {
|
|
23
|
+
if (settled) return;
|
|
24
|
+
settled = true;
|
|
25
|
+
const first = Array.isArray(error) ? error[0] : error;
|
|
26
|
+
if (first) resolve({
|
|
27
|
+
result: false,
|
|
28
|
+
message: typeof first === "string" ? first : first.message
|
|
29
|
+
});
|
|
30
|
+
else resolve(true);
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
const result = validator(void 0, value, callback);
|
|
34
|
+
if (result && typeof result.then === "function") Promise.resolve(result).then(() => {
|
|
35
|
+
if (!settled) callback();
|
|
36
|
+
}, (err) => {
|
|
37
|
+
callback(err instanceof Error ? err : new Error(String(err)));
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
callback(e instanceof Error ? e : new Error(String(e)));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
var TABLE_SELECT_TYPES = /* @__PURE__ */ new Set(["table-select", "tableSelect"]);
|
|
8
46
|
var isTableSelect = (type) => typeof type === "string" && TABLE_SELECT_TYPES.has(type);
|
|
9
47
|
var asyncLoadConfig = (value, initValue, { asyncLoad, name, type }) => {
|
|
10
48
|
if (type === "html" && typeof asyncLoad === "object" && typeof name !== "undefined") {
|
|
@@ -12,7 +50,7 @@ var asyncLoadConfig = (value, initValue, { asyncLoad, name, type }) => {
|
|
|
12
50
|
value.asyncLoad = typeof initValue.asyncLoad === "object" ? initValue.asyncLoad : asyncLoad;
|
|
13
51
|
}
|
|
14
52
|
};
|
|
15
|
-
var MULTIPLE_VALUE_TYPES = new Set([
|
|
53
|
+
var MULTIPLE_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
16
54
|
"checkbox-group",
|
|
17
55
|
"checkboxGroup",
|
|
18
56
|
"table",
|
|
@@ -80,17 +118,94 @@ var getDefaultValue = function(mForm, { defaultValue, type, filter, multiple, na
|
|
|
80
118
|
if (type === "daterange" && !names) return [];
|
|
81
119
|
return "";
|
|
82
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* formState 中与表单 props 无关的公共部分:字段注册表、消息组件、post 请求。
|
|
123
|
+
*
|
|
124
|
+
* 由渲染式表单(`Form.vue`)与无渲染校验(`createHeadlessFormState`)共用,
|
|
125
|
+
* 保证两者提供给字段配置(`onChange` / `validator` / 异步 options 等)的能力一致。
|
|
126
|
+
*/
|
|
127
|
+
var fallbackMessage = {
|
|
128
|
+
error: (msg) => console.error(msg),
|
|
129
|
+
success: (msg) => console.log(msg),
|
|
130
|
+
warning: (msg) => console.warn(msg),
|
|
131
|
+
info: (msg) => console.info(msg),
|
|
132
|
+
closeAll: () => void 0
|
|
133
|
+
};
|
|
134
|
+
var fallbackMessageBox = {
|
|
135
|
+
alert: (msg) => console.log(msg),
|
|
136
|
+
confirm: (msg) => console.log(msg),
|
|
137
|
+
close: (msg) => console.log(msg)
|
|
138
|
+
};
|
|
139
|
+
var createFormStateBase = (ui) => {
|
|
140
|
+
const fields = /* @__PURE__ */ new Map();
|
|
141
|
+
const requestFuc = getConfig("request");
|
|
142
|
+
return {
|
|
143
|
+
fields,
|
|
144
|
+
setField: (prop, field) => fields.set(prop, field),
|
|
145
|
+
getField: (prop) => fields.get(prop),
|
|
146
|
+
deleteField: (prop) => fields.delete(prop),
|
|
147
|
+
$messageBox: ui?.$messageBox ?? fallbackMessageBox,
|
|
148
|
+
$message: ui?.$message ?? fallbackMessage,
|
|
149
|
+
post: (options) => {
|
|
150
|
+
if (requestFuc) return requestFuc({
|
|
151
|
+
method: "POST",
|
|
152
|
+
...options
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* 配置项的 `name` 是否可用于从 model 中下钻取值。
|
|
159
|
+
*
|
|
160
|
+
* 供 `Container.vue` 与无渲染校验(`collectValidatableFields`)共用,两者对「该不该下钻」
|
|
161
|
+
* 的判断必须一致。
|
|
162
|
+
*/
|
|
163
|
+
var isValidName = (name) => {
|
|
164
|
+
const valueType = typeof name;
|
|
165
|
+
if (valueType !== "string" && valueType !== "symbol" && valueType !== "number") return false;
|
|
166
|
+
if (name === "") return false;
|
|
167
|
+
if (valueType === "number") return name >= 0;
|
|
168
|
+
return true;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* 在 `prop` 后追加一段路径。
|
|
172
|
+
*
|
|
173
|
+
* 用于容器把 `prop` 透传给子级时再拼上索引 / key(如 group-list 的行、table 的行)。
|
|
174
|
+
*/
|
|
175
|
+
var appendProp = (prop = "", key) => `${prop}${prop ? "." : ""}${key}`;
|
|
176
|
+
/**
|
|
177
|
+
* 由父级 `prop` 与配置项 `name` 拼出该配置项的完整 `prop`(即 FormItem 的 prop)。
|
|
178
|
+
*
|
|
179
|
+
* 调用方传入的 name 已按 `config.name || ''` 归一化,空 name 表示该配置项不占据值路径上的一层,
|
|
180
|
+
* 此时沿用父级 `prop`。
|
|
181
|
+
*/
|
|
182
|
+
var getItemProp = (prop = "", name = "") => name === "" ? prop ?? "" : appendProp(prop, name);
|
|
183
|
+
/**
|
|
184
|
+
* 归一化配置项的 `type`:求值动态 type、剥离容器语义的 type、驼峰转中划线、补默认值。
|
|
185
|
+
*
|
|
186
|
+
* 供 `Container.vue` 与无渲染校验共用,保证两者分派到的 type 完全一致。
|
|
187
|
+
*/
|
|
188
|
+
var resolveItemType = (mForm, config, props) => {
|
|
189
|
+
let type = "type" in (config || {}) ? config.type : "";
|
|
190
|
+
type = type && filterFunction(mForm, type, props);
|
|
191
|
+
if (type === "form" || type === "container") return "";
|
|
192
|
+
return type?.replace(/([A-Z])/g, "-$1").toLowerCase() || (config?.items ? "" : "text");
|
|
193
|
+
};
|
|
83
194
|
var filterFunction = (mForm, config, props) => {
|
|
84
|
-
if (typeof config === "function")
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
195
|
+
if (typeof config === "function") {
|
|
196
|
+
const formValue = readonly(mForm?.values || props.model);
|
|
197
|
+
return config(mForm, {
|
|
198
|
+
values: readonly(mForm?.initValues || {}),
|
|
199
|
+
model: readonly(props.model),
|
|
200
|
+
parent: readonly(mForm?.parentValues || {}),
|
|
201
|
+
formValue,
|
|
202
|
+
formValues: formValue,
|
|
203
|
+
prop: props.prop,
|
|
204
|
+
config: props.config,
|
|
205
|
+
index: props.index,
|
|
206
|
+
getFormValue: (prop) => getValueByKeyPath(prop, mForm?.values || props.model)
|
|
207
|
+
});
|
|
208
|
+
}
|
|
94
209
|
return config;
|
|
95
210
|
};
|
|
96
211
|
var display = function(mForm, config, props) {
|
|
@@ -99,13 +214,18 @@ var display = function(mForm, config, props) {
|
|
|
99
214
|
if (config === false) return false;
|
|
100
215
|
return true;
|
|
101
216
|
};
|
|
102
|
-
var
|
|
103
|
-
rules = cloneDeep(
|
|
217
|
+
var buildRules = function(mForm, r = [], props, typeMatchValid, adapt = (validator) => validator) {
|
|
218
|
+
let rules = cloneDeep(r);
|
|
104
219
|
if (typeof rules === "object" && !Array.isArray(rules)) rules = [rules];
|
|
220
|
+
if (unref(typeMatchValid) && !rules.some((r) => typeof r.typeMatch !== "undefined")) rules.push({ typeMatch: true });
|
|
105
221
|
return rules.map((item) => {
|
|
222
|
+
if (item.typeMatch) {
|
|
223
|
+
item.validator = adapt(createTypeMatchValidator(mForm, props, item));
|
|
224
|
+
return item;
|
|
225
|
+
}
|
|
106
226
|
if (typeof item.validator === "function") {
|
|
107
227
|
const fnc = item.validator;
|
|
108
|
-
item.validator = (rule, value, callback, source, options) => fnc({
|
|
228
|
+
item.validator = adapt((rule, value, callback, source, options) => fnc({
|
|
109
229
|
rule,
|
|
110
230
|
value: props.config.names ? props.model : value,
|
|
111
231
|
callback,
|
|
@@ -118,11 +238,26 @@ var getRules = function(mForm, rules = [], props) {
|
|
|
118
238
|
formValue: mForm?.values || props.model,
|
|
119
239
|
prop: props.prop,
|
|
120
240
|
config: props.config
|
|
121
|
-
}, mForm);
|
|
241
|
+
}, mForm));
|
|
122
242
|
}
|
|
123
243
|
return item;
|
|
244
|
+
}).filter((item) => {
|
|
245
|
+
if (item.typeMatch === false && typeof item.validator !== "function") return false;
|
|
246
|
+
return true;
|
|
124
247
|
});
|
|
125
248
|
};
|
|
249
|
+
var getRules = function(mForm, r = [], props, typeMatchValid) {
|
|
250
|
+
return buildRules(mForm, r, props, typeMatchValid, adaptFormValidator);
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* 与 `getRules` 相同,但 validator 保持 async-validator(Element Plus)原生签名,不做 UI 库适配。
|
|
254
|
+
*
|
|
255
|
+
* 供无渲染校验(`validateValues`)使用:它直接把规则交给 async-validator 执行,
|
|
256
|
+
* 不经过 TDesign 的 `validator(val)` 调用约定,因此不能套 `adaptFormValidator`。
|
|
257
|
+
*/
|
|
258
|
+
var getNativeRules = function(mForm, r = [], props, typeMatchValid) {
|
|
259
|
+
return buildRules(mForm, r, props, typeMatchValid);
|
|
260
|
+
};
|
|
126
261
|
var initValue = async (mForm, { initValues, config }) => {
|
|
127
262
|
if (!Array.isArray(config)) throw new Error("config应该为数组");
|
|
128
263
|
const initValuesCopy = cloneDeep(initValues);
|
|
@@ -172,4 +307,4 @@ var createObjectProp = (prop, key, name) => {
|
|
|
172
307
|
return `${[...itemPath, key].join(".")}`;
|
|
173
308
|
};
|
|
174
309
|
//#endregion
|
|
175
|
-
export { createObjectProp, createValues, datetimeFormatter, display, filterFunction, getDataByPage, getRules, initValue, sortArray, sortChange };
|
|
310
|
+
export { adaptFormValidator, appendProp, createFormStateBase, createFormStateProxy, createObjectProp, createValues, datetimeFormatter, display, filterFunction, getDataByPage, getItemProp, getNativeRules, getRules, initValue, isValidName, mergeFormContexts, resolveItemType, sortArray, sortChange };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { unref } from "vue";
|
|
2
|
+
//#region packages/form/src/utils/formStateProxy.ts
|
|
3
|
+
/** 无上下文时共用的空对象,避免每次读取都分配新对象 */
|
|
4
|
+
var EMPTY_CONTEXT = Object.freeze({});
|
|
5
|
+
/**
|
|
6
|
+
* 按优先级分层合并上下文,**靠后的层优先**。
|
|
7
|
+
*
|
|
8
|
+
* 用读穿 Proxy 而非 `{ ...a, ...b }`:展开会立即执行 accessor,
|
|
9
|
+
* 而宿主允许用 `{ get stage() { ... } }` 这类读时求值的描述符。
|
|
10
|
+
*/
|
|
11
|
+
var mergeFormContexts = (...layers) => {
|
|
12
|
+
const stack = layers.filter((layer) => Boolean(layer));
|
|
13
|
+
if (stack.length === 0) return EMPTY_CONTEXT;
|
|
14
|
+
if (stack.length === 1) return stack[0];
|
|
15
|
+
const ordered = stack.slice().reverse();
|
|
16
|
+
const owner = (k) => ordered.find((layer) => Reflect.has(layer, k));
|
|
17
|
+
return new Proxy({}, {
|
|
18
|
+
get: (_t, k) => owner(k)?.[k],
|
|
19
|
+
has: (_t, k) => Boolean(owner(k)),
|
|
20
|
+
ownKeys: () => [...new Set(ordered.flatMap((layer) => Reflect.ownKeys(layer)))],
|
|
21
|
+
getOwnPropertyDescriptor: (_t, k) => {
|
|
22
|
+
for (const layer of ordered) {
|
|
23
|
+
const descriptor = Object.getOwnPropertyDescriptor(layer, k);
|
|
24
|
+
if (descriptor) return {
|
|
25
|
+
...descriptor,
|
|
26
|
+
configurable: true
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* 存量下发配置沿用 Vue2 时代的写法,把回调第一个参数当组件实例用,靠 `vm.mForm.xxx`
|
|
34
|
+
* 取表单状态。这类配置除了读,还会往上面挂方法做跨字段通信,例如:
|
|
35
|
+
*
|
|
36
|
+
* ```js
|
|
37
|
+
* vm.mForm.checkPropertyLimit = async (...) => { ... } // 一个字段的 validator 里挂
|
|
38
|
+
* await vm.mForm.checkPropertyLimit(...) // 另一处再取出来调用
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* 所以 `mForm` 必须能指回某个 formState:读能落到 core / context,写能经 `set` trap
|
|
42
|
+
* 落到 coreState 并持久化。
|
|
43
|
+
*
|
|
44
|
+
* 优先级是 core > context > 合成自引用,三者都是刻意的:
|
|
45
|
+
*
|
|
46
|
+
* - **core**:`formState.mForm = formState` 这类直写(如 FormPreview 用自建 services 时)
|
|
47
|
+
* 必须最优先。
|
|
48
|
+
* - **context**:唯一的生产者是把父 formState 整体当 context 传下来的嵌套表单
|
|
49
|
+
* (ComponentForm)。此时 `mForm` 命中 context 指向**父表单**,与 `extendState` 时代
|
|
50
|
+
* 把父 formState 并入子状态的结果一致,跨字段通信仍落在同一份对象上。父 formState
|
|
51
|
+
* 本身也是 Proxy,写入照样持久化,所以这里让 context 赢是对的,别「修正」成指向子表单。
|
|
52
|
+
* - **合成自引用**:前两者都没有时才兜底,让 `vm.mForm.xxx = fn` 落到自己的 coreState。
|
|
53
|
+
*
|
|
54
|
+
* 由此推出一条约束:**不要往 context 里塞普通对象充当 `mForm`**。context 通常是 computed
|
|
55
|
+
* 产物,依赖一变就重建,挂上去的方法会静默丢失。要么直写 core,要么什么都不放交给兜底。
|
|
56
|
+
*
|
|
57
|
+
* 枚举语义上合成的自引用表现得像原型链上的属性:`'mForm' in state` 为真,但不出现在
|
|
58
|
+
* `Object.keys(state)` 里,`getOwnPropertyDescriptor` 也返回 undefined。这是故意的——
|
|
59
|
+
* 按扩展字段打包 formState 的调用方(如发给 AI 的逻辑)会因循环引用炸掉。
|
|
60
|
+
*/
|
|
61
|
+
var SELF_REF_KEY = "mForm";
|
|
62
|
+
/**
|
|
63
|
+
* 将 coreState 与宿主业务上下文关联:读取时优先 core,miss 再读穿到 context。
|
|
64
|
+
*
|
|
65
|
+
* - `get` 用属性访问而非 `Reflect.get(t, k, receiver)`,避免破坏 Vue reactive 的 `__v_raw`;
|
|
66
|
+
* - symbol 键一律只走 core,四个 trap 保持一致。context 是业务数据袋,不承载 symbol 键,
|
|
67
|
+
* 把 Vue / 工具链的内部 symbol 隔离在 core 上才能让 `toRaw` / `isReactive` 判定正确;
|
|
68
|
+
* - `ownKeys` + `getOwnPropertyDescriptor` 保证 `Object.entries(formState)` 能枚举到扩展字段
|
|
69
|
+
* (admin-web-next 的 `pickPanelFormStateExtendFields` 依赖此语义);
|
|
70
|
+
* - `set` 写入 coreState,第三方 `formState.xxx = v` 仍生效且优先于 context。
|
|
71
|
+
*
|
|
72
|
+
* 独立成文件,避免与 `form.ts` ↔ `typeMatch.ts` 形成循环依赖。
|
|
73
|
+
*/
|
|
74
|
+
var createFormStateProxy = (coreState, getContext) => {
|
|
75
|
+
const resolve = () => {
|
|
76
|
+
const ctx = (typeof getContext === "function" ? getContext() : unref(getContext)) || EMPTY_CONTEXT;
|
|
77
|
+
return ctx === proxy ? EMPTY_CONTEXT : ctx;
|
|
78
|
+
};
|
|
79
|
+
const proxy = new Proxy(coreState, {
|
|
80
|
+
get(t, k) {
|
|
81
|
+
if (typeof k === "symbol") return Reflect.get(t, k);
|
|
82
|
+
const v = t[k];
|
|
83
|
+
if (v !== void 0 || Reflect.has(t, k)) return v;
|
|
84
|
+
const ctx = resolve();
|
|
85
|
+
if (k in ctx) return ctx[k];
|
|
86
|
+
return k === SELF_REF_KEY ? proxy : void 0;
|
|
87
|
+
},
|
|
88
|
+
set(t, k, value) {
|
|
89
|
+
t[k] = value;
|
|
90
|
+
return true;
|
|
91
|
+
},
|
|
92
|
+
has: (t, k) => Reflect.has(t, k) || typeof k !== "symbol" && (k in resolve() || k === SELF_REF_KEY),
|
|
93
|
+
ownKeys: (t) => [.../* @__PURE__ */ new Set([...Reflect.ownKeys(t), ...Reflect.ownKeys(resolve()).filter((k) => typeof k !== "symbol")])],
|
|
94
|
+
getOwnPropertyDescriptor: (t, k) => {
|
|
95
|
+
const own = Reflect.getOwnPropertyDescriptor(t, k);
|
|
96
|
+
if (own) return own;
|
|
97
|
+
if (typeof k === "symbol") return void 0;
|
|
98
|
+
const ctx = resolve();
|
|
99
|
+
if (!(k in ctx)) return void 0;
|
|
100
|
+
return {
|
|
101
|
+
configurable: true,
|
|
102
|
+
enumerable: true,
|
|
103
|
+
writable: true,
|
|
104
|
+
value: ctx[k]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return proxy;
|
|
109
|
+
};
|
|
110
|
+
//#endregion
|
|
111
|
+
export { createFormStateProxy, mergeFormContexts };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { clearFieldInnerConfigs, deleteFieldInnerConfig, registerFieldInnerConfig } from "./fieldInnerConfig.js";
|
|
2
|
+
import { clearLeafFieldTypes, deleteLeafFieldType, registerLeafFieldType } from "./fieldValueEffects.js";
|
|
3
|
+
import { clearTypeMatchRules, deleteTypeMatchRule, registerTypeMatchRule } from "./typeMatch.js";
|
|
4
|
+
import { clearContainerWalkers, deleteContainerWalker, registerContainerWalker } from "./collectFields.js";
|
|
5
|
+
import { toLine } from "@tmagic/utils";
|
|
6
|
+
//#region packages/form/src/utils/registerField.ts
|
|
7
|
+
var extraComponents = /* @__PURE__ */ new Map();
|
|
8
|
+
var builtInComponents = /* @__PURE__ */ new Map();
|
|
9
|
+
var applyVueComponent = (type, component, app, builtIn, kind) => {
|
|
10
|
+
const key = toLine(type);
|
|
11
|
+
if (builtIn) builtInComponents.set(key, component);
|
|
12
|
+
else extraComponents.set(key, component);
|
|
13
|
+
app?.component(`m-${kind}-${key}`, component);
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* 把多份字段登记表按 type 浅合并。后一份只覆盖自己带了的 key,未出现的 key 保留前一份。
|
|
17
|
+
*
|
|
18
|
+
* 安装插件时用:Node 侧只登记 `headless`,浏览器再补 `component` / `container`。
|
|
19
|
+
*
|
|
20
|
+
* @param tables - 待合并的登记表,`undefined` 会被跳过
|
|
21
|
+
* @returns 合并后的登记表
|
|
22
|
+
*/
|
|
23
|
+
var mergeFieldOptions = (...tables) => {
|
|
24
|
+
const result = {};
|
|
25
|
+
for (const table of tables) {
|
|
26
|
+
if (!table) continue;
|
|
27
|
+
for (const [type, options] of Object.entries(table)) result[type] = {
|
|
28
|
+
...result[type],
|
|
29
|
+
...pickDefinedFieldOptions(options)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
var FIELD_OPTION_KEYS = [
|
|
35
|
+
"component",
|
|
36
|
+
"container",
|
|
37
|
+
"effect",
|
|
38
|
+
"walk",
|
|
39
|
+
"innerConfig",
|
|
40
|
+
"typeMatch"
|
|
41
|
+
];
|
|
42
|
+
var pickDefinedFieldOptions = (options) => {
|
|
43
|
+
if (!options) return {};
|
|
44
|
+
const next = {};
|
|
45
|
+
for (const key of FIELD_OPTION_KEYS) if (options[key] !== void 0) next[key] = options[key];
|
|
46
|
+
return next;
|
|
47
|
+
};
|
|
48
|
+
var extraFieldOptions = /* @__PURE__ */ new Map();
|
|
49
|
+
var builtInFieldOptions = /* @__PURE__ */ new Map();
|
|
50
|
+
var removeFormComponent = (type) => {
|
|
51
|
+
extraComponents.delete(toLine(type));
|
|
52
|
+
};
|
|
53
|
+
var clearFormComponents = () => {
|
|
54
|
+
extraComponents.clear();
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* 按字段 type 取已登记的渲染组件(`codeSelect` 与 `code-select` 等价)。
|
|
58
|
+
*
|
|
59
|
+
* @param type - 字段 type
|
|
60
|
+
* @returns 已登记的 Vue 组件;未登记则为 `undefined`
|
|
61
|
+
*/
|
|
62
|
+
var getField = (type) => {
|
|
63
|
+
const key = toLine(type);
|
|
64
|
+
return extraComponents.get(key) ?? builtInComponents.get(key);
|
|
65
|
+
};
|
|
66
|
+
var registerFieldImpl = (type, options, app, builtIn) => {
|
|
67
|
+
if (typeof type !== "string" || !type) return;
|
|
68
|
+
const key = toLine(type);
|
|
69
|
+
const store = builtIn ? builtInFieldOptions : extraFieldOptions;
|
|
70
|
+
const incoming = pickDefinedFieldOptions(options);
|
|
71
|
+
const merged = {
|
|
72
|
+
...store.get(key),
|
|
73
|
+
...incoming
|
|
74
|
+
};
|
|
75
|
+
store.set(key, merged);
|
|
76
|
+
if (incoming.walk && (incoming.innerConfig || typeof incoming.effect === "function")) console.warn(`[MForm] registerField("${key}"): walk is set together with innerConfig/effect; headless validation will use walk and innerConfig/effect will be ignored.`);
|
|
77
|
+
if (incoming.component && incoming.container) console.warn(`[MForm] registerField("${key}"): component and container are both set; getField will use container, and both m-fields-* / m-form-* will be registered.`);
|
|
78
|
+
if (merged.component) applyVueComponent(type, merged.component, app, builtIn, "fields");
|
|
79
|
+
if (merged.container) applyVueComponent(type, merged.container, app, builtIn, "form");
|
|
80
|
+
if (merged.typeMatch) registerTypeMatchRule(type, merged.typeMatch, builtIn);
|
|
81
|
+
if (merged.walk) {
|
|
82
|
+
registerContainerWalker(type, merged.walk, builtIn);
|
|
83
|
+
if (!builtIn) {
|
|
84
|
+
deleteLeafFieldType(type);
|
|
85
|
+
deleteFieldInnerConfig(type);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!builtIn) deleteContainerWalker(type);
|
|
90
|
+
if (merged.innerConfig) {
|
|
91
|
+
registerFieldInnerConfig(type, merged.innerConfig, builtIn);
|
|
92
|
+
if (typeof merged.effect === "function") registerLeafFieldType(type, merged.effect, builtIn);
|
|
93
|
+
else if (!builtIn) deleteLeafFieldType(type);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (merged.container && !merged.component && typeof merged.effect !== "function") {
|
|
97
|
+
if (!builtIn) deleteLeafFieldType(type);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!builtIn) deleteFieldInnerConfig(type);
|
|
101
|
+
registerLeafFieldType(type, merged.effect, builtIn);
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* 登记一个字段 type 在无渲染校验里的行为,以及可选的渲染组件。
|
|
105
|
+
*
|
|
106
|
+
* `type` 会按 Container 的规则归一化为中划线形式(`codeSelect` 与 `code-select` 等价)。
|
|
107
|
+
* 对同一 type 重复登记按字段浅合并:后一次只覆盖自己传入的 key,未传入的 key 保留。
|
|
108
|
+
* 传入 `app` 且带了 `component` / `container` 时,会同步 `app.component('m-fields-*'` / `'m-form-*')`。
|
|
109
|
+
*
|
|
110
|
+
* @param type - 字段 type
|
|
111
|
+
* @param [options] - 登记项;省略或空对象视为叶子
|
|
112
|
+
* @param [app] - Vue 应用;省略则不调用 `app.component`
|
|
113
|
+
*/
|
|
114
|
+
var registerField = (type, options, app) => {
|
|
115
|
+
registerFieldImpl(type, options, app, false);
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* 批量登记字段。
|
|
119
|
+
*
|
|
120
|
+
* @param fields - type 到登记项的映射
|
|
121
|
+
* @param [app] - Vue 应用;省略则不调用 `app.component`
|
|
122
|
+
*/
|
|
123
|
+
var registerFields = (fields, app) => {
|
|
124
|
+
for (const [type, options] of Object.entries(fields)) registerFieldImpl(type, options, app, false);
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* 登记内置字段(`clearFields` / `unregisterField` 不会清掉)。
|
|
128
|
+
*
|
|
129
|
+
* @param fields - type 到登记项的映射
|
|
130
|
+
* @param [app] - Vue 应用;省略则不调用 `app.component`
|
|
131
|
+
*/
|
|
132
|
+
var registerBuiltInFields = (fields, app) => {
|
|
133
|
+
for (const [type, options] of Object.entries(fields)) registerFieldImpl(type, options, app, true);
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* 删除业务侧对指定 type 的登记(不影响内置;主要用于单测)。
|
|
137
|
+
*
|
|
138
|
+
* @param type - 字段 type
|
|
139
|
+
*/
|
|
140
|
+
var unregisterField = (type) => {
|
|
141
|
+
extraFieldOptions.delete(toLine(type));
|
|
142
|
+
deleteLeafFieldType(type);
|
|
143
|
+
deleteFieldInnerConfig(type);
|
|
144
|
+
deleteTypeMatchRule(type);
|
|
145
|
+
deleteContainerWalker(type);
|
|
146
|
+
removeFormComponent(type);
|
|
147
|
+
};
|
|
148
|
+
/** 清空业务侧登记(不影响内置;主要用于单测)。 */
|
|
149
|
+
var clearFields = () => {
|
|
150
|
+
extraFieldOptions.clear();
|
|
151
|
+
clearLeafFieldTypes();
|
|
152
|
+
clearFieldInnerConfigs();
|
|
153
|
+
clearTypeMatchRules();
|
|
154
|
+
clearContainerWalkers();
|
|
155
|
+
clearFormComponents();
|
|
156
|
+
};
|
|
157
|
+
//#endregion
|
|
158
|
+
export { clearFields, getField, mergeFieldOptions, registerBuiltInFields, registerField, registerFields, unregisterField };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { validateValues } from "./validateValues.js";
|
|
2
|
+
import { cloneDeep } from "lodash-es";
|
|
3
|
+
//#region packages/form/src/utils/submitHeadless.ts
|
|
4
|
+
var throwIfAborted = (signal, fnName) => {
|
|
5
|
+
if (signal?.aborted) throw signal.reason ?? /* @__PURE__ */ new Error(`${fnName} aborted`);
|
|
6
|
+
};
|
|
7
|
+
var throwIfDialog = (dialog, fnName) => {
|
|
8
|
+
if (dialog) throw new Error(`[MForm] ${fnName}({ dialog: true }) is not available from @tmagic/form/headless. Import from @tmagic/form instead.`);
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* `submitForm` 与 `validateForm` 共用的无渲染校验流程:中断检查 → `validateValues`。
|
|
12
|
+
*/
|
|
13
|
+
var validateWithoutRender = async (fnName, options) => {
|
|
14
|
+
const { signal, config, initValues, parentValues, keyProp, typeMatchValid, useFieldTextInError, context } = options;
|
|
15
|
+
throwIfAborted(signal, fnName);
|
|
16
|
+
const result = await validateValues({
|
|
17
|
+
config,
|
|
18
|
+
initValues,
|
|
19
|
+
parentValues,
|
|
20
|
+
keyProp,
|
|
21
|
+
popperClass: options.popperClass,
|
|
22
|
+
typeMatchValid,
|
|
23
|
+
useFieldTextInError,
|
|
24
|
+
context
|
|
25
|
+
});
|
|
26
|
+
throwIfAborted(signal, fnName);
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* 以命令式方式对一份「表单配置 + 值」做一次校验并取回表单值(无渲染)。
|
|
31
|
+
*
|
|
32
|
+
* `@tmagic/form/headless` 不支持 `dialog: true`。
|
|
33
|
+
*/
|
|
34
|
+
var submitForm = async (options) => {
|
|
35
|
+
throwIfDialog(options.dialog, "submitForm");
|
|
36
|
+
const validated = await validateWithoutRender("submitForm", options);
|
|
37
|
+
if (validated.error) throw new Error(validated.error);
|
|
38
|
+
const values = options.native ? validated.values : cloneDeep(validated.values);
|
|
39
|
+
return options.returnChangeRecords ? {
|
|
40
|
+
values,
|
|
41
|
+
changeRecords: []
|
|
42
|
+
} : values;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* 以命令式方式对一份「表单配置 + 值」做一次静默校验(无渲染)。
|
|
46
|
+
*
|
|
47
|
+
* `@tmagic/form/headless` 不支持 `dialog: true`。
|
|
48
|
+
*/
|
|
49
|
+
var validateForm = async (options) => {
|
|
50
|
+
throwIfDialog(options.dialog, "validateForm");
|
|
51
|
+
return (await validateWithoutRender("validateForm", options)).error;
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { submitForm, validateForm, validateWithoutRender };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { cloneDeep } from "lodash-es";
|
|
2
|
+
import { isNumber } from "@tmagic/utils";
|
|
3
|
+
//#region packages/form/src/utils/tableGroupList.ts
|
|
4
|
+
/**
|
|
5
|
+
* table / group-list 是同一份配置的两种展示形态,可以互相切换(`TableGroupList.vue`)。
|
|
6
|
+
*
|
|
7
|
+
* 本模块收口两种形态之间的配置派生,以及各形态下「子项配置怎么算出来」的规则,
|
|
8
|
+
* 供渲染(`TableGroupList.vue` / `GroupListItem.vue` / `useTableColumns.ts`)与无渲染校验
|
|
9
|
+
* (`collectValidatableFields`)共用,避免两条链路各写一份而产生偏差。
|
|
10
|
+
*/
|
|
11
|
+
/** group-list 形态的 type(兼容驼峰与中划线两种写法) */
|
|
12
|
+
var isGroupListType = (type) => type === "groupList" || type === "group-list";
|
|
13
|
+
/**
|
|
14
|
+
* 归一成带单位的 CSS 长度。
|
|
15
|
+
*
|
|
16
|
+
* 配置常常来自 JSON 或表单输入,`'48'` 这类无单位数字串很常见;直接写进 CSS 变量会让
|
|
17
|
+
* 嵌套层的 `calc()` 整体失效、吸顶静默失灵,所以纯数字一律补 `px`。
|
|
18
|
+
*/
|
|
19
|
+
var toCssLength = (height) => {
|
|
20
|
+
const value = `${height ?? ""}`.trim();
|
|
21
|
+
if (!value) return void 0;
|
|
22
|
+
if (isNumber(value)) return `${value}px`;
|
|
23
|
+
return typeof height === "number" ? void 0 : value;
|
|
24
|
+
};
|
|
25
|
+
/** header.sticky 开启时把 height 写成 CSS 变量,给嵌套吸顶偏移用 */
|
|
26
|
+
var getGroupListHeaderVars = (header) => {
|
|
27
|
+
if (!header?.sticky) return void 0;
|
|
28
|
+
const height = toCssLength(header.height);
|
|
29
|
+
return height ? { "--m-group-list-header-height": height } : void 0;
|
|
30
|
+
};
|
|
31
|
+
/** 按 label 文案长度估算 label 宽度(中文按 20px、其他按 8px,最小 80px) */
|
|
32
|
+
var calcLabelWidth = (label) => {
|
|
33
|
+
if (!label) return "0px";
|
|
34
|
+
const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
|
|
35
|
+
const chLength = label.length - zhLength;
|
|
36
|
+
return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
|
|
37
|
+
};
|
|
38
|
+
/** 由 group-list 形态的配置派生出 table 形态所需的配置;本身已是 table 形态则原样返回 */
|
|
39
|
+
var toTableConfig = (config) => {
|
|
40
|
+
if (!isGroupListType(config.type)) return config;
|
|
41
|
+
const source = config;
|
|
42
|
+
return {
|
|
43
|
+
...config,
|
|
44
|
+
type: "table",
|
|
45
|
+
groupItems: source.items,
|
|
46
|
+
items: source.tableItems || source.items.map((item) => ({
|
|
47
|
+
...item,
|
|
48
|
+
label: item.label || item.text,
|
|
49
|
+
text: null
|
|
50
|
+
}))
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/** 由 table 形态的配置派生出 group-list 形态所需的配置;本身已是 group-list 形态则原样返回 */
|
|
54
|
+
var toGroupListConfig = (config) => {
|
|
55
|
+
if (isGroupListType(config.type)) return config;
|
|
56
|
+
const source = config;
|
|
57
|
+
return {
|
|
58
|
+
...config,
|
|
59
|
+
type: "groupList",
|
|
60
|
+
tableItems: source.items,
|
|
61
|
+
items: source.groupItems || source.items.map((item) => {
|
|
62
|
+
const text = item.text || item.label;
|
|
63
|
+
return {
|
|
64
|
+
...item,
|
|
65
|
+
text,
|
|
66
|
+
labelWidth: calcLabelWidth(text),
|
|
67
|
+
span: item.span || 12
|
|
68
|
+
};
|
|
69
|
+
})
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* group-list 每一行渲染的 row 容器配置(`GroupListItem.vue` 渲染的 `Container` 的 config)。
|
|
74
|
+
*
|
|
75
|
+
* `keyProp` 由调用方从 `mForm.keyProp` 取,默认 `__key`。
|
|
76
|
+
*/
|
|
77
|
+
var getGroupListRowConfig = (config, index, keyProp) => {
|
|
78
|
+
const key = keyProp || "__key";
|
|
79
|
+
return {
|
|
80
|
+
type: "row",
|
|
81
|
+
span: config.span || 24,
|
|
82
|
+
items: config.items,
|
|
83
|
+
labelWidth: config.labelWidth,
|
|
84
|
+
labelPosition: config.labelPosition,
|
|
85
|
+
[key]: `${config[key]}${String(index)}`
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* 列是否会被渲染成一列(`hidden` 列不渲染,`display` 为假的列也不渲染)。
|
|
90
|
+
*
|
|
91
|
+
* `display` 的求值上下文是表格自身的 props,由调用方通过 `evalDisplay` 注入。
|
|
92
|
+
*/
|
|
93
|
+
var isTableColumnRendered = (column, evalDisplay) => column.type !== "hidden" && Boolean(evalDisplay(column.display));
|
|
94
|
+
/**
|
|
95
|
+
* 单元格内 `Container` 实际收到的列配置:按行求值 `itemsFunction`,并去掉已在列层面消费过的 `display`。
|
|
96
|
+
*/
|
|
97
|
+
var makeTableColumnConfig = (column, row) => {
|
|
98
|
+
const newConfig = cloneDeep(column);
|
|
99
|
+
if (typeof column.itemsFunction === "function") newConfig.items = column.itemsFunction(row);
|
|
100
|
+
delete newConfig.display;
|
|
101
|
+
return newConfig;
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
export { calcLabelWidth, getGroupListHeaderVars, getGroupListRowConfig, isGroupListType, isTableColumnRendered, makeTableColumnConfig, toGroupListConfig, toTableConfig };
|