feat(design,editor,element-plus-adapter,form,table,tdesign-vue-next-adapter): 重构table组件,适配tdesign

This commit is contained in:
roymondchen 2025-10-21 18:59:26 +08:00
parent 566b754887
commit 5b16ec00e1
51 changed files with 1733 additions and 1206 deletions

View File

@ -30,3 +30,11 @@ const clickHandler = (...args: any[]) => {
emit('click', ...args); emit('click', ...args);
}; };
</script> </script>
<style lang="scss">
.tmagic-design-button {
.t-button__text {
align-items: center;
}
}
</style>

View File

@ -19,3 +19,16 @@ const uiComponent = ui?.component || 'el-icon';
const props = defineProps<IconProps>(); const props = defineProps<IconProps>();
const uiProps = computed<IconProps>(() => ui?.props(props) || props); const uiProps = computed<IconProps>(() => ui?.props(props) || props);
</script> </script>
<style lang="scss">
.t-t-design-adapter-icon {
justify-content: center;
align-items: center;
display: flex;
svg {
width: 1em;
height: 1em;
}
}
</style>

View File

@ -16,7 +16,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watchEffect } from 'vue'; import { computed, useTemplateRef } from 'vue';
import { getDesignConfig } from './config'; import { getDesignConfig } from './config';
import type { TableProps } from './types'; import type { TableProps } from './types';
@ -37,7 +37,7 @@ const uiProps = computed<TableProps>(() => ui?.props(props) || props);
const emit = defineEmits(['select', 'sort-change', 'expand-change', 'cell-click']); const emit = defineEmits(['select', 'sort-change', 'expand-change', 'cell-click']);
const table = ref<any>(); const tableRef = useTemplateRef('table');
const selectHandler = (...args: any[]) => { const selectHandler = (...args: any[]) => {
emit('select', ...args); emit('select', ...args);
@ -55,27 +55,21 @@ const cellClickHandler = (...args: any[]) => {
emit('cell-click', ...args); emit('cell-click', ...args);
}; };
let $el: HTMLDivElement | undefined;
watchEffect(() => {
$el = table.value?.$el;
});
defineExpose({ defineExpose({
instance: table, getEl: () => tableRef.value?.getTableRef().$el,
$el, getTableRef: () => tableRef.value.getTableRef(),
clearSelection(...args: any[]) { clearSelection(...args: any[]) {
return table.value?.clearSelection(...args); return tableRef.value?.clearSelection(...args);
}, },
toggleRowSelection(...args: any[]) { toggleRowSelection(...args: any[]) {
return table.value?.toggleRowSelection(...args); return tableRef.value?.toggleRowSelection(...args);
}, },
toggleRowExpansion(...args: any[]) { toggleRowExpansion(...args: any[]) {
return table.value?.toggleRowExpansion(...args); return tableRef.value?.toggleRowExpansion(...args);
}, },
}); });
</script> </script>

View File

@ -1,27 +0,0 @@
<template>
<component :is="uiComponent" v-bind="uiProps">
<template #default="{ $index, row }">
<!-- eslint-disable-next-line vue/valid-attribute-name -->
<slot :$index="$index" :row="row"></slot>
</template>
</component>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { getDesignConfig } from './config';
import type { TableColumnProps } from './types';
defineOptions({
name: 'TMTableColumn',
});
const props = defineProps<TableColumnProps>();
const ui = getDesignConfig('components')?.tableColumn;
const uiComponent = ui?.component || 'el-table-column';
const uiProps = computed<TableColumnProps>(() => ui?.props(props) || props);
</script>

View File

@ -1,110 +0,0 @@
<template>
<component
class="tmagic-design-tree"
ref="tree"
:is="uiComponent"
v-bind="uiProps"
@node-click="nodeClickHandler"
@node-contextmenu="contextmenu"
@node-drag-end="handleDragEnd"
@node-collapse="handleCollapse"
@node-expand="handleExpand"
@check="checkHandler"
@mousedown="mousedownHandler"
@mouseup="mouseupHandler"
>
<template #default="{ data, node }">
<slot :data="data" :node="node"></slot>
</template>
</component>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { getDesignConfig } from './config';
import type { TreeProps } from './types';
defineOptions({
name: 'TMTree',
});
const props = defineProps<TreeProps>();
const ui = getDesignConfig('components')?.tree;
const uiComponent = ui?.component || 'el-tree';
const uiProps = computed<TreeProps>(() => ui?.props(props) || props);
const emit = defineEmits([
'node-click',
'node-contextmenu',
'node-drag-end',
'node-collapse',
'node-expand',
'check',
'mousedown',
'mouseup',
]);
const nodeClickHandler = (...args: any[]) => {
emit('node-click', ...args);
};
const contextmenu = (...args: any[]) => {
emit('node-contextmenu', ...args);
};
const handleDragEnd = (...args: any[]) => {
emit('node-drag-end', ...args);
};
const handleCollapse = (...args: any[]) => {
emit('node-collapse', ...args);
};
const handleExpand = (...args: any[]) => {
emit('node-expand', ...args);
};
const checkHandler = (...args: any[]) => {
emit('check', ...args);
};
const mousedownHandler = (...args: any[]) => {
emit('mousedown', ...args);
};
const mouseupHandler = (...args: any[]) => {
emit('mouseup', ...args);
};
const tree = ref<any>();
defineExpose({
getData() {
return tree.value?.data;
},
getStore() {
return tree.value?.store;
},
filter(...args: any[]) {
return tree.value?.filter(...args);
},
getNode(...args: any[]) {
return tree.value?.getNode(...args);
},
setCheckedKeys(...args: any[]) {
return tree.value?.setCheckedKeys(...args);
},
setCurrentKey(...args: any[]) {
return tree.value?.setCurrentKey(...args);
},
});
</script>

View File

@ -46,13 +46,11 @@ export { default as TMagicStep } from './Step.vue';
export { default as TMagicSteps } from './Steps.vue'; export { default as TMagicSteps } from './Steps.vue';
export { default as TMagicSwitch } from './Switch.vue'; export { default as TMagicSwitch } from './Switch.vue';
export { default as TMagicTable } from './Table.vue'; export { default as TMagicTable } from './Table.vue';
export { default as TMagicTableColumn } from './TableColumn.vue';
export { default as TMagicTabPane } from './TabPane.vue'; export { default as TMagicTabPane } from './TabPane.vue';
export { default as TMagicTabs } from './Tabs.vue'; export { default as TMagicTabs } from './Tabs.vue';
export { default as TMagicTag } from './Tag.vue'; export { default as TMagicTag } from './Tag.vue';
export { default as TMagicTimePicker } from './TimePicker.vue'; export { default as TMagicTimePicker } from './TimePicker.vue';
export { default as TMagicTooltip } from './Tooltip.vue'; export { default as TMagicTooltip } from './Tooltip.vue';
export { default as TMagicTree } from './Tree.vue';
export { default as TMagicUpload } from './Upload.vue'; export { default as TMagicUpload } from './Upload.vue';
export const tMagicMessage = { export const tMagicMessage = {

View File

@ -182,6 +182,7 @@ export interface FormItemProps {
prop?: string; prop?: string;
labelWidth?: string | number; labelWidth?: string | number;
rules?: any; rules?: any;
extra?: string;
} }
export interface InputProps { export interface InputProps {
@ -295,17 +296,36 @@ export interface SwitchProps {
} }
export interface TableProps { export interface TableProps {
columns?: TableColumnOptions[];
data?: any[]; data?: any[];
border?: boolean; border?: boolean;
maxHeight?: number | string; maxHeight?: number | string;
defaultExpandAll?: boolean; defaultExpandAll?: boolean;
showHeader?: boolean;
rowKey?: string;
treeProps?: Record<string, any>;
emptyText?: string;
tooltipEffect?: string;
tooltipOptions?: any;
showOverflowTooltip?: boolean;
spanMethod?: (data: any) => any;
} }
export interface TableColumnProps { export interface TableColumnOptions<T = any> {
props: {
class?: string;
label?: string; label?: string;
fixed?: 'left' | 'right' | boolean;
width?: number | string;
type?: 'default' | 'selection' | 'index' | 'expand';
prop?: string;
align?: string; align?: string;
fixed?: string | boolean; headerAlign?: string;
width?: string | number; sortable?: boolean;
sortOrders?: Array<'ascending' | 'descending'>;
selectable?: (row: T, index: number) => boolean;
};
cell?: (scope: { row: T; $index: number }) => any;
} }
export interface TabPaneProps { export interface TabPaneProps {
@ -342,33 +362,6 @@ export interface TooltipProps {
offset?: number; offset?: number;
} }
export interface TreeProps {
data?: any[];
emptyText?: string;
nodeKey?: string;
props?: any;
renderAfterExpand?: boolean;
load?: any;
renderContent?: any;
highlightCurrent?: boolean;
defaultExpandAll?: boolean;
checkOnClickNode?: boolean;
autoExpandParent?: boolean;
defaultExpandedKeys?: any[];
showCheckbox?: boolean;
checkStrictly?: boolean;
defaultCheckedKeys?: any[];
currentNodeKey?: string | number;
filterNodeMethod?: (value: any, data: any, node: any) => boolean;
accordion?: boolean;
indent?: number;
icon?: any;
lazy?: boolean;
draggable?: boolean;
allowDrag?: (node: any) => boolean;
allowDrop?: any;
}
export interface UploadProps { export interface UploadProps {
action?: string; action?: string;
autoUpload?: boolean; autoUpload?: boolean;
@ -635,8 +628,8 @@ export interface Components {
| DefineComponent< | DefineComponent<
TableProps, TableProps,
{ {
instance: any; getEl: () => HTMLElement | undefined;
$el: HTMLDivElement | undefined; getTableRef: () => any;
clearSelection: (...args: any[]) => void; clearSelection: (...args: any[]) => void;
toggleRowSelection: (...args: any[]) => void; toggleRowSelection: (...args: any[]) => void;
toggleRowExpansion: (...args: any[]) => void; toggleRowExpansion: (...args: any[]) => void;
@ -647,11 +640,6 @@ export interface Components {
props: (props: TableProps) => TableProps; props: (props: TableProps) => TableProps;
}; };
tableColumn: {
component: DefineComponent<TableColumnProps, {}, any> | string;
props: (props: TableColumnProps) => TableColumnProps;
};
tabPane: { tabPane: {
component: DefineComponent<TabPaneProps, {}, any> | string; component: DefineComponent<TabPaneProps, {}, any> | string;
props: (props: TabPaneProps) => TabPaneProps; props: (props: TabPaneProps) => TabPaneProps;
@ -677,24 +665,6 @@ export interface Components {
props: (props: TooltipProps) => TooltipProps; props: (props: TooltipProps) => TooltipProps;
}; };
tree: {
component:
| DefineComponent<
TreeProps,
{
getData: () => TreeProps['data'];
getStore: () => any;
filter: (...args: any[]) => any;
getNode: (...args: any[]) => any;
setCheckedKeys: (...args: any[]) => any;
setCurrentKey: (...args: any[]) => any;
},
any
>
| string;
props: (props: TreeProps) => TreeProps;
};
upload: { upload: {
component: component:
| DefineComponent< | DefineComponent<

View File

@ -1,5 +1,5 @@
<template> <template>
<TMagicCollapse class="m-fields-style-setter" :model-value="collapseValue"> <TMagicCollapse class="m-fields-style-setter" v-model="collapseValue">
<template v-for="(item, index) in list" :key="index"> <template v-for="(item, index) in list" :key="index">
<TMagicCollapseItem :name="`${index}`"> <TMagicCollapseItem :name="`${index}`">
<template #title><MIcon :icon="Grid"></MIcon>{{ item.title }}</template> <template #title><MIcon :icon="Grid"></MIcon>{{ item.title }}</template>

View File

@ -5,7 +5,7 @@
<SearchInput @search="filterTextChangeHandler"></SearchInput> <SearchInput @search="filterTextChangeHandler"></SearchInput>
<slot name="component-list" :component-group-list="list"> <slot name="component-list" :component-group-list="list">
<TMagicCollapse class="ui-component-panel" :model-value="collapseValue"> <TMagicCollapse class="ui-component-panel" v-model="collapseValue">
<template v-for="(group, index) in list"> <template v-for="(group, index) in list">
<TMagicCollapseItem v-if="group.items && group.items.length" :key="index" :name="`${index}`"> <TMagicCollapseItem v-if="group.items && group.items.length" :key="index" :name="`${index}`">
<template #title><MIcon :icon="Grid"></MIcon>{{ group.title }}</template> <template #title><MIcon :icon="Grid"></MIcon>{{ group.title }}</template>
@ -34,7 +34,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, ref } from 'vue'; import { computed, inject, ref, watch } from 'vue';
import { Grid } from '@element-plus/icons-vue'; import { Grid } from '@element-plus/icons-vue';
import serialize from 'serialize-javascript'; import serialize from 'serialize-javascript';
@ -74,10 +74,19 @@ const list = computed<ComponentGroup[]>(() =>
items: group.items.filter((item: ComponentItem) => item.text.includes(searchText.value)), items: group.items.filter((item: ComponentItem) => item.text.includes(searchText.value)),
})), })),
); );
const collapseValue = computed(() =>
Array(list.value?.length) const collapseValue = ref();
watch(
list,
() => {
collapseValue.value = Array(list.value?.length)
.fill(1) .fill(1)
.map((x, i) => `${i}`), .map((x, i) => `${i}`);
},
{
immediate: true,
},
); );
let timeout: ReturnType<typeof setTimeout> | undefined; let timeout: ReturnType<typeof setTimeout> | undefined;

View File

@ -1,3 +1,5 @@
@use "../common/var" as *;
.background-position-container { .background-position-container {
display: flex; display: flex;
width: 100%; width: 100%;
@ -6,8 +8,8 @@
flex-wrap: wrap; flex-wrap: wrap;
width: 80px; width: 80px;
height: auto; height: auto;
.el-button { .tmagic-design-button {
& + .el-button { & + .tmagic-design-button {
margin-left: 2px; margin-left: 2px;
} }
&:nth-child(3n + 1) { &:nth-child(3n + 1) {
@ -15,13 +17,18 @@
} }
} }
.t-button--variant-text {
padding-left: 2px;
padding-right: 2px;
}
.position-icon { .position-icon {
position: relative; position: relative;
width: 14px; width: 14px;
height: 14px; height: 14px;
border: 1px solid #1d1f24; border: 1px solid #1d1f24;
&.active { &.active {
background-color: var(--el-color-primary); background-color: var($theme-color);
&::after { &::after {
border: 1px solid #fff; border: 1px solid #fff;
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *

View File

@ -0,0 +1,26 @@
<template>
<ElFormItem v-bind="itemProps">
<template #label>
<slot name="label"></slot>
</template>
<slot></slot>
<div v-if="extra" v-html="extra" class="m-form-tip"></div>
</ElFormItem>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { FormItemProps } from '@tmagic/design';
defineOptions({
name: 'TElAdapterFormItem',
});
const props = defineProps<FormItemProps>();
const itemProps = computed(() => {
const { extra, ...rest } = props;
return rest;
});
</script>

View File

@ -0,0 +1,92 @@
<template>
<ElTable
ref="table"
:data="data"
:border="border"
:max-height="maxHeight"
:default-expand-all="defaultExpandAll"
:show-header="showHeader"
:row-key="rowKey"
:tree-props="treeProps"
:empty-text="emptyText"
:show-overflow-tooltip="showOverflowTooltip"
:tooltip-effect="tooltipEffect"
:tooltip-options="tooltipOptions"
:span-method="spanMethod"
@sort-change="sortChange"
@select="selectHandler"
@select-all="selectAllHandler"
@selection-change="selectionChangeHandler"
@cell-click="cellClickHandler"
@expand-change="expandChange"
>
<template v-for="(item, columnIndex) in columns" :key="columnIndex">
<ElTableColumn v-bind="item.props || {}">
<template #default="scope" v-if="item.cell">
<component :is="item.cell(scope)"></component>
</template>
</ElTableColumn>
</template>
</ElTable>
</template>
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { ElTable, ElTableColumn } from 'element-plus';
import type { TableProps } from '@tmagic/design';
defineOptions({
name: 'TElAdapterTable',
});
const emit = defineEmits(['sort-change', 'select', 'select-all', 'selection-change', 'expand-change', 'cell-click']);
defineProps<TableProps>();
const tableRef = useTemplateRef('table');
const sortChange = (data: any) => {
emit('sort-change', data);
};
const selectHandler = (...args: any[]) => {
emit('select', ...args);
};
const selectAllHandler = (...args: any[]) => {
emit('select-all', ...args);
};
const selectionChangeHandler = (...args: any[]) => {
emit('selection-change', ...args);
};
const cellClickHandler = (...args: any[]) => {
emit('cell-click', ...args);
};
const expandChange = (...args: any[]) => {
emit('expand-change', ...args);
};
const toggleRowSelection = (row: any, selected: boolean) => {
tableRef.value?.toggleRowSelection(row, selected);
};
const toggleRowExpansion = (row: any, expanded: boolean) => {
tableRef.value?.toggleRowExpansion(row, expanded);
};
const clearSelection = () => {
tableRef.value?.clearSelection();
};
defineExpose({
getEl: () => tableRef.value?.$el,
getTableRef: () => tableRef.value,
clearSelection,
toggleRowSelection,
toggleRowExpansion,
});
</script>

View File

@ -18,7 +18,6 @@ import {
ElDropdownItem, ElDropdownItem,
ElDropdownMenu, ElDropdownMenu,
ElForm, ElForm,
ElFormItem,
ElIcon, ElIcon,
ElInput, ElInput,
ElInputNumber, ElInputNumber,
@ -37,14 +36,11 @@ import {
ElStep, ElStep,
ElSteps, ElSteps,
ElSwitch, ElSwitch,
ElTable,
ElTableColumn,
ElTabPane, ElTabPane,
ElTabs, ElTabs,
ElTag, ElTag,
ElTimePicker, ElTimePicker,
ElTooltip, ElTooltip,
ElTree,
ElUpload, ElUpload,
useZIndex, useZIndex,
} from 'element-plus'; } from 'element-plus';
@ -83,17 +79,18 @@ import type {
StepProps, StepProps,
StepsProps, StepsProps,
SwitchProps, SwitchProps,
TableColumnProps,
TableProps, TableProps,
TabPaneProps, TabPaneProps,
TabsProps, TabsProps,
TagProps, TagProps,
TimePickerProps, TimePickerProps,
TooltipProps, TooltipProps,
TreeProps,
UploadProps, UploadProps,
} from '@tmagic/design'; } from '@tmagic/design';
import FormItem from './FormItem.vue';
import Table from './Table.vue';
const adapter: DesignPluginOptions = { const adapter: DesignPluginOptions = {
useZIndex, useZIndex,
message: ElMessage, message: ElMessage,
@ -195,7 +192,7 @@ const adapter: DesignPluginOptions = {
}, },
formItem: { formItem: {
component: ElFormItem as any, component: FormItem as any,
props: (props: FormItemProps) => props, props: (props: FormItemProps) => props,
}, },
@ -275,15 +272,10 @@ const adapter: DesignPluginOptions = {
}, },
table: { table: {
component: ElTable as any, component: Table as any,
props: (props: TableProps) => props, props: (props: TableProps) => props,
}, },
tableColumn: {
component: ElTableColumn as any,
props: (props: TableColumnProps) => props,
},
tabPane: { tabPane: {
component: ElTabPane as any, component: ElTabPane as any,
props: (props: TabPaneProps) => props, props: (props: TabPaneProps) => props,
@ -309,11 +301,6 @@ const adapter: DesignPluginOptions = {
props: (props: TooltipProps) => props, props: (props: TooltipProps) => props,
}, },
tree: {
component: ElTree as any,
props: (props: TreeProps) => props,
},
upload: { upload: {
component: ElUpload as any, component: ElUpload as any,
props: (props: UploadProps) => props, props: (props: UploadProps) => props,

View File

@ -700,6 +700,7 @@ export interface TableConfig extends FormItem {
onSelect?: (mForm: FormState | undefined, data: any) => any; onSelect?: (mForm: FormState | undefined, data: any) => any;
defautSort?: SortProp; defautSort?: SortProp;
defaultSort?: SortProp; defaultSort?: SortProp;
/** 是否支持拖拽排序 */
dropSort?: boolean; dropSort?: boolean;
/** 是否显示全屏按钮 */ /** 是否显示全屏按钮 */
enableFullscreen?: boolean; enableFullscreen?: boolean;

View File

@ -46,8 +46,6 @@
@change="onChangeHandler" @change="onChangeHandler"
@addDiffCount="onAddDiffCount" @addDiffCount="onAddDiffCount"
></component> ></component>
<div v-if="extra && type !== 'table'" v-html="extra" class="m-form-tip"></div>
</TMagicFormItem> </TMagicFormItem>
<TMagicTooltip v-if="config.tip" placement="left"> <TMagicTooltip v-if="config.tip" placement="left">
@ -74,8 +72,6 @@
</TMagicTooltip> </TMagicTooltip>
<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>
<div v-if="extra" v-html="extra" class="m-form-tip"></div>
</TMagicFormItem> </TMagicFormItem>
<TMagicTooltip v-if="config.tip" placement="left"> <TMagicTooltip v-if="config.tip" placement="left">
@ -100,8 +96,6 @@
</TMagicTooltip> </TMagicTooltip>
<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>
<div v-if="extra" v-html="extra" class="m-form-tip"></div>
</TMagicFormItem> </TMagicFormItem>
<TMagicTooltip v-if="config.tip" placement="left"> <TMagicTooltip v-if="config.tip" placement="left">
@ -231,8 +225,6 @@ const text = computed(() => filterFunction(mForm, props.config.text, props));
const tooltip = computed(() => filterFunction(mForm, props.config.tooltip, props)); const tooltip = computed(() => filterFunction(mForm, props.config.tooltip, props));
const extra = computed(() => filterFunction(mForm, props.config.extra, props));
const rule = computed(() => getRules(mForm, props.config.rules, props)); const rule = computed(() => getRules(mForm, props.config.rules, props));
const type = computed((): string => { const type = computed((): string => {
@ -266,6 +258,7 @@ const formItemProps = computed(() => ({
labelWidth: itemLabelWidth.value, labelWidth: itemLabelWidth.value,
labelPosition: props.config.labelPosition, labelPosition: props.config.labelPosition,
rules: rule.value, rules: rule.value,
extra: filterFunction(mForm, props.config.extra, props),
})); }));
const itemLabelWidth = computed(() => props.config.labelWidth ?? props.labelWidth); const itemLabelWidth = computed(() => props.config.labelWidth ?? props.labelWidth);

View File

@ -1,681 +0,0 @@
<template>
<div class="m-fields-table-wrap">
<teleport to="body" :disabled="!isFullscreen">
<div ref="mTable" class="m-fields-table" :class="{ 'm-fields-table-item-extra': config.itemExtra }">
<span v-if="config.extra" style="color: rgba(0, 0, 0, 0.45)" v-html="config.extra"></span>
<TMagicTooltip content="拖拽可排序" placement="left-start" :disabled="config.dropSort !== true">
<TMagicTable
v-if="model[modelName]"
ref="tMagicTable"
style="width: 100%"
:row-key="config.rowKey || 'id'"
:data="data"
:lastData="lastData"
:border="config.border"
:max-height="config.maxHeight"
:default-expand-all="true"
:key="updateKey"
@select="selectHandle"
@sort-change="sortChange"
>
<TMagicTableColumn v-if="config.itemExtra && !config.dropSort" :fixed="'left'" width="30" type="expand">
<template v-slot="scope">
<span v-html="itemExtra(config.itemExtra, scope.$index)" class="m-form-tip"></span>
</template>
</TMagicTableColumn>
<TMagicTableColumn
label="操作"
:width="config.operateColWidth || 100"
align="center"
:fixed="config.fixed === false ? undefined : 'left'"
>
<template v-slot="scope">
<slot name="operateCol" :scope="scope"></slot>
<TMagicButton
v-show="showDelete(scope.$index + 1 + pagecontext * pagesize - 1)"
size="small"
type="danger"
link
title="删除"
:icon="Delete"
@click="removeHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
></TMagicButton>
<TMagicButton
v-if="copyable(scope.$index + 1 + pagecontext * pagesize - 1)"
link
size="small"
type="primary"
title="复制"
:icon="DocumentCopy"
:disabled="disabled"
@click="copyHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
></TMagicButton>
</template>
</TMagicTableColumn>
<TMagicTableColumn v-if="sort && model[modelName] && model[modelName].length > 1" label="排序" width="60">
<template v-slot="scope">
<TMagicTooltip
v-if="scope.$index + 1 + pagecontext * pagesize - 1 !== 0"
content="点击上移,双击置顶"
placement="top"
>
<TMagicButton
plain
size="small"
type="primary"
:icon="ArrowUp"
:disabled="disabled"
link
@click="upHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
@dblclick="topHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
></TMagicButton>
</TMagicTooltip>
<TMagicTooltip
v-if="scope.$index + 1 + pagecontext * pagesize - 1 !== model[modelName].length - 1"
content="点击下移,双击置底"
placement="top"
>
<TMagicButton
plain
size="small"
type="primary"
:icon="ArrowDown"
:disabled="disabled"
link
@click="downHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
@dblclick="bottomHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
></TMagicButton>
</TMagicTooltip>
</template>
</TMagicTableColumn>
<TMagicTableColumn
v-if="selection"
align="center"
header-align="center"
type="selection"
width="45"
></TMagicTableColumn>
<TMagicTableColumn width="60" label="序号" v-if="showIndex && config.showIndex">
<template v-slot="scope">{{ scope.$index + 1 + pagecontext * pagesize }}</template>
</TMagicTableColumn>
<template v-for="(column, index) in config.items">
<TMagicTableColumn
v-if="column.type !== 'hidden' && display(column.display)"
:prop="column.name"
:width="column.width"
:label="column.label"
:sortable="column.sortable"
:sort-orders="['ascending', 'descending']"
:key="column[mForm?.keyProp || '__key'] ?? index"
:class-name="config.dropSort === true ? 'el-table__column--dropable' : ''"
>
<template #default="scope">
<Container
v-if="scope.$index > -1"
labelWidth="0"
:disabled="disabled"
:prop="getProp(scope.$index)"
:rules="column.rules"
:config="makeConfig(column, scope.row)"
:model="scope.row"
:lastValues="lastData[scope.$index]"
:is-compare="isCompare"
:size="size"
@change="changeHandler"
@addDiffCount="onAddDiffCount()"
></Container>
</template>
</TMagicTableColumn>
</template>
</TMagicTable>
</TMagicTooltip>
<slot></slot>
<div style="display: flex; justify-content: space-between; margin: 10px 0">
<TMagicButton v-if="addable" size="small" type="primary" :disabled="disabled" plain @click="newHandler()"
>新增一行</TMagicButton
>
<div style="display: flex">
<TMagicButton
:icon="Grid"
size="small"
type="primary"
@click="toggleMode"
v-if="enableToggleMode && config.enableToggleMode !== false && !isFullscreen"
>展开配置</TMagicButton
>
<TMagicButton
:icon="FullScreen"
size="small"
type="primary"
@click="toggleFullscreen"
v-if="config.enableFullscreen !== false"
>
{{ isFullscreen ? '退出全屏' : '全屏编辑' }}
</TMagicButton>
<TMagicUpload
v-if="importable"
style="display: inline-block"
ref="excelBtn"
action="/noop"
:disabled="disabled"
:on-change="excelHandler"
:auto-upload="false"
>
<TMagicButton size="small" type="success" :disabled="disabled" plain>导入EXCEL</TMagicButton>
</TMagicUpload>
<TMagicButton
v-if="importable"
size="small"
type="warning"
:disabled="disabled"
plain
@click="clearHandler()"
>清空</TMagicButton
>
</div>
</div>
<div class="bottom" style="text-align: right" v-if="config.pagination">
<TMagicPagination
layout="total, sizes, prev, pager, next, jumper"
:hide-on-single-page="model[modelName].length < pagesize"
:current-page="pagecontext + 1"
:page-sizes="[pagesize, 60, 120, 300]"
:page-size="pagesize"
:total="model[modelName].length"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</TMagicPagination>
</div>
</div>
</teleport>
</div>
</template>
<script setup lang="ts">
import { computed, inject, onMounted, ref, toRefs, watchEffect } from 'vue';
import { ArrowDown, ArrowUp, Delete, DocumentCopy, FullScreen, Grid } from '@element-plus/icons-vue';
import { cloneDeep } from 'lodash-es';
import Sortable, { SortableEvent } from 'sortablejs';
import {
TMagicButton,
tMagicMessage,
TMagicPagination,
TMagicTable,
TMagicTableColumn,
TMagicTooltip,
TMagicUpload,
useZIndex,
} from '@tmagic/design';
import { asyncLoadJs } from '@tmagic/utils';
import type { ContainerChangeEventData, FormState, SortProp, TableColumnConfig, TableConfig } from '../schema';
import { display as displayFunc, initValue } from '../utils/form';
import Container from './Container.vue';
defineOptions({
name: 'MFormTable',
});
const props = withDefaults(
defineProps<{
model: any;
lastValues?: any;
isCompare?: boolean;
config: TableConfig;
name: string;
prop?: string;
labelWidth?: string;
sort?: boolean;
disabled?: boolean;
sortKey?: string;
text?: string;
size?: string;
enableToggleMode?: boolean;
showIndex?: boolean;
}>(),
{
prop: '',
sortKey: '',
enableToggleMode: true,
showIndex: true,
lastValues: () => ({}),
isCompare: false,
},
);
const emit = defineEmits(['change', 'select', 'addDiffCount']);
let timer: any | null = null;
const mForm = inject<FormState | undefined>('mForm');
const { nextZIndex } = useZIndex();
const tMagicTable = ref<InstanceType<typeof TMagicTable>>();
const excelBtn = ref<InstanceType<typeof TMagicUpload>>();
const mTable = ref<HTMLDivElement>();
const pagesize = ref(10);
const pagecontext = ref(0);
const updateKey = ref(1);
const isFullscreen = ref(false);
const modelName = computed(() => props.name || props.config.name || '');
const getDataByPage = (data: any[] = []) =>
data.filter(
(item: any, index: number) =>
index >= pagecontext.value * pagesize.value && index + 1 <= (pagecontext.value + 1) * pagesize.value,
);
const pageinationData = computed(() => getDataByPage(props.model[modelName.value]));
const data = computed(() => (props.config.pagination ? pageinationData.value : props.model[modelName.value]));
const lastData = computed(() =>
props.config.pagination ? getDataByPage(props.lastValues[modelName.value]) : props.lastValues[modelName.value] || [],
);
const sortChange = ({ prop, order }: SortProp) => {
if (order === 'ascending') {
props.model[modelName.value] = props.model[modelName.value].sort((a: any, b: any) => a[prop] - b[prop]);
} else if (order === 'descending') {
props.model[modelName.value] = props.model[modelName.value].sort((a: any, b: any) => b[prop] - a[prop]);
}
};
const swapArray = (index1: number, index2: number) => {
props.model[modelName.value].splice(index1, 0, props.model[modelName.value].splice(index2, 1)[0]);
if (props.sortKey) {
for (let i = props.model[modelName.value].length - 1, v = 0; i >= 0; i--, v++) {
props.model[modelName.value][v][props.sortKey] = i;
}
}
mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
};
let sortable: Sortable | undefined;
const rowDrop = () => {
sortable?.destroy();
const tableEl = tMagicTable.value?.instance.$el;
const tBodyEl = tableEl?.querySelector('.el-table__body > tbody');
if (!tBodyEl) {
return;
}
sortable = Sortable.create(tBodyEl, {
draggable: '.tmagic-design-table-row',
filter: 'input', //
preventOnFilter: false, //
direction: 'vertical',
onEnd: ({ newIndex, oldIndex }: SortableEvent) => {
if (typeof newIndex === 'undefined') return;
if (typeof oldIndex === 'undefined') return;
swapArray(newIndex, oldIndex);
emit('change', props.model[modelName.value]);
mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
},
});
};
const newHandler = async (row?: any) => {
if (props.config.max && props.model[modelName.value].length >= props.config.max) {
tMagicMessage.error(`最多新增配置不能超过${props.config.max}`);
return;
}
if (typeof props.config.beforeAddRow === 'function') {
const beforeCheckRes = props.config.beforeAddRow(mForm, {
model: props.model[modelName.value],
formValue: mForm?.values,
prop: props.prop,
});
if (!beforeCheckRes) return;
}
const columns = props.config.items;
const enumValues = props.config.enum || [];
let enumV = [];
const { length } = props.model[modelName.value];
const key = props.config.key || 'id';
let inputs: any = {};
if (enumValues.length) {
if (length >= enumValues.length) {
return;
}
enumV = enumValues.filter((item) => {
let i = 0;
for (; i < length; i++) {
if (item[key] === props.model[modelName.value][i][key]) {
break;
}
}
return i === length;
});
if (enumV.length > 0) {
// eslint-disable-next-line prefer-destructuring
inputs = enumV[0];
}
} else if (Array.isArray(row)) {
columns.forEach((column, index) => {
column.name && (inputs[column.name] = row[index]);
});
} else {
if (typeof props.config.defaultAdd === 'function') {
inputs = await props.config.defaultAdd(mForm, {
model: props.model[modelName.value],
formValue: mForm?.values,
});
} else if (props.config.defaultAdd) {
inputs = props.config.defaultAdd;
}
inputs = await initValue(mForm, {
config: columns,
initValues: inputs,
});
}
if (props.sortKey && length) {
inputs[props.sortKey] = props.model[modelName.value][length - 1][props.sortKey] - 1;
}
props.model[modelName.value].push(inputs);
emit('change', props.model[modelName.value], {
changeRecords: [
{
propPath: `${props.prop}.${props.model[modelName.value].length - 1}`,
value: inputs,
},
],
});
};
onMounted(() => {
if (props.config.defautSort) {
sortChange(props.config.defautSort);
} else if (props.config.defaultSort) {
sortChange(props.config.defaultSort);
}
if (props.sort && props.sortKey) {
props.model[modelName.value].sort((a: any, b: any) => b[props.sortKey] - a[props.sortKey]);
}
});
watchEffect(() => {
if (props.config.dropSort) {
rowDrop();
}
});
const addable = computed(() => {
if (!props.model[modelName.value].length) {
return true;
}
if (typeof props.config.addable === 'function') {
return props.config.addable(mForm, {
model: props.model[modelName.value],
formValue: mForm?.values,
prop: props.prop,
});
}
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
});
const selection = computed(() => {
if (typeof props.config.selection === 'function') {
return props.config.selection(mForm, { model: props.model[modelName.value] });
}
return props.config.selection;
});
const importable = computed(() => {
if (typeof props.config.importable === 'function') {
return props.config.importable(mForm, {
formValue: mForm?.values,
model: props.model[modelName.value],
});
}
return typeof props.config.importable === 'undefined' ? false : props.config.importable;
});
const display = (fuc: any) => displayFunc(mForm, fuc, props);
const itemExtra = (fuc: any, index: number) => {
if (typeof fuc === 'function') {
return fuc(mForm, {
values: mForm?.initValues,
model: props.model,
formValue: mForm ? mForm.values : props.model,
prop: props.prop,
index,
});
}
return fuc;
};
const removeHandler = (index: number) => {
if (props.disabled) return;
props.model[modelName.value].splice(index, 1);
emit('change', props.model[modelName.value]);
};
const selectHandle = (selection: any, row: any) => {
if (typeof props.config.selection === 'string' && props.config.selection === 'single') {
tMagicTable.value?.clearSelection();
tMagicTable.value?.toggleRowSelection(row, true);
}
emit('select', selection, row);
if (typeof props.config.onSelect === 'function') {
props.config.onSelect(mForm, { selection, row, config: props.config });
}
};
const toggleRowSelection = (row: any, selected: boolean) => {
tMagicTable.value?.toggleRowSelection.call(tMagicTable.value, row, selected);
};
const makeConfig = (config: TableColumnConfig, row: any) => {
const newConfig = cloneDeep(config);
if (typeof config.itemsFunction === 'function') {
newConfig.items = config.itemsFunction(row);
}
delete newConfig.display;
return newConfig;
};
const upHandler = (index: number) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
swapArray(index, index - 1);
timer = undefined;
}, 300);
};
const topHandler = (index: number) => {
if (timer) {
clearTimeout(timer);
}
// ,
const moveNum = index;
//
for (let i = 0; i < moveNum; i++) {
swapArray(index, index - 1);
index -= 1;
}
};
const downHandler = (index: number) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
swapArray(index, index + 1);
timer = undefined;
}, 300);
};
const bottomHandler = (index: number) => {
if (timer) {
clearTimeout(timer);
}
// ,
const moveNum = props.model[modelName.value].length - 1 - index;
//
for (let i = 0; i < moveNum; i++) {
swapArray(index, index + 1);
index += 1;
}
};
//
const showDelete = (index: number) => {
const deleteFunc = props.config.delete;
if (deleteFunc && typeof deleteFunc === 'function') {
return deleteFunc(props.model[modelName.value], index, mForm?.values);
}
return true;
};
const copyable = (index: number) => {
const copyableFunc = props.config.copyable;
if (copyableFunc && typeof copyableFunc === 'function') {
return copyableFunc(mForm, {
values: mForm?.initValues || {},
model: props.model,
parent: mForm?.parentValues || {},
formValue: mForm?.values || props.model,
prop: props.prop,
config: props.config,
index,
});
}
return true;
};
const clearHandler = () => {
const len = props.model[modelName.value].length;
props.model[modelName.value].splice(0, len);
mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
};
const excelHandler = async (file: any) => {
if (!file?.raw) {
return false;
}
if (!(globalThis as any).XLSX) {
await asyncLoadJs('https://cdn.bootcdn.net/ajax/libs/xlsx/0.17.0/xlsx.full.min.js');
}
const reader = new FileReader();
reader.onload = () => {
const data = reader.result;
const pdata = (globalThis as any).XLSX.read(data, { type: 'array' });
pdata.SheetNames.forEach((sheetName: string) => {
const arr = (globalThis as any).XLSX.utils.sheet_to_json(pdata.Sheets[sheetName], { header: 1 });
if (arr?.[0]) {
arr.forEach((row: any) => {
newHandler(row);
});
}
setTimeout(() => {
excelBtn.value?.clearFiles();
}, 300);
});
};
reader.readAsArrayBuffer(file.raw);
return false;
};
const handleSizeChange = (val: number) => {
pagesize.value = val;
};
const handleCurrentChange = (val: number) => {
pagecontext.value = val - 1;
};
const copyHandler = (index: number) => {
props.model[modelName.value].push(cloneDeep(props.model[modelName.value][index]));
};
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 toggleFullscreen = () => {
if (!mTable.value) return;
if (isFullscreen.value) {
mTable.value.classList.remove('fixed');
isFullscreen.value = false;
} else {
mTable.value.classList.add('fixed');
mTable.value.style.zIndex = `${nextZIndex()}`;
isFullscreen.value = true;
}
};
const getProp = (index: number) => {
const { prop } = toRefs(props);
return `${prop.value}${prop.value ? '.' : ''}${index + 1 + pagecontext.value * pagesize.value - 1}`;
};
const onAddDiffCount = () => emit('addDiffCount');
const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
emit('change', props.model, eventData);
};
defineExpose({
toggleRowSelection,
});
</script>

View File

@ -5,7 +5,7 @@
:is="itemComponent" :is="itemComponent"
:value="option.value" :value="option.value"
:key="`${option.value}`" :key="`${option.value}`"
@click.prevent="clickHandler(option.value)" @click="clickHandler(option.value)"
> >
<TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip"> <TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip">
<div> <div>

View File

@ -24,7 +24,6 @@ import GroupList from './containers/GroupList.vue';
import Panel from './containers/Panel.vue'; import Panel from './containers/Panel.vue';
import Row from './containers/Row.vue'; import Row from './containers/Row.vue';
import MStep from './containers/Step.vue'; import MStep from './containers/Step.vue';
import Table from './containers/Table.vue';
import Tabs from './containers/Tabs.vue'; import Tabs from './containers/Tabs.vue';
import Cascader from './fields/Cascader.vue'; import Cascader from './fields/Cascader.vue';
import Checkbox from './fields/Checkbox.vue'; import Checkbox from './fields/Checkbox.vue';
@ -46,6 +45,7 @@ import Text from './fields/Text.vue';
import Textarea from './fields/Textarea.vue'; import Textarea from './fields/Textarea.vue';
import Time from './fields/Time.vue'; import Time from './fields/Time.vue';
import Timerange from './fields/Timerange.vue'; import Timerange from './fields/Timerange.vue';
import Table from './table/Table.vue';
import { setConfig } from './utils/config'; import { setConfig } from './utils/config';
import Form from './Form.vue'; import Form from './Form.vue';
import FormDialog from './FormDialog.vue'; import FormDialog from './FormDialog.vue';
@ -66,7 +66,7 @@ export { default as MFieldset } from './containers/Fieldset.vue';
export { default as MPanel } from './containers/Panel.vue'; export { default as MPanel } from './containers/Panel.vue';
export { default as MRow } from './containers/Row.vue'; export { default as MRow } from './containers/Row.vue';
export { default as MTabs } from './containers/Tabs.vue'; export { default as MTabs } from './containers/Tabs.vue';
export { default as MTable } from './containers/Table.vue'; export { default as MTable } from './table/Table.vue';
export { default as MGroupList } from './containers/GroupList.vue'; export { default as MGroupList } from './containers/GroupList.vue';
export { default as MText } from './fields/Text.vue'; export { default as MText } from './fields/Text.vue';
export { default as MNumber } from './fields/Number.vue'; export { default as MNumber } from './fields/Number.vue';

View File

@ -0,0 +1,84 @@
<template>
<slot name="operateCol" :scope="{ $index: index, row: row }"></slot>
<TMagicButton
v-show="showDelete(index + 1 + currentPage * pageSize - 1)"
size="small"
type="danger"
link
title="删除"
:icon="Delete"
@click="removeHandler(index + 1 + currentPage * pageSize - 1)"
></TMagicButton>
<TMagicButton
v-if="copyable(index + 1 + currentPage * pageSize - 1)"
link
size="small"
type="primary"
title="复制"
:icon="DocumentCopy"
:disabled="disabled"
@click="copyHandler(index + 1 + currentPage * pageSize - 1)"
></TMagicButton>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { Delete, DocumentCopy } from '@element-plus/icons-vue';
import { cloneDeep } from 'lodash-es';
import { TMagicButton } from '@tmagic/design';
import type { FormState, TableConfig } from '../schema';
const emit = defineEmits(['change']);
const props = defineProps<{
config: TableConfig;
model: any;
name: string | number;
disabled?: boolean;
currentPage: number;
pageSize: number;
index: number;
row: any;
prop?: string;
}>();
const mForm = inject<FormState | undefined>('mForm');
const removeHandler = (index: number) => {
if (props.disabled) return;
props.model[props.name].splice(index, 1);
emit('change', props.model[props.name]);
};
const copyHandler = (index: number) => {
props.model[props.name].push(cloneDeep(props.model[props.name][index]));
};
//
const showDelete = (index: number) => {
const deleteFunc = props.config.delete;
if (deleteFunc && typeof deleteFunc === 'function') {
return deleteFunc(props.model[props.name], index, mForm?.values);
}
return true;
};
const copyable = (index: number) => {
const copyableFunc = props.config.copyable;
if (copyableFunc && typeof copyableFunc === 'function') {
return copyableFunc(mForm, {
values: mForm?.initValues || {},
model: props.model,
parent: mForm?.parentValues || {},
formValue: mForm?.values || props.model,
prop: props.prop,
config: props.config,
index,
});
}
return true;
};
</script>

View File

@ -0,0 +1,101 @@
<template>
<TMagicTooltip v-if="index + 1 + currentPage * pageSize - 1 !== 0" content="点击上移,双击置顶" placement="top">
<TMagicButton
plain
size="small"
type="primary"
:icon="ArrowUp"
:disabled="disabled"
link
@click="upHandler(index + 1 + currentPage * pageSize - 1)"
@dblclick="topHandler(index + 1 + currentPage * pageSize - 1)"
></TMagicButton>
</TMagicTooltip>
<TMagicTooltip
v-if="index + 1 + currentPage * pageSize - 1 !== model[name].length - 1"
content="点击下移,双击置底"
placement="top"
>
<TMagicButton
plain
size="small"
type="primary"
:icon="ArrowDown"
:disabled="disabled"
link
@click="downHandler(index + 1 + currentPage * pageSize - 1)"
@dblclick="bottomHandler(index + 1 + currentPage * pageSize - 1)"
></TMagicButton>
</TMagicTooltip>
</template>
<script setup lang="ts">
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import { TMagicButton, TMagicTooltip } from '@tmagic/design';
const props = defineProps<{
index: number;
disabled?: boolean;
currentPage: number;
pageSize: number;
name: string | number;
model: any;
}>();
const emit = defineEmits(['swap']);
let timer: ReturnType<typeof setTimeout> | null = null;
const upHandler = (index: number) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
emit('swap', index, index - 1);
timer = null;
}, 300);
};
const topHandler = (index: number) => {
if (timer) {
clearTimeout(timer);
}
// ,
const moveNum = index;
//
for (let i = 0; i < moveNum; i++) {
emit('swap', index, index - 1);
index -= 1;
}
};
const downHandler = (index: number) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
emit('swap', index, index + 1);
timer = null;
}, 300);
};
const bottomHandler = (index: number) => {
if (timer) {
clearTimeout(timer);
}
// ,
const moveNum = props.model[props.name].length - 1 - index;
//
for (let i = 0; i < moveNum; i++) {
emit('swap', index, index + 1);
index += 1;
}
};
</script>

View File

@ -0,0 +1,170 @@
<template>
<div class="m-fields-table-wrap">
<teleport to="body" :disabled="!isFullscreen">
<div ref="mTable" class="m-fields-table" :class="{ 'm-fields-table-item-extra': config.itemExtra }">
<span v-if="config.extra" style="color: rgba(0, 0, 0, 0.45)" v-html="config.extra"></span>
<TMagicTooltip content="拖拽可排序" placement="left-start" :disabled="config.dropSort !== true">
<TMagicTable
v-if="model[modelName]"
ref="tMagicTable"
style="width: 100%"
show-header
:row-key="config.rowKey || 'id'"
:columns="columns"
:data="data"
:border="config.border"
:max-height="config.maxHeight"
:default-expand-all="true"
:key="updateKey"
@select="selectHandle"
@sort-change="sortChangeHandler"
></TMagicTable>
</TMagicTooltip>
<slot></slot>
<div style="display: flex; justify-content: space-between; margin: 10px 0">
<TMagicButton v-if="addable" size="small" type="primary" :disabled="disabled" plain @click="newHandler()"
>新增一行</TMagicButton
>
<div style="display: flex">
<TMagicButton
:icon="Grid"
size="small"
type="primary"
@click="toggleMode"
v-if="enableToggleMode && config.enableToggleMode !== false && !isFullscreen"
>展开配置</TMagicButton
>
<TMagicButton
:icon="FullScreen"
size="small"
type="primary"
@click="toggleFullscreen"
v-if="config.enableFullscreen !== false"
>
{{ isFullscreen ? '退出全屏' : '全屏编辑' }}
</TMagicButton>
<TMagicUpload
v-if="importable"
style="display: inline-block"
ref="excelBtn"
action="/noop"
:disabled="disabled"
:on-change="excelHandler"
:auto-upload="false"
>
<TMagicButton size="small" type="success" :disabled="disabled" plain>导入EXCEL</TMagicButton>
</TMagicUpload>
<TMagicButton v-if="importable" size="small" type="warning" :disabled="disabled" plain @click="clearHandler"
>清空</TMagicButton
>
</div>
</div>
<div class="bottom" style="text-align: right" v-if="config.pagination">
<TMagicPagination
layout="total, sizes, prev, pager, next, jumper"
:hide-on-single-page="model[modelName].length < pageSize"
:current-page="currentPage + 1"
:page-sizes="[pageSize, 60, 120, 300]"
:page-size="pageSize"
:total="model[modelName].length"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</TMagicPagination>
</div>
</div>
</teleport>
</div>
</template>
<script setup lang="ts">
import { computed, ref, useTemplateRef } from 'vue';
import { FullScreen, Grid } from '@element-plus/icons-vue';
import { TMagicButton, TMagicPagination, TMagicTable, TMagicTooltip, TMagicUpload } from '@tmagic/design';
import type { SortProp } from '../schema';
import { sortChange } from '../utils/form';
import type { TableProps } from './type';
import { useAdd } from './useAdd';
import { useFullscreen } from './useFullscreen';
import { useImport } from './useImport';
import { usePagination } from './usePagination';
import { useSelection } from './useSelection';
import { useSortable } from './useSortable';
import { useTableColumns } from './useTableColumns';
defineOptions({
name: 'MFormTable',
});
const props = withDefaults(defineProps<TableProps>(), {
prop: '',
sortKey: '',
enableToggleMode: true,
showIndex: true,
lastValues: () => ({}),
isCompare: false,
});
const emit = defineEmits(['change', 'select', 'addDiffCount']);
const modelName = computed(() => props.name || props.config.name || '');
const tMagicTableRef = useTemplateRef<InstanceType<typeof TMagicTable>>('tMagicTable');
const { pageSize, currentPage, paginationData, handleSizeChange, handleCurrentChange } = usePagination(
props,
modelName,
);
const { addable, newHandler } = useAdd(props, emit);
const { columns } = useTableColumns(props, emit, currentPage, pageSize, modelName);
useSortable(props, emit, tMagicTableRef, modelName);
const { isFullscreen, toggleFullscreen } = useFullscreen();
const { importable, excelHandler, clearHandler } = useImport(props, emit, newHandler);
const { selectHandle, toggleRowSelection } = useSelection(props, emit, tMagicTableRef);
const updateKey = ref(1);
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);
};
defineExpose({
toggleRowSelection,
});
</script>

