feat(form-schema,form,editor): 完善表单配置类型

This commit is contained in:
roymondchen 2026-03-20 12:31:55 +08:00
parent 1664559d8f
commit 55eb546ad6
19 changed files with 234 additions and 185 deletions

View File

@ -63,13 +63,7 @@ import { computed, inject, nextTick, Ref, ref, useTemplateRef, watch } from 'vue
import type { CodeBlockContent } from '@tmagic/core'; import type { CodeBlockContent } from '@tmagic/core';
import { TMagicButton, TMagicDialog, tMagicMessage, tMagicMessageBox, TMagicTag } from '@tmagic/design'; import { TMagicButton, TMagicDialog, tMagicMessage, tMagicMessageBox, TMagicTag } from '@tmagic/design';
import { import { type ContainerChangeEventData, type FormConfig, type FormState, MFormBox } from '@tmagic/form';
type ContainerChangeEventData,
type FormConfig,
type FormState,
MFormBox,
type TableColumnConfig,
} from '@tmagic/form';
import FloatingBox from '@editor/components/FloatingBox.vue'; import FloatingBox from '@editor/components/FloatingBox.vue';
import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height'; import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
@ -118,7 +112,7 @@ const diffChange = () => {
difVisible.value = false; difVisible.value = false;
}; };
const defaultParamColConfig: TableColumnConfig = { const defaultParamColConfig = {
type: 'row', type: 'row',
label: '参数类型', label: '参数类型',
items: [ items: [

View File

@ -115,7 +115,7 @@ watch(
const selectConfig: SelectConfig = { const selectConfig: SelectConfig = {
type: 'select', type: 'select',
name: props.name, name: props.name,
disable: props.disabled, disabled: props.disabled,
options: () => { options: () => {
if (codeDsl.value) { if (codeDsl.value) {
return map(codeDsl.value, (value, key) => ({ return map(codeDsl.value, (value, key) => ({

View File

@ -106,13 +106,18 @@ const type = computed((): string => {
} }
if (type === 'form') return ''; if (type === 'form') return '';
if (type === 'container') return ''; if (type === 'container') return '';
return type?.replace(/([A-Z])/g, '-$1').toLowerCase() || (props.config.items ? '' : 'text'); return (
type?.replace(/([A-Z])/g, '-$1').toLowerCase() ||
(props.config.fieldConfig && 'items' in props.config.fieldConfig ? '' : 'text')
);
}); });
const tagName = computed(() => { const tagName = computed(() => {
const component = const component =
getFormField(type.value || 'container') || getFormField(type.value || 'container') ||
resolveComponent(`m-${props.config.items ? 'form' : 'fields'}-${type.value}`); resolveComponent(
`m-${props.config.fieldConfig && 'items' in props.config.fieldConfig ? 'form' : 'fields'}-${type.value}`,
);
if (typeof component !== 'string') return component; if (typeof component !== 'string') return component;
return 'm-fields-text'; return 'm-fields-text';
}); });

View File

@ -52,12 +52,15 @@ import { inject, Ref, ref } from 'vue';
import type { DataSchema } from '@tmagic/core'; import type { DataSchema } from '@tmagic/core';
import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design'; import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
import { import {
type CodeConfig,
type ContainerChangeEventData, type ContainerChangeEventData,
type DataSourceFieldsConfig, type DataSourceFieldsConfig,
type FieldProps, type FieldProps,
type FormConfig, type FormConfig,
type FormState, type FormState,
MFormBox, MFormBox,
type NumberConfig,
type TextConfig,
} from '@tmagic/form'; } from '@tmagic/form';
import { type ColumnConfig, MagicTable } from '@tmagic/table'; import { type ColumnConfig, MagicTable } from '@tmagic/table';
import { getDefaultValueFromFields } from '@tmagic/utils'; import { getDefaultValueFromFields } from '@tmagic/utils';
@ -247,7 +250,7 @@ const dataSourceFieldsConfig: FormConfig = [
{ text: 'true', value: true }, { text: 'true', value: true },
{ text: 'false', value: false }, { text: 'false', value: false },
], ],
}, } as unknown as CodeConfig | NumberConfig | TextConfig,
{ {
name: 'enable', name: 'enable',
text: '是否可用', text: '是否可用',

View File

@ -59,7 +59,6 @@ import { ActionType } from '@tmagic/core';
import { TMagicButton } from '@tmagic/design'; import { TMagicButton } from '@tmagic/design';
import type { import type {
CascaderOption, CascaderOption,
ChildConfig,
CodeSelectColConfig, CodeSelectColConfig,
ContainerChangeEventData, ContainerChangeEventData,
DataSourceMethodSelectConfig, DataSourceMethodSelectConfig,
@ -90,10 +89,10 @@ const { editorService, dataSourceService, eventsService, codeBlockService, props
// //
const eventNameConfig = computed(() => { const eventNameConfig = computed(() => {
const defaultEventNameConfig: ChildConfig = { const defaultEventNameConfig = {
name: 'name', name: 'name',
text: '事件', text: '事件',
type: (mForm, { formValue }: any) => { type: (mForm: FormState | undefined, { formValue }: any) => {
if ( if (
props.config.src !== 'component' || props.config.src !== 'component' ||
(formValue.type === 'page-fragment-container' && formValue.pageFragmentId) (formValue.type === 'page-fragment-container' && formValue.pageFragmentId)
@ -227,10 +226,10 @@ const targetCompConfig = computed(() => {
// //
const compActionConfig = computed(() => { const compActionConfig = computed(() => {
const defaultCompActionConfig: ChildConfig = { const defaultCompActionConfig = {
name: 'method', name: 'method',
text: '动作', text: '动作',
type: (mForm, { model }: any) => { type: (mForm: FormState | undefined, { model }: any) => {
const to = editorService.getNodeById(model.to); const to = editorService.getNodeById(model.to);
if (to && to.type === 'page-fragment-container' && to.pageFragmentId) { if (to && to.type === 'page-fragment-container' && to.pageFragmentId) {
@ -240,7 +239,7 @@ const compActionConfig = computed(() => {
return 'select'; return 'select';
}, },
checkStrictly: () => props.config.src !== 'component', checkStrictly: () => props.config.src !== 'component',
display: (mForm, { model }: any) => model.actionType === ActionType.COMP, display: (mForm: FormState | undefined, { model }: any) => model.actionType === ActionType.COMP,
options: (mForm: FormState, { model }: any) => { options: (mForm: FormState, { model }: any) => {
const node = editorService.getNodeById(model.to); const node = editorService.getNodeById(model.to);
if (!node?.type) return []; if (!node?.type) return [];

View File

@ -28,10 +28,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { TMagicButton, TMagicInput } from '@tmagic/design'; import { TMagicButton, TMagicInput } from '@tmagic/design';
import type { FieldProps, FormItem } from '@tmagic/form'; import type { FieldProps, StyleSetterConfig } from '@tmagic/form';
const emit = defineEmits(['change']); const emit = defineEmits(['change']);
defineProps<FieldProps<{ type: 'style-setter' } & FormItem>>(); defineProps<FieldProps<StyleSetterConfig>>();
const horizontalList = [ const horizontalList = [
{ {

View File

@ -12,8 +12,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { markRaw } from 'vue'; import { markRaw } from 'vue';
import type { ContainerChangeEventData, FormState } from '@tmagic/form'; import type { ContainerChangeEventData } from '@tmagic/form';
import { MContainer } from '@tmagic/form'; import { defineFormItem, MContainer } from '@tmagic/form';
import type { StyleSchema } from '@tmagic/schema'; import type { StyleSchema } from '@tmagic/schema';
import Box from '../components/Box.vue'; import Box from '../components/Box.vue';
@ -42,7 +42,7 @@ const emit = defineEmits<{
change: [v: string | StyleSchema, eventData: ContainerChangeEventData]; change: [v: string | StyleSchema, eventData: ContainerChangeEventData];
}>(); }>();
const config = { const config = defineFormItem({
items: [ items: [
{ {
name: 'display', name: 'display',
@ -74,7 +74,7 @@ const config = {
tooltip: '垂直方向 起点在下沿 column-reverse', tooltip: '垂直方向 起点在下沿 column-reverse',
}, },
], ],
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.display === 'flex', display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
}, },
{ {
name: 'justifyContent', name: 'justifyContent',
@ -89,7 +89,7 @@ const config = {
{ value: 'space-between', icon: markRaw(JustifyContentSpaceBetween), tooltip: '两端对齐 space-between' }, { value: 'space-between', icon: markRaw(JustifyContentSpaceBetween), tooltip: '两端对齐 space-between' },
{ value: 'space-around', icon: markRaw(JustifyContentSpaceAround), tooltip: '横向平分 space-around' }, { value: 'space-around', icon: markRaw(JustifyContentSpaceAround), tooltip: '横向平分 space-around' },
], ],
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.display === 'flex', display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
}, },
{ {
name: 'alignItems', name: 'alignItems',
@ -104,7 +104,7 @@ const config = {
{ value: 'space-between', icon: markRaw(JustifyContentSpaceBetween), tooltip: '两端对齐 space-between' }, { value: 'space-between', icon: markRaw(JustifyContentSpaceBetween), tooltip: '两端对齐 space-between' },
{ value: 'space-around', icon: markRaw(JustifyContentSpaceAround), tooltip: '横向平分 space-around' }, { value: 'space-around', icon: markRaw(JustifyContentSpaceAround), tooltip: '横向平分 space-around' },
], ],
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.display === 'flex', display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
}, },
{ {
name: 'flexWrap', name: 'flexWrap',
@ -117,7 +117,7 @@ const config = {
{ value: 'wrap', text: '正换行', tooltip: '第一行在上方 wrap' }, { value: 'wrap', text: '正换行', tooltip: '第一行在上方 wrap' },
{ value: 'wrap-reverse', text: '逆换行', tooltip: '第一行在下方 wrap-reverse' }, { value: 'wrap-reverse', text: '逆换行', tooltip: '第一行在下方 wrap-reverse' },
], ],
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.display === 'flex', display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
}, },
{ {
type: 'row', type: 'row',
@ -180,7 +180,7 @@ const config = {
], ],
}, },
], ],
}; });
const change = (value: string | StyleSchema, eventData: ContainerChangeEventData) => { const change = (value: string | StyleSchema, eventData: ContainerChangeEventData) => {
emit('change', value, eventData); emit('change', value, eventData);

View File

@ -102,9 +102,11 @@ class Props extends BaseService {
} }
public async setPropsConfig(type: string, config: FormConfig | PropsFormConfigFunction) { public async setPropsConfig(type: string, config: FormConfig | PropsFormConfigFunction) {
let c = config; let c: FormConfig;
if (typeof config === 'function') { if (typeof config === 'function') {
c = config({ editorService }); c = config({ editorService });
} else {
c = config;
} }
this.state.propsConfigMap[toLine(type)] = await this.fillConfig(Array.isArray(c) ? c : [c]); this.state.propsConfigMap[toLine(type)] = await this.fillConfig(Array.isArray(c) ? c : [c]);

View File

@ -5,79 +5,77 @@ import { dataSourceTemplateRegExp, getKeysArray, isNumber } from '@tmagic/utils'
import BaseFormConfig from './formConfigs/base'; import BaseFormConfig from './formConfigs/base';
import HttpFormConfig from './formConfigs/http'; import HttpFormConfig from './formConfigs/http';
const fillConfig = (config: FormConfig): FormConfig => [ const dataSourceFormConfig = {
...BaseFormConfig(), type: 'tab',
...config, items: [
{ {
type: 'tab', title: '数据定义',
items: [ items: [
{ {
title: '数据定义', name: 'fields',
items: [ type: 'data-source-fields',
{ defaultValue: () => [],
name: 'fields', },
type: 'data-source-fields', ],
defaultValue: () => [], },
}, {
], title: '方法定义',
}, items: [
{ {
title: '方法定义', name: 'methods',
items: [ type: 'data-source-methods',
{ defaultValue: () => [],
name: 'methods', },
type: 'data-source-methods', ],
defaultValue: () => [], },
}, {
], title: '事件配置',
}, items: [
{ {
title: '事件配置', name: 'events',
items: [ src: 'datasource',
{ type: 'event-select',
name: 'events', },
src: 'datasource', ],
type: 'event-select', },
}, {
], title: 'mock数据',
}, items: [
{ {
title: 'mock数据', name: 'mocks',
items: [ type: 'data-source-mocks',
{ defaultValue: () => [],
name: 'mocks', },
type: 'data-source-mocks', ],
defaultValue: () => [], },
}, {
], title: '请求参数裁剪',
}, display: (_formState: FormState, { model }: any) => model.type === 'http',
{ items: [
title: '请求参数裁剪', {
display: (_formState: FormState, { model }: any) => model.type === 'http', name: 'beforeRequest',
items: [ type: 'vs-code',
{ parse: true,
name: 'beforeRequest', autosize: { minRows: 10, maxRows: 30 },
type: 'vs-code', },
parse: true, ],
autosize: { minRows: 10, maxRows: 30 }, },
}, {
], title: '响应数据裁剪',
}, display: (_formState: FormState, { model }: any) => model.type === 'http',
{ items: [
title: '响应数据裁剪', {
display: (_formState: FormState, { model }: any) => model.type === 'http', name: 'afterResponse',
items: [ type: 'vs-code',
{ parse: true,
name: 'afterResponse', autosize: { minRows: 10, maxRows: 30 },
type: 'vs-code', },
parse: true, ],
autosize: { minRows: 10, maxRows: 30 }, },
}, ],
], };
},
], const fillConfig = (config: FormConfig): FormConfig => [...BaseFormConfig(), ...config, dataSourceFormConfig];
},
];
export const getFormConfig = (type: string, configs: Record<string, FormConfig>): FormConfig => { export const getFormConfig = (type: string, configs: Record<string, FormConfig>): FormConfig => {
switch (type) { switch (type) {

View File

@ -24,7 +24,7 @@ import {
NODE_DISABLE_DATA_SOURCE_KEY, NODE_DISABLE_DATA_SOURCE_KEY,
} from '@tmagic/core'; } from '@tmagic/core';
import { tMagicMessage } from '@tmagic/design'; import { tMagicMessage } from '@tmagic/design';
import type { FormConfig, FormState, TabConfig, TabPaneConfig } from '@tmagic/form'; import type { ChildConfig, FormConfig, TabConfig, TabPaneConfig } from '@tmagic/form';
export const arrayOptions = [ export const arrayOptions = [
{ text: '包含', value: 'include' }, { text: '包含', value: 'include' },
@ -107,7 +107,7 @@ export const styleTabConfig: TabPaneConfig = {
'borderStyle', 'borderStyle',
'borderColor', 'borderColor',
], ],
}, } as unknown as ChildConfig,
], ],
}, },
], ],
@ -170,7 +170,7 @@ export const advancedTabConfig: TabPaneConfig = {
export const displayTabConfig: TabPaneConfig = { export const displayTabConfig: TabPaneConfig = {
title: '显示条件', title: '显示条件',
display: (_state: FormState, { model }: any) => model.type !== 'page', display: (_state, { model }) => model.type !== 'page',
items: [ items: [
{ {
name: NODE_CONDS_RESULT_KEY, name: NODE_CONDS_RESULT_KEY,
@ -209,7 +209,7 @@ export const fillConfig = (
const propsConfig: FormConfig = []; const propsConfig: FormConfig = [];
// 组件类型,必须要有 // 组件类型,必须要有
if (!config.find((item) => item.name === 'type')) { if (!config.find((item) => 'name' in item && item.name === 'type')) {
propsConfig.push({ propsConfig.push({
text: 'type', text: 'type',
name: 'type', name: 'type',
@ -217,7 +217,7 @@ export const fillConfig = (
}); });
} }
if (!config.find((item) => item.name === 'id')) { if (!config.find((item) => 'name' in item && item.name === 'id')) {
// 组件id必须要有 // 组件id必须要有
propsConfig.push({ propsConfig.push({
name: 'id', name: 'id',
@ -241,14 +241,16 @@ export const fillConfig = (
}); });
} }
if (!config.find((item) => item.name === 'name')) { if (!config.find((item) => 'name' in item && item.name === 'name')) {
propsConfig.push({ propsConfig.push({
name: 'name', name: 'name',
text: '组件名称', text: '组件名称',
}); });
} }
const noCodeAdvancedTabItems = advancedTabConfig.items.filter((item) => item.type !== 'code-select'); const noCodeAdvancedTabItems = advancedTabConfig.items.filter(
(item) => 'type' in item && item.type !== 'code-select',
);
if (noCodeAdvancedTabItems.length > 0 && disabledCodeBlock) { if (noCodeAdvancedTabItems.length > 0 && disabledCodeBlock) {
advancedTabConfig.items = noCodeAdvancedTabItems; advancedTabConfig.items = noCodeAdvancedTabItems;

View File

@ -129,11 +129,11 @@ export interface FormItem {
expand?: boolean; expand?: boolean;
style?: Record<string, any>; style?: Record<string, any>;
fieldStyle?: Record<string, any>; fieldStyle?: Record<string, any>;
[key: string]: any; labelPosition?: 'top' | 'left' | 'right';
} }
export interface ContainerCommonConfig { export interface ContainerCommonConfig<T extends Record<string, any> = never> {
items: FormConfig; items: FormConfig<T>;
onInitValue?: ( onInitValue?: (
mForm: FormState | undefined, mForm: FormState | undefined,
data: { data: {
@ -351,6 +351,8 @@ export interface DisplayConfig extends FormItem {
export interface TextConfig extends FormItem, Input { export interface TextConfig extends FormItem, Input {
type?: 'text'; type?: 'text';
tooltip?: string; tooltip?: string;
/** 是否可清空 */
clearable?: boolean;
prepend?: string; prepend?: string;
/** 后置元素,一般为标签或按钮 */ /** 后置元素,一般为标签或按钮 */
append?: append?:
@ -436,6 +438,17 @@ export interface TimeConfig extends FormItem, Input {
valueFormat?: 'HH:mm:ss' | string; valueFormat?: 'HH:mm:ss' | string;
} }
/**
*
*/
export interface TimerangeConfig extends FormItem {
type: 'timerange';
names?: string[];
defaultTime?: Date[];
format?: 'HH:mm:ss' | string;
valueFormat?: 'HH:mm:ss' | string;
}
/** /**
* *
*/ */
@ -459,11 +472,11 @@ export interface SwitchConfig extends FormItem {
* *
*/ */
export interface RadioGroupConfig extends FormItem { export interface RadioGroupConfig extends FormItem {
type: 'radio-group'; type: 'radio-group' | 'radioGroup';
childType?: 'default' | 'button'; childType?: 'default' | 'button';
options: { options: {
value: string | number | boolean; value: string | number | boolean;
text: string; text?: string;
icon?: any; icon?: any;
tooltip?: string; tooltip?: string;
}[]; }[];
@ -533,7 +546,7 @@ export interface SelectConfig extends FormItem, Input {
/** /**
* *
*/ */
export interface LinkConfig extends FormItem { export interface LinkConfig<T extends Record<string, any> = never> extends FormItem {
type: 'link'; type: 'link';
href?: string | ((model: Record<string, any>) => string); href?: string | ((model: Record<string, any>) => string);
css?: { css?: {
@ -553,7 +566,7 @@ export interface LinkConfig extends FormItem {
) => string) ) => string)
| string; | string;
form: form:
| FormConfig | FormConfig<T>
| (( | ((
mForm: FormState | undefined, mForm: FormState | undefined,
data: { data: {
@ -561,7 +574,7 @@ export interface LinkConfig extends FormItem {
values?: Readonly<FormValue> | null; values?: Readonly<FormValue> | null;
formValue?: FormValue; formValue?: FormValue;
}, },
) => FormConfig); ) => FormConfig<T>);
fullscreen?: boolean; fullscreen?: boolean;
} }
@ -618,32 +631,38 @@ export interface DynamicFieldConfig extends FormItem {
/** /**
* *
*/ */
export interface RowConfig extends FormItem { export interface RowConfig<T extends Record<string, any> = never> extends FormItem {
type: 'row'; type: 'row';
span: number; span: number;
items: ({ span?: number } & (ChildConfig | EditorChildConfig))[]; items: ({ span?: number } & (ChildConfig<T> | EditorChildConfig | NoInfer<T>))[];
} }
/** /**
* *
*/ */
export interface TabPaneConfig { export interface TabPaneConfig<T extends Record<string, any> = never> {
status?: string; status?: string;
/** 标签页名称,用于关联 model 中的数据 */
name?: string | number;
title: string; title: string;
lazy?: boolean; lazy?: boolean;
labelWidth?: string; labelWidth?: string;
items: FormConfig; items: FormConfig<T>;
display?: boolean | 'expand' | FilterFunction<boolean | 'expand'>;
onTabClick?: (mForm: FormState | undefined, tab: any, data: any) => void; onTabClick?: (mForm: FormState | undefined, tab: any, data: any) => void;
[key: string]: any;
} }
export interface TabConfig extends FormItem, ContainerCommonConfig { export interface TabConfig<T extends Record<string, any> = never> extends FormItem, ContainerCommonConfig<T> {
type: 'tab' | 'dynamic-tab'; type: 'tab' | 'dynamic-tab';
tabType?: string; tabType?: string;
editable?: boolean; editable?: boolean;
dynamic?: boolean; dynamic?: boolean;
tabPosition?: 'top' | 'right' | 'bottom' | 'left'; tabPosition?: 'top' | 'right' | 'bottom' | 'left';
items: TabPaneConfig[]; /** 当前激活的标签页,可以是固定值或动态函数 */
active?:
| string
| ((mForm: FormState | undefined, data: { model: FormValue; formValue?: FormValue; prop: string }) => string);
items: TabPaneConfig<T>[];
onChange?: (mForm: FormState | undefined, data: any) => void; onChange?: (mForm: FormState | undefined, data: any) => void;
onTabAdd?: (mForm: FormState | undefined, data: any) => void; onTabAdd?: (mForm: FormState | undefined, data: any) => void;
onTabRemove?: (mForm: FormState | undefined, tabName: string, data: any) => void; onTabRemove?: (mForm: FormState | undefined, tabName: string, data: any) => void;
@ -654,7 +673,7 @@ export interface TabConfig extends FormItem, ContainerCommonConfig {
/** /**
* *
*/ */
export interface FieldsetConfig extends FormItem, ContainerCommonConfig { export interface FieldsetConfig<T extends Record<string, any> = never> extends FormItem, ContainerCommonConfig<T> {
type: 'fieldset'; type: 'fieldset';
checkbox?: checkbox?:
| boolean | boolean
@ -671,7 +690,7 @@ export interface FieldsetConfig extends FormItem, ContainerCommonConfig {
/** /**
* *
*/ */
export interface PanelConfig extends FormItem, ContainerCommonConfig { export interface PanelConfig<T extends Record<string, any> = never> extends FormItem, ContainerCommonConfig<T> {
type: 'panel'; type: 'panel';
expand?: boolean; expand?: boolean;
title?: string; title?: string;
@ -683,7 +702,9 @@ export interface TableColumnConfig extends FormItem {
label: string; label: string;
width?: string | number; width?: string | number;
sortable?: boolean; sortable?: boolean;
[key: string]: any; items?: FormConfig;
itemsFunction?: (row: any) => FormConfig;
titleTip?: FilterFunction<string>;
} }
/** /**
@ -715,10 +736,11 @@ export interface TableConfig extends FormItem {
importable?: (mForm: FormState | undefined, data: any) => boolean | 'undefined' | boolean; importable?: (mForm: FormState | undefined, data: any) => boolean | 'undefined' | boolean;
/** 是否显示checkbox */ /** 是否显示checkbox */
selection?: (mForm: FormState | undefined, data: any) => boolean | boolean | 'single'; selection?: (mForm: FormState | undefined, data: any) => boolean | boolean | 'single';
/** 新增的默认行 */ /** 新增的默认行,可以是函数动态生成或静态对象 */
defaultAdd?: (mForm: FormState | undefined, data: any) => any; defaultAdd?: ((mForm: FormState | undefined, data: any) => any) | Record<string, any>;
copyHandler?: (mForm: FormState | undefined, data: any) => any; copyHandler?: (mForm: FormState | undefined, data: any) => any;
onSelect?: (mForm: FormState | undefined, data: any) => any; onSelect?: (mForm: FormState | undefined, data: any) => any;
/** @deprecated 请使用 defaultSort */
defautSort?: SortProp; defautSort?: SortProp;
defaultSort?: SortProp; defaultSort?: SortProp;
/** 是否支持拖拽排序 */ /** 是否支持拖拽排序 */
@ -739,15 +761,17 @@ export interface TableConfig extends FormItem {
props?: Record<string, any>; props?: Record<string, any>;
text?: string; text?: string;
}; };
sort?: boolean;
sortKey?: string;
} }
export interface GroupListConfig extends FormItem { export interface GroupListConfig<T extends Record<string, any> = never> extends FormItem {
type: 'table' | 'groupList' | 'group-list'; type: 'table' | 'groupList' | 'group-list';
span?: number; span?: number;
enableToggleMode?: boolean; enableToggleMode?: boolean;
items: FormConfig; items: FormConfig<T>;
groupItems?: FormConfig; groupItems?: FormConfig<T>;
tableItems?: FormConfig; tableItems?: FormConfig<T>;
titleKey?: string; titleKey?: string;
titlePrefix?: string; titlePrefix?: string;
title?: string | FilterFunction<string>; title?: string | FilterFunction<string>;
@ -760,7 +784,8 @@ export interface GroupListConfig extends FormItem {
*/ */
defaultExpandQuantity?: number; defaultExpandQuantity?: number;
addable?: (mForm: FormState | undefined, data: any) => boolean | 'undefined' | boolean; addable?: (mForm: FormState | undefined, data: any) => boolean | 'undefined' | boolean;
defaultAdd?: (mForm: FormState | undefined, data: any) => any; /** 新增的默认值,可以是函数动态生成或静态对象 */
defaultAdd?: ((mForm: FormState | undefined, data: any) => any) | Record<string, any>;
delete?: (model: any, index: number | string | symbol, values: any) => boolean | boolean; delete?: (model: any, index: number | string | symbol, values: any) => boolean | boolean;
copyable?: FilterFunction<boolean>; copyable?: FilterFunction<boolean>;
movable?: ( movable?: (
@ -774,18 +799,17 @@ export interface GroupListConfig extends FormItem {
props?: Record<string, any>; props?: Record<string, any>;
text?: string; text?: string;
}; };
[key: string]: any;
} }
interface StepItemConfig extends FormItem, ContainerCommonConfig { interface StepItemConfig<T extends Record<string, any> = never> extends FormItem, ContainerCommonConfig<T> {
title: string; title: string;
} }
export interface StepConfig extends FormItem { export interface StepConfig<T extends Record<string, any> = never> extends FormItem {
type: 'step'; type: 'step';
/** 每个 step 的间距,不填写将自适应间距。支持百分比。 */ /** 每个 step 的间距,不填写将自适应间距。支持百分比。 */
space?: string | number; space?: string | number;
items: StepItemConfig[]; items: StepItemConfig<T>[];
} }
export interface ComponentConfig extends FormItem { export interface ComponentConfig extends FormItem {
@ -793,26 +817,32 @@ export interface ComponentConfig extends FormItem {
id: string; id: string;
extend: any; extend: any;
display: any; display: any;
component: any;
} }
export interface FlexLayoutConfig extends FormItem, ContainerCommonConfig { export interface FlexLayoutConfig<T extends Record<string, any> = never> extends FormItem, ContainerCommonConfig<T> {
type: 'flex-layout'; type: 'flex-layout';
/** flex 子项间距,默认 '16px' */
gap?: string;
} }
export type ChildConfig = export type ChildConfig<T extends Record<string, any> = never> =
| FormItem | (FormItem & Partial<ContainerCommonConfig<T>>)
| TabConfig | TabConfig<T>
| RowConfig | RowConfig<T>
| FieldsetConfig | FieldsetConfig<T>
| PanelConfig | PanelConfig<T>
| TableConfig | TableConfig
| GroupListConfig | GroupListConfig<T>
| StepConfig | StepConfig<T>
| DisplayConfig | DisplayConfig
| TextConfig | TextConfig
| NumberConfig
| NumberRangeConfig
| HiddenConfig | HiddenConfig
| LinkConfig | LinkConfig<T>
| DaterangeConfig | DaterangeConfig
| TimerangeConfig
| SelectConfig | SelectConfig
| CascaderConfig | CascaderConfig
| HtmlField | HtmlField
@ -823,8 +853,12 @@ export type ChildConfig =
| CheckboxConfig | CheckboxConfig
| SwitchConfig | SwitchConfig
| RadioGroupConfig | RadioGroupConfig
| CheckboxGroupConfig
| TextareaConfig | TextareaConfig
| DynamicFieldConfig | DynamicFieldConfig
| ComponentConfig; | ComponentConfig
| FlexLayoutConfig<T>;
export type FormConfig = (ChildConfig | EditorChildConfig)[]; export type FormItemConfig<T extends Record<string, any> = never> = ChildConfig<T> | EditorChildConfig<T> | NoInfer<T>;
export type FormConfig<T extends Record<string, any> = never> = FormItemConfig<T>[];

View File

@ -1,8 +1,8 @@
import type { DataSourceFieldType, DataSourceSchema } from '@tmagic/schema'; import type { DataSourceFieldType, DataSourceSchema } from '@tmagic/schema';
import type { ChildConfig, FilterFunction, FormItem, FormState, Input } from './base'; import type { FilterFunction, FormItem, FormItemConfig, FormState, Input } from './base';
export interface DataSourceFieldSelectConfig extends FormItem { export interface DataSourceFieldSelectConfig<T extends Record<string, any> = never> extends FormItem {
type: 'data-source-field-select'; type: 'data-source-field-select';
/** /**
* data * data
@ -26,13 +26,13 @@ export interface DataSourceFieldSelectConfig extends FormItem {
}, },
) => boolean); ) => boolean);
dataSourceFieldType?: DataSourceFieldType[]; dataSourceFieldType?: DataSourceFieldType[];
fieldConfig?: ChildConfig; fieldConfig?: FormItemConfig<T>;
/** 是否可以编辑数据源disable表示的是是否可以选择数据源 */ /** 是否可以编辑数据源disable表示的是是否可以选择数据源 */
notEditable?: boolean | FilterFunction; notEditable?: boolean | FilterFunction;
} }
export interface CodeConfig extends FormItem { export interface CodeConfig extends FormItem {
type: 'code'; type: 'vs-code';
language?: string; language?: string;
options?: { options?: {
[key: string]: any; [key: string]: any;
@ -140,8 +140,12 @@ export interface UISelectConfig extends FormItem {
type: 'ui-select'; type: 'ui-select';
} }
export type EditorChildConfig = export interface StyleSetterConfig extends FormItem {
| DataSourceFieldSelectConfig type: 'style-setter';
}
export type EditorChildConfig<T extends Record<string, any> = never> =
| DataSourceFieldSelectConfig<T>
| CodeConfig | CodeConfig
| CodeLinkConfig | CodeLinkConfig
| CodeSelectConfig | CodeSelectConfig
@ -157,4 +161,5 @@ export type EditorChildConfig =
| EventSelectConfig | EventSelectConfig
| KeyValueConfig | KeyValueConfig
| PageFragmentSelectConfig | PageFragmentSelectConfig
| UISelectConfig; | UISelectConfig
| StyleSetterConfig;

View File

@ -1,6 +1,9 @@
import type { FormConfig } from './base'; import type { FormConfig, FormItemConfig } from './base';
export * from './base'; export * from './base';
export * from './editor'; export * from './editor';
export const defineFormConfig = <T = FormConfig>(config: T): T => config; export const defineFormConfig = <T extends Record<string, any> = never>(config: FormConfig<T>): FormConfig<T> => config;
export const defineFormItem = <T extends Record<string, any> = never>(config: FormItemConfig<T>): FormItemConfig<T> =>
config;

View File

@ -218,13 +218,13 @@ const getTextByName = (name: string, config: FormConfig = props.config): string
return typeof item.text === 'string' ? item.text : undefined; return typeof item.text === 'string' ? item.text : undefined;
} }
if (item.items && Array.isArray(item.items)) { if ('items' in item && Array.isArray(item.items)) {
const result = findInConfig(item.items, remainingParts); const result = findInConfig(item.items, remainingParts);
if (result !== undefined) return result; if (result !== undefined) return result;
} }
} }
if (item.items && Array.isArray(item.items)) { if ('items' in item && Array.isArray(item.items)) {
const result = findInConfig(item.items, parts); const result = findInConfig(item.items, parts);
if (result !== undefined) return result; if (result !== undefined) return result;
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div <div
:data-tmagic-id="config.id" :data-tmagic-id="(config as Record<string, any>).id"
:data-tmagic-form-item-prop="itemProp" :data-tmagic-form-item-prop="itemProp"
:class="`m-form-container m-container-${type || ''} ${config.className || ''}${config.tip ? ' has-tip' : ''}`" :class="`m-form-container m-container-${type || ''} ${config.className || ''}${config.tip ? ' has-tip' : ''}`"
:style="config.style" :style="config.style"
@ -28,7 +28,7 @@
<FormLabel <FormLabel
:tip="config.tip" :tip="config.tip"
:type="type" :type="type"
:use-label="config.useLabel" :use-label="(config as CheckboxConfig).useLabel"
:label-title="config.labelTitle" :label-title="config.labelTitle"
:text="text" :text="text"
></FormLabel> ></FormLabel>
@ -61,7 +61,7 @@
></component> ></component>
</TMagicFormItem> </TMagicFormItem>
<TMagicTooltip v-if="config.tip && type === 'checkbox' && !config.useLabel" placement="top"> <TMagicTooltip v-if="config.tip && type === 'checkbox' && !(config as CheckboxConfig).useLabel" placement="top">
<TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon> <TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon>
<template #content> <template #content>
<div v-html="config.tip"></div> <div v-html="config.tip"></div>
@ -80,7 +80,7 @@
<FormLabel <FormLabel
:tip="config.tip" :tip="config.tip"
:type="type" :type="type"
:use-label="config.useLabel" :use-label="(config as CheckboxConfig).useLabel"
:label-title="config.labelTitle" :label-title="config.labelTitle"
:text="text" :text="text"
></FormLabel> ></FormLabel>
@ -95,7 +95,7 @@
<component v-else v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component> <component v-else v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
</TMagicFormItem> </TMagicFormItem>
<TMagicTooltip v-if="config.tip && type === 'checkbox' && !config.useLabel" placement="top"> <TMagicTooltip v-if="config.tip && type === 'checkbox' && !(config as CheckboxConfig).useLabel" placement="top">
<TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon> <TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon>
<template #content> <template #content>
<div v-html="config.tip"></div> <div v-html="config.tip"></div>
@ -112,7 +112,7 @@
<FormLabel <FormLabel
:tip="config.tip" :tip="config.tip"
:type="type" :type="type"
:use-label="config.useLabel" :use-label="(config as CheckboxConfig).useLabel"
:label-title="config.labelTitle" :label-title="config.labelTitle"
:text="text" :text="text"
></FormLabel> ></FormLabel>
@ -127,7 +127,7 @@
<component v-else v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component> <component v-else v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
</TMagicFormItem> </TMagicFormItem>
<TMagicTooltip v-if="config.tip && type === 'checkbox' && !config.useLabel" placement="top"> <TMagicTooltip v-if="config.tip && type === 'checkbox' && !(config as CheckboxConfig).useLabel" placement="top">
<TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon> <TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon>
<template #content> <template #content>
<div v-html="config.tip"></div> <div v-html="config.tip"></div>
@ -174,7 +174,9 @@ import { getValueByKeyPath } from '@tmagic/utils';
import MHidden from '../fields/Hidden.vue'; import MHidden from '../fields/Hidden.vue';
import type { import type {
CheckboxConfig,
ChildConfig, ChildConfig,
ComponentConfig,
ContainerChangeEventData, ContainerChangeEventData,
ContainerCommonConfig, ContainerCommonConfig,
FormState, FormState,
@ -259,13 +261,10 @@ const type = computed((): string => {
}); });
const tagName = computed(() => { const tagName = computed(() => {
if (type.value === 'component' && props.config.component) { if (type.value === 'component' && (props.config as ComponentConfig).component) {
return props.config.component; return (props.config as ComponentConfig).component;
} }
if (!getField(type.value || 'container')) {
console.log(type.value, 'type.value');
}
return getField(type.value || 'container') || `m-${items.value ? 'form' : 'fields'}-${type.value}`; return getField(type.value || 'container') || `m-${items.value ? 'form' : 'fields'}-${type.value}`;
}); });
@ -305,7 +304,7 @@ const fieldsProps = computed(() => ({
name: name.value, name: name.value,
disabled: disabled.value, disabled: disabled.value,
prop: itemProp.value, prop: itemProp.value,
key: props.config[mForm?.keyProps], key: (props.config as Record<string, any>)[mForm?.keyProps],
style: props.config.fieldStyle, style: props.config.fieldStyle,
})); }));

View File

@ -144,7 +144,9 @@ const rowConfig = computed(() => ({
span: props.config.span || 24, span: props.config.span || 24,
items: props.config.items, items: props.config.items,
labelWidth: props.config.labelWidth, labelWidth: props.config.labelWidth,
[mForm?.keyProp || '__key']: `${props.config[mForm?.keyProp || '__key']}${String(props.index)}`, [mForm?.keyProp || '__key']: `${(props.config as Record<string, any>)[mForm?.keyProp || '__key']}${String(
props.index,
)}`,
})); }));
const title = computed(() => { const title = computed(() => {

View File

@ -10,7 +10,7 @@
<TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip"> <TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip">
<div> <div>
<TMagicIcon v-if="option.icon" :size="iconSize"><component :is="option.icon"></component></TMagicIcon> <TMagicIcon v-if="option.icon" :size="iconSize"><component :is="option.icon"></component></TMagicIcon>
<span>{{ option.text }}</span> <span v-if="option.text">{{ option.text }}</span>
</div> </div>
</TMagicTooltip> </TMagicTooltip>
</component> </component>

View File

@ -21,7 +21,7 @@ import dayjs from 'dayjs';
import { TMagicTimePicker } from '@tmagic/design'; import { TMagicTimePicker } from '@tmagic/design';
import type { ChangeRecord, DaterangeConfig, FieldProps } from '../schema'; import type { ChangeRecord, FieldProps, TimerangeConfig } from '../schema';
import { datetimeFormatter } from '../utils/form'; import { datetimeFormatter } from '../utils/form';
import { useAddField } from '../utils/useAddField'; import { useAddField } from '../utils/useAddField';
@ -29,7 +29,7 @@ defineOptions({
name: 'MFormTimeRange', name: 'MFormTimeRange',
}); });
const props = defineProps<FieldProps<DaterangeConfig>>(); const props = defineProps<FieldProps<TimerangeConfig>>();
const emit = defineEmits(['change']); const emit = defineEmits(['change']);

View File

@ -29,11 +29,13 @@ import {
DaterangeConfig, DaterangeConfig,
FilterFunction, FilterFunction,
FormConfig, FormConfig,
FormItem,
FormState, FormState,
FormValue, FormValue,
HtmlField, HtmlField,
Rule, Rule,
SortProp, SortProp,
TableConfig,
TabPaneConfig, TabPaneConfig,
TypeFunction, TypeFunction,
} from '../schema'; } from '../schema';
@ -118,7 +120,7 @@ const initValueItem = function (
) { ) {
const { items } = item as ContainerCommonConfig; const { items } = item as ContainerCommonConfig;
const { names } = item as DaterangeConfig; const { names } = item as DaterangeConfig;
const { type, name } = item as ChildConfig; const { type, name } = item as FormItem;
if (isTableSelect(type) && name) { if (isTableSelect(type) && name) {
value[name] = initValue[name] ?? ''; value[name] = initValue[name] ?? '';
@ -148,14 +150,15 @@ const initValueItem = function (
setValue(mForm, value, initValue, item); setValue(mForm, value, initValue, item);
if (type === 'table') { if (type === 'table') {
if (item.defautSort) { const tableConfig = item as TableConfig;
sortChange(value[name], item.defautSort); if (tableConfig.defautSort) {
} else if (item.defaultSort) { sortChange(value[name], tableConfig.defautSort);
sortChange(value[name], item.defaultSort); } else if (tableConfig.defaultSort) {
sortChange(value[name], tableConfig.defaultSort);
} }
if (item.sort && item.sortKey) { if (tableConfig.sort && tableConfig.sortKey) {
value[name].sort((a: any, b: any) => b[item.sortKey] - a[item.sortKey]); value[name].sort((a: any, b: any) => b[tableConfig.sortKey!] - a[tableConfig.sortKey!]);
} }
} }