@tmagic/form 1.4.8 → 1.4.9

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 (57) hide show
  1. package/package.json +5 -5
  2. package/src/Form.vue +201 -0
  3. package/src/FormBox.vue +120 -0
  4. package/src/FormDialog.vue +173 -0
  5. package/src/FormDrawer.vue +159 -0
  6. package/src/containers/Col.vue +52 -0
  7. package/src/containers/Container.vue +408 -0
  8. package/src/containers/Fieldset.vue +124 -0
  9. package/src/containers/GroupList.vue +139 -0
  10. package/src/containers/GroupListItem.vue +135 -0
  11. package/src/containers/Panel.vue +99 -0
  12. package/src/containers/Row.vue +54 -0
  13. package/src/containers/Step.vue +82 -0
  14. package/src/containers/Table.vue +648 -0
  15. package/src/containers/Tabs.vue +226 -0
  16. package/src/fields/Cascader.vue +131 -0
  17. package/src/fields/Checkbox.vue +58 -0
  18. package/src/fields/CheckboxGroup.vue +43 -0
  19. package/src/fields/ColorPicker.vue +30 -0
  20. package/src/fields/Date.vue +38 -0
  21. package/src/fields/DateTime.vue +47 -0
  22. package/src/fields/Daterange.vue +99 -0
  23. package/src/fields/Display.vue +21 -0
  24. package/src/fields/DynamicField.vue +90 -0
  25. package/src/fields/Hidden.vue +16 -0
  26. package/src/fields/Link.vue +86 -0
  27. package/src/fields/Number.vue +49 -0
  28. package/src/fields/NumberRange.vue +50 -0
  29. package/src/fields/RadioGroup.vue +28 -0
  30. package/src/fields/Select.vue +449 -0
  31. package/src/fields/Switch.vue +59 -0
  32. package/src/fields/Text.vue +170 -0
  33. package/src/fields/Textarea.vue +46 -0
  34. package/src/fields/Time.vue +34 -0
  35. package/src/fields/Timerange.vue +76 -0
  36. package/src/index.ts +139 -0
  37. package/src/schema.ts +757 -0
  38. package/src/shims-vue.d.ts +6 -0
  39. package/src/theme/date-time.scss +7 -0
  40. package/src/theme/fieldset.scss +28 -0
  41. package/src/theme/form-box.scss +13 -0
  42. package/src/theme/form-dialog.scss +13 -0
  43. package/src/theme/form-drawer.scss +11 -0
  44. package/src/theme/form.scss +43 -0
  45. package/src/theme/group-list.scss +23 -0
  46. package/src/theme/index.scss +14 -0
  47. package/src/theme/link.scss +3 -0
  48. package/src/theme/number-range.scss +8 -0
  49. package/src/theme/panel.scss +24 -0
  50. package/src/theme/select.scss +3 -0
  51. package/src/theme/table.scss +70 -0
  52. package/src/theme/tabs.scss +27 -0
  53. package/src/theme/text.scss +6 -0
  54. package/src/utils/config.ts +27 -0
  55. package/src/utils/containerProps.ts +50 -0
  56. package/src/utils/form.ts +268 -0
  57. package/src/utils/useAddField.ts +40 -0
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
- "version": "1.4.8",
2
+ "version": "1.4.9",
3
3
  "name": "@tmagic/form",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
- "dist/*",
7
6
  "src/theme/*"
8
7
  ],
9
8
  "main": "dist/tmagic-form.umd.cjs",
@@ -24,7 +23,8 @@
24
23
  },
25
24
  "files": [
26
25
  "dist",
27
- "types"
26
+ "types",
27
+ "src"
28
28
  ],
29
29
  "license": "Apache-2.0",
