@tmagic/table 1.8.0-beta.2 → 1.8.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,73 @@
1
+ <script lang="ts">
2
+ import { defineComponent, h, PropType, vShow, withDirectives } from 'vue';
3
+
4
+ import { TMagicButton, TMagicTooltip } from '@tmagic/design';
5
+
6
+ import { disabled, formatActionText as formatter } from './actionHelpers';
7
+ import { ColumnActionConfig } from './schema';
8
+
9
+ export default defineComponent({
10
+ name: 'MTableActionButton',
11
+
12
+ props: {
13
+ action: {
14
+ type: Object as PropType<ColumnActionConfig>,
15
+ required: true,
16
+ },
17
+ row: {
18
+ type: null as unknown as PropType<any>,
19
+ required: true,
20
+ },
21
+ index: {
22
+ type: Number,
23
+ required: true,
24
+ },
25
+ btnClass: {
26
+ type: String,
27
+ default: 'action-btn',
28
+ },
29
+ visible: {
30
+ type: Boolean,
31
+ default: true,
32
+ },
33
+ },
34
+
35
+ emits: ['click'],
36
+
37
+ setup(props, { emit }) {
38
+ const onClick = () => emit('click', props.action, props.row, props.index);
39
+
40
+ return () => {
41
+ const button = withDirectives(
42
+ h(
43
+ TMagicButton,
44
+ {
45
+ class: props.btnClass,
46
+ link: true,
47
+ size: 'small',
48
+ type: props.action.buttonType || 'primary',
49
+ icon: props.action.icon,
50
+ disabled: disabled(props.action.disabled, props.row),
51
+ onClick,
52
+ },
53
+ () => h('span', { innerHTML: formatter(props.action.text, props.row) }),
54
+ ),
55
+ [[vShow, props.visible]],
56
+ );
57
+
58
+ if (!props.action.tooltip) {
59
+ return button;
60
+ }
61
+
62
+ return h(
63
+ TMagicTooltip,
64
+ {
65
+ placement: props.action.tooltipPlacement || 'top',
66
+ content: props.action.tooltip,
67
+ },
68
+ () => button,
69
+ );
70
+ };
71
+ },
72
+ });
73
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <TMagicPopconfirm
3
+ placement="top"
4
+ :width="action.popconfirmWidth"
5
+ :title="formatter(action.confirmText, row) || '确定执行此操作?'"
6
+ @confirm="onConfirm"
7
+ >
8
+ <template #reference>
9
+ <ActionButton :action="action" :row="row" :index="index" :btn-class="btnClass" :visible="visible" />
10
+ </template>
11
+ </TMagicPopconfirm>
12
+ </template>
13
+
14
+ <script lang="ts" setup>
15
+ import { TMagicPopconfirm } from '@tmagic/design';
16
+
17
+ import ActionButton from './ActionButton.vue';
18
+ import { formatActionText as formatter } from './actionHelpers';
19
+ import { ColumnActionConfig } from './schema';
20
+
21
+ defineOptions({
22
+ name: 'MTableActionPopconfirm',
23
+ });
24
+
25
+ const props = withDefaults(
26
+ defineProps<{
27
+ action: ColumnActionConfig;
28
+ row: any;
29
+ index: number;
30
+ btnClass?: string;
31
+ visible?: boolean;
32
+ }>(),
33
+ {
34
+ btnClass: 'action-btn',
35
+ visible: true,
36
+ },
37
+ );
38
+
39
+ const emit = defineEmits<{
40
+ confirm: [action: ColumnActionConfig, row: any, index: number];
41
+ }>();
42
+
43
+ const onConfirm = () => emit('confirm', props.action, props.row, props.index);
44
+ </script>
@@ -1,23 +1,69 @@
1
1
  <template>
