diff --git a/docs/.vitepress/theme/components/demo-block.vue b/docs/.vitepress/theme/components/demo-block.vue
index bfccc7a3..4c251545 100644
--- a/docs/.vitepress/theme/components/demo-block.vue
+++ b/docs/.vitepress/theme/components/demo-block.vue
@@ -189,6 +189,8 @@
import hljs from 'highlight.js';
import serialize from 'serialize-javascript';
+import { MForm } from '@tmagic/form';
+
export function stripScript(content) {
const result = content.match(/<(script)>([\s\S]+)<\/\1>/);
return result && result[2] ? result[2].trim() : '';
@@ -210,6 +212,10 @@ export function stripTemplate(content) {
export default {
props: ['type', 'config'],
+ components: {
+ MForm,
+ },
+
data() {
return {
codepen: {
diff --git a/packages/editor/src/fields/CodeLink.vue b/packages/editor/src/fields/CodeLink.vue
index 89b05b02..58d9a58c 100644
--- a/packages/editor/src/fields/CodeLink.vue
+++ b/packages/editor/src/fields/CodeLink.vue
@@ -1,12 +1,12 @@
-
+
diff --git a/packages/form/src/index.ts b/packages/form/src/index.ts
index 4673607e..3079ff16 100644
--- a/packages/form/src/index.ts
+++ b/packages/form/src/index.ts
@@ -16,44 +16,8 @@
* limitations under the License.
*/
-import type { App } from 'vue';
-
-import Container from './containers/Container.vue';
-import Fieldset from './containers/Fieldset.vue';
-import FlexLayout from './containers/FlexLayout.vue';
-import GroupList from './containers/GroupList.vue';
-import Panel from './containers/Panel.vue';
-import Row from './containers/Row.vue';
-import MStep from './containers/Step.vue';
-import Tabs from './containers/Tabs.vue';
-import Cascader from './fields/Cascader.vue';
-import Checkbox from './fields/Checkbox.vue';
-import CheckboxGroup from './fields/CheckboxGroup.vue';
-import ColorPicker from './fields/ColorPicker.vue';
-import Date from './fields/Date.vue';
-import Daterange from './fields/Daterange.vue';
-import DateTime from './fields/DateTime.vue';
-import Display from './fields/Display.vue';
-import DynamicField from './fields/DynamicField.vue';
-import Hidden from './fields/Hidden.vue';
-import Link from './fields/Link.vue';
-import Number from './fields/Number.vue';
-import NumberRange from './fields/NumberRange.vue';
-import RadioGroup from './fields/RadioGroup.vue';
-import Select from './fields/Select.vue';
-import Switch from './fields/Switch.vue';
-import Text from './fields/Text.vue';
-import Textarea from './fields/Textarea.vue';
-import Time from './fields/Time.vue';
-import Timerange from './fields/Timerange.vue';
-import Table from './table/Table.vue';
-import { setConfig } from './utils/config';
-import Form from './Form.vue';
-import FormDialog from './FormDialog.vue';
import type { FormConfig } from './schema';
-import './theme/index.scss';
-
export * from './schema';
export * from './utils/form';
export * from './utils/useAddField';
@@ -91,52 +55,14 @@ export { default as MSelect } from './fields/Select.vue';
export { default as MCascader } from './fields/Cascader.vue';
export { default as MDynamicField } from './fields/DynamicField.vue';
+export {
+ deleteField as deleteFormField,
+ getField as getFormField,
+ registerField as registerFormField,
+} from './utils/config';
+
+export type { FormInstallOptions } from './plugin';
+
export const createForm = (config: FormConfig | T) => config;
-export interface FormInstallOptions {
- [key: string]: any;
-}
-
-const defaultInstallOpt: FormInstallOptions = {};
-
-export default {
- install(app: App, opt: FormInstallOptions = {}) {
- const option = Object.assign(defaultInstallOpt, opt);
-
- app.config.globalProperties.$MAGIC_FORM = option;
- setConfig(option);
-
- app.component('m-form', Form);
- app.component('m-form-dialog', FormDialog);
- app.component('m-form-container', Container);
- app.component('m-form-fieldset', Fieldset);
- app.component('m-form-group-list', GroupList);
- app.component('m-form-panel', Panel);
- app.component('m-form-row', Row);
- app.component('m-form-step', MStep);
- app.component('m-form-table', Table);
- app.component('m-form-tab', Tabs);
- app.component('m-form-flex-layout', FlexLayout);
- app.component('m-fields-text', Text);
- app.component('m-fields-img-upload', Text);
- app.component('m-fields-number', Number);
- app.component('m-fields-number-range', NumberRange);
- app.component('m-fields-textarea', Textarea);
- app.component('m-fields-hidden', Hidden);
- app.component('m-fields-date', Date);
- app.component('m-fields-datetime', DateTime);
- app.component('m-fields-daterange', Daterange);
- app.component('m-fields-timerange', Timerange);
- app.component('m-fields-time', Time);
- app.component('m-fields-checkbox', Checkbox);
- app.component('m-fields-switch', Switch);
- app.component('m-fields-color-picker', ColorPicker);
- app.component('m-fields-checkbox-group', CheckboxGroup);
- app.component('m-fields-radio-group', RadioGroup);
- app.component('m-fields-display', Display);
- app.component('m-fields-link', Link);
- app.component('m-fields-select', Select);
- app.component('m-fields-cascader', Cascader);
- app.component('m-fields-dynamic-field', DynamicField);
- },
-};
+export { default } from './plugin';
diff --git a/packages/form/src/plugin.ts b/packages/form/src/plugin.ts
new file mode 100644
index 00000000..8abc6f55
--- /dev/null
+++ b/packages/form/src/plugin.ts
@@ -0,0 +1,102 @@
+/*
+ * Tencent is pleased to support the open source community by making TMagicEditor available.
+ *
+ * Copyright (C) 2025 Tencent. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { type App } from 'vue';
+
+import Container from './containers/Container.vue';
+import Fieldset from './containers/Fieldset.vue';
+import FlexLayout from './containers/FlexLayout.vue';
+import GroupList from './containers/GroupList.vue';
+import Panel from './containers/Panel.vue';
+import Row from './containers/Row.vue';
+import MStep from './containers/Step.vue';
+import Tabs from './containers/Tabs.vue';
+import Cascader from './fields/Cascader.vue';
+import Checkbox from './fields/Checkbox.vue';
+import CheckboxGroup from './fields/CheckboxGroup.vue';
+import ColorPicker from './fields/ColorPicker.vue';
+import Date from './fields/Date.vue';
+import Daterange from './fields/Daterange.vue';
+import DateTime from './fields/DateTime.vue';
+import Display from './fields/Display.vue';
+import DynamicField from './fields/DynamicField.vue';
+import Hidden from './fields/Hidden.vue';
+import Link from './fields/Link.vue';
+import Number from './fields/Number.vue';
+import NumberRange from './fields/NumberRange.vue';
+import RadioGroup from './fields/RadioGroup.vue';
+import Select from './fields/Select.vue';
+import Switch from './fields/Switch.vue';
+import Text from './fields/Text.vue';
+import Textarea from './fields/Textarea.vue';
+import Time from './fields/Time.vue';
+import Timerange from './fields/Timerange.vue';
+import Table from './table/Table.vue';
+import { setConfig } from './utils/config';
+import Form from './Form.vue';
+import FormDialog from './FormDialog.vue';
+
+import './theme/index.scss';
+
+export interface FormInstallOptions {
+ [key: string]: any;
+}
+
+const defaultInstallOpt: FormInstallOptions = {};
+
+export default {
+ install(app: App, opt: FormInstallOptions = {}) {
+ const option = Object.assign(defaultInstallOpt, opt);
+
+ app.config.globalProperties.$MAGIC_FORM = option;
+ setConfig(option);
+
+ app.component('m-form', Form);
+ app.component('m-form-dialog', FormDialog);
+ app.component('m-form-container', Container);
+ app.component('m-form-fieldset', Fieldset);
+ app.component('m-form-group-list', GroupList);
+ app.component('m-form-panel', Panel);
+ app.component('m-form-row', Row);
+ app.component('m-form-step', MStep);
+ app.component('m-form-table', Table);
+ app.component('m-form-tab', Tabs);
+ app.component('m-form-flex-layout', FlexLayout);
+ app.component('m-fields-text', Text);
+ app.component('m-fields-img-upload', Text);
+ app.component('m-fields-number', Number);
+ app.component('m-fields-number-range', NumberRange);
+ app.component('m-fields-textarea', Textarea);
+ app.component('m-fields-hidden', Hidden);
+ app.component('m-fields-date', Date);
+ app.component('m-fields-datetime', DateTime);
+ app.component('m-fields-daterange', Daterange);
+ app.component('m-fields-timerange', Timerange);
+ app.component('m-fields-time', Time);
+ app.component('m-fields-checkbox', Checkbox);
+ app.component('m-fields-switch', Switch);
+ app.component('m-fields-color-picker', ColorPicker);
+ app.component('m-fields-checkbox-group', CheckboxGroup);
+ app.component('m-fields-radio-group', RadioGroup);
+ app.component('m-fields-display', Display);
+ app.component('m-fields-link', Link);
+ app.component('m-fields-select', Select);
+ app.component('m-fields-cascader', Cascader);
+ app.component('m-fields-dynamic-field', DynamicField);
+ },
+};
diff --git a/packages/form/src/table/useSortable.ts b/packages/form/src/table/useSortable.ts
index ed5449e3..47483b5e 100644
--- a/packages/form/src/table/useSortable.ts
+++ b/packages/form/src/table/useSortable.ts
@@ -1,5 +1,5 @@
import { inject, nextTick, type Ref, type ShallowRef, watchEffect } from 'vue';
-import Sortable, { type SortableEvent } from 'sortablejs';
+import type { default as SortableType, SortableEvent } from 'sortablejs';
import { type TMagicTable } from '@tmagic/design';
import type { FormState } from '@tmagic/form-schema';
@@ -8,6 +8,9 @@ import { sortArray } from '../utils/form';
import type { TableProps } from './type';
+let SortablePromise: Promise | undefined;
+const loadSortable = () => (SortablePromise ??= import('sortablejs').then((m) => m.default));
+
export const useSortable = (
props: TableProps,
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
@@ -17,15 +20,16 @@ export const useSortable = (
) => {
const mForm = inject('mForm');
- let sortable: Sortable | undefined;
- const rowDrop = () => {
+ let sortable: SortableType | undefined;
+ const rowDrop = async () => {
sortable?.destroy();
const tableEl = tMagicTableRef.value?.getEl();
const tBodyEl = tableEl?.querySelector('.el-table__body > tbody') || tableEl?.querySelector('.t-table__body');
if (!tBodyEl) {
return;
}
- sortable = Sortable.create(tBodyEl, {
+
+ sortable = (await loadSortable()).create(tBodyEl, {
draggable: '.tmagic-design-table-row',
filter: 'input', // 表单组件选字操作和触发拖拽会冲突,优先保证选字操作
preventOnFilter: false, // 允许选字
diff --git a/packages/form/src/utils/config.ts b/packages/form/src/utils/config.ts
index 916bf91d..56cf552e 100644
--- a/packages/form/src/utils/config.ts
+++ b/packages/form/src/utils/config.ts
@@ -16,6 +16,8 @@
* limitations under the License.
*/
+import type { Component } from 'vue';
+
let $MAGIC_FORM = {} as any;
const setConfig = (option: any): void => {
@@ -24,4 +26,17 @@ const setConfig = (option: any): void => {
const getConfig = (key: string): T => $MAGIC_FORM[key];
-export { getConfig, setConfig };
+const fieldRegistry = new Map();
+
+const registerField = (tagName: string, component: Component): void => {
+ if (fieldRegistry.has(tagName)) {
+ return;
+ }
+ fieldRegistry.set(tagName, component);
+};
+
+const getField = (tagName: string): Component | undefined => fieldRegistry.get(tagName);
+
+const deleteField = (tagName: string): boolean => fieldRegistry.delete(tagName);
+
+export { deleteField, getConfig, getField, registerField, setConfig };
diff --git a/playground/src/pages/Form.vue b/playground/src/pages/Form.vue
index 254a9adb..5a08a8c3 100644
--- a/playground/src/pages/Form.vue
+++ b/playground/src/pages/Form.vue
@@ -3,7 +3,7 @@
表单字段展示
-
-
+
+