@tmagic/form 1.6.1 → 1.7.0-beta.1

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.
Files changed (42) hide show
  1. package/dist/style.css +23 -3
  2. package/dist/tmagic-form.js +1741 -1501
  3. package/dist/tmagic-form.umd.cjs +1774 -1530
  4. package/package.json +4 -4
  5. package/src/Form.vue +70 -9
  6. package/src/containers/Container.vue +109 -134
  7. package/src/containers/Fieldset.vue +31 -7
  8. package/src/containers/FlexLayout.vue +59 -0
  9. package/src/containers/Tabs.vue +11 -1
  10. package/src/fields/Cascader.vue +25 -16
  11. package/src/fields/Checkbox.vue +3 -3
  12. package/src/fields/CheckboxGroup.vue +1 -1
  13. package/src/fields/ColorPicker.vue +2 -2
  14. package/src/fields/Date.vue +2 -2
  15. package/src/fields/DateTime.vue +2 -2
  16. package/src/fields/Daterange.vue +2 -2
  17. package/src/fields/Number.vue +2 -2
  18. package/src/fields/NumberRange.vue +6 -6
  19. package/src/fields/RadioGroup.vue +7 -15
  20. package/src/fields/Select.vue +2 -2
  21. package/src/fields/Switch.vue +2 -2
  22. package/src/fields/Text.vue +20 -8
  23. package/src/fields/Textarea.vue +3 -2
  24. package/src/fields/Time.vue +2 -2
  25. package/src/fields/Timerange.vue +2 -2
  26. package/src/index.ts +5 -2
  27. package/src/table/ActionsColumn.vue +97 -0
  28. package/src/table/SortColumn.vue +101 -0
  29. package/src/table/Table.vue +170 -0
  30. package/src/table/type.ts +18 -0
  31. package/src/table/useAdd.ts +111 -0
  32. package/src/table/useFullscreen.ts +28 -0
  33. package/src/table/useImport.ts +68 -0
  34. package/src/table/usePagination.ts +30 -0
  35. package/src/table/useSelection.ts +34 -0
  36. package/src/table/useSortable.ts +48 -0
  37. package/src/table/useTableColumns.ts +194 -0
  38. package/src/theme/container.scss +17 -0
  39. package/src/theme/form.scss +15 -3
  40. package/src/utils/form.ts +56 -2
  41. package/types/index.d.ts +65 -36
  42. package/src/containers/Table.vue +0 -681
