feat: improve vbenCheckButtonGroup (#6329)

* 按钮组支持单选清除和多选限制最大选项数

* 按钮组支持icon插槽来定制图标
This commit is contained in:
Netfan 2025-06-03 23:11:56 +08:00 committed by GitHub
parent ea776aa710
commit 4102cc2211
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 8 deletions

View File

@ -29,14 +29,25 @@ export type ValueType = boolean | number | string;
export interface VbenButtonGroupProps export interface VbenButtonGroupProps
extends Pick<VbenButtonProps, 'disabled'> { extends Pick<VbenButtonProps, 'disabled'> {
/** 单选模式下允许清除选中 */
allowClear?: boolean;
/** 值改变前的回调 */
beforeChange?: ( beforeChange?: (
value: ValueType, value: ValueType,
isChecked: boolean, isChecked: boolean,
) => boolean | PromiseLike<boolean | undefined> | undefined; ) => boolean | PromiseLike<boolean | undefined> | undefined;
/** 按钮样式 */
btnClass?: any; btnClass?: any;
/** 按钮间隔距离 */
gap?: number; gap?: number;
/** 多选模式下限制最多选择的数量。0表示不限制 */
maxCount?: number;
/** 是否允许多选 */
multiple?: boolean; multiple?: boolean;
/** 选项 */
options?: { [key: string]: any; label: CustomRenderType; value: ValueType }[]; options?: { [key: string]: any; label: CustomRenderType; value: ValueType }[];
/** 显示图标 */
showIcon?: boolean; showIcon?: boolean;
/** 尺寸 */
size?: 'large' | 'middle' | 'small'; size?: 'large' | 'middle' | 'small';
} }

View File

@ -19,6 +19,8 @@ const props = withDefaults(defineProps<VbenButtonGroupProps>(), {
multiple: false, multiple: false,
showIcon: true, showIcon: true,
size: 'middle', size: 'middle',
allowClear: false,
maxCount: 0,
}); });
const emit = defineEmits(['btnClick']); const emit = defineEmits(['btnClick']);
const btnDefaultProps = computed(() => { const btnDefaultProps = computed(() => {
@ -82,13 +84,23 @@ async function onBtnClick(value: ValueType) {
if (innerValue.value.includes(value)) { if (innerValue.value.includes(value)) {
innerValue.value = innerValue.value.filter((item) => item !== value); innerValue.value = innerValue.value.filter((item) => item !== value);
} else { } else {
if (props.maxCount > 0 && innerValue.value.length >= props.maxCount) {
innerValue.value = innerValue.value.slice(0, props.maxCount - 1);
}
innerValue.value.push(value); innerValue.value.push(value);
} }
modelValue.value = innerValue.value; modelValue.value = innerValue.value;
} else {
if (props.allowClear && innerValue.value.includes(value)) {
innerValue.value = [];
modelValue.value = undefined;
emit('btnClick', undefined);
return;
} else { } else {
innerValue.value = [value]; innerValue.value = [value];
modelValue.value = value; modelValue.value = value;
} }
}
emit('btnClick', value); emit('btnClick', value);
} }
</script> </script>
@ -112,12 +124,18 @@ async function onBtnClick(value: ValueType) {
@click="onBtnClick(btn.value)" @click="onBtnClick(btn.value)"
> >
<div class="icon-wrapper" v-if="props.showIcon"> <div class="icon-wrapper" v-if="props.showIcon">
<slot
name="icon"
:loading="loadingValues.includes(btn.value)"
:checked="innerValue.includes(btn.value)"
>
<LoaderCircle <LoaderCircle
class="animate-spin" class="animate-spin"
v-if="loadingValues.includes(btn.value)" v-if="loadingValues.includes(btn.value)"
/> />
<CircleCheckBig v-else-if="innerValue.includes(btn.value)" /> <CircleCheckBig v-else-if="innerValue.includes(btn.value)" />
<Circle v-else /> <Circle v-else />
</slot>
</div> </div>
<slot name="option" :label="btn.label" :value="btn.value" :data="btn"> <slot name="option" :label="btn.label" :value="btn.value" :data="btn">
<VbenRenderContent :content="btn.label" /> <VbenRenderContent :content="btn.label" />

View File

@ -9,6 +9,7 @@ import {
VbenButtonGroup, VbenButtonGroup,
VbenCheckButtonGroup, VbenCheckButtonGroup,
} from '@vben/common-ui'; } from '@vben/common-ui';
import { LoaderCircle, Square, SquareCheckBig } from '@vben/icons';
import { Button, Card, message } from 'ant-design-vue'; import { Button, Card, message } from 'ant-design-vue';
@ -51,6 +52,7 @@ const compProps = reactive({
gap: 0, gap: 0,
showIcon: true, showIcon: true,
size: 'middle', size: 'middle',
allowClear: false,
} as Recordable<any>); } as Recordable<any>);
const [Form] = useVbenForm({ const [Form] = useVbenForm({
@ -63,6 +65,9 @@ const [Form] = useVbenForm({
} }
}); });
}, },
commonConfig: {
labelWidth: 150,
},
schema: [ schema: [
{ {
component: 'RadioGroup', component: 'RadioGroup',
@ -109,6 +114,20 @@ const [Form] = useVbenForm({
fieldName: 'beforeChange', fieldName: 'beforeChange',
label: '前置回调', label: '前置回调',
}, },
{
component: 'Switch',
defaultValue: false,
fieldName: 'allowClear',
label: '允许清除',
help: '单选时是否允许取消选中值为undefined',
},
{
component: 'InputNumber',
defaultValue: 0,
fieldName: 'maxCount',
label: '最大选中数量',
help: '多选时有效0表示不限制',
},
], ],
showDefaultActions: false, showDefaultActions: false,
submitOnChange: true, submitOnChange: true,
@ -186,6 +205,21 @@ function onBtnClick(value: any) {
v-bind="compProps" v-bind="compProps"
/> />
</div> </div>
<p class="mt-4">自定义图标{{ checkValue }}</p>
<div class="mt-2 flex flex-col gap-2">
<VbenCheckButtonGroup
v-model="checkValue"
multiple
:options="options"
v-bind="compProps"
>
<template #icon="{ loading, checked }">
<LoaderCircle class="animate-spin" v-if="loading" />
<SquareCheckBig v-else-if="checked" />
<Square v-else />
</template>
</VbenCheckButtonGroup>
</div>
</Card> </Card>
<Card title="设置" class="mt-4"> <Card title="设置" class="mt-4">