@tmagic/form 1.5.0-beta.8 → 1.5.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.
@@ -48,7 +48,7 @@ import { CaretBottom, CaretRight, CaretTop, Delete } from '@element-plus/icons-v
48
48
 
49
49
  import { TMagicButton, TMagicIcon } from '@tmagic/design';
50
50
 
51
- import { FormState, GroupListConfig } from '../schema';
51
+ import type { ContainerChangeEventData, FormState, GroupListConfig } from '../schema';
52
52
  import { filterFunction } from '../utils/form';
53
53
 
54
54
  import Container from './Container.vue';
@@ -103,7 +103,9 @@ const itemExtra = computed(() => filterFunction(mForm, props.config.itemExtra, p
103
103
 
104
104
  const removeHandler = () => emit('remove-item', props.index);
105
105
 
106
- const changeHandler = () => emit('change');
106
+ const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
107
+ emit('change', props.model, eventData);
108
+ };
107
109
 
108
110
  const expandHandler = () => {
109
111
  expand.value = !expand.value;
@@ -63,7 +63,7 @@ import { CaretBottom, CaretRight } from '@element-plus/icons-vue';
63
63
 
64
64
  import { TMagicButton, TMagicCard } from '@tmagic/design';
65
65
 
66
- import { FormState, PanelConfig } from '../schema';
66
+ import type { ContainerChangeEventData, FormState, PanelConfig } from '../schema';
67
67
  import { filterFunction } from '../utils/form';
68
68
 
69
69
  import Container from './Container.vue';
@@ -84,7 +84,10 @@ const props = defineProps<{
84
84
  disabled?: boolean;
85
85
  }>();
86
86
 
87
- const emit = defineEmits(['change', 'addDiffCount']);
87
+ const emit = defineEmits<{
88
+ change: [v: any, eventData: ContainerChangeEventData];
89
+ addDiffCount: [];
90
+ }>();
88
91
 
89
92
  const mForm = inject<FormState | undefined>('mForm');
90
93
 
@@ -94,6 +97,8 @@ const items = computed(() => props.config.items);
94
97
 
95
98
  const filter = (config: any) => filterFunction(mForm, config, props);
96
99
 
97
- const changeHandler = () => emit('change', props.model);
100
+ const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
101
+ emit('change', props.model, eventData);
102
+ };
98
103
  const onAddDiffCount = () => emit('addDiffCount');
99
104
  </script>
@@ -24,7 +24,7 @@ import { inject } from 'vue';
24
24
 
25
25
  import { TMagicRow } from '@tmagic/design';
26
26
 
27
- import { FormState, RowConfig } from '../schema';
27
+ import type { ContainerChangeEventData, FormState, RowConfig } from '../schema';
28
28
 
29
29
  import Col from './Col.vue';
30
30
 
@@ -45,10 +45,15 @@ const props = defineProps<{
45
45
  disabled?: boolean;
46
46
  }>();
47
47
 
48
- const emit = defineEmits(['change', 'addDiffCount']);
48
+ const emit = defineEmits<{
49
+ change: [v: any, eventData: ContainerChangeEventData];
50
+ addDiffCount: [];
51
+ }>();
49
52
 
50
53
  const mForm = inject<FormState | undefined>('mForm');
51
54
 
52
- const changeHandler = () => emit('change', props.name ? props.model[props.name] : props.model);
55
+ const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
56
+ emit('change', props.name ? props.model[props.name] : props.model, eventData);
57
+ };
53
58
  const onAddDiffCount = () => emit('addDiffCount');
54
59
  </script>
@@ -37,7 +37,7 @@ import { inject, ref, watchEffect } from 'vue';
37
37
 
38
38
  import { TMagicStep, TMagicSteps } from '@tmagic/design';
39
39
 
40
- import { FormState, StepConfig } from '../schema';
40
+ import type { ContainerChangeEventData, FormState, StepConfig } from '../schema';
41
41
 
42
42
  import Container from './Container.vue';
43
43
 
