@tmagic/form 1.8.0-manmanyu.9 → 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/src/containers/Tabs.vue
CHANGED
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"
|
|
71
71
|
:size="size"
|
|
72
72
|
:label-width="tab.labelWidth || labelWidth"
|
|
73
|
+
:label-position="tab.labelPosition || labelPosition"
|
|
73
74
|
:expand-more="expandMore"
|
|
74
75
|
@change="changeHandler"
|
|
75
76
|
@addDiffCount="onAddDiffCount(Number(tabIndex))"
|
|
@@ -85,6 +86,7 @@ import { isEmpty } from 'lodash-es';
|
|
|
85
86
|
import { getDesignConfig, TMagicBadge } from '@tmagic/design';
|
|
86
87
|
|
|
87
88
|
import type { ContainerChangeEventData, FormState, TabConfig, TabPaneConfig } from '../schema';
|
|
89
|
+
import { applyMountValueEffects } from '../utils/collectFields';
|
|
88
90
|
import { display as displayFunc, filterFunction, initValue } from '../utils/form';
|
|
89
91
|
|
|
90
92
|
import Container from './Container.vue';
|
|
@@ -106,6 +108,7 @@ const props = withDefaults(
|
|
|
106
108
|
name: string;
|
|
107
109
|
size?: string;
|
|
108
110
|
labelWidth?: string;
|
|
111
|
+
labelPosition?: 'top' | 'left' | 'right';
|
|
109
112
|
prop?: string;
|
|
110
113
|
expandMore?: boolean;
|
|
111
114
|
disabled?: boolean;
|
|
@@ -203,6 +206,13 @@ const onTabAdd = async () => {
|
|
|
203
206
|
prop: props.prop,
|
|
204
207
|
config: props.config,
|
|
205
208
|
});
|
|
209
|
+
// 自定义回调可能直接 push 原始对象;对现有标签页再规整一遍(effect 必须幂等)
|
|
210
|
+
const tabsModel = props.model[props.name];
|
|
211
|
+
if (Array.isArray(tabsModel) && Array.isArray(props.config.items)) {
|
|
212
|
+
for (const tab of tabsModel) {
|
|
213
|
+
applyMountValueEffects(mForm, props.config.items, tab);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
206
216
|
emit('change', props.model[props.name]);
|
|
207
217
|
} else {
|
|
208
218
|
const newObj = await initValue(mForm, {
|
|
@@ -210,6 +220,9 @@ const onTabAdd = async () => {
|
|
|
210
220
|
initValues: {},
|
|
211
221
|
});
|
|
212
222
|
|
|
223
|
+
// 新增标签页的值在挂到表单上之前先规整一遍,与整表初始化走同一份登记表
|
|
224
|
+
applyMountValueEffects(mForm, props.config.items, newObj);
|
|
225
|
+
|
|
213
226
|
newObj.title = `标签${tabs.value.length + 1}`;
|
|
214
227
|
|
|
215
228
|
props.model[props.name].push(newObj);
|
|
@@ -43,7 +43,7 @@ import { cloneDeep } from 'lodash-es';
|
|
|
43
43
|
|
|
44
44
|
import { TMagicButton, TMagicTooltip } from '@tmagic/design';
|
|
45
45
|
|
|
46
|
-
import type { FormState, TableConfig } from '
|
|
46
|
+
import type { FormState, TableConfig } from '@form/schema';
|
|
47
47
|
|
|
48
48
|
const emit = defineEmits(['change']);
|
|
49
49
|
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
>
|
|
9
9
|
<div class="m-fields-table" :class="{ 'm-fields-table-item-extra': config.itemExtra }">
|
|
10
10
|
<span v-if="config.extra" style="color: rgba(0, 0, 0, 0.45)" v-html="config.extra"></span>
|
|
11
|
+
<!-- v-if 必须挂在 Tooltip 上:表格未就绪时 ElOnlyChild 会报 no valid child node found -->
|
|
11
12
|
<TMagicTooltip
|
|
13
|
+
v-if="model[modelName]"
|
|
12
14
|
content="拖拽可排序"
|
|
13
15
|
placement="left-start"
|
|
14
16
|
:disabled="config.dropSort !== true || config.dropSortHandle"
|
|
15
17
|
>
|
|
16
18
|
<TMagicTable
|
|
17
|
-
v-if="model[modelName]"
|
|
18
19
|
ref="tMagicTable"
|
|
19
20
|
style="width: 100%"
|
|
20
21
|
show-header
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
:key="updateKey"
|
|
28
29
|
@select="selectHandle"
|
|
29
30
|
@sort-change="sortChangeHandler"
|
|
31
|
+
:empty-text="`暂无${config.titlePrefix || ''}数据`"
|
|
30
32
|
></TMagicTable>
|
|
31
33
|
</TMagicTooltip>
|
|
32
34
|
|
|
@@ -91,8 +93,8 @@ import { FullScreen } from '@element-plus/icons-vue';
|
|
|
91
93
|
|
|
92
94
|
import { TMagicButton, TMagicPagination, TMagicTable, TMagicTooltip, TMagicUpload } from '@tmagic/design';
|
|
93
95
|
|
|
94
|
-
import type { SortProp } from '
|
|
95
|
-
import { sortChange } from '
|
|
96
|
+
import type { SortProp } from '@form/schema';
|
|
97
|
+
import { sortChange } from '@form/utils/form';
|
|
96
98
|
|
|
97
99
|
import type { TableProps } from './type';
|
|
98
100
|
import { useFullscreen } from './useFullscreen';
|
|
@@ -4,7 +4,7 @@ import type { default as SortableType, SortableEvent } from 'sortablejs';
|
|
|
4
4
|
import { type TMagicTable } from '@tmagic/design';
|
|
5
5
|
import type { FormState } from '@tmagic/form-schema';
|
|
6
6
|
|
|
7
|
-
import { sortArray } from '
|
|
7
|
+
import { sortArray } from '@form/utils/form';
|
|
8
8
|
|
|
9
9
|
import type { TableProps } from './type';
|
|
10
10
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { computed, h, inject, type Ref } from 'vue';
|
|
2
2
|
import { WarningFilled } from '@element-plus/icons-vue';
|
|
3
|
-
import { cloneDeep } from 'lodash-es';
|
|
4
3
|
|
|
5
4
|
import { type TableColumnOptions, TMagicIcon, TMagicTooltip } from '@tmagic/design';
|
|
6
|
-
import type { FormItemConfig, FormState
|
|
5
|
+
import type { FormItemConfig, FormState } from '@tmagic/form-schema';
|
|
6
|
+
|
|
7
|
+
import type { ContainerChangeEventData } from '@form/schema';
|
|
8
|
+
import { isGlobalFlat } from '@form/utils/config';
|
|
9
|
+
import { appendProp, display as displayFunc, getDataByPage, sortArray } from '@form/utils/form';
|
|
10
|
+
import { isTableColumnRendered, makeTableColumnConfig } from '@form/utils/tableGroupList';
|
|
7
11
|
|
|
8
|
-
import type { ContainerChangeEventData } from '../../schema';
|
|
9
|
-
import { display as displayFunc, getDataByPage, sortArray } from '../../utils/form';
|
|
10
12
|
import Container from '../Container.vue';
|
|
11
13
|
|
|
12
14
|
import ActionsColumn from './ActionsColumn.vue';
|
|
@@ -64,18 +66,7 @@ export const useTableColumns = (
|
|
|
64
66
|
return props.config.selection;
|
|
65
67
|
});
|
|
66
68
|
|
|
67
|
-
const getProp = (index: number) =>
|
|
68
|
-
return `${props.prop}${props.prop ? '.' : ''}${index + 1 + currentPage.value * pageSize.value - 1}`;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const makeConfig = (config: TableColumnConfig, row: any): TableColumnConfig => {
|
|
72
|
-
const newConfig = cloneDeep(config);
|
|
73
|
-
if (typeof config.itemsFunction === 'function') {
|
|
74
|
-
newConfig.items = config.itemsFunction(row);
|
|
75
|
-
}
|
|
76
|
-
delete newConfig.display;
|
|
77
|
-
return newConfig;
|
|
78
|
-
};
|
|
69
|
+
const getProp = (index: number) => appendProp(props.prop, index + currentPage.value * pageSize.value);
|
|
79
70
|
|
|
80
71
|
const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
|
|
81
72
|
emit('change', props.model, eventData);
|
|
@@ -111,24 +102,26 @@ export const useTableColumns = (
|
|
|
111
102
|
},
|
|
112
103
|
});
|
|
113
104
|
}
|
|
114
|
-
|
|
115
|
-
let actionFixed: 'left' | 'right' | undefined = props.config.fixed === false ? undefined :
|
|
116
|
-
|
|
105
|
+
const defaultFixed: 'left' | 'right' = isGlobalFlat.value ? 'right' : 'left';
|
|
106
|
+
let actionFixed: 'left' | 'right' | undefined = props.config.fixed === false ? undefined : defaultFixed;
|
|
117
107
|
if (typeof props.config.fixed === 'string' && ['left', 'right'].includes(props.config.fixed)) {
|
|
118
108
|
actionFixed = props.config.fixed;
|
|
119
109
|
}
|
|
120
110
|
|
|
111
|
+
const actionFlat = props.config.flat === undefined && isGlobalFlat.value ? true : props.config.flat;
|
|
112
|
+
|
|
121
113
|
const actionColumn = {
|
|
122
114
|
props: {
|
|
123
115
|
label: '操作',
|
|
124
116
|
fixed: actionFixed,
|
|
117
|
+
|
|
125
118
|
width: props.config.operateColWidth ?? (props.config.dropSortHandle && props.config.dropSort ? 132 : 112),
|
|
126
119
|
align: 'center',
|
|
127
120
|
},
|
|
128
121
|
cell: ({ row, $index }: any) =>
|
|
129
122
|
h(ActionsColumn, {
|
|
130
123
|
row,
|
|
131
|
-
flat:
|
|
124
|
+
flat: actionFlat,
|
|
132
125
|
index: $index,
|
|
133
126
|
model: props.model,
|
|
134
127
|
config: props.config,
|
|
@@ -182,7 +175,7 @@ export const useTableColumns = (
|
|
|
182
175
|
}
|
|
183
176
|
|
|
184
177
|
for (const column of props.config.items) {
|
|
185
|
-
if (column
|
|
178
|
+
if (isTableColumnRendered(column, display)) {
|
|
186
179
|
const titleTipValue = titleTip(column.titleTip);
|
|
187
180
|
|
|
188
181
|
columns.push({
|
|
@@ -200,7 +193,7 @@ export const useTableColumns = (
|
|
|
200
193
|
disabled: props.disabled,
|
|
201
194
|
prop: getProp($index),
|
|
202
195
|
rules: column.rules,
|
|
203
|
-
config:
|
|
196
|
+
config: makeTableColumnConfig(column, row) as FormItemConfig,
|
|
204
197
|
model: row,
|
|
205
198
|
lastValues: lastData.value[$index],
|
|
206
199
|
isCompare: props.isCompare,
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
:last-values="lastValues"
|
|
13
13
|
:prop="prop"
|
|
14
14
|
:label-width="labelWidth"
|
|
15
|
+
:label-position="labelPosition"
|
|
15
16
|
:show-index="showIndex"
|
|
16
17
|
:sort-key="sortKey"
|
|
17
18
|
:sort="sort"
|
|
@@ -20,13 +21,8 @@
|
|
|
20
21
|
@addDiffCount="onAddDiffCount"
|
|
21
22
|
@add="onAdd"
|
|
22
23
|
>
|
|
23
|
-
<template #toggle-button>
|
|
24
|
-
<TMagicButton
|
|
25
|
-
v-if="config.enableToggleMode || enableToggleMode"
|
|
26
|
-
:icon="Grid"
|
|
27
|
-
size="small"
|
|
28
|
-
@click="toggleDisplayMode"
|
|
29
|
-
>
|
|
24
|
+
<template #toggle-button v-if="config.enableToggleMode || enableToggleMode">
|
|
25
|
+
<TMagicButton :icon="Grid" size="small" @click="toggleDisplayMode">
|
|
30
26
|
{{ displayMode === 'table' ? '展开配置' : '切换为表格' }}
|
|
31
27
|
</TMagicButton>
|
|
32
28
|
</template>
|
|
@@ -35,19 +31,23 @@
|
|
|
35
31
|
<TMagicButton
|
|
36
32
|
:class="displayMode === 'table' ? 'm-form-table-add-button' : ''"
|
|
37
33
|
:size="addButtonSize"
|
|
38
|
-
:plain="displayMode === 'table'"
|
|
39
34
|
:icon="Plus"
|
|
40
|
-
text
|
|
41
35
|
:disabled="disabled"
|
|
42
|
-
v-bind="
|
|
43
|
-
@click="
|
|
36
|
+
v-bind="addButtonProps"
|
|
37
|
+
@click="handleAdd"
|
|
44
38
|
>
|
|
45
39
|
{{
|
|
46
40
|
currentConfig.addButtonConfig?.text ||
|
|
47
|
-
(displayMode === 'table'
|
|
41
|
+
(displayMode === 'table'
|
|
42
|
+
? `新增一行${groupListConfig.titlePrefix || ''}`
|
|
43
|
+
: `新增${groupListConfig.titlePrefix || ''}`)
|
|
48
44
|
}}
|
|
49
45
|
</TMagicButton>
|
|
50
46
|
</template>
|
|
47
|
+
|
|
48
|
+
<template #title="slotProps" v-if="$slots.title">
|
|
49
|
+
<slot name="title" v-bind="slotProps"></slot>
|
|
50
|
+
</template>
|
|
51
51
|
</component>
|
|
52
52
|
</template>
|
|
53
53
|
|
|
@@ -58,11 +58,14 @@ import { Grid, Plus } from '@element-plus/icons-vue';
|
|
|
58
58
|
import { TMagicButton } from '@tmagic/design';
|
|
59
59
|
import type { GroupListConfig, TableConfig } from '@tmagic/form-schema';
|
|
60
60
|
|
|
61
|
-
import type { ContainerChangeEventData } from '
|
|
61
|
+
import type { ContainerChangeEventData } from '@form/schema';
|
|
62
|
+
import { isGroupListType, toGroupListConfig, toTableConfig } from '@form/utils/tableGroupList';
|
|
63
|
+
|
|
62
64
|
import MFormGroupList from '../GroupList.vue';
|
|
63
65
|
import MFormTable from '../table/Table.vue';
|
|
64
66
|
|
|
65
67
|
import { useAdd } from './useAdd';
|
|
68
|
+
import { useScrollLastItemIntoView } from './useScrollLastItemIntoView';
|
|
66
69
|
|
|
67
70
|
defineOptions({
|
|
68
71
|
name: 'MFormTableGroupList',
|
|
@@ -77,6 +80,7 @@ const props = defineProps<{
|
|
|
77
80
|
name: string;
|
|
78
81
|
prop?: string;
|
|
79
82
|
labelWidth?: string;
|
|
83
|
+
labelPosition?: 'top' | 'left' | 'right';
|
|
80
84
|
disabled?: boolean;
|
|
81
85
|
size?: string;
|
|
82
86
|
enableToggleMode?: true;
|
|
@@ -89,88 +93,58 @@ const emit = defineEmits(['change', 'select', 'addDiffCount']);
|
|
|
89
93
|
|
|
90
94
|
const { addable, newHandler } = useAdd(props, emit);
|
|
91
95
|
|
|
92
|
-
const isGroupListType = (type: string | undefined) => type === 'groupList' || type === 'group-list';
|
|
93
|
-
|
|
94
96
|
const displayMode = ref<'table' | 'groupList'>(isGroupListType(props.config.type) ? 'groupList' : 'table');
|
|
95
97
|
|
|
96
|
-
const calcLabelWidth = (label: string) => {
|
|
97
|
-
if (!label) return '0px';
|
|
98
|
-
const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
|
|
99
|
-
const chLength = label.length - zhLength;
|
|
100
|
-
return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
98
|
// 当原始 config 是 table 形态时,table 模式直接透传;
|
|
104
|
-
// 若原始是 groupList,则基于它派生出 table 所需的 config
|
|
105
|
-
const tableConfig = computed<TableConfig>(() =>
|
|
106
|
-
if (!isGroupListType(props.config.type)) {
|
|
107
|
-
return props.config as TableConfig;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const source = props.config as GroupListConfig;
|
|
111
|
-
return {
|
|
112
|
-
...props.config,
|
|
113
|
-
type: 'table',
|
|
114
|
-
groupItems: source.items,
|
|
115
|
-
items:
|
|
116
|
-
source.tableItems ||
|
|
117
|
-
(source.items as any[]).map((item: any) => ({
|
|
118
|
-
...item,
|
|
119
|
-
label: item.label || item.text,
|
|
120
|
-
text: null,
|
|
121
|
-
})),
|
|
122
|
-
} as any as TableConfig;
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// 反向派生 groupList 所需的 config
|
|
126
|
-
const groupListConfig = computed<GroupListConfig>(() => {
|
|
127
|
-
if (isGroupListType(props.config.type)) {
|
|
128
|
-
return props.config as GroupListConfig;
|
|
129
|
-
}
|
|
99
|
+
// 若原始是 groupList,则基于它派生出 table 所需的 config(反之同理)
|
|
100
|
+
const tableConfig = computed<TableConfig>(() => toTableConfig(props.config));
|
|
130
101
|
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
...props.config,
|
|
134
|
-
type: 'groupList',
|
|
135
|
-
tableItems: source.items,
|
|
136
|
-
items:
|
|
137
|
-
source.groupItems ||
|
|
138
|
-
(source.items as any[]).map((item: any) => {
|
|
139
|
-
const text = item.text || item.label;
|
|
140
|
-
return {
|
|
141
|
-
...item,
|
|
142
|
-
text,
|
|
143
|
-
labelWidth: calcLabelWidth(text),
|
|
144
|
-
span: item.span || 12,
|
|
145
|
-
};
|
|
146
|
-
}),
|
|
147
|
-
} as any as GroupListConfig;
|
|
148
|
-
});
|
|
102
|
+
const groupListConfig = computed<GroupListConfig>(() => toGroupListConfig(props.config));
|
|
149
103
|
|
|
150
104
|
// 运行时类型由 displayMode 决定,`<component :is>` 无法做联合类型收窄,统一转 any 交给子组件处理
|
|
151
105
|
const currentConfig = computed<any>(() => (displayMode.value === 'table' ? tableConfig.value : groupListConfig.value));
|
|
152
106
|
|
|
153
107
|
// 保持原 Table/GroupList 模式下新增按钮的不同尺寸策略
|
|
154
108
|
const addButtonSize = computed(() => {
|
|
109
|
+
if (currentConfig.value.addButtonConfig?.sticky) return 'default';
|
|
155
110
|
if (displayMode.value === 'table') return 'small';
|
|
156
111
|
return props.config.enableToggleMode !== false ? 'small' : 'default';
|
|
157
112
|
});
|
|
158
113
|
|
|
114
|
+
const addButtonProps = computed(() => {
|
|
115
|
+
const custom = currentConfig.value.addButtonConfig?.props;
|
|
116
|
+
if (custom) return { type: 'primary', ...custom };
|
|
117
|
+
if (displayMode.value === 'table') return { type: 'primary', plain: true };
|
|
118
|
+
return { type: 'primary', text: true };
|
|
119
|
+
});
|
|
120
|
+
|
|
159
121
|
const toggleDisplayMode = () => {
|
|
160
122
|
displayMode.value = displayMode.value === 'table' ? 'groupList' : 'table';
|
|
161
123
|
};
|
|
162
124
|
|
|
125
|
+
const tableGroupListRef = useTemplateRef<InstanceType<typeof MFormTable>>('tableGroupList');
|
|
126
|
+
|
|
127
|
+
const { scrollLastItemIntoView } = useScrollLastItemIntoView(
|
|
128
|
+
tableGroupListRef,
|
|
129
|
+
() => Boolean(currentConfig.value.scrollLastItemIntoView) && displayMode.value === 'groupList',
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const handleAdd = async () => {
|
|
133
|
+
const expectedCount = await newHandler();
|
|
134
|
+
if (expectedCount !== null) await scrollLastItemIntoView(expectedCount);
|
|
135
|
+
};
|
|
136
|
+
|
|
163
137
|
const onChange = (v: any, eventData?: ContainerChangeEventData) => emit('change', v, eventData);
|
|
164
138
|
const onSelect = (...args: any[]) => emit('select', ...args);
|
|
165
139
|
const onAddDiffCount = () => emit('addDiffCount');
|
|
166
|
-
const onAdd = (rows: any[]) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
140
|
+
const onAdd = async (rows: any[]) => {
|
|
141
|
+
let expectedCount: number | null = null;
|
|
142
|
+
for (const row of rows) {
|
|
143
|
+
expectedCount = (await newHandler(row)) ?? expectedCount;
|
|
144
|
+
}
|
|
145
|
+
if (expectedCount !== null) await scrollLastItemIntoView(expectedCount);
|
|
170
146
|
};
|
|
171
147
|
|
|
172
|
-
const tableGroupListRef = useTemplateRef<InstanceType<typeof MFormTable>>('tableGroupList');
|
|
173
|
-
|
|
174
148
|
defineExpose({
|
|
175
149
|
toggleRowSelection: (row: any, selected: boolean) => tableGroupListRef.value?.toggleRowSelection?.(row, selected),
|
|
176
150
|
});
|
|
@@ -3,7 +3,9 @@ import { computed, inject } from 'vue';
|
|
|
3
3
|
import { tMagicMessage } from '@tmagic/design';
|
|
4
4
|
import type { FormConfig, FormState, TableConfig, TableGroupListCommonConfig } from '@tmagic/form-schema';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { applyMountValueEffects } from '@form/utils/collectFields';
|
|
7
|
+
import { initValue } from '@form/utils/form';
|
|
8
|
+
|
|
7
9
|
import type { TableProps } from '../table/type';
|
|
8
10
|
|
|
9
11
|
export const useAdd = (
|
|
@@ -22,54 +24,63 @@ export const useAdd = (
|
|
|
22
24
|
|
|
23
25
|
if (!modelName) return false;
|
|
24
26
|
|
|
25
|
-
if (
|
|
26
|
-
return
|
|
27
|
+
if (typeof props.config.addable === 'function') {
|
|
28
|
+
return Boolean(
|
|
29
|
+
props.config.addable(mForm, {
|
|
30
|
+
model: props.model[modelName],
|
|
31
|
+
formValue: mForm?.values,
|
|
32
|
+
prop: props.prop,
|
|
33
|
+
config: props.config,
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
27
36
|
}
|
|
28
37
|
|
|
29
|
-
if (
|
|
30
|
-
return
|
|
31
|
-
model: props.model[modelName],
|
|
32
|
-
formValue: mForm?.values,
|
|
33
|
-
prop: props.prop,
|
|
34
|
-
config: props.config,
|
|
35
|
-
});
|
|
38
|
+
if (!props.model[modelName]?.length) {
|
|
39
|
+
return true;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
|
|
42
|
+
return typeof props.config.addable === 'undefined' ? true : Boolean(props.config.addable);
|
|
39
43
|
});
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
/**
|
|
46
|
+
* 新增一项,返回新增后列表应有的长度;没有新增(超上限 / 被 beforeAddRow 拦下 / enum 用尽)返回 null。
|
|
47
|
+
*
|
|
48
|
+
* 只抛 change,不直接改 `props.model`:写回由 `MForm` 按 changeRecords 的 propPath 完成。
|
|
49
|
+
* 返回长度是给调用方用的——新项要等写回后才出现在 DOM 里,靠它才能判断等到了没有。
|
|
50
|
+
*/
|
|
51
|
+
const newHandler = async (row?: any): Promise<number | null> => {
|
|
52
|
+
const modelName = `${props.name || props.config.name || ''}`;
|
|
53
|
+
const list: any[] = Array.isArray(props.model[modelName]) ? props.model[modelName] : [];
|
|
54
|
+
|
|
55
|
+
if (props.config.max && list.length >= props.config.max) {
|
|
45
56
|
tMagicMessage.error(`最多新增配置不能超过${props.config.max}条`);
|
|
46
|
-
return;
|
|
57
|
+
return null;
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
if (typeof props.config.beforeAddRow === 'function') {
|
|
50
61
|
const beforeCheckRes = await props.config.beforeAddRow(mForm, {
|
|
51
|
-
model:
|
|
62
|
+
model: list,
|
|
52
63
|
formValue: mForm?.values,
|
|
53
64
|
prop: props.prop,
|
|
54
65
|
});
|
|
55
|
-
if (!beforeCheckRes) return;
|
|
66
|
+
if (!beforeCheckRes) return null;
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
const columns = props.config.items;
|
|
59
70
|
const enumValues = props.config.enum || [];
|
|
60
71
|
let enumV = [];
|
|
61
|
-
const { length } =
|
|
72
|
+
const { length } = list;
|
|
62
73
|
const key = props.config.key || 'id';
|
|
63
74
|
let inputs: any = {};
|
|
64
75
|
|
|
65
76
|
if (enumValues.length) {
|
|
66
77
|
if (length >= enumValues.length) {
|
|
67
|
-
return;
|
|
78
|
+
return null;
|
|
68
79
|
}
|
|
69
80
|
enumV = enumValues.filter((item) => {
|
|
70
81
|
let i = 0;
|
|
71
82
|
for (; i < length; i++) {
|
|
72
|
-
if (item[key] ===
|
|
83
|
+
if (item[key] === list[i][key]) {
|
|
73
84
|
break;
|
|
74
85
|
}
|
|
75
86
|
}
|
|
@@ -87,7 +98,7 @@ export const useAdd = (
|
|
|
87
98
|
} else {
|
|
88
99
|
if (typeof props.config.defaultAdd === 'function') {
|
|
89
100
|
inputs = await props.config.defaultAdd(mForm, {
|
|
90
|
-
model:
|
|
101
|
+
model: list,
|
|
91
102
|
prop: props.prop,
|
|
92
103
|
formValue: mForm?.values,
|
|
93
104
|
});
|
|
@@ -101,18 +112,22 @@ export const useAdd = (
|
|
|
101
112
|
});
|
|
102
113
|
}
|
|
103
114
|
|
|
115
|
+
// enum / Excel 导入的数组行与默认新增共用同一份规整,字段组件不再在 setup 里改 model
|
|
116
|
+
applyMountValueEffects(mForm, columns as FormConfig, inputs);
|
|
117
|
+
|
|
104
118
|
if (props.sortKey && length) {
|
|
105
|
-
inputs[props.sortKey] =
|
|
119
|
+
inputs[props.sortKey] = list[length - 1][props.sortKey] - 1;
|
|
106
120
|
}
|
|
107
121
|
|
|
108
|
-
emit('change', [...
|
|
122
|
+
emit('change', [...list, inputs], {
|
|
109
123
|
changeRecords: [
|
|
110
124
|
{
|
|
111
|
-
propPath: `${props.prop}.${
|
|
125
|
+
propPath: `${props.prop}.${length}`,
|
|
112
126
|
value: inputs,
|
|
113
127
|
},
|
|
114
128
|
],
|
|
115
129
|
});
|
|
130
|
+
return length + 1;
|
|
116
131
|
};
|
|
117
132
|
|
|
118
133
|
return {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { nextTick, type Ref } from 'vue';
|
|
2
|
+
|
|
3
|
+
type ListRef = Ref<{ $el?: unknown } | HTMLElement | null | undefined>;
|
|
4
|
+
|
|
5
|
+
interface ScrollTarget {
|
|
6
|
+
root: HTMLElement;
|
|
7
|
+
last: HTMLElement | null;
|
|
8
|
+
count: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const nextFrame = () => new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
|
|
12
|
+
|
|
13
|
+
/** 滚动要用的列表根与最后一项;组件已卸载(宿主重建了整棵表单)时返回 null */
|
|
14
|
+
const resolveScrollTarget = (listRef: ListRef): ScrollTarget | null => {
|
|
15
|
+
const inst = listRef.value;
|
|
16
|
+
const root = inst instanceof HTMLElement ? inst : inst?.$el;
|
|
17
|
+
if (!(root instanceof HTMLElement)) return null;
|
|
18
|
+
|
|
19
|
+
const items = root.querySelectorAll(':scope > .m-fields-group-list-item');
|
|
20
|
+
const last = items[items.length - 1];
|
|
21
|
+
return { root, last: last instanceof HTMLElement ? last : null, count: items.length };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** 吸顶标题自身的高度:滚动后新项的标题不能被上一层标题压住 */
|
|
25
|
+
const getStickyHeaderOffset = (item: HTMLElement): number => {
|
|
26
|
+
const header = item.querySelector(':scope > .el-card__header, :scope > .t-card__header');
|
|
27
|
+
if (!(header instanceof HTMLElement)) return 0;
|
|
28
|
+
return Number.parseFloat(getComputedStyle(header).top) || 0;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** 吸底「新增」按钮盖住的高度;嵌套列表的 footer 还用 bottom 给外层按钮留了位 */
|
|
32
|
+
const getStickyFooterOffset = (root: HTMLElement): number => {
|
|
33
|
+
const footer = root.querySelector(':scope > .m-fields-group-list-footer.is-sticky-full');
|
|
34
|
+
if (!(footer instanceof HTMLElement)) return 0;
|
|
35
|
+
return footer.getBoundingClientRect().height + (Number.parseFloat(getComputedStyle(footer).bottom) || 0);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 等列表渲染出至少 `expectedCount` 项、且最后一项的 DOM 节点连续两帧不变再返回。
|
|
40
|
+
*
|
|
41
|
+
* 新增只抛 change,写回要经过宿主(如编辑器属性面板)的异步校验与表单值重建,
|
|
42
|
+
* 新项不一定当帧就出现;期间宿主还可能自己再追加项,末尾节点会换掉。
|
|
43
|
+
* 只判「节点不变」会滚到旧的最后一项上,所以先等够数量再等稳定。
|
|
44
|
+
*/
|
|
45
|
+
const waitForStableTarget = async (
|
|
46
|
+
listRef: ListRef,
|
|
47
|
+
expectedCount: number,
|
|
48
|
+
timeout = 600,
|
|
49
|
+
): Promise<ScrollTarget | null> => {
|
|
50
|
+
const deadline = Date.now() + timeout;
|
|
51
|
+
let previous: HTMLElement | null = null;
|
|
52
|
+
|
|
53
|
+
while (Date.now() < deadline) {
|
|
54
|
+
const target = resolveScrollTarget(listRef);
|
|
55
|
+
if (!target) return null;
|
|
56
|
+
if (target.count >= expectedCount && target.last && target.last === previous) return target;
|
|
57
|
+
previous = target.last;
|
|
58
|
+
await nextFrame();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return resolveScrollTarget(listRef);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 新增后把最后一项滚进视口,避开吸顶标题与吸底按钮。
|
|
66
|
+
*
|
|
67
|
+
* `enabled` 为 false(未开配置、或不在 group-list 形态)时直接返回。
|
|
68
|
+
*/
|
|
69
|
+
export const useScrollLastItemIntoView = (listRef: ListRef, enabled: () => boolean) => {
|
|
70
|
+
const scrollLastItemIntoView = async (expectedCount: number) => {
|
|
71
|
+
if (!enabled()) return;
|
|
72
|
+
|
|
73
|
+
// 先让已经同步写回的那部分渲染出来,稳定判定的第一帧就能拿到新项,少等一帧
|
|
74
|
+
await nextTick();
|
|
75
|
+
|
|
76
|
+
const target = await waitForStableTarget(listRef, expectedCount);
|
|
77
|
+
if (!target?.last) return;
|
|
78
|
+
|
|
79
|
+
const { root, last } = target;
|
|
80
|
+
|
|
81
|
+
// 新项是追加在末尾的,下方往往没有多余内容可滚,用 `start` 会被浏览器夹住、
|
|
82
|
+
// 仍压在吸底按钮下面。`nearest` 只滚到刚好露出,配合两侧 scroll-margin 避开吸顶标题与吸底按钮。
|
|
83
|
+
const top = getStickyHeaderOffset(last);
|
|
84
|
+
const bottom = getStickyFooterOffset(root);
|
|
85
|
+
last.style.scrollMarginTop = top ? `${top}px` : '';
|
|
86
|
+
last.style.scrollMarginBottom = bottom ? `${bottom}px` : '';
|
|
87
|
+
|
|
88
|
+
// 用瞬时定位而不是 smooth:平滑滚动要持续几百毫秒,期间宿主的回写重绘会把滚动位置改掉,
|
|
89
|
+
// 动画继续奔向旧目标,看起来就是「先弹回顶部再滚下来」。瞬时定位没有这个窗口。
|
|
90
|
+
last.scrollIntoView({ behavior: 'auto', block: 'nearest' });
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return { scrollLastItemIntoView };
|
|
94
|
+
};
|
|
@@ -11,9 +11,9 @@ import { computed, inject } from 'vue';
|
|
|
11
11
|
|
|
12
12
|
import { TMagicCheckbox, TMagicCheckboxGroup } from '@tmagic/design';
|
|
13
13
|
|
|
14
|
-
import type { CheckboxGroupConfig, CheckboxGroupOption, FieldProps, FormState } from '
|
|
15
|
-
import { filterFunction } from '
|
|
16
|
-
import { useAddField } from '
|
|
14
|
+
import type { CheckboxGroupConfig, CheckboxGroupOption, FieldProps, FormState } from '@form/schema';
|
|
15
|
+
import { filterFunction } from '@form/utils/form';
|
|
16
|
+
import { useAddField } from '@form/utils/useAddField';
|
|
17
17
|
|
|
18
18
|
defineOptions({
|
|
19
19
|
name: 'MFormCheckGroup',
|
|
@@ -25,11 +25,6 @@ const emit = defineEmits(['change']);
|
|
|
25
25
|
|
|
26
26
|
useAddField(props.prop);
|
|
27
27
|
|
|
28
|
-
// 初始化选项
|
|
29
|
-
if (props.model && !props.model[props.name]) {
|
|
30
|
-
props.model[props.name] = [];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
28
|
const changeHandler = (v: Array<string | number | boolean>) => {
|
|
34
29
|
emit('change', v);
|
|
35
30
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2025 Tencent. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { FieldMountValueEffect } from '@form/utils/fieldValueEffects';
|
|
20
|
+
|
|
21
|
+
/** 空值初始化为空数组。 */
|
|
22
|
+
export const effect: FieldMountValueEffect = ({ config, model }) => {
|
|
23
|
+
const { name } = config as any;
|
|
24
|
+
if (model && !model[name]) {
|
|
25
|
+
model[name] = [];
|
|
26
|
+
}
|
|
27
|
+
};
|