View File

@ -0,0 +1,18 @@
import type { TableConfig } from '@tmagic/form-schema';
export interface TableProps {
model: any;
lastValues?: any;
isCompare?: boolean;
config: TableConfig;
name: string;
prop?: string;
labelWidth?: string;
sort?: boolean;
disabled?: boolean;
sortKey?: string;
text?: string;
size?: string;
enableToggleMode?: boolean;
showIndex?: boolean;
}

View File

@ -0,0 +1,111 @@
import { computed, inject } from 'vue';
import { tMagicMessage } from '@tmagic/design';
import type { FormState } from '@tmagic/form-schema';
import { initValue } from '../utils/form';
import type { TableProps } from './type';
export const useAdd = (
props: TableProps,
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
) => {
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 || '';
if (props.config.max && props.model[modelName].length >= props.config.max) {
tMagicMessage.error(`最多新增配置不能超过${props.config.max}`);
return;
}
if (typeof props.config.beforeAddRow === 'function') {
const beforeCheckRes = props.config.beforeAddRow(mForm, {
model: props.model[modelName],
formValue: mForm?.values,
prop: props.prop,
});
if (!beforeCheckRes) return;
}
const columns = props.config.items;
const enumValues = props.config.enum || [];
let enumV = [];
const { length } = props.model[modelName];
const key = props.config.key || 'id';
let inputs: any = {};
if (enumValues.length) {
if (length >= enumValues.length) {
return;
}
enumV = enumValues.filter((item) => {
let i = 0;
for (; i < length; i++) {
if (item[key] === props.model[modelName][i][key]) {
break;
}
}
return i === length;
});
if (enumV.length > 0) {
// eslint-disable-next-line prefer-destructuring
inputs = enumV[0];
}
} else if (Array.isArray(row)) {
columns.forEach((column, index) => {
column.name && (inputs[column.name] = row[index]);
});
} else {
if (typeof props.config.defaultAdd === 'function') {
inputs = await props.config.defaultAdd(mForm, {
model: props.model[modelName],
formValue: mForm?.values,
});
} else if (props.config.defaultAdd) {
inputs = props.config.defaultAdd;
}
inputs = await initValue(mForm, {
config: columns,
initValues: inputs,
});
}
if (props.sortKey && length) {
inputs[props.sortKey] = props.model[modelName][length - 1][props.sortKey] - 1;
}
emit('change', [...props.model[modelName], inputs], {
changeRecords: [
{
propPath: `${props.prop}.${props.model[modelName].length}`,
value: inputs,
},
],
});
};
return {
addable,
newHandler,
};
};