@@ -48,6 +48,7 @@ defineOptions({
48
48
  const props = withDefaults(
49
49
  defineProps<{
50
50
  model: any;
51
+ name?: string;
51
52
  lastValues?: any;
52
53
  isCompare?: boolean;
53
54
  config: StepConfig;
@@ -61,7 +62,10 @@ const props = withDefaults(
61
62
  },
62
63
  );
63
64
 
64
- const emit = defineEmits(['change', 'addDiffCount']);
65
+ const emit = defineEmits<{
66
+ change: [v: any, eventData: ContainerChangeEventData];
67
+ addDiffCount: [];
68
+ }>();
65
69
 
66
70
  const mForm = inject<FormState | undefined>('mForm');
67
71
  const active = ref(1);
@@ -75,8 +79,9 @@ const stepClick = (index: number) => {
75
79
  mForm?.$emit('update:stepActive', active.value);
76
80
  };
77
81
 
78
- const changeHandler = () => {
79
- emit('change', props.model);
82
+ const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
83
+ emit('change', props.model, eventData);
80
84
  };
85
+
81
86
  const onAddDiffCount = () => emit('addDiffCount');
82
87
  </script>
@@ -113,7 +113,7 @@
113
113
  :lastValues="lastData[scope.$index]"
114
114
  :is-compare="isCompare"
115
115
  :size="size"
116
- @change="$emit('change', model[modelName])"
116
+ @change="changeHandler"
117
117
  @addDiffCount="onAddDiffCount()"
118
118
  ></Container>
119
119
  </template>
@@ -206,7 +206,7 @@ import {
206
206
  } from '@tmagic/design';
207
207
  import { asyncLoadJs, sleep } from '@tmagic/utils';
208
208
 
209
- import { FormState, SortProp, TableColumnConfig, TableConfig } from '../schema';
209
+ import type { ContainerChangeEventData, FormState, SortProp, TableColumnConfig, TableConfig } from '../schema';
210
210
  import { display as displayFunc, initValue } from '../utils/form';
211
211
 
212
212
  import Container from './Container.vue';
@@ -395,7 +395,15 @@ const newHandler = async (row?: any) => {
395
395
  }
396
396
 
397
397
  props.model[modelName.value].push(inputs);
398
- emit('change', props.model[modelName.value]);
398
+
399
+ emit('change', props.model[modelName.value], {
400
+ changeRecords: [
401
+ {
402
+ propPath: `${props.prop}.${props.model[modelName.value].length - 1}`,
403
+ value: inputs,
404
+ },
405
+ ],
406
+ });
399
407
  };
400
408
 
401
409
  onMounted(() => {
@@ -644,6 +652,10 @@ const getProp = (index: number) => {
644
652
 
645
653
  const onAddDiffCount = () => emit('addDiffCount');
646
654
 
655
+ const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
656
+ emit('change', props.model, eventData);
657
+ };
658
+
647
659
  defineExpose({
648
660
  toggleRowSelection,
649
661
  });
@@ -71,12 +71,12 @@
71
71
 
72
72
  <script setup lang="ts">
73
73
  import { computed, inject, ref, watchEffect } from 'vue';
74
- import { cloneDeep, isEmpty } from 'lodash-es';
74
+ import { isEmpty } from 'lodash-es';
75
75
 
76
76
  import { getDesignConfig, TMagicBadge } from '@tmagic/design';
77
77
 
78
- import { FormState, TabConfig, TabPaneConfig } from '../schema';
79
- import { display as displayFunc, filterFunction } from '../utils/form';
78
+ import type { ContainerChangeEventData, FormState, TabConfig, TabPaneConfig } from '../schema';
79
+ import { display as displayFunc, filterFunction, initValue } from '../utils/form';
80
80
 
81
81
  import Container from './Container.vue';
82
82
 
@@ -137,7 +137,10 @@ const props = withDefaults(
137
137
  },
138
138
  );
139
139
 
140
- const emit = defineEmits(['change', 'addDiffCount']);
140
+ const emit = defineEmits<{
141
+ change: [v: any, eventData?: ContainerChangeEventData];
142
+ addDiffCount: [];
143
+ }>();
141
144
 
142
145
  const mForm = inject<FormState | undefined>('mForm');
143
146
  const activeTabName = ref(getActive(mForm, props, ''));
@@ -166,8 +169,8 @@ const tabItems = (tab: TabPaneConfig) => (props.config.dynamic ? props.config.it
166
169
 
167
170
  const tabClickHandler = (tab: any) => tabClick(mForm, tab, props);
168
171
 
169
- const onTabAdd = () => {
170
- if (!props.config.name) throw new Error('dynamic tab 必须配置name');
172
+ const onTabAdd = async () => {
173
+ if (!props.name) throw new Error('dynamic tab 必须配置name');
171
174
 
172
175
  if (typeof props.config.onTabAdd === 'function') {
173
176
  props.config.onTabAdd(mForm, {
@@ -175,17 +178,32 @@ const onTabAdd = () => {
175
178
  prop: props.prop,
176
179
  config: props.config,
177
180
  });
178
- } else if (tabs.value.length > 0) {
179
- const newObj = cloneDeep(tabs.value[0]);
181
+ emit('change', props.model);
182
+ } else {
183
+ const newObj = await initValue(mForm, {
184
+ config: props.config.items,
185
+ initValues: {},
186
+ });
187
+
180
188
  newObj.title = `标签${tabs.value.length + 1}`;
181
- props.model[props.config.name].push(newObj);
189
+
190
+ props.model[props.name].push(newObj);
191
+
192
+ emit('change', props.model[props.name], {
193
+ changeRecords: [
194
+ {
195
+ propPath: `${props.prop}.${props.model[props.name].length - 1}`,
196
+ value: newObj,
197
+ },
198
+ ],
199
+ });
182
200
  }
183
- emit('change', props.model);
184
- mForm?.$emit('field-change', props.prop, props.model[props.config.name]);
201
+
202
+ mForm?.$emit('field-change', props.prop, props.model[props.name]);
185
203
  };
186
204
 
187
205
  const onTabRemove = (tabName: string) => {
188
- if (!props.config.name) throw new Error('dynamic tab 必须配置name');
206
+ if (!props.name) throw new Error('dynamic tab 必须配置name');
189
207
 
190
208
  if (typeof props.config.onTabRemove === 'function') {
191
209
  props.config.onTabRemove(mForm, tabName, {
@@ -194,23 +212,20 @@ const onTabRemove = (tabName: string) => {
194
212
  config: props.config,
195
213
  });
196
214
  } else {
197
- props.model[props.config.name].splice(+tabName, 1);
215
+ props.model[props.name].splice(+tabName, 1);
198
216
 
199
217
  // 防止删除后没有选中的问题
200
- if (tabName < activeTabName.value || activeTabName.value >= props.model[props.config.name].length) {
218
+ if (tabName < activeTabName.value || activeTabName.value >= props.model[props.name].length) {
201
219
  activeTabName.value = (+activeTabName.value - 1).toString();
202
220
  tabClick(mForm, { name: activeTabName.value }, props);
203
221
  }
204
222
  }
205
223
  emit('change', props.model);
206
- mForm?.$emit('field-change', props.prop, props.model[props.config.name]);
224
+ mForm?.$emit('field-change', props.prop, props.model[props.name]);
207
225
  };
208
226
 
209
- const changeHandler = () => {
210
- emit('change', props.model);
211
- if (typeof props.config.onChange === 'function') {
212
- props.config.onChange(mForm, { model: props.model, prop: props.prop, config: props.config });
213
- }
227
+ const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
228
+ emit('change', props.model, eventData);
214
229
  };
215
230
 
216
231
  // 在tabs组件中收集事件触发次数,即该tab下的差异数
@@ -32,9 +32,9 @@ const emit = defineEmits<{
32
32
 
33
33
  useAddField(props.prop);
34
34
 
35
- const value = props.model?.[props.name].toString();
35
+ const value = props.model?.[props.name]?.toString();
36
36
  if (props.model) {
37
- if (value === 'Invalid Date') {
37
+ if (!value || value === 'Invalid Date') {
38
38
  props.model[props.name] = '';
39
39
  } else {
40
40
  props.model[props.name] = datetimeFormatter(
@@ -59,7 +59,7 @@ const changeFieldMap = async () => {
59
59
  let oldVal = props.model?.[v.name] || '';
60
60
  if (!oldVal && v.defaultValue !== undefined) {
61
61
  oldVal = v.defaultValue;
62
- emit('change', oldVal, v.name);
62
+ emit('change', oldVal, { modifyKey: v.name });
63
63
  }
64
64
  fieldMap.value[v.name] = oldVal;
65
65
  fieldLabelMap.value[v.name] = v.label || '';
@@ -85,6 +85,8 @@ onBeforeUnmount(() => {
85
85
  });
86
86
 
87
87
  const inputChangeHandler = (key: string) => {
88
- emit('change', fieldMap.value[key], key);
88
+ emit('change', fieldMap.value[key], {
89
+ modifyKey: key,
90
+ });
89
91
  };
90
92
  </script>
@@ -1,43 +1,48 @@
1
1
  <template>
2
- <TMagicPopover width="220px" :visible="popoverVisible" :destroy-on-close="true">
3
- <template #reference>
4
- <TMagicInput
5
- v-model="model[name]"
6
- clearable
7
- :size="size"
8
- :placeholder="config.placeholder"
9
- :disabled="disabled"
10
- @change="changeHandler"
11
- @input="inputHandler"
12
- @keyup="keyUpHandler($event)"
13
- >
14
- <template #append v-if="appendConfig">
15
- <TMagicButton
16
- v-if="appendConfig.type === 'button'"
17
- style="color: #409eff"
18
- :size="size"
19
- @click.prevent="buttonClickHandler"
20
- >
21
- {{ appendConfig.text }}
22
- </TMagicButton>
23
- </template>
24
- </TMagicInput>
25
- </template>
26
-
27
- <div class="m-form-item__content">
28
- <div class="m-form-validate__warning">输入内容前后有空格,是否移除空格?</div>
29
- <div style="display: flex; justify-content: flex-end">
30
- <TMagicButton link size="small" @click="popoverVisible = false">保持原样</TMagicButton>
31
- <TMagicButton type="primary" size="small" @click="confirmTrimHandler">移除空格</TMagicButton>
2
+ <div style="width: 100%">
3
+ <TMagicInput
4
+ v-model="model[name]"
5
+ ref="input"
6
+ clearable
7
+ :size="size"
8
+ :placeholder="config.placeholder"
9
+ :disabled="disabled"
10
+ @change="changeHandler"
11
+ @input="inputHandler"
12
+ @keyup="keyUpHandler($event)"
13
+ >
14
+ <template #append v-if="appendConfig">
15
+ <TMagicButton
16
+ v-if="appendConfig.type === 'button'"
17
+ style="color: #409eff"
18
+ :size="size"
19
+ @click.prevent="buttonClickHandler"
20
+ >
21
+ {{ appendConfig.text }}
22
+ </TMagicButton>
23
+ </template>
24
+ </TMagicInput>
25
+
26
+ <Teleport to="body">
27
+ <div v-if="popoverVisible" class="tmagic-form-text-popper m-form-item__content" ref="popoverEl">
28
+ <div class="m-form-validate__warning">输入内容前后有空格,是否移除空格?</div>
29
+ <div style="display: flex; justify-content: flex-end">
30
+ <TMagicButton link size="small" @click="popoverVisible = false">保持原样</TMagicButton>
31
+ <TMagicButton type="primary" size="small" @click="confirmTrimHandler">移除空格</TMagicButton>
32
+ </div>
33
+ <span class="tmagic-form-text-popper-arrow" data-popper-arrow></span>
32
34
  </div>
33
- </div>
34
- </TMagicPopover>
35
+ </Teleport>
36
+ </div>
35
37
  </template>
36
38
 
37
39
  <script lang="ts" setup>
38
- import { computed, inject, ref } from 'vue';
40
+ import { computed, inject, ref, shallowRef, watch } from 'vue';
41
+ import type { Instance } from '@popperjs/core';
42
+ import { createPopper } from '@popperjs/core';
43
+ import { debounce } from 'lodash-es';
39
44
 
40
- import { TMagicButton, TMagicInput, TMagicPopover } from '@tmagic/design';
45
+ import { TMagicButton, TMagicInput } from '@tmagic/design';
41
46
  import { isNumber } from '@tmagic/utils';
42
47
 
43
48
  import type { FieldProps, FormState, TextConfig } from '../schema';
@@ -85,11 +90,11 @@ const confirmTrimHandler = () => {
85
90
  popoverVisible.value = false;
86
91
  };
87
92
 
88
- const checkWhiteSpace = (value: unknown) => {
93
+ const checkWhiteSpace = debounce((value: unknown) => {
89
94
  if (typeof value === 'string' && !props.config.trim) {
90
95
  popoverVisible.value = value.trim() !== value;
91
96
  }
92
- };
97
+ }, 300);
93
98
 
94
99
  const changeHandler = (value: string) => {
95
100
  emit('change', value);
@@ -167,4 +172,34 @@ const keyUpHandler = ($event: KeyboardEvent) => {
167
172
  props.model[props.name] = `${num}${unit || ''}`;
168
173
  emit('change', props.model[props.name]);
169
174
  };
175
+
176
+ const popoverEl = ref<HTMLDivElement>();
177
+ const input = ref<InstanceType<typeof TMagicInput>>();
178
+ const instanceRef = shallowRef<Instance | undefined>();
179
+
180
+ watch(popoverEl, (el) => {
181
+ destroyPopover();
182
+
183
+ if (!input.value?.$el || !el) return;
184
+
185
+ instanceRef.value = createPopper(input.value.$el, el, {
186
+ placement: props.config.tooltip ? 'top' : 'bottom',
187
+ strategy: 'absolute',
188
+ modifiers: [
189
+ {
190
+ name: 'offset',
191
+ options: {
192
+ offset: [0, 10],
193
+ },
194
+ },
195
+ ],
196
+ });
197
+ });
198
+
199
+ const destroyPopover = () => {
200
+ if (!instanceRef.value) return;
201
+
202
+ instanceRef.value.destroy();
203
+ instanceRef.value = undefined;
204
+ };
170
205
  </script>
package/src/index.ts CHANGED
@@ -98,8 +98,8 @@ export interface FormInstallOptions {
98
98
  const defaultInstallOpt: FormInstallOptions = {};
99
99
 
100
100
  export default {
101
- install(app: App, opt?: FormInstallOptions) {
102
- const option = Object.assign(defaultInstallOpt, opt || {});
101
+ install(app: App, opt: FormInstallOptions = {}) {
102
+ const option = Object.assign(defaultInstallOpt, opt);
103
103
 
104
104
  // eslint-disable-next-line no-param-reassign
105
105
  app.config.globalProperties.$MAGIC_FORM = option;
package/src/schema.ts CHANGED
@@ -16,11 +16,23 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
+ import type { TMagicMessage, TMagicMessageBox } from '@tmagic/design';
20
+
19
21
  export interface ValidateError {
20
22
  message: string;
21
23
  field: string;
22
24
  }
23
25
 
26
+ export interface ChangeRecord {
27
+ propPath?: string;
28
+ value: any;
29
+ }
30
+
31
+ export interface ContainerChangeEventData {
32
+ modifyKey?: string;
33
+ changeRecords?: ChangeRecord[];
34
+ }
35
+
24
36
  export interface FieldProps<T = any> {
25
37
  config: T;
26
38
  model: any;
@@ -49,6 +61,8 @@ export type FormState = {
49
61
  setField: (prop: string, field: any) => void;
50
62
  getField: (prop: string) => any;
51
63
  deleteField: (prop: string) => any;
64
+ $messageBox: TMagicMessageBox;
65
+ $message: TMagicMessage;
52
66
  [key: string]: any;
53
67
  };
54
68
 
@@ -157,34 +171,34 @@ export interface Input {
157
171
  export type TypeFunction = (
158
172
  mForm: FormState | undefined,
159
173
  data: {
160
- model: Record<any, any>;
174
+ model: FormValue;
161
175
  },
162
176
  ) => string;
163
177
 
164
178
  export type FilterFunction<T = boolean> = (
165
179
  mForm: FormState | undefined,
166
180
  data: {
167
- model: Record<any, any>;
168
- values: Record<any, any>;
169
- parent?: Record<any, any>;
170
- formValue: Record<any, any>;
181
+ model: FormValue;
182
+ values: FormValue;
183
+ parent?: FormValue;
184
+ formValue: FormValue;
171
185
  prop: string;
172
186
  config: any;
173
187
  index?: number;
174
188
  },
175
189
  ) => T;
176
190
 
177
- type OnChangeHandler = (
178
- mForm: FormState | undefined,
179
- value: any,
180
- data: {
181
- model: Record<any, any>;
182
- values: Record<any, any>;
183
- parent?: Record<any, any>;
184
- formValue: Record<any, any>;
185
- config: any;
186
- },
187
- ) => any;
191
+ export interface OnChangeHandlerData {
192
+ model: FormValue;
193
+ values?: FormValue;
194
+ parent?: FormValue;
195
+ formValue?: FormValue;
196
+ config: any;
197
+ prop: string;
198
+ changeRecords: ChangeRecord[];
199
+ }
200
+
201
+ export type OnChangeHandler = (mForm: FormState | undefined, value: any, data: OnChangeHandlerData) => any;
188
202
 
189
203
  type DefaultValueFunction = (mForm: FormState | undefined) => any;
190
204
 
@@ -29,13 +29,13 @@
29
29
  margin-bottom: 10px;
30
30
  }
31
31
 
32
- .el-form-item.hidden {
32
+ .el-form-item.tmagic-form-hidden {
33
33
  > .el-form-item__label {
34
34
  display: none;
35
35
  }
36
36
  }
37
37
 
38
- .t-form__item.hidden {
38
+ .t-form__item.tmagic-form-hidden {
39
39
  > .t-form__label {
40
40
  display: none;
41
41
  }
@@ -4,3 +4,59 @@
4
4
  width: 100%;
5
5
  line-height: 1.4;
6
6
  }
7
+
8
+ .tmagic-form-text-popper {
9
+ min-width: 150px;
10
+ line-height: 1.4;
11
+ background-color: #fff;
12
+ box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
13
+ color: #606266;
14
+ border: 1px solid #e4e7ed;
15
+ border-radius: 4px;
16
+ font-size: 14px;
17
+ overflow-wrap: break-word;
18
+ box-sizing: border-box;
19
+ padding: 10px;
20
+
21
+ &:focus {
22
+ outline: none;
23
+ }
24
+ }
25
+
26
+ .tmagic-form-text-popper[data-popper-placement^="top"]
27
+ > .tmagic-form-text-popper-arrow {
28
+ bottom: -4px;
29
+ }
30
+
31
+ .tmagic-form-text-popper[data-popper-placement^="bottom"]
32
+ > .tmagic-form-text-popper-arrow {
33
+ top: -4px;
34
+ }
35
+
36
+ .tmagic-form-text-popper[data-popper-placement^="left"]
37
+ > .tmagic-form-text-popper-arrow {
38
+ right: -4px;
39
+ }
40
+
41
+ .tmagic-form-text-popper[data-popper-placement^="right"]
42
+ > .tmagic-form-text-popper-arrow {
43
+ left: -4px;
44
+ }
45
+
46
+ .tmagic-form-text-popper-arrow,
47
+ .tmagic-form-text-popper-arrow::before {
48
+ position: absolute;
49
+ width: 8px;
50
+ height: 8px;
51
+ background: inherit;
52
+ }
53
+
54
+ .tmagic-form-text-popper-arrow {
55
+ visibility: hidden;
56
+ }
57
+
58
+ .tmagic-form-text-popper-arrow::before {
59
+ visibility: visible;
60
+ content: "";
61
+ transform: rotate(45deg);
62
+ }