fix(design): 修复 Popconfirm 浮层被裁剪及 Element Plus 复合方位失效

This commit is contained in:
roymondchen 2026-11-05 13:52:02 +08:00
parent 657758dec2
commit 8e4391972b
3 changed files with 63 additions and 4 deletions

View File

@ -24,7 +24,11 @@ defineOptions({
const emit = defineEmits(['confirm', 'cancel']);
const props = defineProps<PopconfirmProps>();
// Boolean 类型的属性缺省时会被 Vue 转成 false,会把浮层强制内联渲染到侧栏等 overflow 容器中,
// 导致 placement 计算出的位置被裁剪。与 PopconfirmProps 注释及底层组件默认值保持一致。
const props = withDefaults(defineProps<PopconfirmProps>(), {
teleported: true,
});
const ui = getDesignConfig('components')?.popconfirm;

View File

@ -95,6 +95,26 @@ import type {
import Table from './Table.vue';
/**
* PopconfirmProps 的复合方位沿用 TDesign 命名(如 right-top),
* Element Plus 使用 popper.js 的 *-start / *-end 命名,未转换时这些取值会被 Popper 忽略。
*/
const elementPlusPlacements: Record<string, string> = {
'top-left': 'top-start',
'top-right': 'top-end',
'bottom-left': 'bottom-start',
'bottom-right': 'bottom-end',
'left-top': 'left-start',
'left-bottom': 'left-end',
'right-top': 'right-start',
'right-bottom': 'right-end',
};
const popconfirmProps = (props: PopconfirmProps): PopconfirmProps => {
const placement = props.placement ? elementPlusPlacements[props.placement] : undefined;
return placement ? { ...props, placement: placement as PopconfirmProps['placement'] } : props;
};
const adapter: DesignPluginOptions = {
adapterType: 'element-plus',
useZIndex,
@ -329,7 +349,7 @@ const adapter: DesignPluginOptions = {
popconfirm: {
component: ElPopconfirm as any,
props: (props: PopconfirmProps) => props,
props: popconfirmProps,
},
},
loading: ElLoading.directive,

View File

@ -18,7 +18,9 @@
import { describe, expect, test, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import { setDesignConfig } from '@tmagic/design';
// 必须与 setDesignConfig 来自同一模块实例:从 @tmagic/design/src 导入 src/Popconfirm.vue 时,
// 它读取的是源码里独立的 config 模块,未注入 adapter 就会退化成原生的 <el-popconfirm> 标签。
import { setDesignConfig, TMagicPopconfirm } from '@tmagic/design';
import elementPlusAdapter from '../../element-plus-adapter/src/index';
import ActionsColumn from '../src/ActionsColumn.vue';
@ -101,8 +103,41 @@ describe('ActionsColumn popconfirm (real element-plus)', () => {
(e.textContent || '').includes('确定删除?'),
);
expect(popper).toBeTruthy();
expect(popper.style.width).toBe('240px');
expect(popper?.style.width).toBe('240px');
wrapper.unmount();
});
});
describe('TMagicPopconfirm placement(真实 element-plus)', () => {
const mountPopconfirm = (placement: string) =>
mount(TMagicPopconfirm, {
props: { title: '确认内容', placement } as any,
slots: { reference: '<button class="ref">ref</button>' },
attachTo: document.body,
});
test.each([
['right', 'right'],
['right-top', 'right-start'],
['top-left', 'top-start'],
])('placement=%s 作用到 popper 的 %s', async (placement, expected) => {
const wrapper = mountPopconfirm(placement);
await wrapper.find('.ref').trigger('click');
await vi.waitFor(() => {
expect(document.querySelector('.el-popper')?.getAttribute('data-popper-placement')).toBe(expected);
});
wrapper.unmount();
});
test('默认 teleported=true,浮层挂到 body 而非内联渲染', async () => {
const wrapper = mountPopconfirm('right');
expect(wrapper.props('teleported')).toBe(true);
await wrapper.find('.ref').trigger('click');
await vi.waitFor(() => {
expect(document.querySelector('.el-popper')).toBeTruthy();
});
expect(wrapper.element.contains(document.querySelector('.el-popper')!)).toBe(false);
wrapper.unmount();
});
});