View File

@ -0,0 +1,28 @@
import { ref, useTemplateRef } from 'vue';
import { useZIndex } from '@tmagic/design';
export const useFullscreen = () => {
const isFullscreen = ref(false);
const mTableEl = useTemplateRef<HTMLDivElement>('mTable');
const { nextZIndex } = useZIndex();
const toggleFullscreen = () => {
if (!mTableEl.value) return;
if (isFullscreen.value) {
mTableEl.value.classList.remove('fixed');
isFullscreen.value = false;
} else {
mTableEl.value.classList.add('fixed');
mTableEl.value.style.zIndex = `${nextZIndex()}`;
isFullscreen.value = true;
}
};
return {
isFullscreen,
toggleFullscreen,
};
};

View File

@ -0,0 +1,68 @@
import { computed, inject, useTemplateRef } from 'vue';
import type { TMagicUpload } from '@tmagic/design';
import type { FormState } from '@tmagic/form-schema';
import { asyncLoadJs } from '@tmagic/utils';
import type { TableProps } from './type';
export const useImport = (
props: TableProps,
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
newHandler: (row: any) => void,
) => {
const mForm = inject<FormState | undefined>('mForm');
const modelName = computed(() => props.name || props.config.name || '');
const importable = computed(() => {
if (typeof props.config.importable === 'function') {
return props.config.importable(mForm, {
formValue: mForm?.values,
model: props.model[modelName.value],
});
}
return typeof props.config.importable === 'undefined' ? false : props.config.importable;
});
const excelBtn = useTemplateRef<InstanceType<typeof TMagicUpload>>('excelBtn');
const excelHandler = async (file: any) => {
if (!file?.raw) {
return false;
}
if (!(globalThis as any).XLSX) {
await asyncLoadJs('https://cdn.bootcdn.net/ajax/libs/xlsx/0.17.0/xlsx.full.min.js');
}
const reader = new FileReader();
reader.onload = () => {
const data = reader.result;
const pdata = (globalThis as any).XLSX.read(data, { type: 'array' });
pdata.SheetNames.forEach((sheetName: string) => {
const arr = (globalThis as any).XLSX.utils.sheet_to_json(pdata.Sheets[sheetName], { header: 1 });
if (arr?.[0]) {
arr.forEach((row: any) => {
newHandler(row);
});
}
setTimeout(() => {
excelBtn.value?.clearFiles();
}, 300);
});
};
reader.readAsArrayBuffer(file.raw);
return false;
};
const clearHandler = () => {
emit('change', []);
mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
};
return {
importable,
excelHandler,
clearHandler,
};
};

