mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-03 14:58:47 +08:00
feat(table): 为操作列新增 Popconfirm 二次确认能力
支持 popconfirm、confirmText、popconfirmWidth 配置,并扩展 Popconfirm 组件的 width 与 teleported 属性。
This commit is contained in:
parent
9aa251ce57
commit
9e8272b521
@ -234,6 +234,9 @@ export interface PaginationProps {
|
||||
|
||||
export interface PopconfirmProps {
|
||||
title?: string;
|
||||
width?: string | number;
|
||||
/** 浮层是否插入到 body,默认 true。设为 false 时浮层内联渲染,便于嵌套在 hover 浮层中避免父级因 mouseleave 收起。 */
|
||||
teleported?: boolean;
|
||||
placement?:
|
||||
| 'top'
|
||||
| 'left'
|
||||
|
||||
@ -1,23 +1,46 @@
|
||||
<template>
|
||||
<TMagicTooltip
|
||||
v-for="(action, actionIndex) in config.actions"
|
||||
:placement="action.tooltipPlacement || 'top'"
|
||||
:key="actionIndex"
|
||||
:disabled="!Boolean(action.tooltip)"
|
||||
:content="action.tooltip"
|
||||
>
|
||||
<TMagicButton
|
||||
v-show="display(action.display, row) && !editState[index]"
|
||||
class="action-btn"
|
||||
link
|
||||
size="small"
|
||||
:type="action.buttonType || 'primary'"
|
||||
:icon="action.icon"
|
||||
:disabled="disabled(action.disabled, row)"
|
||||
@click="actionHandler(action, row, index)"
|
||||
><span v-html="formatter(action.text, row)"></span
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
<template v-for="(action, actionIndex) in config.actions" :key="actionIndex">
|
||||
<TMagicPopconfirm
|
||||
v-if="action.popconfirm"
|
||||
placement="top"
|
||||
:width="action.popconfirmWidth"
|
||||
:title="formatter(action.confirmText, row) || '确定执行此操作?'"
|
||||
@confirm="actionHandler(action, row, index)"
|
||||
>
|
||||
<template #reference>
|
||||
<TMagicButton
|
||||
v-show="display(action.display, row) && !editState[index]"
|
||||
class="action-btn"
|
||||
link
|
||||
size="small"
|
||||
:type="action.buttonType || 'primary'"
|
||||
:icon="action.icon"
|
||||
:disabled="disabled(action.disabled, row)"
|
||||
>
|
||||
<span v-html="formatter(action.text, row)"></span>
|
||||
</TMagicButton>
|
||||
</template>
|
||||
</TMagicPopconfirm>
|
||||
|
||||
<TMagicTooltip
|
||||
v-else
|
||||
:placement="action.tooltipPlacement || 'top'"
|
||||
:disabled="!Boolean(action.tooltip)"
|
||||
:content="action.tooltip"
|
||||
>
|
||||
<TMagicButton
|
||||
v-show="display(action.display, row) && !editState[index]"
|
||||
class="action-btn"
|
||||
link
|
||||
size="small"
|
||||
:type="action.buttonType || 'primary'"
|
||||
:icon="action.icon"
|
||||
:disabled="disabled(action.disabled, row)"
|
||||
@click="actionHandler(action, row, index)"
|
||||
><span v-html="formatter(action.text, row)"></span
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
</template>
|
||||
|
||||
<TMagicButton
|
||||
class="action-btn"
|
||||
@ -42,7 +65,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
|
||||
import { TMagicButton, tMagicMessage, TMagicPopconfirm, TMagicTooltip } from '@tmagic/design';
|
||||
|
||||
import { ColumnActionConfig, ColumnConfig } from './schema';
|
||||
|
||||
|
||||
@ -28,6 +28,12 @@ export interface ColumnActionConfig {
|
||||
tooltip?: string;
|
||||
tooltipPlacement?: string;
|
||||
icon?: any;
|
||||
/** 为 true 时用 Popconfirm 包裹按钮,点击后需二次确认才会触发 handler */
|
||||
popconfirm?: boolean;
|
||||
/** Popconfirm 的确认提示文案,支持函数动态生成 */
|
||||
confirmText?: string | ((row: any) => string);
|
||||
/** Popconfirm 浮层宽度,数字按 px 处理 */
|
||||
popconfirmWidth?: string | number;
|
||||
handler?: (row: any, index: number) => Promise<any> | any;
|
||||
before?: (row: any, index: number) => Promise<void> | void;
|
||||
after?: (row: any, index: number) => Promise<void> | void;
|
||||
|
||||
@ -86,6 +86,14 @@ export const createDesignMock = () => ({
|
||||
return () => h('div', { class: 'tmagic-popover-stub' }, [slots.reference?.(), slots.default?.()]);
|
||||
},
|
||||
}),
|
||||
TMagicPopconfirm: defineComponent({
|
||||
name: 'TMagicPopconfirm',
|
||||
props: ['title', 'placement', 'width'],
|
||||
emits: ['confirm', 'cancel'],
|
||||
setup(props, { slots }) {
|
||||
return () => h('div', { class: 'tmagic-popconfirm-stub' }, [props.title, slots.reference?.()]);
|
||||
},
|
||||
}),
|
||||
tMagicMessage,
|
||||
});
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import ActionsColumn from '../src/ActionsColumn.vue';
|
||||
import ComponentColumn from '../src/ComponentColumn.vue';
|
||||
import ExpandColumn from '../src/ExpandColumn.vue';
|
||||
import PopoverColumn from '../src/PopoverColumn.vue';
|
||||
import { ColumnActionConfig } from '../src/schema';
|
||||
import TextColumn from '../src/TextColumn.vue';
|
||||
import { tMagicMessage } from '../test-support/design.mock';
|
||||
|
||||
@ -126,7 +127,7 @@ describe('ActionsColumn.vue', () => {
|
||||
after: vi.fn(),
|
||||
before: vi.fn(),
|
||||
},
|
||||
],
|
||||
] as ColumnActionConfig[],
|
||||
},
|
||||
row: { id: 1, visible: true, locked: false },
|
||||
index: 0,
|
||||
@ -180,6 +181,34 @@ describe('ActionsColumn.vue', () => {
|
||||
expect(deleteAction.handler).toHaveBeenCalledWith(props.row, 0);
|
||||
expect(deleteAction.after).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('popconfirm action 用 Popconfirm 包裹并在确认时触发 handler', async () => {
|
||||
const props = baseProps();
|
||||
props.config.actions[1].popconfirm = true;
|
||||
props.config.actions[1].confirmText = (row: any) => `确定删除${row.id}?`;
|
||||
const wrapper = mount(ActionsColumn, { props });
|
||||
const deleteAction = props.config.actions[1];
|
||||
|
||||
expect(wrapper.find('.tmagic-popconfirm-stub').exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('确定删除1?');
|
||||
|
||||
// 普通点击按钮不应立即触发 handler,需等待 Popconfirm 确认
|
||||
const btn = wrapper.findAll('.action-btn').find((b) => b.text().includes('删除'));
|
||||
await btn?.trigger('click');
|
||||
expect(deleteAction.handler).not.toHaveBeenCalled();
|
||||
|
||||
await wrapper.findComponent({ name: 'TMagicPopconfirm' }).vm.$emit('confirm');
|
||||
expect(deleteAction.handler).toHaveBeenCalledWith(props.row, 0);
|
||||
});
|
||||
|
||||
test('popconfirm 透传 popconfirmWidth 到 TMagicPopconfirm', () => {
|
||||
const props = baseProps();
|
||||
props.config.actions[1].popconfirm = true;
|
||||
props.config.actions[1].popconfirmWidth = 240;
|
||||
const wrapper = mount(ActionsColumn, { props });
|
||||
const popconfirm = wrapper.findComponent({ name: 'TMagicPopconfirm' });
|
||||
expect(popconfirm.props('width')).toBe(240);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ComponentColumn.vue', () => {
|
||||
|
||||
108
packages/table/tests/popconfirm.spec.ts
Normal file
108
packages/table/tests/popconfirm.spec.ts
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||
*
|
||||
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import { setDesignConfig } from '@tmagic/design';
|
||||
|
||||
import elementPlusAdapter from '../../element-plus-adapter/src/index';
|
||||
import ActionsColumn from '../src/ActionsColumn.vue';
|
||||
|
||||
setDesignConfig(elementPlusAdapter);
|
||||
|
||||
describe('ActionsColumn popconfirm (real element-plus)', () => {
|
||||
test('点击按钮弹出 Popconfirm,确认后触发 handler', async () => {
|
||||
const handler = vi.fn();
|
||||
const wrapper = mount(ActionsColumn, {
|
||||
props: {
|
||||
columns: [],
|
||||
config: {
|
||||
actions: [
|
||||
{
|
||||
text: '删除',
|
||||
buttonType: 'danger',
|
||||
popconfirm: true,
|
||||
confirmText: (row: any) => `确定删除${row.title}?`,
|
||||
handler,
|
||||
},
|
||||
],
|
||||
},
|
||||
row: { title: 't1' },
|
||||
index: 0,
|
||||
editState: [],
|
||||
} as any,
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
const btn = wrapper.findAll('.action-btn').find((b) => b.text().includes('删除'));
|
||||
await btn?.trigger('click');
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toContain('确定删除t1?');
|
||||
});
|
||||
|
||||
const confirmBtn = Array.from(document.querySelectorAll('button')).find(
|
||||
(b) => b.textContent?.trim().toLowerCase() === 'yes',
|
||||
);
|
||||
confirmBtn?.click();
|
||||
await vi.waitFor(() => {
|
||||
expect(handler).toHaveBeenCalledWith({ title: 't1' }, 0);
|
||||
});
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('popconfirmWidth 透传到浮层宽度', async () => {
|
||||
const handler = vi.fn();
|
||||
const wrapper = mount(ActionsColumn, {
|
||||
props: {
|
||||
columns: [],
|
||||
config: {
|
||||
actions: [
|
||||
{
|
||||
text: '删除',
|
||||
popconfirm: true,
|
||||
popconfirmWidth: 240,
|
||||
confirmText: '确定删除?',
|
||||
handler,
|
||||
},
|
||||
],
|
||||
},
|
||||
row: {},
|
||||
index: 0,
|
||||
editState: [],
|
||||
} as any,
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
const btn = wrapper.findAll('.action-btn').find((b) => b.text().includes('删除'));
|
||||
await btn?.trigger('click');
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toContain('确定删除?');
|
||||
});
|
||||
|
||||
const popper = Array.from(document.querySelectorAll('.el-popper')).find((e) =>
|
||||
(e.textContent || '').includes('确定删除?'),
|
||||
);
|
||||
expect(popper).toBeTruthy();
|
||||
expect(popper.style.width).toBe('240px');
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<TPopconfirm :content="title" :placement="placement" @confirm="confirmHandler" @cancel="cancelHandler">
|
||||
<TPopconfirm
|
||||
:content="title"
|
||||
:placement="placement"
|
||||
:popup-props="popupProps"
|
||||
@confirm="confirmHandler"
|
||||
@cancel="cancelHandler"
|
||||
>
|
||||
<template #default>
|
||||
<slot name="reference"></slot>
|
||||
</template>
|
||||
@ -7,6 +13,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { Popconfirm as TPopconfirm } from 'tdesign-vue-next';
|
||||
|
||||
import type { PopconfirmProps } from '@tmagic/design';
|
||||
@ -15,10 +22,16 @@ defineOptions({
|
||||
name: 'TTDesignAdapterPopconfirm',
|
||||
});
|
||||
|
||||
defineProps<PopconfirmProps>();
|
||||
const props = defineProps<PopconfirmProps>();
|
||||
|
||||
const emit = defineEmits(['confirm', 'cancel']);
|
||||
|
||||
const popupProps = computed(() => {
|
||||
if (!props.width) return undefined;
|
||||
const width = typeof props.width === 'number' ? `${props.width}px` : props.width;
|
||||
return { overlayInnerStyle: { width } };
|
||||
});
|
||||
|
||||
const confirmHandler = (...args: any[]) => {
|
||||
emit('confirm', ...args);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user