2
- <TMagicTooltip
3
- v-for="(action, actionIndex) in config.actions"
4
- :placement="action.tooltipPlacement || 'top'"
5
- :key="actionIndex"
6
- :disabled="!Boolean(action.tooltip)"
7
- :content="action.tooltip"
8
- >
9
- <TMagicButton
10
- v-show="display(action.display, row) && !editState[index]"
11
- class="action-btn"
12
- link
13
- size="small"
14
- :type="action.buttonType || 'primary'"
15
- :icon="action.icon"
16
- :disabled="disabled(action.disabled, row)"
17
- @click="actionHandler(action, row, index)"
18
- ><span v-html="formatter(action.text, row)"></span
19
- ></TMagicButton>
20
- </TMagicTooltip>
2
+ <template v-for="(action, actionIndex) in config.actions" :key="actionIndex">
3
+ <ActionPopconfirm
4
+ v-if="action.popconfirm"
5
+ :action="action"
6
+ :row="row"
7
+ :index="index"
8
+ :visible="display(action.display, row) && !editState[index]"
9
+ @confirm="actionHandler"
10
+ />
11
+
12
+ <TMagicPopover
13
+ v-else-if="action.type === 'sub-actions'"
14
+ v-model:visible="popoverVisible"
15
+ trigger="click"
16
+ :placement="action.subActionConfig?.placement || 'bottom'"
17
+ :width="action.subActionConfig?.popoverWidth"
18
+ :popper-class="action.subActionConfig?.popoverClass"
19
+ :destroy-on-close="action.subActionConfig?.popoverDestroyOnClose ?? true"
20
+ @clickoutside="popoverVisible = false"
21
+ >
22
+ <template #reference>
23
+ <TMagicButton
24
+ v-show="action.subActionConfig?.items?.length && display(action.display, row) && !editState[index]"
25
+ class="action-btn"
26
+ link
27
+ size="small"
28
+ :type="action.buttonType || 'primary'"
29
+ :icon="popoverVisible ? ArrowDown : ArrowRight"
30
+ @click.stop="togglePopover"
31
+ ></TMagicButton>
32
+ </template>
33
+ <div class="tmagic-table-sub-actions">
34
+ <template v-for="(subAction, subIndex) in action.subActionConfig?.items" :key="subIndex">
35
+ <ActionPopconfirm
36
+ v-if="subAction.popconfirm"
37
+ :action="subAction"
38
+ :row="row"
39
+ :index="index"
40
+ btn-class="sub-action-btn"
41
+ :visible="display(subAction.display, row)"
42
+ @confirm="actionHandler"
43
+ />
44
+
45
+ <ActionButton
46
+ v-else
47
+ :action="subAction"
48
+ :row="row"
49
+ :index="index"
50
+ btn-class="sub-action-btn"
51
+ :visible="display(subAction.display, row)"
52
+ @click="actionHandler"
53
+ />
54
+ </template>
55
+ </div>
56
+ </TMagicPopover>
57
+
58
+ <ActionButton
59
+ v-else
60
+ :action="action"
61
+ :row="row"
62
+ :index="index"
63
+ :visible="display(action.display, row) && !editState[index]"
64
+ @click="actionHandler"
65
+ />
66
+ </template>
21
67
 
22
68
  <TMagicButton
23
69
  class="action-btn"
@@ -39,11 +85,23 @@
39
85
  >
40
86
  </template>
41
87
 
88
+ <script lang="ts">
89
+ import { ref } from 'vue';
90
+
91
+ // 所有行共享:同一时间仅允许一个 sub-actions 气泡展开,激活新的会自动关闭上一个
92
+ const activePopoverId = ref<symbol | null>(null);
93
+ </script>
94
+
42
95
  <script lang="ts" setup>
96
+ import { watch } from 'vue';
97
+ import { ArrowDown, ArrowRight } from '@element-plus/icons-vue';
43
98
  import { cloneDeep } from 'lodash-es';
44
99
 
45
- import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
100
+ import { TMagicButton, tMagicMessage, TMagicPopover } from '@tmagic/design';
46
101
 
102
+ import ActionButton from './ActionButton.vue';
103
+ import { display } from './actionHelpers';
104
+ import ActionPopconfirm from './ActionPopconfirm.vue';
47
105
  import { ColumnActionConfig, ColumnConfig } from './schema';
48
106
 