30
30
  "engines": {
@@ -54,8 +54,8 @@
54
54
  "peerDependencies": {
55
55
  "vue": "^3.4.27",
56
56
  "typescript": "*",
57
- "@tmagic/design": "1.4.8",
58
- "@tmagic/utils": "1.4.8"
57
+ "@tmagic/design": "1.4.9",
58
+ "@tmagic/utils": "1.4.9"
59
59
  },
60
60
  "peerDependenciesMeta": {
61
61
  "typescript": {
package/src/Form.vue ADDED
@@ -0,0 +1,201 @@
1
+ <template>
2
+ <TMagicForm
3
+ class="m-form"
4
+ ref="tMagicForm"
5
+ :model="values"
6
+ :label-width="labelWidth"
7
+ :style="`height: ${height}`"
8
+ :inline="inline"
9
+ :label-position="labelPosition"
10
+ >
11
+ <template v-if="initialized && Array.isArray(config)">
12
+ <Container
13
+ v-for="(item, index) in config"
14
+ :disabled="disabled"
15
+ :key="item[keyProp] ?? index"
16
+ :config="item"
17
+ :model="values"
18
+ :last-values="lastValuesProcessed"
19
+ :is-compare="isCompare"
20
+ :label-width="item.labelWidth || labelWidth"
21
+ :step-active="stepActive"
22
+ :size="size"
23
+ @change="changeHandler"
24
+ ></Container>
25
+ </template>
26
+ </TMagicForm>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { provide, reactive, ref, toRaw, watch, watchEffect } from 'vue';
31
+ import { cloneDeep, isEqual } from 'lodash-es';
32
+
33
+ import { TMagicForm } from '@tmagic/design';
34
+
35
+ import Container from './containers/Container.vue';
36
+ import { getConfig } from './utils/config';
37
+ import { initValue } from './utils/form';
38
+ import type { FormConfig, FormState, FormValue, ValidateError } from './schema';
39
+
40
+ defineOptions({
41
+ name: 'MForm',
42
+ });
43
+
44
+ const props = withDefaults(
45
+ defineProps<{
46
+ /** 表单配置 */
47
+ config: FormConfig;
48
+ /** 表单值 */
49
+ initValues: Record<string, any>;
50
+ /** 需对比的值(开启对比模式时传入) */
51
+ lastValues?: Record<string, any>;
52
+ /** 是否开启对比模式 */
53
+ isCompare?: boolean;
54
+ parentValues?: Record<string, any>;
55
+ labelWidth?: string;
56
+ disabled?: boolean;
57
+ height?: string;
58
+ stepActive?: string | number;
59
+ size?: 'small' | 'default' | 'large';
60
+ inline?: boolean;
61
+ labelPosition?: string;
62
+ keyProp?: string;
63
+ popperClass?: string;
64
+ extendState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
65
+ }>(),
66
+ {
67
+ config: () => [],
68
+ initValues: () => ({}),
69
+ lastValues: () => ({}),
70
+ isCompare: false,
71
+ parentValues: () => ({}),
72
+ labelWidth: '200px',
73
+ disabled: false,
74
+ height: 'auto',
75
+ stepActive: 1,
76
+ inline: false,
77
+ labelPosition: 'right',
78
+ keyProp: '__key',
79
+ },
80
+ );
81
+
82
+ const emit = defineEmits(['change', 'error', 'field-input', 'field-change']);
83
+
84
+ const tMagicForm = ref<InstanceType<typeof TMagicForm>>();
85
+ const initialized = ref(false);
86
+ const values = ref<FormValue>({});
87
+ const lastValuesProcessed = ref<FormValue>({});
88
+ const fields = new Map<string, any>();
89
+
90
+ const requestFuc = getConfig('request') as Function;
91
+
92
+ const formState: FormState = reactive<FormState>({
93
+ keyProp: props.keyProp,
94
+ popperClass: props.popperClass,
95
+ config: props.config,
96
+ initValues: props.initValues,
97
+ isCompare: props.isCompare,
98
+ lastValues: props.lastValues,
99
+ parentValues: props.parentValues,
100
+ values,
101
+ lastValuesProcessed,
102
+ $emit: emit as (event: string, ...args: any[]) => void,
103
+ fields,
104
+ setField: (prop: string, field: any) => fields.set(prop, field),
105
+ getField: (prop: string) => fields.get(prop),
106
+ deleteField: (prop: string) => fields.delete(prop),
107
+ post: (options: any) => {
108
+ if (requestFuc) {
109
+ return requestFuc({
110
+ ...options,
111
+ method: 'POST',
112
+ });
113
+ }
114
+ },
115
+ });
116
+
117
+ watchEffect(async () => {
118
+ formState.initValues = props.initValues;
119
+ formState.lastValues = props.lastValues;
120
+ formState.isCompare = props.isCompare;
121
+ formState.config = props.config;
122
+ formState.keyProp = props.keyProp;
123
+ formState.popperClass = props.popperClass;
124
+ formState.parentValues = props.parentValues;
125
+
126
+ if (typeof props.extendState === 'function') {
127
+ const state = (await props.extendState(formState)) || {};
128
+ Object.entries(state).forEach(([key, value]) => {
129
+ formState[key] = value;
130
+ });
131
+ }
132
+ });
133
+
134
+ provide('mForm', formState);
135
+
136
+ watch(
137
+ [() => props.config, () => props.initValues],
138
+ ([config], [preConfig]) => {
139
+ if (!isEqual(toRaw(config), toRaw(preConfig))) {
140
+ initialized.value = false;
141
+ }
142
+
143
+ initValue(formState, {
144
+ initValues: props.initValues,
145
+ config: props.config,
146
+ }).then((value) => {
147
+ values.value = value;
148
+ // 非对比模式,初始化完成
149
+ initialized.value = !props.isCompare;
150
+ });
151
+
152
+ if (props.isCompare) {
153
+ // 对比模式下初始化待对比的表单值
154
+ initValue(formState, {
155
+ initValues: props.lastValues,
156
+ config: props.config,
157
+ }).then((value) => {
158
+ lastValuesProcessed.value = value;
159
+ initialized.value = true;
160
+ });
161
+ }
162
+ },
163
+ { immediate: true },
164
+ );
165
+
166
+ const changeHandler = () => {
167
+ emit('change', values.value);
168
+ };
169
+
170
+ defineExpose({
171
+ values,
172
+ lastValuesProcessed,
173
+ formState,
174
+ initialized,
175
+
176
+ changeHandler,
177
+
178
+ resetForm: () => tMagicForm.value?.resetFields(),
179
+
180
+ submitForm: async (native?: boolean): Promise<any> => {
181
+ try {
182
+ await tMagicForm.value?.validate();
183
+ return native ? values.value : cloneDeep(toRaw(values.value));
184
+ } catch (invalidFields: any) {
185
+ emit('error', invalidFields);
186
+
187
+ const error: string[] = [];
188
+
189
+ Object.entries(invalidFields).forEach(([, ValidateError]) => {
190
+ (ValidateError as ValidateError[]).forEach(({ field, message }) => {
191
+ if (field && message) error.push(`${field} -> ${message}`);
192
+ if (field && !message) error.push(`${field} -> 出现错误`);
193
+ if (!field && message) error.push(`${message}`);
194
+ });
195
+ });
196
+
197
+ throw new Error(error.join('<br>'));
198
+ }
199
+ },
200
+ });
201
+ </script>
@@ -0,0 +1,120 @@
1
+ <template>
2
+ <div class="m-form-box" :style="style">
3
+ <div class="m-box-body" :style="bodyHeight ? { height: `${bodyHeight}px` } : {}">
4
+ <TMagicScrollbar>
5
+ <Form
6
+ ref="form"
7
+ :size="size"
8
+ :disabled="disabled"
9
+ :config="config"
10
+ :init-values="values"
11
+ :parent-values="parentValues"
12
+ :label-width="labelWidth"
13
+ :label-position="labelPosition"
14
+ :inline="inline"
15
+ @change="changeHandler"
16
+ ></Form>
17
+ <slot></slot>
18
+ </TMagicScrollbar>
19
+ </div>
20
+
21
+ <div class="dialog-footer" :style="`height: ${footerHeight}px`">
22
+ <div>
23
+ <slot name="left"></slot>
24
+ </div>
25
+ <div>
26
+ <slot name="footer">
27
+ <TMagicButton type="primary" :size="size" :disabled="disabled" :loading="saveFetch" @click="submitHandler">{{
28
+ confirmText
29
+ }}</TMagicButton>
30
+ </slot>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import { computed, ref, watchEffect } from 'vue';
38
+
39
+ import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
40
+
41
+ import Form from './Form.vue';
42
+ import type { FormConfig } from './schema';
43
+
44
+ defineOptions({
45
+ name: 'MFormBox',
46
+ });
47
+
48
+ const props = withDefaults(
49
+ defineProps<{
50
+ config?: FormConfig;
51
+ values?: Object;
52
+ parentValues?: Object;
53
+ width?: number;
54
+ height?: number;
55
+ labelWidth?: string;
56
+ disabled?: boolean;
57
+ size?: 'small' | 'default' | 'large';
58
+ confirmText?: string;
59
+ inline?: boolean;
60
+ labelPosition?: string;
61
+ }>(),
62
+ {
63
+ config: () => [],
64
+ values: () => ({}),
65
+ confirmText: '确定',
66
+ },
67
+ );
68
+
69
+ const emit = defineEmits(['submit', 'change', 'error']);
70
+
71
+ const footerHeight = 60;
72
+
73
+ const style = computed(() => {
74
+ const style: { width?: string; height?: string } = {};
75
+ if (typeof props.width === 'number') {
76
+ style.width = `${props.width}px`;
77
+ }
78
+ if (typeof props.height === 'number') {
79
+ style.height = `${props.height}px`;
80
+ }
81
+ return style;
82
+ });
83
+
84
+ const form = ref<InstanceType<typeof Form>>();
85
+ const saveFetch = ref(false);
86
+
87
+ const bodyHeight = ref(0);
88
+
89
+ watchEffect(() => {
90
+ if (!props.height) {
91
+ return;
92
+ }
93
+ bodyHeight.value = props.height - footerHeight;
94
+ });
95
+
96
+ const submitHandler = async () => {
97
+ try {
98
+ const values = await form.value?.submitForm();
99
+ emit('submit', values);
100
+ } catch (e) {
101
+ emit('error', e);
102
+ }
103
+ };
104
+
105
+ const changeHandler = (value: any) => {
106
+ emit('change', value);
107
+ };
108
+
109
+ const show = () => {};
110
+
111
+ const hide = () => {};
112
+
113
+ defineExpose({
114
+ form,
115
+ saveFetch,
116
+
117
+ show,
118
+ hide,
119
+ });
120
+ </script>
@@ -0,0 +1,173 @@
1
+ <template>
2
+ <TMagicDialog
3
+ v-model="dialogVisible"
4
+ class="m-form-dialog"
5
+ top="20px"
6
+ append-to-body
7
+ :title="title"
8
+ :width="width"
9
+ :zIndex="zIndex"
10
+ :fullscreen="fullscreen"
11
+ :close-on-click-modal="false"
12
+ @close="closeHandler"
13
+ >
14
+ <div
15
+ v-if="dialogVisible"
16
+ class="m-dialog-body"
17
+ :style="`max-height: ${bodyHeight}; overflow-y: auto; overflow-x: hidden;`"
18
+ >
19
+ <Form
20
+ v-model="stepActive"
21
+ ref="form"
22
+ :size="size"
23
+ :disabled="disabled"
24
+ :config="config"
25
+ :init-values="values"
26
+ :parent-values="parentValues"
27
+ :label-width="labelWidth"
28
+ :label-position="labelPosition"
29
+ :inline="inline"
30
+ @change="changeHandler"
31
+ ></Form>
32
+ <slot></slot>
33
+ </div>
34
+
35
+ <template #footer>
36
+ <TMagicRow class="dialog-footer">
37
+ <TMagicCol :span="12" style="text-align: left">
38
+ <div style="min-height: 1px">
39
+ <slot name="left"></slot>
40
+ </div>
41
+ </TMagicCol>
42
+ <TMagicCol :span="12">
43
+ <slot name="footer">
44
+ <TMagicButton @click="cancel" size="small">取 消</TMagicButton>
45
+ <TMagicButton v-if="hasStep && stepActive > 1" type="info" size="small" @click="preStep"
46
+ >上一步</TMagicButton
47
+ >
48
+ <TMagicButton v-if="hasStep && stepCount > stepActive" type="info" size="small" @click="nextStep"
49
+ >下一步</TMagicButton
50
+ >
51
+ <TMagicButton v-else type="primary" size="small" :disabled="disabled" :loading="saveFetch" @click="save">{{
52
+ confirmText
53
+ }}</TMagicButton>
54
+ </slot>
55
+ </TMagicCol>
56
+ </TMagicRow>
57
+ </template>
58
+ </TMagicDialog>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ import { computed, ref } from 'vue';
63
+
64
+ import { TMagicButton, TMagicCol, TMagicDialog, TMagicRow } from '@tmagic/design';
65
+
66
+ import Form from './Form.vue';
67
+ import { FormConfig, StepConfig } from './schema';
68
+
69
+ defineOptions({
70
+ name: 'MFormDialog',
71
+ });
72
+
73
+ const props = withDefaults(
74
+ defineProps<{
75
+ config?: FormConfig;
76
+ values?: Object;
77
+ parentValues?: Object;
78
+ width?: string | number;
79
+ labelWidth?: string;
80
+ fullscreen?: boolean;
81
+ disabled?: boolean;
82
+ title?: string;
83
+ inline?: boolean;
84
+ labelPosition?: string;
85
+ zIndex?: number;
86
+ size?: 'small' | 'default' | 'large';
87
+ confirmText?: string;
88
+ }>(),
89
+ {
90
+ config: () => [],
91
+ values: () => ({}),
92
+ confirmText: '确定',
93
+ },
94
+ );
95
+
96
+ const emit = defineEmits(['close', 'submit', 'error', 'change']);
97
+
98
+ const form = ref<InstanceType<typeof Form>>();
99
+ const dialogVisible = ref(false);
100
+ const saveFetch = ref(false);
101
+ const stepActive = ref(1);
102
+ const bodyHeight = ref(`${document.body.clientHeight - 194}px`);
103
+
104
+ const stepCount = computed(() => {
105
+ const { length } = props.config;
106
+ for (let index = 0; index < length; index++) {
107
+ if (props.config[index].type === 'step') {
108
+ return (props.config[index] as StepConfig).items.length;
109
+ }
110
+ }
111
+ return 0;
112
+ });
113
+
114
+ const hasStep = computed(() => {
115
+ const { length } = props.config;
116
+ for (let index = 0; index < length; index++) {
117
+ if (props.config[index].type === 'step') {
118
+ return true;
119
+ }
120
+ }
121
+
122
+ return false;
123
+ });
124
+
125
+ const closeHandler = () => {
126
+ stepActive.value = 1;
127
+ emit('close');
128
+ };
129
+
130
+ const save = async () => {
131
+ try {
132
+ const values = await form.value?.submitForm();
133
+ emit('submit', values);
134
+ } catch (e) {
135
+ emit('error', e);
136
+ }
137
+ };
138
+
139
+ const preStep = () => {
140
+ stepActive.value -= 1;
141
+ };
142
+
143
+ const nextStep = () => {
144
+ stepActive.value += 1;
145
+ };
146
+
147
+ const changeHandler = (value: any) => {
148
+ emit('change', value);
149
+ };
150
+
151
+ const show = () => {
152
+ dialogVisible.value = true;
153
+ };
154
+
155
+ const hide = () => {
156
+ dialogVisible.value = false;
157
+ };
158
+
159
+ const cancel = () => {
160
+ hide();
161
+ };
162
+
163
+ defineExpose({
164
+ form,
165
+ saveFetch,
166
+ dialogVisible,
167
+
168
+ cancel,
169
+ save,
170
+ show,
171
+ hide,
172
+ });
173
+ </script>
@@ -0,0 +1,159 @@
1
+ <template>
2
+ <TMagicDrawer
3
+ class="m-form-drawer"
4
+ ref="drawer"
5
+ v-model="visible"
6
+ :title="title"
7
+ :close-on-press-escape="closeOnPressEscape"
8
+ :append-to-body="true"
9
+ :show-close="true"
10
+ :close-on-click-modal="true"
11
+ :size="width"
12
+ :zIndex="zIndex"
13
+ :before-close="beforeClose"
14
+ @open="openHandler"
15
+ @opened="openedHandler"
16
+ @close="closeHandler"
17
+ @closed="closedHandler"
18
+ >
19
+ <div v-if="visible" ref="drawerBody" class="m-drawer-body">
20
+ <Form
21
+ ref="form"
22
+ :size="size"
23
+ :disabled="disabled"
24
+ :config="config"
25
+ :init-values="values"
26
+ :parent-values="parentValues"
27
+ :label-width="labelWidth"
28
+ :label-position="labelPosition"
29
+ :inline="inline"
30
+ @change="changeHandler"
31
+ ></Form>
32
+ <slot></slot>
33
+ </div>
34
+
35
+ <template #footer>
36
+ <TMagicRow class="dialog-footer">
37
+ <TMagicCol :span="12" style="text-align: left">
38
+ <div style="min-height: 1px">
39
+ <slot name="left"></slot>
40
+ </div>
41
+ </TMagicCol>
42
+ <TMagicCol :span="12">
43
+ <slot name="footer">
44
+ <TMagicButton @click="handleClose">关闭</TMagicButton>
45
+ <TMagicButton type="primary" :disabled="disabled" :loading="saveFetch" @click="submitHandler">{{
46
+ confirmText
47
+ }}</TMagicButton>
48
+ </slot>
49
+ </TMagicCol>
50
+ </TMagicRow>
51
+ </template>
52
+ </TMagicDrawer>
53
+ </template>
54
+
55
+ <script setup lang="ts">
56
+ import { ref, watchEffect } from 'vue';
57
+
58
+ import { TMagicButton, TMagicCol, TMagicDrawer, TMagicRow } from '@tmagic/design';
59
+
60
+ import Form from './Form.vue';
61
+ import type { FormConfig } from './schema';
62
+
63
+ defineOptions({
64
+ name: 'MFormDialog',
65
+ });
66
+
67
+ withDefaults(
68
+ defineProps<{
69
+ config?: FormConfig;
70
+ values?: Object;
71
+ parentValues?: Object;
72
+ width?: string | number;
73
+ labelWidth?: string;
74
+ disabled?: boolean;
75
+ closeOnPressEscape?: boolean;
76
+ title?: string;
77
+ zIndex?: number;
78
+ size?: 'small' | 'default' | 'large';
79
+ confirmText?: string;
80
+ inline?: boolean;
81
+ labelPosition?: string;
82
+ /** 关闭前的回调,会暂停 Drawer 的关闭; done 是个 function type 接受一个 boolean 参数, 执行 done 使用 true 参数或不提供参数将会终止关闭 */
83
+ beforeClose?: (done: (cancel?: boolean) => void) => void;
84
+ }>(),
85
+ {
86
+ closeOnPressEscape: true,
87
+ config: () => [],
88
+ values: () => ({}),
89
+ confirmText: '确定',
90
+ },
91
+ );
92
+
93
+ const emit = defineEmits(['close', 'closed', 'submit', 'error', 'change', 'open', 'opened']);
94
+
95
+ const drawer = ref<InstanceType<typeof TMagicDrawer>>();
96
+ const form = ref<InstanceType<typeof Form>>();
97
+ const drawerBody = ref<HTMLDivElement>();
98
+ const visible = ref(false);
99
+ const saveFetch = ref(false);
100
+ const bodyHeight = ref(0);
101
+
102
+ watchEffect(() => {
103
+ if (drawerBody.value) {
104
+ bodyHeight.value = drawerBody.value.clientHeight;
105
+ }
106
+ });
107
+
108
+ const submitHandler = async () => {
109
+ try {
110
+ const values = await form.value?.submitForm();
111
+ emit('submit', values);
112
+ } catch (e) {
113
+ emit('error', e);
114
+ }
115
+ };
116
+
117
+ const changeHandler = (value: any) => {
118
+ emit('change', value);
119
+ };
120
+
121
+ const openHandler = () => {
122
+ emit('open');
123
+ };
124
+
125
+ const openedHandler = () => {
126
+ emit('opened');
127
+ };
128
+
129
+ const closeHandler = () => {
130
+ emit('close');
131
+ };
132
+
133
+ const closedHandler = () => {
134
+ emit('closed');
135
+ };
136
+
137
+ const show = () => {
138
+ visible.value = true;
139
+ };
140
+
141
+ const hide = () => {
142
+ visible.value = false;
143
+ };
144
+
145
+ /** 用于关闭 Drawer, 该方法会调用传入的 before-close 方法 */
146
+ const handleClose = () => {
147
+ drawer.value?.handleClose();
148
+ };
149
+
150
+ defineExpose({
151
+ form,
152
+ saveFetch,
153
+ bodyHeight,
154
+
155
+ show,
156
+ hide,
157
+ handleClose,
158
+ });
159
+ </script>