feat(table): 抽取 ActionPopconfirm 并优化 sub-actions 展开交互

将 Popconfirm 逻辑抽离为独立组件,ActionButton 内置 tooltip 支持,sub-actions 触发按钮改用箭头图标切换展开状态。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
roymondchen 2026-07-06 16:46:22 +08:00
parent 78a1259665
commit b183e71f64
5 changed files with 129 additions and 83 deletions

View File

@ -37,6 +37,7 @@
"url": "https://github.com/Tencent/tmagic-editor.git"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"lodash-es": "^4.17.21"
},
"devDependencies": {

View File

@ -1,20 +1,26 @@
<template>
<TMagicButton
v-show="visible"
:class="btnClass"
link
size="small"
:type="action.buttonType || 'primary'"
:icon="action.icon"
:disabled="disabled(action.disabled, row)"
@click="onClick"
<TMagicTooltip
:placement="action.tooltipPlacement || 'top'"
:disabled="!Boolean(action.tooltip)"
:content="action.tooltip"
>
<span v-html="formatter(action.text, row)"></span>
</TMagicButton>
<TMagicButton
v-show="visible"
:class="btnClass"
link
size="small"
:type="action.buttonType || 'primary'"
:icon="action.icon"
:disabled="disabled(action.disabled, row)"
@click="onClick"
>
<span v-html="formatter(action.text, row)"></span>
</TMagicButton>
</TMagicTooltip>
</template>
<script lang="ts" setup>
import { TMagicButton } from '@tmagic/design';
import { TMagicButton, TMagicTooltip } from '@tmagic/design';
import { disabled, formatActionText as formatter } from './actionHelpers';
import { ColumnActionConfig } from './schema';

View File

@ -0,0 +1,44 @@
<template>
<TMagicPopconfirm
placement="top"
:width="action.popconfirmWidth"
:title="formatter(action.confirmText, row) || '确定执行此操作?'"
@confirm="onConfirm"
>
<template #reference>
<ActionButton :action="action" :row="row" :index="index" :btn-class="btnClass" :visible="visible" />
</template>
</TMagicPopconfirm>
</template>
<script lang="ts" setup>
import { TMagicPopconfirm } from '@tmagic/design';
import ActionButton from './ActionButton.vue';
import { formatActionText as formatter } from './actionHelpers';
import { ColumnActionConfig } from './schema';
defineOptions({
name: 'MTableActionPopconfirm',
});
const props = withDefaults(
defineProps<{
action: ColumnActionConfig;
row: any;
index: number;
btnClass?: string;
visible?: boolean;
}>(),
{
btnClass: 'action-btn',
visible: true,
},
);
const emit = defineEmits<{
confirm: [action: ColumnActionConfig, row: any, index: number];
}>();
const onConfirm = () => emit('confirm', props.action, props.row, props.index);
</script>

View File

@ -1,24 +1,17 @@
<template>
<template v-for="(action, actionIndex) in config.actions" :key="actionIndex">
<TMagicPopconfirm
<ActionPopconfirm
v-if="action.popconfirm"
placement="top"
:width="action.popconfirmWidth"
:title="formatter(action.confirmText, row) || '确定执行此操作?'"
@confirm="actionHandler(action, row, index)"
>
<template #reference>
<ActionButton
:action="action"
:row="row"
:index="index"
:visible="display(action.display, row) && !editState[index]"
/>
</template>
</TMagicPopconfirm>
:action="action"
:row="row"
:index="index"
:visible="display(action.display, row) && !editState[index]"
@confirm="actionHandler"
/>
<TMagicPopover
v-else-if="action.type === 'sub-actions'"
v-model:visible="popoverVisible"
trigger="click"
:placement="action.subActionConfig?.placement || 'bottom'"
:width="action.subActionConfig?.popoverWidth"
@ -26,66 +19,49 @@
:destroy-on-close="action.subActionConfig?.popoverDestroyOnClose"
>
<template #reference>
<ActionButton
:action="action"
:row="row"
:index="index"
:visible="display(action.display, row) && !editState[index]"
/>
<TMagicButton
v-show="action.subActionConfig?.items?.length && display(action.display, row) && !editState[index]"
class="action-btn"
link
size="small"
:type="action.buttonType || 'primary'"
:icon="popoverVisible ? ArrowDown : ArrowRight"
@click.stop="togglePopover"
></TMagicButton>
</template>
<div class="sub-actions">
<template v-for="(subAction, subIndex) in action.subActionConfig?.items" :key="subIndex">
<TMagicPopconfirm
<ActionPopconfirm
v-if="subAction.popconfirm"
placement="top"
:width="subAction.popconfirmWidth"
:title="formatter(subAction.confirmText, row) || '确定执行此操作?'"
@confirm="actionHandler(subAction, row, index)"
>
<template #reference>
<ActionButton
:action="subAction"
:row="row"
:index="index"
btn-class="sub-action-btn"
:visible="display(subAction.display, row)"
/>
</template>
</TMagicPopconfirm>
:action="subAction"
:row="row"
:index="index"
btn-class="sub-action-btn"
:visible="display(subAction.display, row)"
@confirm="actionHandler"
/>
<TMagicTooltip
<ActionButton
v-else
:placement="subAction.tooltipPlacement || 'top'"
:disabled="!Boolean(subAction.tooltip)"
:content="subAction.tooltip"
>
<ActionButton
:action="subAction"
:row="row"
:index="index"
btn-class="sub-action-btn"
:visible="display(subAction.display, row)"
@click="actionHandler"
/>
</TMagicTooltip>
:action="subAction"
:row="row"
:index="index"
btn-class="sub-action-btn"
:visible="display(subAction.display, row)"
@click="actionHandler"
/>
</template>
</div>
</TMagicPopover>
<TMagicTooltip
<ActionButton
v-else
:placement="action.tooltipPlacement || 'top'"
:disabled="!Boolean(action.tooltip)"
:content="action.tooltip"
>
<ActionButton
:action="action"
:row="row"
:index="index"
:visible="display(action.display, row) && !editState[index]"
@click="actionHandler"
/>
</TMagicTooltip>
:action="action"
:row="row"
:index="index"
:visible="display(action.display, row) && !editState[index]"
@click="actionHandler"
/>
</template>
<TMagicButton
@ -109,12 +85,15 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue';
import { cloneDeep } from 'lodash-es';
import { TMagicButton, tMagicMessage, TMagicPopconfirm, TMagicPopover, TMagicTooltip } from '@tmagic/design';
import { TMagicButton, tMagicMessage, TMagicPopover } from '@tmagic/design';
import ActionButton from './ActionButton.vue';
import { display, formatActionText as formatter } from './actionHelpers';
import { display } from './actionHelpers';
import ActionPopconfirm from './ActionPopconfirm.vue';
import { ColumnActionConfig, ColumnConfig } from './schema';
defineOptions({
@ -138,6 +117,11 @@ const props = withDefaults(
},
);
const popoverVisible = ref(false);
const togglePopover = () => {
popoverVisible.value = !popoverVisible.value;
};
const emit = defineEmits<{
'after-action': [{ index: number }];
'after-action-cancel': [{ index: number }];
@ -192,15 +176,23 @@ defineExpose({
});
</script>
<style scoped>
<style lang="scss">
.sub-actions {
display: flex;
flex-direction: column;
gap: 4px;
}
align-items: flex-start;
.sub-actions .sub-action-btn {
justify-content: flex-start;
width: 100%;
.sub-action-btn {
width: 100%;
}
.tmagic-design-button + .tmagic-design-button {
margin-left: 0;
}
.tmagic-design-button {
justify-content: flex-start;
}
}
</style>

3
pnpm-lock.yaml generated
View File

@ -497,6 +497,9 @@ importers:
packages/table:
dependencies:
'@element-plus/icons-vue':
specifier: ^2.3.2
version: 2.3.2(vue@3.5.38(typescript@6.0.3))
'@tmagic/design':
specifier: workspace:*
version: link:../design