View File

@ -0,0 +1,30 @@
import { computed, type Ref, ref } from 'vue';
import { getDataByPage } from '../utils/form';
import type { TableProps } from './type';
export const usePagination = (props: TableProps, modelName: Ref<string | number>) => {
const pageSize = ref(10);
/**
*
*/
const currentPage = ref(0);
const paginationData = computed(() => getDataByPage(props.model[modelName.value], currentPage.value, pageSize.value));
const handleSizeChange = (val: number) => {
pageSize.value = val;
};
const handleCurrentChange = (val: number) => {
currentPage.value = val - 1;
};
return {
pageSize,
currentPage,
paginationData,
handleSizeChange,
handleCurrentChange,
};
};

View File

@ -0,0 +1,34 @@
import { inject, type ShallowRef } from 'vue';
import type { TMagicTable } from '@tmagic/design';
import type { FormState } from '@tmagic/form-schema';
import type { TableProps } from './type';
export const useSelection = (
props: TableProps,
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
tMagicTableRef: ShallowRef<InstanceType<typeof TMagicTable> | null>,
) => {
const mForm = inject<FormState | undefined>('mForm');
const selectHandle = (selection: any, row: any) => {
if (typeof props.config.selection === 'string' && props.config.selection === 'single') {
tMagicTableRef.value?.clearSelection();
tMagicTableRef.value?.toggleRowSelection(row, true);
}
emit('select', selection, row);
if (typeof props.config.onSelect === 'function') {
props.config.onSelect(mForm, { selection, row, config: props.config });
}
};
const toggleRowSelection = (row: any, selected: boolean) => {
tMagicTableRef.value?.toggleRowSelection.call(tMagicTableRef.value?.getTableRef(), row, selected);
};
return {
selectHandle,
toggleRowSelection,
};
};

