feat: sidebar button config (#5818)

* feat: 新增 PreferenceCheckboxItem 组件

* feat(preferences): 添加侧边栏按钮配置功能

* feat: 新增按钮点击事件触发功能

* feat(SidebarPreferences): 新增侧边栏折叠按钮与固定按钮配置

* feat(ui): 新增侧边栏固定按钮及配置选项

* fix(test): 修正侧边栏配置项缺失问题
pull/62/head
Jin Mao 2025-03-31 10:17:42 +08:00 committed by YunaiV
parent ed2da6315e
commit a61d9003dd
11 changed files with 138 additions and 6 deletions

View File

@ -68,10 +68,12 @@ exports[`defaultPreferences immutability test > should not modify the config obj
"sidebar": { "sidebar": {
"autoActivateChild": false, "autoActivateChild": false,
"collapsed": false, "collapsed": false,
"collapsedButton": true,
"collapsedShowTitle": false, "collapsedShowTitle": false,
"enable": true, "enable": true,
"expandOnHover": true, "expandOnHover": true,
"extraCollapse": false, "extraCollapse": false,
"fixedButton": true,
"hidden": false, "hidden": false,
"width": 224, "width": 224,
}, },

View File

@ -68,10 +68,12 @@ const defaultPreferences: Preferences = {
sidebar: { sidebar: {
autoActivateChild: false, autoActivateChild: false,
collapsed: false, collapsed: false,
collapsedButton: true,
collapsedShowTitle: false, collapsedShowTitle: false,
enable: true, enable: true,
expandOnHover: true, expandOnHover: true,
extraCollapse: false, extraCollapse: false,
fixedButton: true,
hidden: false, hidden: false,
width: 224, width: 224,
}, },

View File

@ -132,6 +132,8 @@ interface SidebarPreferences {
autoActivateChild: boolean; autoActivateChild: boolean;
/** 侧边栏是否折叠 */ /** 侧边栏是否折叠 */
collapsed: boolean; collapsed: boolean;
/** 侧边栏折叠按钮是否可见 */
collapsedButton: boolean;
/** 侧边栏折叠时是否显示title */ /** 侧边栏折叠时是否显示title */
collapsedShowTitle: boolean; collapsedShowTitle: boolean;
/** 侧边栏是否可见 */ /** 侧边栏是否可见 */
@ -140,6 +142,8 @@ interface SidebarPreferences {
expandOnHover: boolean; expandOnHover: boolean;
/** 侧边栏扩展区域是否折叠 */ /** 侧边栏扩展区域是否折叠 */
extraCollapse: boolean; extraCollapse: boolean;
/** 侧边栏固定按钮是否可见 */
fixedButton: boolean;
/** 侧边栏是否隐藏 - css */ /** 侧边栏是否隐藏 - css */
hidden: boolean; hidden: boolean;
/** 侧边栏宽度 */ /** 侧边栏宽度 */

View File

@ -65,9 +65,14 @@ interface Props {
show?: boolean; show?: boolean;
/** /**
* 显示折叠按钮 * 显示折叠按钮
* @default false * @default true
*/ */
showCollapseButton?: boolean; showCollapseButton?: boolean;
/**
* 显示固定按钮
* @default true
*/
showFixedButton?: boolean;
/** /**
* 主题 * 主题
*/ */
@ -95,6 +100,7 @@ const props = withDefaults(defineProps<Props>(), {
paddingTop: 0, paddingTop: 0,
show: true, show: true,
showCollapseButton: true, showCollapseButton: true,
showFixedButton: true,
zIndex: 0, zIndex: 0,
}); });
@ -267,7 +273,7 @@ function handleMouseleave() {
@mouseleave="handleMouseleave" @mouseleave="handleMouseleave"
> >
<SidebarFixedButton <SidebarFixedButton
v-if="!collapse && !isSidebarMixed" v-if="!collapse && !isSidebarMixed && showFixedButton"
v-model:expand-on-hover="expandOnHover" v-model:expand-on-hover="expandOnHover"
/> />
<div v-if="slots.logo" :style="headerStyle"> <div v-if="slots.logo" :style="headerStyle">

View File

@ -106,6 +106,11 @@ interface VbenLayoutProps {
* @default false * @default false
*/ */
sidebarCollapse?: boolean; sidebarCollapse?: boolean;
/**
*
* @default true
*/
sidebarCollapsedButton?: boolean;
/** /**
* title * title
* @default true * @default true
@ -121,6 +126,11 @@ interface VbenLayoutProps {
* @default 48 * @default 48
*/ */
sidebarExtraCollapsedWidth?: number; sidebarExtraCollapsedWidth?: number;
/**
*
* @default true
*/
sidebarFixedButton?: boolean;
/** /**
* *
* @default false * @default false

View File

@ -49,8 +49,10 @@ const props = withDefaults(defineProps<Props>(), {
headerVisible: true, headerVisible: true,
isMobile: false, isMobile: false,
layout: 'sidebar-nav', layout: 'sidebar-nav',
sidebarCollapsedButton: true,
sidebarCollapseShowTitle: false, sidebarCollapseShowTitle: false,
sidebarExtraCollapsedWidth: 60, sidebarExtraCollapsedWidth: 60,
sidebarFixedButton: true,
sidebarHidden: false, sidebarHidden: false,
sidebarMixedWidth: 80, sidebarMixedWidth: 80,
sidebarTheme: 'dark', sidebarTheme: 'dark',
@ -487,6 +489,8 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
v-model:expand-on-hovering="sidebarExpandOnHovering" v-model:expand-on-hovering="sidebarExpandOnHovering"
v-model:extra-collapse="sidebarExtraCollapse" v-model:extra-collapse="sidebarExtraCollapse"
v-model:extra-visible="sidebarExtraVisible" v-model:extra-visible="sidebarExtraVisible"
:show-collapse-button="sidebarCollapsedButton"
:show-fixed-button="sidebarFixedButton"
:collapse-width="getSideCollapseWidth" :collapse-width="getSideCollapseWidth"
:dom-visible="!isMobile" :dom-visible="!isMobile"
:extra-width="sidebarExtraWidth" :extra-width="sidebarExtraWidth"

View File

@ -20,7 +20,7 @@ const props = withDefaults(defineProps<VbenButtonGroupProps>(), {
showIcon: true, showIcon: true,
size: 'middle', size: 'middle',
}); });
const emit = defineEmits(['btnClick']);
const btnDefaultProps = computed(() => { const btnDefaultProps = computed(() => {
return { return {
...objectOmit(props, ['options', 'btnClass', 'size', 'disabled']), ...objectOmit(props, ['options', 'btnClass', 'size', 'disabled']),
@ -90,6 +90,7 @@ async function onBtnClick(value: ValueType) {
innerValue.value = [value]; innerValue.value = [value];
modelValue.value = value; modelValue.value = value;
} }
emit('btnClick', value);
} }
</script> </script>
<template> <template>

View File

@ -192,6 +192,8 @@ const headerSlots = computed(() => {
:sidebar-collapse="preferences.sidebar.collapsed" :sidebar-collapse="preferences.sidebar.collapsed"
:sidebar-collapse-show-title="preferences.sidebar.collapsedShowTitle" :sidebar-collapse-show-title="preferences.sidebar.collapsedShowTitle"
:sidebar-enable="sidebarVisible" :sidebar-enable="sidebarVisible"
:sidebar-collapsed-button="preferences.sidebar.collapsedButton"
:sidebar-fixed-button="preferences.sidebar.fixedButton"
:sidebar-expand-on-hover="preferences.sidebar.expandOnHover" :sidebar-expand-on-hover="preferences.sidebar.expandOnHover"
:sidebar-extra-collapse="preferences.sidebar.extraCollapse" :sidebar-extra-collapse="preferences.sidebar.extraCollapse"
:sidebar-hidden="preferences.sidebar.hidden" :sidebar-hidden="preferences.sidebar.hidden"

View File

@ -0,0 +1,63 @@
<script setup lang="ts">
import type { SelectOption } from '@vben/types';
import { useSlots } from 'vue';
import { CircleHelp } from '@vben/icons';
import { VbenCheckButtonGroup, VbenTooltip } from '@vben-core/shadcn-ui';
defineOptions({
name: 'PreferenceCheckboxItem',
});
withDefaults(
defineProps<{
disabled?: boolean;
items: SelectOption[];
multiple?: boolean;
onBtnClick?: (value: string) => void;
placeholder?: string;
}>(),
{
disabled: false,
placeholder: '',
items: () => [],
onBtnClick: () => {},
multiple: false,
},
);
const inputValue = defineModel<string[]>();
const slots = useSlots();
</script>
<template>
<div
:class="{
'hover:bg-accent': !slots.tip,
'pointer-events-none opacity-50': disabled,
}"
class="my-1 flex w-full items-center justify-between rounded-md px-2 py-1"
>
<span class="flex items-center text-sm">
<slot></slot>
<VbenTooltip v-if="slots.tip" side="bottom">
<template #trigger>
<CircleHelp class="ml-1 size-3 cursor-help" />
</template>
<slot name="tip"></slot>
</VbenTooltip>
</span>
<VbenCheckButtonGroup
v-model="inputValue"
class="h-8 w-[165px]"
:options="items"
:disabled="disabled"
:multiple="multiple"
@btn-click="onBtnClick"
/>
</div>
</template>

View File

@ -1,8 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import type { LayoutType } from '@vben/types'; import type { LayoutType } from '@vben/types';
import { onMounted } from 'vue';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import CheckboxItem from '../checkbox-item.vue';
import NumberFieldItem from '../number-field-item.vue'; import NumberFieldItem from '../number-field-item.vue';
import SwitchItem from '../switch-item.vue'; import SwitchItem from '../switch-item.vue';
@ -18,6 +21,27 @@ const sidebarAutoActivateChild = defineModel<boolean>(
); );
const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed'); const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
const sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover'); const sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
const sidebarButtons = defineModel<string[]>('sidebarButtons', { default: [] });
const sidebarCollapsedButton = defineModel<boolean>('sidebarCollapsedButton');
const sidebarFixedButton = defineModel<boolean>('sidebarFixedButton');
onMounted(() => {
if (
sidebarCollapsedButton.value &&
!sidebarButtons.value.includes('collapsed')
) {
sidebarButtons.value.push('collapsed');
}
if (sidebarFixedButton.value && !sidebarButtons.value.includes('fixed')) {
sidebarButtons.value.push('fixed');
}
});
const handleCheckboxChange = () => {
sidebarCollapsedButton.value = !!sidebarButtons.value.includes('collapsed');
sidebarFixedButton.value = !!sidebarButtons.value.includes('fixed');
};
</script> </script>
<template> <template>
@ -53,6 +77,17 @@ const sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
> >
{{ $t('preferences.sidebar.autoActivateChild') }} {{ $t('preferences.sidebar.autoActivateChild') }}
</SwitchItem> </SwitchItem>
<CheckboxItem
:items="[
{ label: '收缩按钮', value: 'collapsed' },
{ label: '固定按钮', value: 'fixed' },
]"
multiple
v-model="sidebarButtons"
:on-btn-click="handleCheckboxChange"
>
按钮配置
</CheckboxItem>
<NumberFieldItem <NumberFieldItem
v-model="sidebarWidth" v-model="sidebarWidth"
:disabled="!sidebarEnable || disabled" :disabled="!sidebarEnable || disabled"

View File

@ -93,8 +93,9 @@ const sidebarCollapsedShowTitle = defineModel<boolean>(
const sidebarAutoActivateChild = defineModel<boolean>( const sidebarAutoActivateChild = defineModel<boolean>(
'sidebarAutoActivateChild', 'sidebarAutoActivateChild',
); );
const SidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover'); const sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
const sidebarCollapsedButton = defineModel<boolean>('sidebarCollapsedButton');
const sidebarFixedButton = defineModel<boolean>('sidebarFixedButton');
const headerEnable = defineModel<boolean>('headerEnable'); const headerEnable = defineModel<boolean>('headerEnable');
const headerMode = defineModel<LayoutHeaderModeType>('headerMode'); const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
const headerMenuAlign = const headerMenuAlign =
@ -317,8 +318,10 @@ async function handleReset() {
v-model:sidebar-collapsed="sidebarCollapsed" v-model:sidebar-collapsed="sidebarCollapsed"
v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle" v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle"
v-model:sidebar-enable="sidebarEnable" v-model:sidebar-enable="sidebarEnable"
v-model:sidebar-expand-on-hover="SidebarExpandOnHover" v-model:sidebar-expand-on-hover="sidebarExpandOnHover"
v-model:sidebar-width="sidebarWidth" v-model:sidebar-width="sidebarWidth"
v-model:sidebar-collapsed-button="sidebarCollapsedButton"
v-model:sidebar-fixed-button="sidebarFixedButton"
:current-layout="appLayout" :current-layout="appLayout"
:disabled="!isSideMode" :disabled="!isSideMode"
/> />