49
107
  defineOptions({
@@ -67,38 +125,32 @@ const props = withDefaults(
67
125
  },
68
126
  );
69
127
 
70
- const emit = defineEmits<{
71
- 'after-action': [{ index: number }];
72
- 'after-action-cancel': [{ index: number }];
73
- }>();
128
+ const popoverId = Symbol('sub-actions-popover');
129
+ const popoverVisible = ref(false);
74
130
 
75
- const display = (fuc: boolean | Function | undefined, row: any) => {
76
- if (typeof fuc === 'function') {
77
- return fuc(row);
78
- }
79
- if (typeof fuc === 'boolean') {
80
- return fuc;
131
+ watch(popoverVisible, (visible) => {
132
+ if (visible) {
133
+ activePopoverId.value = popoverId;
134
+ } else if (activePopoverId.value === popoverId) {
135
+ activePopoverId.value = null;
81
136
  }
82
- return true;
83
- };
137
+ });
84
138
 
85
- const disabled = (fuc: boolean | Function | undefined, row: any) => {
86
- if (typeof fuc === 'function') {
87
- return fuc(row);
88
- }
89
- if (typeof fuc === 'boolean') {
90
- return fuc;
139
+ watch(activePopoverId, (id) => {
140
+ if (id !== popoverId && popoverVisible.value) {
141
+ popoverVisible.value = false;
91
142
  }
92
- return false;
93
- };
143
+ });
94
144
 
95
- const formatter = (fuc: string | Function | undefined, row: any) => {
96
- if (typeof fuc === 'function') {
97
- return fuc(row);
98
- }
99
- return fuc;
145
+ const togglePopover = () => {
146
+ popoverVisible.value = !popoverVisible.value;
100
147
  };
101
148
 
149
+ const emit = defineEmits<{
150
+ 'after-action': [{ index: number }];
151
+ 'after-action-cancel': [{ index: number }];
152
+ }>();
153
+
102
154
  const actionHandler = async (action: ColumnActionConfig, row: any, index: number) => {
103
155
  await action.before?.(row, index);
104
156
  if (action.type === 'edit') {
@@ -106,7 +158,8 @@ const actionHandler = async (action: ColumnActionConfig, row: any, index: number
106
158
  } else {
107
159
  await action.handler?.(row, index);
108
160
  }
109
- action.after?.(row, index);
161
+ await action.after?.(row, index);
162
+ popoverVisible.value = false;
110
163
  };
111
164
 
112
165
  const save = async (index: number, config: ColumnConfig) => {
@@ -140,4 +193,10 @@ const cancel = async (index: number, config: ColumnConfig) => {
140
193
  }
141
194
  emit('after-action-cancel', { index });
142
195
  };
196
+
197
+ defineExpose({
198
+ actionHandler,
199
+ save,
200
+ cancel,
201
+ });
143
202
  </script>
@@ -1,19 +1,20 @@
1
1
  <template>
2
2
  <component
3
3
  :is="config.component"
4
- v-bind="componentProps(row, index)"
5
- v-on="componentListeners(row, index)"
4
+ v-bind="resolveComponentProps(config, row, index)"
5
+ v-on="resolveComponentListeners(config, row, index)"
6
6
  ></component>
7
7
  </template>
8
8
 
9
9
  <script lang="ts" setup>
10
+ import { resolveComponentListeners, resolveComponentProps } from './componentHelpers';
10
11
  import { ColumnConfig } from './schema';
11
12
 
12
13
  defineOptions({
13
14
  name: 'MTableColumn',
14
15
  });
15
16
 
16
- const props = withDefaults(
17
+ withDefaults(
17
18
  defineProps<{
18
19
  config: ColumnConfig;
19
20
  row: any;
@@ -23,18 +24,4 @@ const props = withDefaults(
23
24
  config: () => ({}),
24
25
  },
25
26
  );
26
-
27
- const componentProps = (row: any, index: number) => {
28
- if (typeof props.config.props === 'function') {
29
- return props.config.props(row, index) || {};
30
- }
31
- return props.config.props || {};
32
- };
33
-
34
- const componentListeners = (row: any, index: number) => {
35
- if (typeof props.config.listeners === 'function') {
36
- return props.config.listeners(row, index) || {};
37
- }
38
- return props.config.listeners || {};
39
- };
40
27
  </script>
@@ -47,8 +47,8 @@
47
47
  import { TMagicButton, TMagicTag, TMagicTooltip } from '@tmagic/design';
48
48
  import { type ContainerChangeEventData, MForm } from '@tmagic/form';
49
49
  import type { FormItemConfig, FormValue } from '@tmagic/form-schema';
50
- import { setValueByKeyPath } from '@tmagic/utils';
51
50
 
51
+ import { applyInlineEditChange } from './formHelpers';
52
52
  import { ColumnConfig } from './schema';
53
53
  import { formatter } from './utils';
54
54
 
@@ -70,12 +70,6 @@ const props = withDefaults(
70
70
  );
71
71
 
72
72
  const formChangeHandler = (v: FormValue, eventData: ContainerChangeEventData) => {
73
- if (eventData.changeRecords?.length) {
74
- for (const record of eventData.changeRecords) {
75
- if (record.propPath) {
76
- setValueByKeyPath(record.propPath, record.value, props.editState[props.index]);
77
- }
78
- }
79
- }
73
+ applyInlineEditChange(props.editState[props.index], eventData);
80
74
  };
81
75
  </script>
@@ -0,0 +1,26 @@
1
+ export const display = (fuc: boolean | Function | undefined, row: any) => {
2
+ if (typeof fuc === 'function') {
3
+ return fuc(row);
4
+ }
5
+ if (typeof fuc === 'boolean') {
6
+ return fuc;
7
+ }
8
+ return true;
9
+ };
10
+
11
+ export const disabled = (fuc: boolean | Function | undefined, row: any) => {
12
+ if (typeof fuc === 'function') {
13
+ return fuc(row);
14
+ }
15
+ if (typeof fuc === 'boolean') {
16
+ return fuc;
17
+ }
18
+ return false;
19
+ };
20
+
21
+ export const formatActionText = (fuc: string | Function | undefined, row: any) => {
22
+ if (typeof fuc === 'function') {
23
+ return fuc(row);
24
+ }
25
+ return fuc;
26
+ };
@@ -0,0 +1,15 @@
1
+ import type { ColumnConfig } from './schema';
2
+
3
+ export const resolveComponentProps = (config: ColumnConfig, row: any, index: number) => {
4
+ if (typeof config.props === 'function') {
5
+ return config.props(row, index) || {};
6
+ }
7
+ return config.props || {};
8
+ };
9
+
10
+ export const resolveComponentListeners = (config: ColumnConfig, row: any, index: number) => {
11
+ if (typeof config.listeners === 'function') {
12
+ return config.listeners(row, index) || {};
13
+ }
14
+ return config.listeners || {};
15
+ };
@@ -0,0 +1,12 @@
1
+ import type { ContainerChangeEventData } from '@tmagic/form';
2
+ import { setValueByKeyPath } from '@tmagic/utils';
3
+
4
+ export const applyInlineEditChange = (target: any, eventData: ContainerChangeEventData) => {
5
+ if (eventData.changeRecords?.length) {
6
+ for (const record of eventData.changeRecords) {
7
+ if (record.propPath) {
8
+ setValueByKeyPath(record.propPath, record.value, target);
9
+ }
10
+ }
11
+ }
12
+ };
package/src/schema.ts CHANGED
@@ -18,8 +18,44 @@
18
18
 
19
19
  import { FormConfig, FormValue } from '@tmagic/form';
20
20
 
21
+ export type ColumnActionPlacement =
22
+ | 'auto'
23
+ | 'auto-start'
24
+ | 'auto-end'
25
+ | 'top'
26
+ | 'bottom'
27
+ | 'left'
28
+ | 'right'
29
+ | 'top-start'
30
+ | 'top-end'
31
+ | 'bottom-start'
32
+ | 'bottom-end'
33
+ | 'left-start'
34
+ | 'left-end'
35
+ | 'right-start'
36
+ | 'right-end';
37
+
38
+ /** 当 type 为 sub-actions 时,更多菜单(Popover)的配置 */
39
+ export interface ColumnSubActionConfig {
40
+ /** Popover 的弹出位置,默认 bottom */
41
+ placement?: ColumnActionPlacement;
42
+ /** Popover 浮层宽度,数字按 px 处理 */
43
+ popoverWidth?: string | number;
44
+ /** 附加到 Popover 浮层上的自定义 class */
45
+ popoverClass?: string;
46
+ /** Popover 关闭后是否销毁内容,默认 false */
47
+ popoverDestroyOnClose?: boolean;
48
+ /** 更多菜单中的子动作配置 */
49
+ items?: ColumnActionConfig[];
50
+ }
51
+
21
52
  export interface ColumnActionConfig {
22
- type?: 'delete' | 'copy' | 'edit' | string;
53
+ /**
54
+ * 动作类型:
55
+ * - `delete` / `copy` / `edit`:内置语义,`edit` 会进入行内编辑态。
56
+ * - `sub-actions`:点击后以 Popover 形式展开更多菜单,菜单配置通过 `subActionConfig` 提供。
57
+ */
58
+ type?: 'delete' | 'copy' | 'edit' | 'sub-actions' | string;
23
59
  buttonType?: string;
24
60
  display?: boolean | ((row: any) => boolean);
25
61
  disabled?: boolean | ((row: any) => boolean);
@@ -28,10 +64,21 @@ export interface ColumnActionConfig {
28
64
  tooltip?: string;
29
65
  tooltipPlacement?: string;
30
66
  icon?: any;
67
+ /** 为 true 时用 Popconfirm 包裹按钮,点击后需二次确认才会触发 handler */
68
+ popconfirm?: boolean;
69
+ /** Popconfirm 的确认提示文案,支持函数动态生成 */
70
+ confirmText?: string | ((row: any) => string);
71
+ /** Popconfirm 浮层宽度,数字按 px 处理 */
72
+ popconfirmWidth?: string | number;
73
+ /** 当 type 为 sub-actions 时,更多菜单(Popover)的配置 */
74
+ subActionConfig?: ColumnSubActionConfig;
31
75
  handler?: (row: any, index: number) => Promise<any> | any;
32
76
  before?: (row: any, index: number) => Promise<void> | void;
33
77
  after?: (row: any, index: number) => Promise<void> | void;
34
- action?: (data: { data: any; index: number }) => Promise<void> | void;
78
+ action?: (data: {
79
+ data: any;
80
+ index: number;
81
+ }) => Promise<{ ret: number; msg?: string } | void> | { ret: number; msg?: string } | void;
35
82
  cancel?: (data: { index: number }) => Promise<void> | void;
36
83
  }
37
84
 
@@ -13,3 +13,22 @@
13
13
  color: #999;
14
14
  }
15
15
  }
16
+
17
+ .tmagic-table-sub-actions {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 4px;
21
+ align-items: flex-start;
22
+
23
+ .sub-action-btn {
24
+ width: 100%;
25
+ }
26
+
27
+ .tmagic-design-button + .tmagic-design-button {
28
+ margin-left: 0;
29
+ }
30
+
31
+ .tmagic-design-button {
32
+ justify-content: flex-start;
33
+ }
34
+ }
package/types/index.d.ts CHANGED
@@ -2,8 +2,27 @@ import { App } from "vue";
2
2
  import { FormConfig, FormValue } from "@tmagic/form";
3
3
 
4
4
  //#region temp/packages/table/src/schema.d.ts
5
+ 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';
6
+ /** 当 type 为 sub-actions 时,更多菜单(Popover)的配置 */
7
+ interface ColumnSubActionConfig {
8
+ /** Popover 的弹出位置,默认 bottom */
9
+ placement?: ColumnActionPlacement;
10
+ /** Popover 浮层宽度,数字按 px 处理 */
11
+ popoverWidth?: string | number;
12
+ /** 附加到 Popover 浮层上的自定义 class */
13
+ popoverClass?: string;
14
+ /** Popover 关闭后是否销毁内容,默认 false */
15
+ popoverDestroyOnClose?: boolean;
16
+ /** 更多菜单中的子动作配置 */
17
+ items?: ColumnActionConfig[];
18
+ }
5
19
  interface ColumnActionConfig {
6
- type?: 'delete' | 'copy' | 'edit' | string;
20
+ /**
21
+ * 动作类型:
22
+ * - `delete` / `copy` / `edit`:内置语义,`edit` 会进入行内编辑态。
23
+ * - `sub-actions`:点击后以 Popover 形式展开更多菜单,菜单配置通过 `subActionConfig` 提供。
24
+ */
25
+ type?: 'delete' | 'copy' | 'edit' | 'sub-actions' | string;
7
26
  buttonType?: string;
8
27
  display?: boolean | ((row: any) => boolean);
9
28
  disabled?: boolean | ((row: any) => boolean);
@@ -12,13 +31,27 @@ interface ColumnActionConfig {
12
31
  tooltip?: string;
13
32
  tooltipPlacement?: string;
14
33
  icon?: any;
34
+ /** 为 true 时用 Popconfirm 包裹按钮,点击后需二次确认才会触发 handler */
35
+ popconfirm?: boolean;
36
+ /** Popconfirm 的确认提示文案,支持函数动态生成 */
37
+ confirmText?: string | ((row: any) => string);
38
+ /** Popconfirm 浮层宽度,数字按 px 处理 */
39
+ popconfirmWidth?: string | number;
40
+ /** 当 type 为 sub-actions 时,更多菜单(Popover)的配置 */
41
+ subActionConfig?: ColumnSubActionConfig;
15
42
  handler?: (row: any, index: number) => Promise<any> | any;
16
43
  before?: (row: any, index: number) => Promise<void> | void;
17
44
  after?: (row: any, index: number) => Promise<void> | void;
18
45
  action?: (data: {
19
46
  data: any;
20
47
  index: number;
21
- }) => Promise<void> | void;
48
+ }) => Promise<{
49
+ ret: number;
50
+ msg?: string;
51
+ } | void> | {
52
+ ret: number;
53
+ msg?: string;
54
+ } | void;
22
55
  cancel?: (data: {
23
56
  index: number;
24
57
  }) => Promise<void> | void;
@@ -94,18 +127,18 @@ declare const __VLS_export: import("@vue/runtime-core").DefineComponent<__VLS_Pr
94
127
  "sort-change": (...args: any[]) => void;
95
128
  "expand-change": (...args: any[]) => void;
96
129
  "cell-click": (...args: any[]) => void;
130
+ "select-all": (...args: any[]) => void;
97
131
  "after-action": (...args: any[]) => void;
98
132
  "after-action-cancel": (...args: any[]) => void;
99
- "select-all": (...args: any[]) => void;
100
133
  "selection-change": (...args: any[]) => void;
101
134
  }, string, import("@vue/runtime-core").PublicProps, Readonly<__VLS_Props> & Readonly<{
102
135
  onSelect?: ((...args: any[]) => any) | undefined;
103
136
  "onSort-change"?: ((...args: any[]) => any) | undefined;
104
137
  "onExpand-change"?: ((...args: any[]) => any) | undefined;
105
138
  "onCell-click"?: ((...args: any[]) => any) | undefined;
139
+ "onSelect-all"?: ((...args: any[]) => any) | undefined;
106
140
  "onAfter-action"?: ((...args: any[]) => any) | undefined;
107
141
  "onAfter-action-cancel"?: ((...args: any[]) => any) | undefined;
108
- "onSelect-all"?: ((...args: any[]) => any) | undefined;
109
142
  "onSelection-change"?: ((...args: any[]) => any) | undefined;
110
143
  }>, {
111
144
  loading: boolean;
@@ -127,4 +160,4 @@ declare const _default$1: {
127
160
  install(app: App): void;
128
161
  };
129
162
  //#endregion
130
- export { ColumnActionConfig, ColumnConfig, _default as MagicTable, createColumns, _default$1 as default, formatter };
163
+ export { ColumnActionConfig, ColumnActionPlacement, ColumnConfig, ColumnSubActionConfig, _default as MagicTable, createColumns, _default$1 as default, formatter };