View File

@ -0,0 +1,48 @@
import { inject, type Ref, type ShallowRef, watchEffect } from 'vue';
import Sortable, { type SortableEvent } from 'sortablejs';
import { type TMagicTable } from '@tmagic/design';
import type { FormState } from '@tmagic/form-schema';
import { sortArray } from '../utils/form';
import type { TableProps } from './type';
export const useSortable = (
props: TableProps,
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
tMagicTableRef: ShallowRef<InstanceType<typeof TMagicTable> | null>,
modelName: Ref<string | number>,
) => {
const mForm = inject<FormState | undefined>('mForm');
let sortable: Sortable | undefined;
const rowDrop = () => {
sortable?.destroy();
const tableEl = tMagicTableRef.value?.getEl();
const tBodyEl = tableEl?.querySelector('.el-table__body > tbody');
if (!tBodyEl) {
return;
}
sortable = Sortable.create(tBodyEl, {
draggable: '.tmagic-design-table-row',
filter: 'input', // 表单组件选字操作和触发拖拽会冲突,优先保证选字操作
preventOnFilter: false, // 允许选字
direction: 'vertical',
onEnd: ({ newIndex, oldIndex }: SortableEvent) => {
if (typeof newIndex === 'undefined') return;
if (typeof oldIndex === 'undefined') return;
const newData = sortArray(props.model[modelName.value], newIndex, oldIndex, props.sortKey);
emit('change', newData);
mForm?.$emit('field-change', newData);
},
});
};
watchEffect(() => {
if (props.config.dropSort) {
rowDrop();
}
});
};

View File

@ -0,0 +1,193 @@
import { computed, h, inject, type Ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import type { TableColumnOptions } from '@tmagic/design';
import type { FormState, TableColumnConfig } from '@tmagic/form-schema';
import Container from '../containers/Container.vue';
import type { ContainerChangeEventData } from '../schema';
import { display as displayFunc, getDataByPage, sortArray } from '../utils/form';
import ActionsColumn from './ActionsColumn.vue';
import SortColumn from './SortColumn.vue';
import type { TableProps } from './type';
export const useTableColumns = (
props: TableProps,
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
currentPage: Ref<number>,
pageSize: Ref<number>,
modelName: Ref<string | number>,
) => {
const mForm = inject<FormState | undefined>('mForm');
const display = (fuc: any) => displayFunc(mForm, fuc, props);
const lastData = computed(() =>
props.config.pagination
? getDataByPage(props.lastValues[modelName.value], currentPage.value, pageSize.value)
: props.lastValues[modelName.value] || [],
);
const itemExtra = (fuc: any, index: number) => {
if (typeof fuc === 'function') {
return fuc(mForm, {
values: mForm?.initValues,
model: props.model,
formValue: mForm ? mForm.values : props.model,
prop: props.prop,
index,
});
}
return fuc;
};
const selection = computed(() => {
if (typeof props.config.selection === 'function') {
return props.config.selection(mForm, { model: props.model[modelName.value] });
}
return props.config.selection;
});
const getProp = (index: number) => {
return `${props.prop}${props.prop ? '.' : ''}${index + 1 + currentPage.value * pageSize.value - 1}`;
};
const makeConfig = (config: TableColumnConfig, row: any) => {
const newConfig = cloneDeep(config);
if (typeof config.itemsFunction === 'function') {
newConfig.items = config.itemsFunction(row);
}
delete newConfig.display;
return newConfig;
};
const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
emit('change', props.model, eventData);
};
const onAddDiffCount = () => emit('addDiffCount');
const columns = computed<TableColumnOptions[]>(() => {
const columns: TableColumnOptions[] = [];
if (props.config.itemExtra && !props.config.dropSort) {
columns.push({
props: {
fixed: 'left',
width: 30,
type: 'expand',
},
cell: ({ $index }: any) =>
h('span', {
innerHTML: itemExtra(props.config.itemExtra, $index),
class: 'm-form-tip',
}),
});
}
columns.push({
props: {
label: '操作',
fixed: props.config.fixed === false ? undefined : 'left',
width: props.config.operateColWidth || 100,
align: 'center',
},
cell: ({ row, $index }: any) =>
h(ActionsColumn, {
row,
index: $index,
model: props.model,
config: props.config,
prop: props.prop,
disabled: props.disabled,
name: modelName.value,
currentPage: currentPage.value,
pageSize: pageSize.value,
onChange: (v: any) => {
emit('change', v);
},
}),
});
if (props.sort && props.model[modelName.value] && props.model[modelName.value].length > 1) {
columns.push({
props: {
label: '排序',
width: 80,
},
cell: ({ $index }: any) =>
h(SortColumn, {
index: $index,
model: props.model,
disabled: props.disabled,
name: modelName.value,
currentPage: currentPage.value,
pageSize: pageSize.value,
onSwap: (index1: number, index2: number) => {
const newData = sortArray(props.model[modelName.value], index1, index2, props.sortKey);
emit('change', newData);
mForm?.$emit('field-change', newData);
},
}),
});
}
if (selection.value) {
columns.push({
props: {
align: 'center',
headerAlign: 'center',
type: 'selection',
width: 45,
},
});
}
if (props.showIndex && props.config.showIndex) {
columns.push({
props: {
label: '序号',
width: 60,
},
cell: ({ $index }: any) => h('span', $index + 1 + currentPage.value * pageSize.value),
});
}
for (const column of props.config.items) {
if (column.type !== 'hidden' && display(column.display)) {
columns.push({
props: {
prop: column.name,
label: column.label,
width: column.width,
sortable: column.sortable,
sortOrders: ['ascending', 'descending'],
class: props.config.dropSort === true ? 'el-table__column--dropable' : '',
},
cell: ({ row, $index }: any) =>
h(Container, {
labelWidth: '0',
disabled: props.disabled,
prop: getProp($index),
rules: column.rules,
config: makeConfig(column, row),
model: row,
lastValues: lastData.value[$index],
isCompare: props.isCompare,
size: props.size,
onChange: changeHandler,
onAddDiffCount,
}),
});
}
}
return columns;
});
return {
columns,
};
};