@@ -0,0 +1,170 @@
1
+ <template>
2
+ <div class="m-fields-table-wrap">
3
+ <teleport to="body" :disabled="!isFullscreen">
4
+ <div ref="mTable" class="m-fields-table" :class="{ 'm-fields-table-item-extra': config.itemExtra }">
5
+ <span v-if="config.extra" style="color: rgba(0, 0, 0, 0.45)" v-html="config.extra"></span>
6
+ <TMagicTooltip content="拖拽可排序" placement="left-start" :disabled="config.dropSort !== true">
7
+ <TMagicTable
8
+ v-if="model[modelName]"
9
+ ref="tMagicTable"
10
+ style="width: 100%"
11
+ show-header
12
+ :row-key="config.rowKey || 'id'"
13
+ :columns="columns"
14
+ :data="data"
15
+ :border="config.border"
16
+ :max-height="config.maxHeight"
17
+ :default-expand-all="true"
18
+ :key="updateKey"
19
+ @select="selectHandle"
20
+ @sort-change="sortChangeHandler"
21
+ ></TMagicTable>
22
+ </TMagicTooltip>
23
+
24
+ <slot></slot>
25
+
26
+ <div style="display: flex; justify-content: space-between; margin: 10px 0">
27
+ <TMagicButton v-if="addable" size="small" type="primary" :disabled="disabled" plain @click="newHandler()"
28
+ >新增一行</TMagicButton
29
+ >
30
+
31
+ <div style="display: flex">
32
+ <TMagicButton
33
+ :icon="Grid"
34
+ size="small"
35
+ type="primary"
36
+ @click="toggleMode"
37
+ v-if="enableToggleMode && config.enableToggleMode !== false && !isFullscreen"
38
+ >展开配置</TMagicButton
39
+ >
40
+ <TMagicButton
41
+ :icon="FullScreen"
42
+ size="small"
43
+ type="primary"
44
+ @click="toggleFullscreen"
45
+ v-if="config.enableFullscreen !== false"
46
+ >
47
+ {{ isFullscreen ? '退出全屏' : '全屏编辑' }}
48
+ </TMagicButton>
49
+ <TMagicUpload
50
+ v-if="importable"
51
+ style="display: inline-block"
52
+ ref="excelBtn"
53
+ action="/noop"
54
+ :disabled="disabled"
55
+ :on-change="excelHandler"
56
+ :auto-upload="false"
57
+ >
58
+ <TMagicButton size="small" type="success" :disabled="disabled" plain>导入EXCEL</TMagicButton>
59
+ </TMagicUpload>
60
+ <TMagicButton v-if="importable" size="small" type="warning" :disabled="disabled" plain @click="clearHandler"
61
+ >清空</TMagicButton
62
+ >
63
+ </div>
64
+ </div>
65
+
66
+ <div class="bottom" style="text-align: right" v-if="config.pagination">
67
+ <TMagicPagination
68
+ layout="total, sizes, prev, pager, next, jumper"
69
+ :hide-on-single-page="model[modelName].length < pageSize"
70
+ :current-page="currentPage + 1"
71
+ :page-sizes="[pageSize, 60, 120, 300]"
72
+ :page-size="pageSize"
73
+ :total="model[modelName].length"
74
+ @size-change="handleSizeChange"
75
+ @current-change="handleCurrentChange"
76
+ >
77
+ </TMagicPagination>
78
+ </div>
79
+ </div>
80
+ </teleport>
81
+ </div>
82
+ </template>
83
+
84
+ <script setup lang="ts">
85
+ import { computed, ref, useTemplateRef } from 'vue';
86
+ import { FullScreen, Grid } from '@element-plus/icons-vue';
87
+
88
+ import { TMagicButton, TMagicPagination, TMagicTable, TMagicTooltip, TMagicUpload } from '@tmagic/design';
89
+
90
+ import type { SortProp } from '../schema';
91
+ import { sortChange } from '../utils/form';
92
+
93
+ import type { TableProps } from './type';
94
+ import { useAdd } from './useAdd';
95
+ import { useFullscreen } from './useFullscreen';
96
+ import { useImport } from './useImport';
97
+ import { usePagination } from './usePagination';
98
+ import { useSelection } from './useSelection';
99
+ import { useSortable } from './useSortable';
100
+ import { useTableColumns } from './useTableColumns';
101
+
102
+ defineOptions({
103
+ name: 'MFormTable',
104
+ });
105
+
106
+ const props = withDefaults(defineProps<TableProps>(), {
107
+ prop: '',
108
+ sortKey: '',
109
+ enableToggleMode: true,
110
+ showIndex: true,
111
+ lastValues: () => ({}),
112
+ isCompare: false,
113
+ });
114
+
115
+ const emit = defineEmits(['change', 'select', 'addDiffCount']);
116
+
117
+ const modelName = computed(() => props.name || props.config.name || '');
118
+ const tMagicTableRef = useTemplateRef<InstanceType<typeof TMagicTable>>('tMagicTable');
119
+
120
+ const { pageSize, currentPage, paginationData, handleSizeChange, handleCurrentChange } = usePagination(
121
+ props,
122
+ modelName,
123
+ );
124
+
125
+ const { addable, newHandler } = useAdd(props, emit);
126
+ const { columns } = useTableColumns(props, emit, currentPage, pageSize, modelName);
127
+ useSortable(props, emit, tMagicTableRef, modelName);
128
+ const { isFullscreen, toggleFullscreen } = useFullscreen();
129
+ const { importable, excelHandler, clearHandler } = useImport(props, emit, newHandler);
130
+ const { selectHandle, toggleRowSelection } = useSelection(props, emit, tMagicTableRef);
131
+
132
+ const updateKey = ref(1);
133
+
134
+ const data = computed(() => (props.config.pagination ? paginationData.value : props.model[modelName.value]));
135
+
136
+ const toggleMode = () => {
137
+ const calcLabelWidth = (label: string) => {
138
+ if (!label) return '0px';
139
+ const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
140
+ const chLength = label.length - zhLength;
141
+ return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
142
+ };
143
+
144
+ // 切换为groupList的形式
145
+ props.config.type = 'groupList';
146
+ props.config.enableToggleMode = true;
147
+ props.config.tableItems = props.config.items;
148
+ props.config.items =
149
+ props.config.groupItems ||
150
+ props.config.items.map((item: any) => {
151
+ const text = item.text || item.label;
152
+ const labelWidth = calcLabelWidth(text);
153
+ return {
154
+ ...item,
155
+ text,
156
+ labelWidth,
157
+ span: item.span || 12,
158
+ };
159
+ });
160
+ };
161
+
162
+ const sortChangeHandler = (sortOptions: SortProp) => {
163
+ const modelName = props.name || props.config.name || '';
164
+ sortChange(props.model[modelName], sortOptions);
165
+ };
166
+
167
+ defineExpose({
168
+ toggleRowSelection,
169
+ });
170
+ </script>
@@ -0,0 +1,18 @@
1
+ import type { TableConfig } from '@tmagic/form-schema';
2
+
3
+ export interface TableProps {
4
+ model: any;
5
+ lastValues?: any;
6
+ isCompare?: boolean;
7
+ config: TableConfig;
8
+ name: string;
9
+ prop?: string;
10
+ labelWidth?: string;
11
+ sort?: boolean;
12
+ disabled?: boolean;
13
+ sortKey?: string;
14
+ text?: string;
15
+ size?: string;
16
+ enableToggleMode?: boolean;
17
+ showIndex?: boolean;
18
+ }
@@ -0,0 +1,111 @@
1
+ import { computed, inject } from 'vue';
2
+
3
+ import { tMagicMessage } from '@tmagic/design';
4
+ import type { FormState } from '@tmagic/form-schema';
5
+
6
+ import { initValue } from '../utils/form';
7
+
8
+ import type { TableProps } from './type';
9
+
10
+ export const useAdd = (
11
+ props: TableProps,
12
+ emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
13
+ ) => {
14
+ const mForm = inject<FormState | undefined>('mForm');
15
+
16
+ const addable = computed(() => {
17
+ const modelName = props.name || props.config.name || '';
18
+ if (!props.model[modelName].length) {
19
+ return true;
20
+ }
21
+ if (typeof props.config.addable === 'function') {
22
+ return props.config.addable(mForm, {
23
+ model: props.model[modelName],
24
+ formValue: mForm?.values,
25
+ prop: props.prop,
26
+ });
27
+ }
28
+ return typeof props.config.addable === 'undefined' ? true : props.config.addable;
29
+ });
30
+
31
+ const newHandler = async (row?: any) => {
32
+ const modelName = props.name || props.config.name || '';
33
+
34
+ if (props.config.max && props.model[modelName].length >= props.config.max) {
35
+ tMagicMessage.error(`最多新增配置不能超过${props.config.max}条`);
36
+ return;
37
+ }
38
+
39
+ if (typeof props.config.beforeAddRow === 'function') {
40
+ const beforeCheckRes = props.config.beforeAddRow(mForm, {
41
+ model: props.model[modelName],
42
+ formValue: mForm?.values,
43
+ prop: props.prop,
44
+ });
45
+ if (!beforeCheckRes) return;
46
+ }
47
+
48
+ const columns = props.config.items;
49
+ const enumValues = props.config.enum || [];
50
+ let enumV = [];
51
+ const { length } = props.model[modelName];
52
+ const key = props.config.key || 'id';
53
+ let inputs: any = {};
54
+
55
+ if (enumValues.length) {
56
+ if (length >= enumValues.length) {
57
+ return;
58
+ }
59
+ enumV = enumValues.filter((item) => {
60
+ let i = 0;
61
+ for (; i < length; i++) {
62
+ if (item[key] === props.model[modelName][i][key]) {
63
+ break;
64
+ }
65
+ }
66
+ return i === length;
67
+ });
68
+
69
+ if (enumV.length > 0) {
70
+ // eslint-disable-next-line prefer-destructuring
71
+ inputs = enumV[0];
72
+ }
73
+ } else if (Array.isArray(row)) {
74
+ columns.forEach((column, index) => {
75
+ column.name && (inputs[column.name] = row[index]);
76
+ });
77
+ } else {
78
+ if (typeof props.config.defaultAdd === 'function') {
79
+ inputs = await props.config.defaultAdd(mForm, {
80
+ model: props.model[modelName],
81
+ formValue: mForm?.values,
82
+ });
83
+ } else if (props.config.defaultAdd) {
84
+ inputs = props.config.defaultAdd;
85
+ }
86
+
87
+ inputs = await initValue(mForm, {
88
+ config: columns,
89
+ initValues: inputs,
90
+ });
91
+ }
92
+
93
+ if (props.sortKey && length) {
94
+ inputs[props.sortKey] = props.model[modelName][length - 1][props.sortKey] - 1;
95
+ }
96
+
97
+ emit('change', [...props.model[modelName], inputs], {
98
+ changeRecords: [
99
+ {
100
+ propPath: `${props.prop}.${props.model[modelName].length}`,
101
+ value: inputs,
102
+ },
103
+ ],
104
+ });
105
+ };
106
+
107
+ return {
108
+ addable,
109
+ newHandler,
110
+ };
111
+ };
@@ -0,0 +1,28 @@
1
+ import { ref, useTemplateRef } from 'vue';
2
+
3
+ import { useZIndex } from '@tmagic/design';
4
+
5
+ export const useFullscreen = () => {
6
+ const isFullscreen = ref(false);
7
+ const mTableEl = useTemplateRef<HTMLDivElement>('mTable');
8
+
9
+ const { nextZIndex } = useZIndex();
10
+
11
+ const toggleFullscreen = () => {
12
+ if (!mTableEl.value) return;
13
+
14
+ if (isFullscreen.value) {
15
+ mTableEl.value.classList.remove('fixed');
16
+ isFullscreen.value = false;
17
+ } else {
18
+ mTableEl.value.classList.add('fixed');
19
+ mTableEl.value.style.zIndex = `${nextZIndex()}`;
20
+ isFullscreen.value = true;
21
+ }
22
+ };
23
+
24
+ return {
25
+ isFullscreen,
26
+ toggleFullscreen,
27
+ };
28
+ };
@@ -0,0 +1,68 @@
1
+ import { computed, inject, useTemplateRef } from 'vue';
2
+
3
+ import type { TMagicUpload } from '@tmagic/design';
4
+ import type { FormState } from '@tmagic/form-schema';
5
+ import { asyncLoadJs } from '@tmagic/utils';
6
+
7
+ import type { TableProps } from './type';
8
+
9
+ export const useImport = (
10
+ props: TableProps,
11
+ emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
12
+ newHandler: (row: any) => void,
13
+ ) => {
14
+ const mForm = inject<FormState | undefined>('mForm');
15
+ const modelName = computed(() => props.name || props.config.name || '');
16
+ const importable = computed(() => {
17
+ if (typeof props.config.importable === 'function') {
18
+ return props.config.importable(mForm, {
19
+ formValue: mForm?.values,
20
+ model: props.model[modelName.value],
21
+ });
22
+ }
23
+ return typeof props.config.importable === 'undefined' ? false : props.config.importable;
24
+ });
25
+
26
+ const excelBtn = useTemplateRef<InstanceType<typeof TMagicUpload>>('excelBtn');
27
+
28
+ const excelHandler = async (file: any) => {
29
+ if (!file?.raw) {
30
+ return false;
31
+ }
32
+
33
+ if (!(globalThis as any).XLSX) {
34
+ await asyncLoadJs('https://cdn.bootcdn.net/ajax/libs/xlsx/0.17.0/xlsx.full.min.js');
35
+ }
36
+
37
+ const reader = new FileReader();
38
+ reader.onload = () => {
39
+ const data = reader.result;
40
+ const pdata = (globalThis as any).XLSX.read(data, { type: 'array' });
41
+ pdata.SheetNames.forEach((sheetName: string) => {
42
+ const arr = (globalThis as any).XLSX.utils.sheet_to_json(pdata.Sheets[sheetName], { header: 1 });
43
+ if (arr?.[0]) {
44
+ arr.forEach((row: any) => {
45
+ newHandler(row);
46
+ });
47
+ }
48
+ setTimeout(() => {
49
+ excelBtn.value?.clearFiles();
50
+ }, 300);
51
+ });
52
+ };
53
+ reader.readAsArrayBuffer(file.raw);
54
+
55
+ return false;
56
+ };
57
+
58
+ const clearHandler = () => {
59
+ emit('change', []);
60
+ mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
61
+ };
62
+
63
+ return {
64
+ importable,
65
+ excelHandler,
66
+ clearHandler,
67
+ };
68
+ };
@@ -0,0 +1,30 @@
1
+ import { computed, type Ref, ref } from 'vue';
2
+
3
+ import { getDataByPage } from '../utils/form';
4
+
5
+ import type { TableProps } from './type';
6
+
7
+ export const usePagination = (props: TableProps, modelName: Ref<string | number>) => {
8
+ const pageSize = ref(10);
9
+ /**
10
+ * 当前页码
11
+ */
12
+ const currentPage = ref(0);
13
+
14
+ const paginationData = computed(() => getDataByPage(props.model[modelName.value], currentPage.value, pageSize.value));
15
+ const handleSizeChange = (val: number) => {
16
+ pageSize.value = val;
17
+ };
18
+
19
+ const handleCurrentChange = (val: number) => {
20
+ currentPage.value = val - 1;
21
+ };
22
+
23
+ return {
24
+ pageSize,
25
+ currentPage,
26
+ paginationData,
27
+ handleSizeChange,
28
+ handleCurrentChange,
29
+ };
30
+ };
@@ -0,0 +1,34 @@
1
+ import { inject, type ShallowRef } from 'vue';
2
+
3
+ import type { TMagicTable } from '@tmagic/design';
4
+ import type { FormState } from '@tmagic/form-schema';
5
+
6
+ import type { TableProps } from './type';
7
+
8
+ export const useSelection = (
9
+ props: TableProps,
10
+ emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
11
+ tMagicTableRef: ShallowRef<InstanceType<typeof TMagicTable> | null>,
12
+ ) => {
13
+ const mForm = inject<FormState | undefined>('mForm');
14
+
15
+ const selectHandle = (selection: any, row: any) => {
16
+ if (typeof props.config.selection === 'string' && props.config.selection === 'single') {
17
+ tMagicTableRef.value?.clearSelection();
18
+ tMagicTableRef.value?.toggleRowSelection(row, true);
19
+ }
20
+ emit('select', selection, row);
21
+ if (typeof props.config.onSelect === 'function') {
22
+ props.config.onSelect(mForm, { selection, row, config: props.config });
23
+ }
24
+ };
25
+
26
+ const toggleRowSelection = (row: any, selected: boolean) => {
27
+ tMagicTableRef.value?.toggleRowSelection.call(tMagicTableRef.value?.getTableRef(), row, selected);
28
+ };
29
+
30
+ return {
31
+ selectHandle,
32
+ toggleRowSelection,
33
+ };
34
+ };
@@ -0,0 +1,48 @@
1
+ import { inject, type Ref, type ShallowRef, watchEffect } from 'vue';
2
+ import Sortable, { type SortableEvent } from 'sortablejs';
3
+
4
+ import { type TMagicTable } from '@tmagic/design';
5
+ import type { FormState } from '@tmagic/form-schema';
6
+
7
+ import { sortArray } from '../utils/form';
8
+
9
+ import type { TableProps } from './type';
10
+
11
+ export const useSortable = (
12
+ props: TableProps,
13
+ emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
14
+ tMagicTableRef: ShallowRef<InstanceType<typeof TMagicTable> | null>,
15
+ modelName: Ref<string | number>,
16
+ ) => {
17
+ const mForm = inject<FormState | undefined>('mForm');
18
+
19
+ let sortable: Sortable | undefined;
20
+ const rowDrop = () => {
21
+ sortable?.destroy();
22
+ const tableEl = tMagicTableRef.value?.getEl();
23
+ const tBodyEl = tableEl?.querySelector('.el-table__body > tbody');
24
+ if (!tBodyEl) {
25
+ return;
26
+ }
27
+ sortable = Sortable.create(tBodyEl, {
28
+ draggable: '.tmagic-design-table-row',
29
+ filter: 'input', // 表单组件选字操作和触发拖拽会冲突,优先保证选字操作
30
+ preventOnFilter: false, // 允许选字
31
+ direction: 'vertical',
32
+ onEnd: ({ newIndex, oldIndex }: SortableEvent) => {
33
+ if (typeof newIndex === 'undefined') return;
34
+ if (typeof oldIndex === 'undefined') return;
35
+ const newData = sortArray(props.model[modelName.value], newIndex, oldIndex, props.sortKey);
36
+
37
+ emit('change', newData);
38
+ mForm?.$emit('field-change', newData);
39
+ },
40
+ });
41
+ };
42
+
43
+ watchEffect(() => {
44
+ if (props.config.dropSort) {
45
+ rowDrop();
46
+ }
47
+ });
48
+ };