diff --git a/packages/table/src/ActionButton.vue b/packages/table/src/ActionButton.vue
new file mode 100644
index 00000000..195af554
--- /dev/null
+++ b/packages/table/src/ActionButton.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
diff --git a/packages/table/src/ActionsColumn.vue b/packages/table/src/ActionsColumn.vue
index ff25030c..166e9857 100644
--- a/packages/table/src/ActionsColumn.vue
+++ b/packages/table/src/ActionsColumn.vue
@@ -8,37 +8,83 @@
@confirm="actionHandler(action, row, index)"
>
-
-
-
+
+
+
+
+
+
+
+
-
+
@@ -65,8 +111,10 @@
+
+
diff --git a/packages/table/src/ComponentColumn.vue b/packages/table/src/ComponentColumn.vue
index 00414b71..d09e5c67 100644
--- a/packages/table/src/ComponentColumn.vue
+++ b/packages/table/src/ComponentColumn.vue
@@ -1,19 +1,20 @@
diff --git a/packages/table/src/TextColumn.vue b/packages/table/src/TextColumn.vue
index 1308f345..bfb45a88 100644
--- a/packages/table/src/TextColumn.vue
+++ b/packages/table/src/TextColumn.vue
@@ -47,8 +47,8 @@
import { TMagicButton, TMagicTag, TMagicTooltip } from '@tmagic/design';
import { type ContainerChangeEventData, MForm } from '@tmagic/form';
import type { FormItemConfig, FormValue } from '@tmagic/form-schema';
-import { setValueByKeyPath } from '@tmagic/utils';
+import { applyInlineEditChange } from './formHelpers';
import { ColumnConfig } from './schema';
import { formatter } from './utils';
@@ -70,12 +70,6 @@ const props = withDefaults(
);
const formChangeHandler = (v: FormValue, eventData: ContainerChangeEventData) => {
- if (eventData.changeRecords?.length) {
- for (const record of eventData.changeRecords) {
- if (record.propPath) {
- setValueByKeyPath(record.propPath, record.value, props.editState[props.index]);
- }
- }
- }
+ applyInlineEditChange(props.editState[props.index], eventData);
};
diff --git a/packages/table/src/actionHelpers.ts b/packages/table/src/actionHelpers.ts
new file mode 100644
index 00000000..01be098a
--- /dev/null
+++ b/packages/table/src/actionHelpers.ts
@@ -0,0 +1,26 @@
+export const display = (fuc: boolean | Function | undefined, row: any) => {
+ if (typeof fuc === 'function') {
+ return fuc(row);
+ }
+ if (typeof fuc === 'boolean') {
+ return fuc;
+ }
+ return true;
+};
+
+export const disabled = (fuc: boolean | Function | undefined, row: any) => {
+ if (typeof fuc === 'function') {
+ return fuc(row);
+ }
+ if (typeof fuc === 'boolean') {
+ return fuc;
+ }
+ return false;
+};
+
+export const formatActionText = (fuc: string | Function | undefined, row: any) => {
+ if (typeof fuc === 'function') {
+ return fuc(row);
+ }
+ return fuc;
+};
diff --git a/packages/table/src/componentHelpers.ts b/packages/table/src/componentHelpers.ts
new file mode 100644
index 00000000..fafe7115
--- /dev/null
+++ b/packages/table/src/componentHelpers.ts
@@ -0,0 +1,15 @@
+import type { ColumnConfig } from './schema';
+
+export const resolveComponentProps = (config: ColumnConfig, row: any, index: number) => {
+ if (typeof config.props === 'function') {
+ return config.props(row, index) || {};
+ }
+ return config.props || {};
+};
+
+export const resolveComponentListeners = (config: ColumnConfig, row: any, index: number) => {
+ if (typeof config.listeners === 'function') {
+ return config.listeners(row, index) || {};
+ }
+ return config.listeners || {};
+};
diff --git a/packages/table/src/formHelpers.ts b/packages/table/src/formHelpers.ts
new file mode 100644
index 00000000..34f5f58c
--- /dev/null
+++ b/packages/table/src/formHelpers.ts
@@ -0,0 +1,12 @@
+import type { ContainerChangeEventData } from '@tmagic/form';
+import { setValueByKeyPath } from '@tmagic/utils';
+
+export const applyInlineEditChange = (target: any, eventData: ContainerChangeEventData) => {
+ if (eventData.changeRecords?.length) {
+ for (const record of eventData.changeRecords) {
+ if (record.propPath) {
+ setValueByKeyPath(record.propPath, record.value, target);
+ }
+ }
+ }
+};
diff --git a/packages/table/src/schema.ts b/packages/table/src/schema.ts
index 05d71ca8..aa93c81f 100644
--- a/packages/table/src/schema.ts
+++ b/packages/table/src/schema.ts
@@ -18,8 +18,44 @@
import { FormConfig, FormValue } from '@tmagic/form';
+export type ColumnActionPlacement =
+ | 'auto'
+ | 'auto-start'
+ | 'auto-end'
+ | 'top'
+ | 'bottom'
+ | 'left'
+ | 'right'
+ | 'top-start'
+ | 'top-end'
+ | 'bottom-start'
+ | 'bottom-end'
+ | 'left-start'
+ | 'left-end'
+ | 'right-start'
+ | 'right-end';
+
+/** 当 type 为 sub-actions 时,更多菜单(Popover)的配置 */
+export interface ColumnSubActionConfig {
+ /** Popover 的弹出位置,默认 bottom */
+ placement?: ColumnActionPlacement;
+ /** Popover 浮层宽度,数字按 px 处理 */
+ popoverWidth?: string | number;
+ /** 附加到 Popover 浮层上的自定义 class */
+ popoverClass?: string;
+ /** Popover 关闭后是否销毁内容,默认 false */
+ popoverDestroyOnClose?: boolean;
+ /** 更多菜单中的子动作配置 */
+ items?: ColumnActionConfig[];
+}
+
export interface ColumnActionConfig {
- type?: 'delete' | 'copy' | 'edit' | string;
+ /**
+ * 动作类型:
+ * - `delete` / `copy` / `edit`:内置语义,`edit` 会进入行内编辑态。
+ * - `sub-actions`:点击后以 Popover 形式展开更多菜单,菜单配置通过 `subActionConfig` 提供。
+ */
+ type?: 'delete' | 'copy' | 'edit' | 'sub-actions' | string;
buttonType?: string;
display?: boolean | ((row: any) => boolean);
disabled?: boolean | ((row: any) => boolean);
@@ -34,10 +70,15 @@ export interface ColumnActionConfig {
confirmText?: string | ((row: any) => string);
/** Popconfirm 浮层宽度,数字按 px 处理 */
popconfirmWidth?: string | number;
+ /** 当 type 为 sub-actions 时,更多菜单(Popover)的配置 */
+ subActionConfig?: ColumnSubActionConfig;
handler?: (row: any, index: number) => Promise | any;
before?: (row: any, index: number) => Promise | void;
after?: (row: any, index: number) => Promise | void;
- action?: (data: { data: any; index: number }) => Promise | void;
+ action?: (data: {
+ data: any;
+ index: number;
+ }) => Promise<{ ret: number; msg?: string } | void> | { ret: number; msg?: string } | void;
cancel?: (data: { index: number }) => Promise | void;
}
diff --git a/packages/table/tests/columns.spec.ts b/packages/table/tests/columns.spec.ts
index dc9c4c9b..e322a90d 100644
--- a/packages/table/tests/columns.spec.ts
+++ b/packages/table/tests/columns.spec.ts
@@ -7,9 +7,13 @@ import { describe, expect, test, vi } from 'vitest';
import { defineComponent, h } from 'vue';
import { mount } from '@vue/test-utils';
+import ActionButton from '../src/ActionButton.vue';
+import { disabled, display, formatActionText } from '../src/actionHelpers';
import ActionsColumn from '../src/ActionsColumn.vue';
import ComponentColumn from '../src/ComponentColumn.vue';
+import { resolveComponentListeners, resolveComponentProps } from '../src/componentHelpers';
import ExpandColumn from '../src/ExpandColumn.vue';
+import { applyInlineEditChange } from '../src/formHelpers';
import PopoverColumn from '../src/PopoverColumn.vue';
import { ColumnActionConfig } from '../src/schema';
import TextColumn from '../src/TextColumn.vue';
@@ -96,7 +100,7 @@ describe('TextColumn.vue', () => {
},
});
expect(wrapper.find('.mform-stub').exists()).toBe(true);
- wrapper.vm.formChangeHandler({}, { changeRecords: [{ propPath: 'name', value: 'new' }] });
+ applyInlineEditChange(editState[0], { changeRecords: [{ propPath: 'name', value: 'new' }] });
expect(editState[0].name).toBe('new');
});
@@ -108,6 +112,31 @@ describe('TextColumn.vue', () => {
});
});
+describe('actionHelpers', () => {
+ test('display / disabled / formatActionText 辅助函数', () => {
+ expect(display(() => false, {})).toBe(false);
+ expect(display(true, {})).toBe(true);
+ expect(display(undefined, {})).toBe(true);
+ expect(disabled(() => true, {})).toBe(true);
+ expect(disabled(false, {})).toBe(false);
+ expect(formatActionText((row: any) => row.id, { id: 2 })).toBe(2);
+ expect(formatActionText('静态', {})).toBe('静态');
+ });
+});
+
+describe('ActionButton.vue', () => {
+ test('渲染操作按钮', () => {
+ const wrapper = mount(ActionButton, {
+ props: {
+ action: { text: '操作' },
+ row: { id: 1 },
+ index: 0,
+ },
+ });
+ expect(wrapper.text()).toContain('操作');
+ });
+});
+
describe('ActionsColumn.vue', () => {
const baseProps = () => ({
columns: [],
@@ -134,17 +163,6 @@ describe('ActionsColumn.vue', () => {
editState: [] as any[],
});
- test('display / disabled / formatter 辅助函数', () => {
- const wrapper = mount(ActionsColumn, { props: baseProps() });
- expect(wrapper.vm.display(() => false, {})).toBe(false);
- expect(wrapper.vm.display(true, {})).toBe(true);
- expect(wrapper.vm.display(undefined, {})).toBe(true);
- expect(wrapper.vm.disabled(() => true, {})).toBe(true);
- expect(wrapper.vm.disabled(false, {})).toBe(false);
- expect(wrapper.vm.formatter((row: any) => row.id, { id: 2 })).toBe(2);
- expect(wrapper.vm.formatter('静态', {})).toBe('静态');
- });
-
test('编辑 / 保存 / 取消流程', async () => {
const props = baseProps();
const wrapper = mount(ActionsColumn, { props });
@@ -233,18 +251,12 @@ describe('ComponentColumn.vue', () => {
},
});
expect(wrapper.find('.inner').text()).toBe('Cell');
- wrapper.vm.componentListeners({ name: 'Cell' }, 3).click();
+ resolveComponentListeners(wrapper.props('config'), { name: 'Cell' }, 3).click();
expect(onClick).toHaveBeenCalledWith(3);
- const wrapper2 = mount(ComponentColumn, {
- props: {
- config: { component: innerComp, props: { label: '静态' }, listeners: {} },
- row: {},
- index: 0,
- },
- });
- expect(wrapper2.vm.componentProps({}, 0)).toEqual({ label: '静态' });
- expect(wrapper2.vm.componentListeners({}, 0)).toEqual({});
+ const staticConfig = { component: innerComp, props: { label: '静态' }, listeners: {} };
+ expect(resolveComponentProps(staticConfig, {}, 0)).toEqual({ label: '静态' });
+ expect(resolveComponentListeners(staticConfig, {}, 0)).toEqual({});
});
});