View File

@ -19,17 +19,21 @@
height: 100%; height: 100%;
} }
.el-table { .tmagic-design-table {
.cell > div.m-form-container { .cell > div.m-form-container {
display: block; display: block;
&.has-tip {
display: flex;
}
} }
} }
.el-tabs { .tmagic-design-tabs {
margin-bottom: 10px; margin-bottom: 10px;
} }
.el-form-item.tmagic-form-hidden { .tmagic-design-form-item.tmagic-form-hidden {
> .el-form-item__label { > .el-form-item__label {
display: none; display: none;
} }
@ -39,5 +43,9 @@
> .t-form__label { > .t-form__label {
display: none; display: none;
} }
> .t-form__controls {
margin-left: 0 !important;
}
} }
} }

View File

@ -31,6 +31,7 @@ import {
FormValue, FormValue,
HtmlField, HtmlField,
Rule, Rule,
SortProp,
TabPaneConfig, TabPaneConfig,
TypeFunction, TypeFunction,
} from '../schema'; } from '../schema';
@ -136,6 +137,18 @@ const initValueItem = function (
setValue(mForm, value, initValue, item); setValue(mForm, value, initValue, item);
if (type === 'table') {
if (item.defautSort) {
sortChange(value[name], item.defautSort);
} else if (item.defaultSort) {
sortChange(value[name], item.defaultSort);
}
if (item.sort && item.sortKey) {
value[name].sort((a: any, b: any) => b[item.sortKey] - a[item.sortKey]);
}
}
return value; return value;
}; };
@ -297,3 +310,36 @@ export const datetimeFormatter = (
} }
return defaultValue; return defaultValue;
}; };
export const getDataByPage = (data: any[] = [], pagecontext: number, pagesize: number) =>
data.filter(
(item: any, index: number) => index >= pagecontext * pagesize && index + 1 <= (pagecontext + 1) * pagesize,
);
export const sortArray = (data: any[], newIndex: number, oldIndex: number, sortKey?: string) => {
if (newIndex === oldIndex) {
return data;
}
if (newIndex < 0 || newIndex >= data.length || oldIndex < 0 || oldIndex >= data.length) {
return data;
}
const newData = data.toSpliced(newIndex, 0, ...data.splice(oldIndex, 1));
if (sortKey) {
for (let i = newData.length - 1, v = 0; i >= 0; i--, v++) {
newData[v][sortKey] = i;
}
}
return newData;
};
export const sortChange = (data: any[], { prop, order }: SortProp) => {
if (order === 'ascending') {
data = data.sort((a: any, b: any) => a[prop] - b[prop]);
} else if (order === 'descending') {
data = data.sort((a: any, b: any) => b[prop] - a[prop]);
}
};

View File

@ -18,7 +18,7 @@
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
import type { FormState } from '@form/index'; import type { FormState } from '@form/index';
import { datetimeFormatter, display, filterFunction, getRules, initValue } from '@form/utils/form'; import { datetimeFormatter, display, filterFunction, getRules, initValue, sortArray } from '@form/utils/form';
// form state mock 数据 // form state mock 数据
const mForm: FormState = { const mForm: FormState = {
@ -32,6 +32,19 @@ const mForm: FormState = {
setField: (prop: string, field: any) => field, setField: (prop: string, field: any) => field,
getField: (prop: string) => prop, getField: (prop: string) => prop,
deleteField: (prop: string) => prop, deleteField: (prop: string) => prop,
$messageBox: {
alert: () => Promise.resolve(),
confirm: () => Promise.resolve(),
prompt: () => Promise.resolve(),
close: () => undefined,
},
$message: {
success: () => undefined,
warning: () => undefined,
info: () => undefined,
error: () => undefined,
closeAll: () => undefined,
},
}; };
describe('filterFunction', () => { describe('filterFunction', () => {
@ -339,3 +352,71 @@ describe('datetimeFormatter', () => {
expect(datetimeFormatter(date.toISOString(), defaultValue, 'timestamp')).toBe(date.getTime()); expect(datetimeFormatter(date.toISOString(), defaultValue, 'timestamp')).toBe(date.getTime());
}); });
}); });
describe('sortArray', () => {
test('索引相同时不执行任何操作', () => {
const data = [1, 2, 3, 4, 5];
expect(sortArray(data, 2, 2)).toEqual(data);
});
test('正常交换两个元素的位置', () => {
const data = [1, 2, 3, 4, 5];
expect(sortArray(data, 0, 3)).toEqual([4, 1, 2, 3, 5]);
});
test('从后往前移动元素', () => {
const data = [1, 2, 3, 4, 5];
expect(sortArray(data, 3, 1)).toEqual([1, 3, 4, 2, 5]);
});
test('使用sortKey参数重新排序', () => {
const data = [
{ id: 1, order: 0 },
{ id: 2, order: 1 },
{ id: 3, order: 2 },
{ id: 4, order: 3 },
];
expect(sortArray(data, 0, 2, 'order')).toEqual([
{ id: 3, order: 3 },
{ id: 1, order: 2 },
{ id: 2, order: 1 },
{ id: 4, order: 0 },
]);
});
test('移动第一个元素到最后', () => {
const data = [1, 2, 3, 4, 5];
expect(sortArray(data, 4, 0)).toEqual([2, 3, 4, 5, 1]);
});
test('移动最后一个元素到第一个', () => {
const data = [1, 2, 3, 4, 5];
expect(sortArray(data, 0, 4)).toEqual([5, 1, 2, 3, 4]);
});
test('空数组不执行任何操作', () => {
const data: any[] = [];
expect(sortArray(data, 0, 1)).toEqual([]);
});
test('只有一个元素的数组不执行任何操作', () => {
const data = [1];
expect(sortArray(data, 0, 0)).toEqual([1]);
});
test('索引超出范围时正常处理', () => {
const data = [1, 2, 3];
// 索引超出范围应该由调用方处理,这里测试函数的行为
expect(sortArray(data, 5, 1)).toEqual(data);
expect(sortArray(data, 1, 5)).toEqual(data);
});
});

View File

