Skip to content

Commit 3ddfaaf

Browse files
committed
feat(components): refactoring button configuration, supporting the merging and sorting of custom and preset buttons.
1 parent 1ff4d82 commit 3ddfaaf

File tree

1 file changed

+216
-37
lines changed

1 file changed

+216
-37
lines changed
Lines changed: 216 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,253 @@
11
<script setup lang="ts">
2+
import { computed } from 'vue';
23
import { $t } from '@/locales';
34
45
defineOptions({
56
name: 'TableHeaderOperation'
67
});
78
8-
interface Props {
9-
itemAlign?: NaiveUI.Align;
10-
disabledDelete?: boolean;
9+
/** button item config type */
10+
export interface TableButtonItem {
11+
/** button unique identifier */
12+
key: string;
13+
/** button text, if not passed, use default internationalized text */
14+
text?: string;
15+
/** button icon */
16+
icon: string;
17+
/** button type */
18+
type?: 'default' | 'primary' | 'info' | 'success' | 'warning' | 'error';
19+
/** whether to be ghost button */
20+
ghost?: boolean;
21+
/** whether to disable button */
22+
disabled?: boolean;
23+
/** button order, the smaller the value, the closer to the front */
24+
order?: number;
25+
/** whether to need confirm */
26+
needConfirm?: boolean;
27+
/** confirm text */
28+
confirmText?: string;
29+
/** button extra props */
30+
buttonProps?: Record<string, any>;
31+
}
32+
33+
/** preset button type */
34+
export type PresetButtonKey = 'add' | 'delete' | 'refresh';
35+
36+
/** button type constant */
37+
const ButtonType = {
38+
DEFAULT: 'default',
39+
PRIMARY: 'primary',
40+
INFO: 'info',
41+
SUCCESS: 'success',
42+
WARNING: 'warning',
43+
ERROR: 'error'
44+
} as const;
45+
46+
/** component props */
47+
export interface TableHeaderOperationProps {
48+
/** internal element alignment */
49+
itemAlign?: 'start' | 'end' | 'center' | 'baseline' | 'stretch';
50+
/** loading status */
1151
loading?: boolean;
52+
/** button config list */
53+
buttons?: TableButtonItem[];
54+
/** whether to show preset buttons */
55+
showPresetButtons?: boolean;
56+
/** disabled preset buttons */
57+
disabledPresetButtons?: PresetButtonKey[];
58+
/** hidden preset buttons */
59+
hiddenPresetButtons?: PresetButtonKey[];
1260
}
1361
14-
defineProps<Props>();
62+
const props = withDefaults(defineProps<TableHeaderOperationProps>(), {
63+
itemAlign: 'center',
64+
loading: false,
65+
showPresetButtons: true,
66+
disabledPresetButtons: () => [],
67+
hiddenPresetButtons: () => [],
68+
buttons: () => []
69+
});
1570
71+
/** component events */
1672
interface Emits {
73+
/** add button click event */
1774
(e: 'add'): void;
75+
/** delete button click event */
1876
(e: 'delete'): void;
77+
/** refresh button click event */
1978
(e: 'refresh'): void;
79+
/** custom button click event */
80+
(e: 'click', key: string): void;
2081
}
2182
2283
const emit = defineEmits<Emits>();
2384
24-
const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
85+
const columns = defineModel<any[]>('columns', {
2586
default: () => []
2687
});
2788
28-
function add() {
29-
emit('add');
30-
}
89+
/** get preset button config */
90+
const getPresetButtons = (): TableButtonItem[] => {
91+
return [
92+
{
93+
key: 'add',
94+
text: $t('common.add'),
95+
icon: 'ic-round-plus',
96+
type: ButtonType.PRIMARY,
97+
ghost: true,
98+
order: 10,
99+
disabled: props.disabledPresetButtons.includes('add')
100+
},
101+
{
102+
key: 'delete',
103+
text: $t('common.batchDelete'),
104+
icon: 'ic-round-delete',
105+
type: ButtonType.ERROR,
106+
ghost: true,
107+
order: 20,
108+
disabled: props.disabledPresetButtons.includes('delete'),
109+
needConfirm: true,
110+
confirmText: $t('common.confirmDelete')
111+
},
112+
{
113+
key: 'refresh',
114+
text: $t('common.refresh'),
115+
icon: 'mdi-refresh',
116+
type: ButtonType.DEFAULT,
117+
order: 30,
118+
disabled: props.disabledPresetButtons.includes('refresh')
119+
}
120+
].filter(btn => !props.hiddenPresetButtons.includes(btn.key as PresetButtonKey));
121+
};
122+
123+
/** merge button config */
124+
const mergedButtons = computed(() => {
125+
const result: TableButtonItem[] = [];
126+
127+
// add preset buttons
128+
if (props.showPresetButtons) {
129+
result.push(...getPresetButtons());
130+
}
131+
132+
// add custom buttons
133+
if (props.buttons?.length) {
134+
// merge custom buttons with preset buttons
135+
props.buttons.forEach(customBtn => {
136+
const existingIndex = result.findIndex(btn => btn.key === customBtn.key);
31137
32-
function batchDelete() {
33-
emit('delete');
138+
if (existingIndex >= 0) {
139+
// override preset buttons
140+
result[existingIndex] = {
141+
...result[existingIndex],
142+
...customBtn
143+
};
144+
} else {
145+
// add new button
146+
result.push({
147+
order: 100, // custom button default order value
148+
...customBtn
149+
});
150+
}
151+
});
152+
}
153+
154+
// sort by order
155+
return result.sort((a, b) => (a.order || 0) - (b.order || 0));
156+
});
157+
158+
/** handle button click event */
159+
function handleButtonClick(key: string) {
160+
switch (key) {
161+
case 'add':
162+
emit('add');
163+
break;
164+
case 'delete':
165+
emit('delete');
166+
break;
167+
case 'refresh':
168+
emit('refresh');
169+
break;
170+
default:
171+
emit('click', key);
172+
break;
173+
}
34174
}
35175
36-
function refresh() {
37-
emit('refresh');
176+
/** get button text */
177+
function getButtonText(button: TableButtonItem): string {
178+
if (button.text) return button.text;
179+
180+
// use default internationalized text
181+
switch (button.key) {
182+
case 'add':
183+
return $t('common.add');
184+
case 'delete':
185+
return $t('common.batchDelete');
186+
case 'refresh':
187+
return $t('common.refresh');
188+
default:
189+
return button.key;
190+
}
38191
}
39192
</script>
40193

41194
<template>
42-
<NSpace :align="itemAlign" wrap justify="end" class="lt-sm:w-200px">
195+
<NSpace :align="itemAlign" wrap justify="end" class="table-header-operation lt-sm:w-200px">
43196
<slot name="prefix"></slot>
44197
<slot name="default">
45-
<NButton size="small" ghost type="primary" @click="add">
46-
<template #icon>
47-
<icon-ic-round-plus class="text-icon" />
48-
</template>
49-
{{ $t('common.add') }}
50-
</NButton>
51-
<NPopconfirm @positive-click="batchDelete">
52-
<template #trigger>
53-
<NButton size="small" ghost type="error" :disabled="disabledDelete">
54-
<template #icon>
55-
<icon-ic-round-delete class="text-icon" />
56-
</template>
57-
{{ $t('common.batchDelete') }}
58-
</NButton>
59-
</template>
60-
{{ $t('common.confirmDelete') }}
61-
</NPopconfirm>
62-
</slot>
63-
<NButton size="small" @click="refresh">
64-
<template #icon>
65-
<icon-mdi-refresh class="text-icon" :class="{ 'animate-spin': loading }" />
198+
<template v-for="button in mergedButtons" :key="button.key">
199+
<!-- button that needs confirmation -->
200+
<NPopconfirm v-if="button.needConfirm" @positive-click="handleButtonClick(button.key)">
201+
<template #trigger>
202+
<NButton
203+
size="small"
204+
:ghost="button.ghost"
205+
:type="button.type"
206+
:disabled="button.disabled"
207+
v-bind="button.buttonProps"
208+
>
209+
<template #icon>
210+
<SvgIcon
211+
:icon="button.icon"
212+
class="text-icon"
213+
:class="{ 'animate-spin': loading && button.key === 'refresh' }"
214+
/>
215+
</template>
216+
{{ getButtonText(button) }}
217+
</NButton>
218+
</template>
219+
{{ button.confirmText }}
220+
</NPopconfirm>
221+
222+
<!-- normal button -->
223+
<NButton
224+
v-else
225+
size="small"
226+
:ghost="button.ghost"
227+
:type="button.type"
228+
:disabled="button.disabled"
229+
v-bind="button.buttonProps"
230+
@click="handleButtonClick(button.key)"
231+
>
232+
<template #icon>
233+
<SvgIcon
234+
:icon="button.icon"
235+
class="text-icon"
236+
:class="{ 'animate-spin': loading && button.key === 'refresh' }"
237+
/>
238+
</template>
239+
{{ getButtonText(button) }}
240+
</NButton>
66241
</template>
67-
{{ $t('common.refresh') }}
68-
</NButton>
242+
</slot>
243+
69244
<TableColumnSetting v-model:columns="columns" />
70245
<slot name="suffix"></slot>
71246
</NSpace>
72247
</template>
73248

74-
<style scoped></style>
249+
<style scoped>
250+
.table-header-operation :deep(.n-button) {
251+
margin-left: 8px;
252+
}
253+
</style>

0 commit comments

Comments
 (0)