mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-04-23 18:28:34 +00:00
refactor(form): 抽出 TableGroupList 父组件统一管理 Table/GroupList 切换
- 新增 TableGroupList 父组件集中持有 displayMode 状态并派生两种形态的 config,避免原实现通过直接改写 props.config 来触发视图切换 - 将公共的 toggle/add 按钮上移到 TableGroupList,Table/GroupList 通过 具名 slot + scoped slot 暴露位置与业务钩子(newHandler/addHandler) - m-form-table、m-form-group-list 统一注册为 TableGroupList,对外导出 的 MTable/MGroupList 也指向它,新增 MTableGroupList 显式导出 - useAdd 移除重复的 addable 计算,由父组件统一管理 Made-with: Cursor
This commit is contained in:
parent
b46b571214
commit
9f21f8f1d5
@ -27,30 +27,19 @@
|
||||
></MFieldsGroupListItem>
|
||||
|
||||
<div class="m-fields-group-list-footer">
|
||||
<TMagicButton v-if="config.enableToggleMode" :icon="Grid" size="small" @click="toggleMode"
|
||||
>切换为表格</TMagicButton
|
||||
>
|
||||
<slot name="toggle-button"></slot>
|
||||
<div style="display: flex; justify-content: flex-end; flex: 1">
|
||||
<TMagicButton
|
||||
v-if="addable"
|
||||
:size="config.enableToggleMode ? 'small' : 'default'"
|
||||
:icon="Plus"
|
||||
v-bind="config.addButtonConfig?.props || { type: 'primary' }"
|
||||
:disabled="disabled"
|
||||
@click="addHandler"
|
||||
>{{ config.addButtonConfig?.text || '新增' }}</TMagicButton
|
||||
>
|
||||
<slot name="add-button" :trigger="addHandler"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue';
|
||||
import { Grid, Plus } from '@element-plus/icons-vue';
|
||||
import { inject } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import { TMagicButton, tMagicMessage } from '@tmagic/design';
|
||||
import { tMagicMessage } from '@tmagic/design';
|
||||
|
||||
import type { ContainerChangeEventData, FormState, GroupListConfig } from '../schema';
|
||||
import { initValue } from '../utils/form';
|
||||
@ -80,21 +69,6 @@ const emit = defineEmits<{
|
||||
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const addable = computed(() => {
|
||||
if (!props.name) return false;
|
||||
|
||||
if (typeof props.config.addable === 'function') {
|
||||
return props.config.addable(mForm, {
|
||||
model: props.model[props.name],
|
||||
formValue: mForm?.values,
|
||||
prop: props.prop,
|
||||
config: props.config,
|
||||
});
|
||||
}
|
||||
|
||||
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
|
||||
});
|
||||
|
||||
const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
|
||||
emit('change', props.model, eventData);
|
||||
};
|
||||
@ -167,17 +141,6 @@ const swapHandler = (idx1: number, idx2: number) => {
|
||||
emit('change', props.model[props.name]);
|
||||
};
|
||||
|
||||
const toggleMode = () => {
|
||||
props.config.type = 'table';
|
||||
props.config.groupItems = props.config.items;
|
||||
props.config.items = (props.config.tableItems ||
|
||||
props.config.items.map((item: any) => ({
|
||||
...item,
|
||||
label: item.label || item.text,
|
||||
text: null,
|
||||
}))) as any;
|
||||
};
|
||||
|
||||
const onAddDiffCount = () => emit('addDiffCount');
|
||||
|
||||
const getLastValues = (item: any, index: number) => item?.[index] || {};
|
||||
|
||||
169
packages/form/src/containers/TableGroupList.vue
Normal file
169
packages/form/src/containers/TableGroupList.vue
Normal file
@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<component
|
||||
:is="displayMode === 'table' ? MFormTable : MFormGroupList"
|
||||
v-bind="$attrs"
|
||||
:model="model"
|
||||
:name="`${name}`"
|
||||
:config="currentConfig"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:is-compare="isCompare"
|
||||
:last-values="lastValues"
|
||||
:prop="prop"
|
||||
:label-width="labelWidth"
|
||||
@change="onChange"
|
||||
@select="onSelect"
|
||||
@addDiffCount="onAddDiffCount"
|
||||
>
|
||||
<template #toggle-button>
|
||||
<TMagicButton v-if="config.enableToggleMode !== false" :icon="Grid" size="small" @click="toggleDisplayMode">
|
||||
{{ displayMode === 'table' ? '展开配置' : '切换为表格' }}
|
||||
</TMagicButton>
|
||||
</template>
|
||||
|
||||
<template #add-button="{ trigger }">
|
||||
<TMagicButton
|
||||
v-if="addable"
|
||||
:class="displayMode === 'table' ? 'm-form-table-add-button' : ''"
|
||||
:size="addButtonSize"
|
||||
:plain="displayMode === 'table'"
|
||||
:icon="Plus"
|
||||
:disabled="disabled"
|
||||
v-bind="currentConfig.addButtonConfig?.props || { type: 'primary' }"
|
||||
@click="trigger"
|
||||
>
|
||||
{{ currentConfig.addButtonConfig?.text || (displayMode === 'table' ? '新增一行' : '新增') }}
|
||||
</TMagicButton>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { Grid, Plus } from '@element-plus/icons-vue';
|
||||
|
||||
import { TMagicButton } from '@tmagic/design';
|
||||
import type { FormState, GroupListConfig, TableConfig } from '@tmagic/form-schema';
|
||||
|
||||
import type { ContainerChangeEventData } from '../schema';
|
||||
import MFormTable from '../table/Table.vue';
|
||||
|
||||
import MFormGroupList from './GroupList.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'MFormTableGroupList',
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
model: any;
|
||||
lastValues?: any;
|
||||
isCompare?: boolean;
|
||||
config: TableConfig | GroupListConfig;
|
||||
name: string;
|
||||
prop?: string;
|
||||
labelWidth?: string;
|
||||
disabled?: boolean;
|
||||
size?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['change', 'select', 'addDiffCount']);
|
||||
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const addable = computed(() => {
|
||||
const modelName = props.name || props.config.name || '';
|
||||
|
||||
if (!modelName) return false;
|
||||
|
||||
if (!props.model[modelName].length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof props.config.addable === 'function') {
|
||||
return props.config.addable(mForm, {
|
||||
model: props.model[modelName],
|
||||
formValue: mForm?.values,
|
||||
prop: props.prop,
|
||||
config: props.config,
|
||||
});
|
||||
}
|
||||
|
||||
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
|
||||
});
|
||||
|
||||
const isGroupListType = (type: string | undefined) => type === 'groupList' || type === 'group-list';
|
||||
|
||||
const displayMode = ref<'table' | 'groupList'>(isGroupListType(props.config.type) ? 'groupList' : 'table');
|
||||
|
||||
const calcLabelWidth = (label: string) => {
|
||||
if (!label) return '0px';
|
||||
const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
|
||||
const chLength = label.length - zhLength;
|
||||
return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
|
||||
};
|
||||
|
||||
// 当原始 config 是 table 形态时,table 模式直接透传;
|
||||
// 若原始是 groupList,则基于它派生出 table 所需的 config
|
||||
const tableConfig = computed<TableConfig>(() => {
|
||||
if (!isGroupListType(props.config.type)) {
|
||||
return props.config as TableConfig;
|
||||
}
|
||||
|
||||
const source = props.config as GroupListConfig;
|
||||
return {
|
||||
...props.config,
|
||||
type: 'table',
|
||||
groupItems: source.items,
|
||||
items:
|
||||
source.tableItems ||
|
||||
(source.items as any[]).map((item: any) => ({
|
||||
...item,
|
||||
label: item.label || item.text,
|
||||
text: null,
|
||||
})),
|
||||
} as any as TableConfig;
|
||||
});
|
||||
|
||||
// 反向派生 groupList 所需的 config
|
||||
const groupListConfig = computed<GroupListConfig>(() => {
|
||||
if (isGroupListType(props.config.type)) {
|
||||
return props.config as GroupListConfig;
|
||||
}
|
||||
|
||||
const source = props.config as TableConfig;
|
||||
return {
|
||||
...props.config,
|
||||
type: 'groupList',
|
||||
tableItems: source.items,
|
||||
items:
|
||||
source.groupItems ||
|
||||
(source.items as any[]).map((item: any) => {
|
||||
const text = item.text || item.label;
|
||||
return {
|
||||
...item,
|
||||
text,
|
||||
labelWidth: calcLabelWidth(text),
|
||||
span: item.span || 12,
|
||||
};
|
||||
}),
|
||||
} as any as GroupListConfig;
|
||||
});
|
||||
|
||||
// 运行时类型由 displayMode 决定,`<component :is>` 无法做联合类型收窄,统一转 any 交给子组件处理
|
||||
const currentConfig = computed<any>(() => (displayMode.value === 'table' ? tableConfig.value : groupListConfig.value));
|
||||
|
||||
// 保持原 Table/GroupList 模式下新增按钮的不同尺寸策略
|
||||
const addButtonSize = computed(() => {
|
||||
if (displayMode.value === 'table') return 'small';
|
||||
return props.config.enableToggleMode !== false ? 'small' : 'default';
|
||||
});
|
||||
|
||||
const toggleDisplayMode = () => {
|
||||
displayMode.value = displayMode.value === 'table' ? 'groupList' : 'table';
|
||||
};
|
||||
|
||||
const onChange = (v: any, eventData?: ContainerChangeEventData) => emit('change', v, eventData);
|
||||
const onSelect = (...args: any[]) => emit('select', ...args);
|
||||
const onAddDiffCount = () => emit('addDiffCount');
|
||||
</script>
|
||||
@ -32,8 +32,9 @@ export { default as MFlexLayout } from './containers/FlexLayout.vue';
|
||||
export { default as MPanel } from './containers/Panel.vue';
|
||||
export { default as MRow } from './containers/Row.vue';
|
||||
export { default as MTabs } from './containers/Tabs.vue';
|
||||
export { default as MTable } from './table/Table.vue';
|
||||
export { default as MGroupList } from './containers/GroupList.vue';
|
||||
export { default as MTable } from './containers/TableGroupList.vue';
|
||||
export { default as MGroupList } from './containers/TableGroupList.vue';
|
||||
export { default as MTableGroupList } from './containers/TableGroupList.vue';
|
||||
export { default as MText } from './fields/Text.vue';
|
||||
export { default as MNumber } from './fields/Number.vue';
|
||||
export { default as MNumberRange } from './fields/NumberRange.vue';
|
||||
|
||||
@ -21,10 +21,10 @@ 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 TableGroupList from './containers/TableGroupList.vue';
|
||||
import Tabs from './containers/Tabs.vue';
|
||||
import Cascader from './fields/Cascader.vue';
|
||||
import Checkbox from './fields/Checkbox.vue';
|
||||
@ -46,7 +46,6 @@ 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';
|
||||
@ -70,11 +69,11 @@ export default {
|
||||
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-group-list', TableGroupList);
|
||||
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-table', TableGroupList);
|
||||
app.component('m-form-tab', Tabs);
|
||||
app.component('m-form-flex-layout', FlexLayout);
|
||||
app.component('m-fields-text', Text);
|
||||
|
||||
@ -34,13 +34,7 @@
|
||||
|
||||
<div style="display: flex; justify-content: space-between; margin: 10px 0">
|
||||
<div style="display: flex">
|
||||
<TMagicButton
|
||||
:icon="Grid"
|
||||
size="small"
|
||||
@click="toggleMode"
|
||||
v-if="enableToggleMode && config.enableToggleMode !== false && !isFullscreen"
|
||||
>展开配置</TMagicButton
|
||||
>
|
||||
<slot name="toggle-button" v-if="enableToggleMode && !isFullscreen"></slot>
|
||||
<TMagicButton
|
||||
:icon="FullScreen"
|
||||
size="small"
|
||||
@ -64,17 +58,7 @@
|
||||
>清空</TMagicButton
|
||||
>
|
||||
</div>
|
||||
<TMagicButton
|
||||
v-if="addable"
|
||||
class="m-form-table-add-button"
|
||||
size="small"
|
||||
plain
|
||||
:icon="Plus"
|
||||
v-bind="config.addButtonConfig?.props || { type: 'primary' }"
|
||||
:disabled="disabled"
|
||||
@click="newHandler()"
|
||||
>{{ config.addButtonConfig?.text || '新增一行' }}</TMagicButton
|
||||
>
|
||||
<slot name="add-button" :trigger="newHandler"></slot>
|
||||
</div>
|
||||
|
||||
<div class="bottom" style="text-align: right" v-if="config.pagination">
|
||||
@ -97,7 +81,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import { FullScreen, Grid, Plus } from '@element-plus/icons-vue';
|
||||
import { FullScreen } from '@element-plus/icons-vue';
|
||||
|
||||
import { TMagicButton, TMagicPagination, TMagicTable, TMagicTooltip, TMagicUpload, useZIndex } from '@tmagic/design';
|
||||
|
||||
@ -139,7 +123,7 @@ const { pageSize, currentPage, paginationData, handleSizeChange, handleCurrentCh
|
||||
const { nextZIndex } = useZIndex();
|
||||
const updateKey = ref(1);
|
||||
|
||||
const { addable, newHandler } = useAdd(props, emit);
|
||||
const { newHandler } = useAdd(props, emit);
|
||||
const { columns } = useTableColumns(props, emit, currentPage, pageSize, modelName);
|
||||
useSortable(props, emit, tMagicTableRef, modelName, updateKey);
|
||||
const { isFullscreen, toggleFullscreen } = useFullscreen();
|
||||
@ -148,32 +132,6 @@ const { selectHandle, toggleRowSelection } = useSelection(props, emit, tMagicTab
|
||||
|
||||
const data = computed(() => (props.config.pagination ? paginationData.value : props.model[modelName.value]));
|
||||
|
||||
const toggleMode = () => {
|
||||
const calcLabelWidth = (label: string) => {
|
||||
if (!label) return '0px';
|
||||
const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
|
||||
const chLength = label.length - zhLength;
|
||||
return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
|
||||
};
|
||||
|
||||
// 切换为groupList的形式
|
||||
props.config.type = 'groupList';
|
||||
props.config.enableToggleMode = true;
|
||||
props.config.tableItems = props.config.items;
|
||||
props.config.items =
|
||||
props.config.groupItems ||
|
||||
props.config.items.map((item: any) => {
|
||||
const text = item.text || item.label;
|
||||
const labelWidth = calcLabelWidth(text);
|
||||
return {
|
||||
...item,
|
||||
text,
|
||||
labelWidth,
|
||||
span: item.span || 12,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const sortChangeHandler = (sortOptions: SortProp) => {
|
||||
const modelName = props.name || props.config.name || '';
|
||||
sortChange(props.model[modelName], sortOptions);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { computed, inject } from 'vue';
|
||||
import { inject } from 'vue';
|
||||
|
||||
import { tMagicMessage } from '@tmagic/design';
|
||||
import type { FormConfig, FormState } from '@tmagic/form-schema';
|
||||
@ -13,21 +13,6 @@ export const useAdd = (
|
||||
) => {
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const addable = computed(() => {
|
||||
const modelName = props.name || props.config.name || '';
|
||||
if (!props.model[modelName].length) {
|
||||
return true;
|
||||
}
|
||||
if (typeof props.config.addable === 'function') {
|
||||
return props.config.addable(mForm, {
|
||||
model: props.model[modelName],
|
||||
formValue: mForm?.values,
|
||||
prop: props.prop,
|
||||
});
|
||||
}
|
||||
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
|
||||
});
|
||||
|
||||
const newHandler = async (row?: any) => {
|
||||
const modelName = props.name || props.config.name || '';
|
||||
|
||||
@ -106,7 +91,6 @@ export const useAdd = (
|
||||
};
|
||||
|
||||
return {
|
||||
addable,
|
||||
newHandler,
|
||||
};
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user