@ -1,6 +1,4 @@
<template> <template>
<TMagicTableColumn :label="config.label" :width="config.width" :fixed="config.fixed">
<template v-slot="scope">
<TMagicTooltip <TMagicTooltip
v-for="(action, actionIndex) in config.actions" v-for="(action, actionIndex) in config.actions"
:placement="action.tooltipPlacement || 'top'" :placement="action.tooltipPlacement || 'top'"
@ -9,41 +7,40 @@
:content="action.tooltip" :content="action.tooltip"
> >
<TMagicButton <TMagicButton
v-show="display(action.display, scope.row) && !editState[scope.$index]" v-show="display(action.display, row) && !editState[index]"
class="action-btn" class="action-btn"
link link
size="small" size="small"
:type="action.buttonType || 'primary'" :type="action.buttonType || 'primary'"
:icon="action.icon" :icon="action.icon"
:disabled="disabled(action.disabled, scope.row)" :disabled="disabled(action.disabled, row)"
@click="actionHandler(action, scope.row, scope.$index)" @click="actionHandler(action, row, index)"
><span v-html="formatter(action.text, scope.row)"></span ><span v-html="formatter(action.text, row)"></span
></TMagicButton> ></TMagicButton>
</TMagicTooltip> </TMagicTooltip>
<TMagicButton <TMagicButton
class="action-btn" class="action-btn"
v-show="editState[scope.$index]" v-show="editState[index]"
link link
type="primary" type="primary"
size="small" size="small"
@click="save(scope.$index, config)" @click="save(index, config)"
>保存</TMagicButton >保存</TMagicButton
> >
<TMagicButton <TMagicButton
class="action-btn" class="action-btn"
v-show="editState[scope.$index]" v-show="editState[index]"
link link
type="primary" type="primary"
size="small" size="small"
@click="editState[scope.$index] = undefined" @click="editState[index] = undefined"
>取消</TMagicButton >取消</TMagicButton
> >
</template>
</TMagicTableColumn>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { TMagicButton, tMagicMessage, TMagicTableColumn, TMagicTooltip } from '@tmagic/design'; import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
import { ColumnActionConfig, ColumnConfig } from './schema'; import { ColumnActionConfig, ColumnConfig } from './schema';
@ -53,10 +50,12 @@ defineOptions({
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
columns: any[]; columns: ColumnConfig[];
config: ColumnConfig; config: ColumnConfig;
rowkeyName?: string; rowkeyName?: string;
editState?: any; editState?: any;
row: any;
index: number;
}>(), }>(),
{ {
columns: () => [], columns: () => [],
@ -114,7 +113,9 @@ const save = async (index: number, config: ColumnConfig) => {
props.columns props.columns
.filter((item) => item.type) .filter((item) => item.type)
.forEach((column) => { .forEach((column) => {
if (column.prop) {
data[column.prop] = row[column.prop]; data[column.prop] = row[column.prop];
}
}); });
const res: any = await action({ const res: any = await action({

View File

@ -1,25 +1,12 @@
<template> <template>
<TMagicTableColumn
show-overflow-tooltip
:label="config.label"
:width="config.width"
:fixed="config.fixed"
:sortable="config.sortable"
:prop="config.prop"
>
<template v-slot="scope">
<component <component
:is="config.component" :is="config.component"
v-bind="componentProps(scope.row, scope.$index)" v-bind="componentProps(row, index)"
v-on="componentListeners(scope.row, scope.$index)" v-on="componentListeners(row, index)"
></component> ></component>
</template>
</TMagicTableColumn>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { TMagicTableColumn } from '@tmagic/design';
import { ColumnConfig } from './schema'; import { ColumnConfig } from './schema';
defineOptions({ defineOptions({
@ -29,6 +16,8 @@ defineOptions({
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
config: ColumnConfig; config: ColumnConfig;
row: any;
index: number;
}>(), }>(),
{ {
config: () => ({}), config: () => ({}),

View File

@ -1,26 +1,20 @@
<template> <template>
<!-- @ts-nocheck -->
<TMagicTableColumn type="expand" :width="config.width" :fixed="config.fixed">
<template #default="scope">
<MTable <MTable
v-if="config.table" v-if="config.table"
:show-header="false" :show-header="false"
:columns="config.table" :columns="config.table"
:data="(config.prop && scope.row[config.prop]) || []" :data="(config.prop && row[config.prop]) || []"
></MTable> ></MTable>
<MForm <MForm
v-if="config.form" v-if="config.form"
:config="config.form" :config="config.form"
:init-values="config.values || (config.prop && scope.row[config.prop]) || {}" :init-values="config.values || (config.prop && row[config.prop]) || {}"
></MForm> ></MForm>
<div v-if="config.expandContent" v-html="config.expandContent(scope.row, config.prop)"></div> <div v-if="config.expandContent" v-html="config.expandContent(row, config.prop)"></div>
<component v-if="config.component" :is="config.component" v-bind="componentProps(scope.row)"></component> <component v-if="config.component" :is="config.component" v-bind="componentProps(row)"></component>
</template>
</TMagicTableColumn>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { TMagicTableColumn } from '@tmagic/design';
import { MForm } from '@tmagic/form'; import { MForm } from '@tmagic/form';
import { ColumnConfig } from './schema'; import { ColumnConfig } from './schema';
@ -33,6 +27,7 @@ defineOptions({
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
config: ColumnConfig; config: ColumnConfig;
row: any;
}>(), }>(),
{ {
config: () => ({}), config: () => ({}),

View File

@ -1,7 +1,4 @@
<template> <template>
<!-- @ts-nocheck -->
<TMagicTableColumn :label="config.label" :width="config.width" :fixed="config.fixed">
<template v-slot="scope">
<TMagicPopover <TMagicPopover
v-if="config.popover" v-if="config.popover"
:placement="config.popover.placement" :placement="config.popover.placement"
@ -13,20 +10,16 @@
v-if="config.popover.tableEmbed" v-if="config.popover.tableEmbed"
:show-header="config.showHeader" :show-header="config.showHeader"
:columns="config.table" :columns="config.table"
:data="(config.prop && scope.row[config.prop]) || []" :data="(config.prop && row[config.prop]) || []"
></MTable> ></MTable>
<template #reference> <template #reference>
<TMagicButton link type="primary"> <TMagicButton link type="primary">{{ config.text || formatter(config, row, { index: index }) }}</TMagicButton>
{{ config.text || formatter(config, scope.row, { index: scope.$index }) }}</TMagicButton
>
</template> </template>
</TMagicPopover> </TMagicPopover>
</template>
</TMagicTableColumn>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { TMagicButton, TMagicPopover, TMagicTableColumn } from '@tmagic/design'; import { TMagicButton, TMagicPopover } from '@tmagic/design';
import { ColumnConfig } from './schema'; import { ColumnConfig } from './schema';
import MTable from './Table.vue'; import MTable from './Table.vue';
@ -39,6 +32,8 @@ defineOptions({
withDefaults( withDefaults(
defineProps<{ defineProps<{
config: ColumnConfig; config: ColumnConfig;
row: any;
index: number;
}>(), }>(),
{ {
config: () => ({}), config: () => ({}),

View File

@ -1,10 +1,11 @@
<template> <template>
<TMagicTable <TMagicTable
tooltip-effect="dark" v-loading="loading"
:tooltip-options="{ popperOptions: { strategy: 'absolute' } }"
class="m-table" class="m-table"
ref="tMagicTable" ref="tMagicTable"
v-loading="loading" :show-overflow-tooltip="true"
tooltip-effect="dark"
:tooltip-options="{ popperOptions: { strategy: 'absolute' } }"
:data="tableData" :data="tableData"
:show-header="showHeader" :show-header="showHeader"
:max-height="bodyHeight" :max-height="bodyHeight"
@ -14,59 +15,21 @@
:tree-props="{ children: 'children' }" :tree-props="{ children: 'children' }"
:empty-text="emptyText || '暂无数据'" :empty-text="emptyText || '暂无数据'"
:span-method="objectSpanMethod" :span-method="objectSpanMethod"
:columns="tableColumns"
@sort-change="sortChange" @sort-change="sortChange"
@select="selectHandler" @select="selectHandler"
@select-all="selectAllHandler" @select-all="selectAllHandler"
@selection-change="selectionChangeHandler" @selection-change="selectionChangeHandler"
@cell-click="cellClickHandler" @cell-click="cellClickHandler"
@expand-change="expandChange" @expand-change="expandChange"
> ></TMagicTable>
<template v-for="(item, columnIndex) in columns">
<template v-if="item.type === 'expand'">
<ExpandColumn :config="item" :key="columnIndex"></ExpandColumn>
</template>
<template v-else-if="item.type === 'component'">
<ComponentColumn :config="item" :key="columnIndex"></ComponentColumn>
</template>
<template v-else-if="item.selection">
<component
width="40"
type="selection"
:is="tableColumnComponent?.component || 'el-table-column'"
:key="columnIndex"
:selectable="item.selectable"
></component>
</template>
<template v-else-if="item.actions">
<ActionsColumn
:columns="columns"
:config="item"
:rowkey-name="rowkeyName"
:edit-state="editState"
:key="columnIndex"
@after-action="$emit('after-action')"
></ActionsColumn>
</template>
<template v-else-if="item.type === 'popover'">
<PopoverColumn :key="columnIndex" :config="item"></PopoverColumn>
</template>
<template v-else>
<TextColumn :key="columnIndex" :config="item" :edit-state="editState"></TextColumn>
</template>
</template>
</TMagicTable>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed, h, ref, useTemplateRef } from 'vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { getDesignConfig, TMagicTable } from '@tmagic/design'; import { TMagicTable } from '@tmagic/design';
import ActionsColumn from './ActionsColumn.vue'; import ActionsColumn from './ActionsColumn.vue';
import ComponentColumn from './ComponentColumn.vue'; import ComponentColumn from './ComponentColumn.vue';
@ -117,11 +80,72 @@ const emit = defineEmits([
'cell-click', 'cell-click',
]); ]);
const tMagicTable = ref<InstanceType<typeof TMagicTable>>(); const cellRender = (config: ColumnConfig, { row = {}, $index }: any) => {
if (config.type === 'expand') {
return h(ExpandColumn, {
config,
row,
});
}
if (config.type === 'component') {
return h(ComponentColumn, {
config,
row,
index: $index,
});
}
if (config.actions) {
return h(ActionsColumn, {
config,
row,
index: $index,
rowkeyName: props.rowkeyName,
editState: editState.value,
columns: props.columns,
});
}
if (config.type === 'popover') {
return h(PopoverColumn, {
config,
row,
index: $index,
});
}
return h(TextColumn, {
config,
row,
index: $index,
editState: editState.value,
});
};
const tableColumns = computed(() =>
props.columns.map((item) => {
let type: 'default' | 'selection' | 'index' | 'expand' = 'default';
if (item.type === 'expand') {
type = 'expand';
} else if (item.selection) {
type = 'selection';
}
return {
props: {
label: item.label,
fixed: item.fixed,
width: item.width ?? (item.selection ? 40 : undefined),
prop: item.prop,
type,
selectable: item.selectable,
},
cell: type === 'selection' ? undefined : ({ row, $index }: any) => cellRender(item, { row, $index }),
};
}),
);
const tMagicTableRef = useTemplateRef('tMagicTable');
const editState = ref([]); const editState = ref([]);
const tableColumnComponent = getDesignConfig('components')?.tableColumn;
const selectionColumn = computed(() => { const selectionColumn = computed(() => {
const column = props.columns.filter((item) => item.selection); const column = props.columns.filter((item) => item.selection);
return column.length ? column[0] : null; return column.length ? column[0] : null;
@ -171,15 +195,15 @@ const expandChange = (...args: any[]) => {
}; };
const toggleRowSelection = (row: any, selected: boolean) => { const toggleRowSelection = (row: any, selected: boolean) => {
tMagicTable.value?.toggleRowSelection(row, selected); tMagicTableRef.value?.toggleRowSelection(row, selected);
}; };
const toggleRowExpansion = (row: any, expanded: boolean) => { const toggleRowExpansion = (row: any, expanded: boolean) => {
tMagicTable.value?.toggleRowExpansion(row, expanded); tMagicTableRef.value?.toggleRowExpansion(row, expanded);
}; };
const clearSelection = () => { const clearSelection = () => {
tMagicTable.value?.clearSelection(); tMagicTableRef.value?.clearSelection();
}; };
const objectSpanMethod = (data: any) => { const objectSpanMethod = (data: any) => {

View File

@ -1,25 +1,14 @@
<template> <template>
<TMagicTableColumn
show-overflow-tooltip
:label="config.label"
:width="config.width"
:fixed="config.fixed"
:sortable="config.sortable"
:prop="config.prop"
>
<template v-slot="scope">
<div v-if="config.type === 'index'"> <div v-if="config.type === 'index'">
{{ {{ config.pageIndex && config.pageSize ? config.pageIndex * config.pageSize + index + 1 : index + 1 }}
config.pageIndex && config.pageSize ? config.pageIndex * config.pageSize + scope.$index + 1 : scope.$index + 1
}}
</div> </div>
<TMagicForm v-else-if="config.type && editState[scope.$index]" label-width="0" :model="editState[scope.$index]"> <TMagicForm v-else-if="config.type && editState[index]" label-width="0" :model="editState[index]">
<m-form-container <m-form-container
:prop="config.prop" :prop="config.prop"
:rules="config.rules" :rules="config.rules"
:config="config" :config="config"
:name="config.prop" :name="config.prop"
:model="editState[scope.$index]" :model="editState[index]"
></m-form-container> ></m-form-container>
</TMagicForm> </TMagicForm>
@ -27,43 +16,37 @@
v-else-if="config.action === 'actionLink' && config.prop" v-else-if="config.action === 'actionLink' && config.prop"
link link
type="primary" type="primary"
@click="config.handler?.(scope.row)" @click="config.handler?.(row)"
> >
<span v-html="formatter(config, scope.row, { index: scope.$index })"></span> <span v-html="formatter(config, row, { index: index })"></span>
</TMagicButton> </TMagicButton>
<a v-else-if="config.action === 'img' && config.prop" target="_blank" :href="scope.row[config.prop]" <a v-else-if="config.action === 'img' && config.prop" target="_blank" :href="row[config.prop]"
><img :src="scope.row[config.prop]" height="50" ><img :src="row[config.prop]" height="50"
/></a> /></a>
<a <a v-else-if="config.action === 'link' && config.prop" target="_blank" :href="row[config.prop]" class="keep-all">{{
v-else-if="config.action === 'link' && config.prop" row[config.prop]
target="_blank" }}</a>
:href="scope.row[config.prop]"
class="keep-all"
>{{ scope.row[config.prop] }}</a
>
<el-tooltip v-else-if="config.action === 'tip'" placement="left"> <el-tooltip v-else-if="config.action === 'tip'" placement="left">
<template #content> <template #content>
<div>{{ formatter(config, scope.row, { index: scope.$index }) }}</div> <div>{{ formatter(config, row, { index: index }) }}</div>
</template> </template>
<TMagicButton link type="primary">{{ config.buttonText || '扩展配置' }}</TMagicButton> <TMagicButton link type="primary">{{ config.buttonText || '扩展配置' }}</TMagicButton>
</el-tooltip> </el-tooltip>
<TMagicTag <TMagicTag
v-else-if="config.action === 'tag' && config.prop" v-else-if="config.action === 'tag' && config.prop"
:type="typeof config.type === 'function' ? config.type(scope.row[config.prop], scope.row) : config.type" :type="typeof config.type === 'function' ? config.type(row[config.prop], row) : config.type"
close-transition close-transition
>{{ formatter(config, scope.row, { index: scope.$index }) }}</TMagicTag >{{ formatter(config, row, { index: index }) }}</TMagicTag
> >
<div v-else v-html="formatter(config, scope.row, { index: scope.$index })"></div> <div v-else v-html="formatter(config, row, { index: index })"></div>
</template>
</TMagicTableColumn>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { TMagicButton, TMagicForm, TMagicTableColumn, TMagicTag } from '@tmagic/design'; import { TMagicButton, TMagicForm, TMagicTag } from '@tmagic/design';
import { ColumnConfig } from './schema'; import { ColumnConfig } from './schema';
import { formatter } from './utils'; import { formatter } from './utils';
@ -76,6 +59,8 @@ withDefaults(
defineProps<{ defineProps<{
config: ColumnConfig; config: ColumnConfig;
editState?: any; editState?: any;
row: any;
index: number;
}>(), }>(),
{ {
config: () => ({}), config: () => ({}),

View File

@ -39,7 +39,7 @@
], ],
"peerDependencies": { "peerDependencies": {
"@tmagic/design": "workspace:*", "@tmagic/design": "workspace:*",
"tdesign-vue-next": "^1.9.8", "tdesign-vue-next": "^1.17.1",
"vue": "catalog:", "vue": "catalog:",
"typescript": "catalog:" "typescript": "catalog:"
}, },

View File

@ -0,0 +1,47 @@
<template>
<TCheckbox v-model="checked" :disabled="disabled" :value="value" @change="changeHandler">
<template #default v-if="$slots.default"> <slot></slot> </template>
</TCheckbox>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { Checkbox as TCheckbox } from 'tdesign-vue-next';
import type { CheckboxProps } from '@tmagic/design';
defineOptions({
name: 'TTDesignAdapterCheckbox',
});
const props = defineProps<CheckboxProps>();
const emit = defineEmits(['change', 'update:modelValue']);
const checked = ref(false);
watch(
() => props.modelValue,
(v) => {
if (typeof props.trueValue !== 'undefined') {
checked.value = v === props.trueValue;
} else if (typeof props.falseValue !== 'undefined') {
checked.value = v !== props.falseValue;
} else {
checked.value = Boolean(v);
}
},
{
immediate: true,
},
);
const changeHandler = (v: boolean) => {
updateModelValue(v);
emit('change', v ? (props.trueValue ?? true) : (props.falseValue ?? false));
};
const updateModelValue = (v: boolean) => {
emit('update:modelValue', v ? (props.trueValue ?? true) : (props.falseValue ?? false));
};
</script>

View File

@ -8,7 +8,7 @@
:size="size === 'default' ? 'medium' : size" :size="size === 'default' ? 'medium' : size"
:separator="rangeSeparator" :separator="rangeSeparator"
:format="format" :format="format"
:valueType="valueFormat === 's' ? 'time-stamp' : valueFormat" :valueType="valueType"
@change="changeHandler" @change="changeHandler"
@update:modelValue="updateModelValue" @update:modelValue="updateModelValue"
/> />
@ -21,7 +21,7 @@
:size="size === 'default' ? 'medium' : size" :size="size === 'default' ? 'medium' : size"
:format="format" :format="format"
:enableTimePicker="type.includes('time')" :enableTimePicker="type.includes('time')"
:valueType="valueFormat === 's' ? 'time-stamp' : valueFormat" :valueType="valueType"
@change="changeHandler" @change="changeHandler"
@update:modelValue="updateModelValue" @update:modelValue="updateModelValue"
/> />
@ -54,6 +54,8 @@ const mode = computed(() => {
return map[props.type] || props.type; return map[props.type] || props.type;
}); });
const valueType = computed(() => (props.valueFormat === 's' ? 'time-stamp' : props.valueFormat.replace(/\//g, '-')));
const emit = defineEmits(['change', 'update:modelValue']); const emit = defineEmits(['change', 'update:modelValue']);
const changeHandler = (v: any) => { const changeHandler = (v: any) => {

View File

@ -0,0 +1,43 @@
<template>
<TDialog
:visible="modelValue"
:attach="appendToBody ? 'body' : undefined"
:header="title"
:width="width"
:mode="fullscreen ? 'full-screen' : 'modal'"
:close-on-overlay-click="closeOnClickModal"
:close-on-esc-keydown="closeOnPressEscape"
:destroy-on-close="destroyOnClose"
@before-open="beforeClose"
@close="closeHandler"
@update:visible="updateModelValue"
>
<slot></slot>
<template #footer>
<slot name="footer"></slot>
</template>
</TDialog>
</template>
<script setup lang="ts">
import { Dialog as TDialog } from 'tdesign-vue-next';
import type { DialogProps } from '@tmagic/design';
defineOptions({
name: 'TTDesignAdapterDialog',
});
defineProps<DialogProps>();
const emit = defineEmits(['close', 'update:modelValue']);
const closeHandler = (...args: any[]) => {
emit('close', ...args);
};
const updateModelValue = (v: any) => {
emit('update:modelValue', v);
};
</script>

View File

@ -1,7 +1,11 @@
<template> <template>
<i> <i class="t-t-design-adapter-icon">
<slot></slot> <slot></slot>
</i> </i>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
defineOptions({
name: 'TTDesignAdapterIcon',
});
</script>

View File

@ -0,0 +1,23 @@
<template>
<TRadio :value="value" @click="clickHandler">
<template #default v-if="$slots.default"> <slot></slot> </template>
</TRadio>
</template>
<script lang="ts" setup>
import { Radio as TRadio } from 'tdesign-vue-next';
import type { RadioProps } from '@tmagic/design';
defineOptions({
name: 'TTDesignAdapterRadio',
});
defineProps<RadioProps>();
const emit = defineEmits(['click']);
const clickHandler = () => {
emit('click');
};
</script>

View File

@ -0,0 +1,23 @@
<template>
<TRadioButton :value="value" :disabled="disabled" @click="clickHandler">
<template #default v-if="$slots.default"> <slot></slot> </template>
</TRadioButton>
</template>
<script lang="ts" setup>
import { RadioButton as TRadioButton } from 'tdesign-vue-next';
import type { RadioButtonProps } from '@tmagic/design';
defineOptions({
name: 'TTDesignAdapterRadioButton',
});
defineProps<RadioButtonProps>();
const emit = defineEmits(['click']);
const clickHandler = () => {
emit('click');
};
</script>

View File

@ -0,0 +1,124 @@
<template>
<TTable
ref="table"
:data="data"
:bordered="border"
:max-height="maxHeight"
:default-expand-all="defaultExpandAll"
:show-header="showHeader"
:row-key="rowKey"
:tree="treeProps"
:empty="emptyText"
:columns="tableColumns"
@sort-change="sortChange"
@select-change="selectHandler"
@cell-click="cellClickHandler"
@expand-change="expandChange"
/>
</template>
<script setup lang="ts">
import { computed, useTemplateRef } from 'vue';
import { Table as TTable } from 'tdesign-vue-next';
import type { TableProps } from '@tmagic/design';
defineOptions({
name: 'TTDesignAdapterTable',
});
const emit = defineEmits(['sort-change', 'select', 'select-all', 'selection-change', 'expand-change', 'cell-click']);
const props = defineProps<TableProps>();
const tableRef = useTemplateRef('table');
// TDesign
const tableColumns = computed(() => {
if (!props.columns) return [];
const columns = [];
for (const item of props.columns) {
if (item.props.type === 'expand') {
continue;
}
let colKey = item.props?.prop || item.props?.type;
if (!colKey) {
colKey = 'tmagic_table_operation';
}
const column: any = {
thClassName: item.props?.class,
colKey,
title: item.props?.label,
width: item.props?.width,
fixed: item.props?.fixed === true ? 'left' : item.props?.fixed || undefined,
ellipsis: props.showOverflowTooltip,
sorter: item.props?.sortable,
align: item.props?.align,
};
//
if (item.cell) {
column.cell = (h: any, { row, rowIndex }: any) => {
return item.cell?.({ row, $index: rowIndex });
};
}
columns.push(column);
}
return columns;
});
const sortChange = (data: any) => {
emit('sort-change', data);
};
const selectHandler = (selectedRowKeys: any[], options: any) => {
const { selectedRowData, type } = options;
if (type === 'check') {
emit('select', selectedRowData);
} else if (type === 'uncheck') {
emit('select', selectedRowData);
}
emit('selection-change', selectedRowData);
};
const cellClickHandler = (context: any) => {
const { row, col, e } = context;
emit('cell-click', row, col, undefined, e);
};
const expandChange = (expandedRowKeys: any[], options: any) => {
emit('expand-change', options.expandedRowData, options.currentRowData);
};
const toggleRowSelection = (_row: any, _selected: boolean) => {
// TDesign selectedRowKeys
//
console.warn('toggleRowSelection needs to be implemented based on TDesign API');
};
const toggleRowExpansion = (_row: any, _expanded: boolean) => {
// TDesign expandedRowKeys
console.warn('toggleRowExpansion needs to be implemented based on TDesign API');
};
const clearSelection = () => {
// TDesign selectedRowKeys
console.warn('clearSelection needs to be implemented based on TDesign API');
};
defineExpose({
getEl: () => tableRef.value?.$el,
getTableRef: () => tableRef.value,
clearSelection,
toggleRowSelection,
toggleRowExpansion,
});
</script>

View File

@ -1,5 +0,0 @@
<template>
<div></div>
</template>
<script setup lang="ts"></script>

View File

@ -4,13 +4,11 @@ import {
Button as TButton, Button as TButton,
Card as TCard, Card as TCard,
Cascader as TCascader, Cascader as TCascader,
Checkbox as TCheckbox,
CheckboxGroup as TCheckboxGroup, CheckboxGroup as TCheckboxGroup,
Col as TCol, Col as TCol,
Collapse as TCollapse, Collapse as TCollapse,
CollapsePanel as TCollapsePanel, CollapsePanel as TCollapsePanel,
ColorPicker as TColorPicker, ColorPicker as TColorPicker,
Dialog as TDialog,
DialogPlugin, DialogPlugin,
Divider as TDivider, Divider as TDivider,
Drawer as TDrawer, Drawer as TDrawer,
@ -23,15 +21,12 @@ import {
Option as TOption, Option as TOption,
OptionGroup as TOptionGroup, OptionGroup as TOptionGroup,
Pagination as TPagination, Pagination as TPagination,
Radio as TRadio,
RadioButton as TRadioButton,
RadioGroup as TRadioGroup, RadioGroup as TRadioGroup,
Row as TRow, Row as TRow,
Select as TSelect, Select as TSelect,
StepItem as TStepItem, StepItem as TStepItem,
Steps as TSteps, Steps as TSteps,
Switch as TSwitch, Switch as TSwitch,
Table as TTable,
TabPanel as TTabPanel, TabPanel as TTabPanel,
Tabs as TTabs, Tabs as TTabs,
Tag as TTag, Tag as TTag,
@ -71,7 +66,6 @@ import type {
StepProps, StepProps,
StepsProps, StepsProps,
SwitchProps, SwitchProps,
TableColumnProps,
TableProps, TableProps,
TabPaneProps, TabPaneProps,
TabsProps, TabsProps,
@ -81,11 +75,15 @@ import type {
UploadProps, UploadProps,
} from '@tmagic/design'; } from '@tmagic/design';
import Checkbox from './Checkbox.vue';
import DatePicker from './DatePicker.vue'; import DatePicker from './DatePicker.vue';
import Dialog from './Dialog.vue';
import Icon from './Icon.vue'; import Icon from './Icon.vue';
import Input from './Input.vue'; import Input from './Input.vue';
import Radio from './Radio.vue';
import RadioButton from './RadioButton.vue';
import Scrollbar from './Scrollbar.vue'; import Scrollbar from './Scrollbar.vue';
import TableColumn from './TableColumn.vue'; import Table from './Table.vue';
const adapter: any = { const adapter: any = {
message: MessagePlugin, message: MessagePlugin,
@ -119,7 +117,7 @@ const adapter: any = {
props: (props: ButtonProps) => ({ props: (props: ButtonProps) => ({
theme: props.type, theme: props.type,
size: props.size === 'default' ? 'medium' : props.size, size: props.size === 'default' ? 'medium' : props.size,
icon: () => (props.icon ? h(props.icon) : null), icon: () => (props.icon ? h(Icon, null, { default: () => h(props.icon) }) : null),
variant: props.link || props.text ? 'text' : 'base', variant: props.link || props.text ? 'text' : 'base',
shape: props.circle ? 'circle' : 'rectangle', shape: props.circle ? 'circle' : 'rectangle',
}), }),
@ -153,13 +151,8 @@ const adapter: any = {
}, },
checkbox: { checkbox: {
component: TCheckbox, component: Checkbox,
props: (props: CheckboxProps) => ({ props: (props: CheckboxProps) => props,
modelValue: props.modelValue,
label: props.label,
value: props.value,
disabled: props.disabled,
}),
}, },
checkboxGroup: { checkboxGroup: {
@ -174,14 +167,14 @@ const adapter: any = {
col: { col: {
component: TCol, component: TCol,
props: (props: ColProps) => ({ props: (props: ColProps) => ({
span: props.span, span: props.span ? props.span / 2 : 12,
}), }),
}, },
collapse: { collapse: {
component: TCollapse, component: TCollapse,
props: (props: CollapseProps) => ({ props: (props: CollapseProps) => ({
value: props.modelValue, modelValue: props.modelValue,
expandIconPlacement: 'right', expandIconPlacement: 'right',
}), }),
}, },
@ -212,15 +205,8 @@ const adapter: any = {
}, },
dialog: { dialog: {
component: TDialog, component: Dialog,
props: (props: DialogProps) => ({ props: (props: DialogProps) => props,
visible: props.modelValue,
attach: props.appendToBody ? 'body' : '',
header: props.title,
width: props.width,
mode: props.fullscreen ? 'full-screen' : 'modal',
closeOnOverlayClick: props.closeOnClickModal,
}),
}, },
divider: { divider: {
@ -295,6 +281,7 @@ const adapter: any = {
labelWidth: props.labelWidth, labelWidth: props.labelWidth,
name: props.prop, name: props.prop,
rules: props.rules, rules: props.rules,
help: props.extra,
}), }),
}, },
@ -347,18 +334,13 @@ const adapter: any = {
}, },
radio: { radio: {
component: TRadio, component: Radio,
props: (props: RadioProps) => ({ props: (props: RadioProps) => props,
label: props.label,
value: props.value,
}),
}, },
radioButton: { radioButton: {
component: TRadioButton, component: RadioButton,
props: (props: RadioButtonProps) => ({ props: (props: RadioButtonProps) => props,
label: props.label,
}),
}, },
radioGroup: { radioGroup: {
@ -424,15 +406,10 @@ const adapter: any = {
}, },
table: { table: {
component: TTable, component: Table,
props: (props: TableProps) => props, props: (props: TableProps) => props,
}, },
tableColumn: {
component: TableColumn,
props: (props: TableColumnProps) => props,
},
tabPane: { tabPane: {
component: TTabPanel, component: TTabPanel,
props: (props: TabPaneProps) => ({ props: (props: TabPaneProps) => ({

View File

@ -1,16 +1,11 @@
<template> <template>
<div class="m-editor-nav-menu"> <div class="m-editor-nav-menu">
<TMagicButton <div v-for="(item, index) in data" :key="index" class="menu-item button">
v-for="(item, index) in data" <TMagicButton size="small" link @click="item.handler">
class="menu-item button"
:key="index"
size="small"
link
@click="item.handler"
>
<TMagicIcon><component :is="item.icon"></component></TMagicIcon><span>{{ item.text }}</span> <TMagicIcon><component :is="item.icon"></component></TMagicIcon><span>{{ item.text }}</span>
</TMagicButton> </TMagicButton>
</div> </div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -85,6 +85,10 @@ export default defineConfig({
find: /^@tmagic\/element-plus-adapter/, find: /^@tmagic\/element-plus-adapter/,
replacement: path.join(__dirname, '../packages/element-plus-adapter/src/index.ts'), replacement: path.join(__dirname, '../packages/element-plus-adapter/src/index.ts'),
}, },
{
find: /^@tmagic\/tdesign-vue-next-adapter/,
replacement: path.join(__dirname, '../packages/tdesign-vue-next-adapter/src/index.ts'),
},
] : [], ] : [],
}, },