Appearance
Sidebar 侧边栏
侧边栏组件用于创建应用程序的导航侧边栏,支持多种布局、展开/折叠功能和响应式设计。
基础用法
首页
Create Project
Deploy your new project in one-click.vue
<script setup lang="ts">
import { ref, computed, defineAsyncComponent } from 'vue';
const CardDemo1 = defineAsyncComponent(() => import('../Card/CardDemo1.vue'));
const TabsDemo1 = defineAsyncComponent(() => import('../Tabs/TabsDemo1.vue'));
const GridDemo1 = defineAsyncComponent(() => import('../Grid/GridDemo1.vue'));
const FormDemo1 = defineAsyncComponent(() => import('../Form/FormDemo1.vue'));
const FormDemo2 = defineAsyncComponent(() => import('../Form/FormDemo2.vue'));
const ModalDemo1 = defineAsyncComponent(
() => import('../Modal/ModalDemo1.vue')
);
const DrawerDemo1 = defineAsyncComponent(
() => import('../Drawer/DrawerDemo.vue')
);
/**
* 侧边栏组件演示
* 包含侧边栏展开/收起、多级菜单、动态组件加载等功能
*/
// ==================== 状态管理 ====================
const active = ref(true); // 侧边栏展开/收起状态
const mainMenuActive = ref(true); // 主导航组展开状态
const settingsMenuActive = ref(false); // 设置导航组展开状态
const activeItem = ref('home'); // 当前激活的菜单项
// ==================== 菜单配置数据 ====================
/**
* 主导航菜单项配置
*/
const mainMenuItems = [
{ id: 'home', label: '首页', icon: 'house', component: CardDemo1 },
{
id: 'dashboard',
label: '仪表盘',
icon: 'chart-area',
component: TabsDemo1,
},
{ id: 'users', label: '用户管理', icon: 'users', component: GridDemo1 },
{ id: 'search', label: '搜索', icon: 'search', component: FormDemo1 },
];
/**
* 设置导航菜单项配置
*/
const settingsMenuItems = [
{ id: 'settings', label: '设置', icon: 'settings', component: FormDemo2 },
{
id: 'help',
label: '帮助中心',
icon: 'hand-helping',
component: ModalDemo1,
},
{ id: 'logout', label: '退出登录', icon: 'log-out', component: DrawerDemo1 },
];
// ==================== 计算属性 ====================
/**
* 获取当前激活菜单项的显示标签
*/
const currentActiveLabel = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.label ||
settingsMenuItems.find(item => item.id === activeItem.value)?.label
);
});
/**
* 获取当前激活菜单项对应的组件
*/
const currentActiveComponent = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.component ||
settingsMenuItems.find(item => item.id === activeItem.value)?.component
);
});
// ==================== 事件处理函数 ====================
/**
* 切换侧边栏展开/收起状态,默认组件内已更新active值
*/
const toggleSidebar = () => {
console.log('toggleSidebar', active.value);
};
/**
* 设置当前激活的菜单项
* @param itemId - 菜单项ID
*/
const setActiveItem = (itemId: string) => {
activeItem.value = itemId;
};
</script>
<template>
<div class="demo-container">
<SidebarProvider v-model:defaultOpen="active">
<div class="demo-content">
<!-- 侧边栏 -->
<Sidebar :collapsed-width="49" :width="236">
<!-- 侧边栏头部 -->
<SidebarHeader>
<div class="sidebar-title">{{ active ? 'Zui' : 'zui' }}</div>
</SidebarHeader>
<!-- 侧边栏内容 -->
<SidebarContent>
<SidebarMenu>
<!-- 主导航组 -->
<SidebarGroup v-model:default-open="mainMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="layout-grid" :size="18" />
<span>主导航</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': mainMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<div class="group-label">
<!-- <div class="group-icon-no-active"> -->
<Icon name="layout-grid" :size="18" />
<!-- </div> -->
</div>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem v-for="item in mainMenuItems" :key="item.id">
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
<SidebarMenuButton
v-else
asChild
center
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="
width: 100%;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
"
>
<Icon
class="menu-icon-collapsed"
:name="item.icon"
size="16"
/>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
<!-- 设置导航组 -->
<SidebarGroup v-model:default-open="settingsMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div
style="
display: flex;
align-items: center;
gap: 8px;
margin-top: var(--space-1);
"
>
<Icon name="codepen" :size="18" />
<span>系统</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': settingsMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="codepen" :size="18" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
<!-- 收起状态下的菜单按钮 -->
<SidebarMenuButton
v-else
asChild
center
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="
width: 100%;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
"
>
<Icon
class="menu-icon-collapsed"
:name="item.icon"
size="16"
/>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
</SidebarMenu>
</SidebarContent>
<!-- 侧边栏底部 -->
<SidebarFooter>
<div class="user-info" :class="{ collapsed: !active }">
<div class="user-avatar">
<Icon name="user" :size="24" />
</div>
<div v-if="active" class="user-details">
<div class="user-name">用户名称</div>
<div class="user-role">管理员</div>
</div>
</div>
</SidebarFooter>
</Sidebar>
<!-- 主内容区域 -->
<main class="main-content">
<div class="main-header">
<SidebarTrigger asChild @click="toggleSidebar">
<Icon :name="active ? 'panel-right' : 'panel-left'" :size="20" />
</SidebarTrigger>
<span>{{ currentActiveLabel }}</span>
</div>
<div class="main-body">
<component
v-if="currentActiveComponent"
:is="currentActiveComponent"
/>
</div>
</main>
</div>
</SidebarProvider>
</div>
</template>
<style scoped>
/* ==================== 布局样式 ==================== */
.demo-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.demo-content {
display: flex;
width: 100%;
min-height: 600px;
overflow: hidden;
}
/* ==================== 侧边栏样式 ==================== */
.sidebar-title {
font-size: 18px;
font-weight: 600;
text-align: center;
padding: var(--space-3) 0px;
color: var(--color-default);
}
/* ==================== 主内容区域样式 ==================== */
.main-content {
flex: 1;
overflow: hidden;
/* display: flex;
flex-direction: column; */
}
.main-header {
display: flex;
align-items: center;
padding: var(--padding-3);
border-bottom: 1px solid var(--color-border-1);
gap: var(--space-2);
width: 100%;
}
.main-body {
flex: 1;
padding: var(--padding-3);
height: calc(100% - var(--sidebar-header-height));
overflow: auto;
}
/* ==================== 菜单相关样式 ==================== */
.menu-icon {
height: 22px;
font-size: 16px;
}
.menu-icon-collapsed {
/* width: 100%; */
/* padding: var(--padding-1); */
display: flex;
align-items: center;
justify-content: center;
/* height: 22px; */
/* font-size: 18px; */
}
.group-label {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
/* ==================== 用户信息样式 ==================== */
.user-info {
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
padding: 8px 0;
width: 100%;
transition: opacity 0.9s ease;
}
.user-info.collapsed {
justify-content: center;
}
.user-avatar {
border: 1px solid var(--color-border-1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-primary);
width: 32px;
height: 32px;
}
.user-details {
transition: opacity 0.9s ease;
}
.user-name {
font-size: 14px;
font-weight: 900;
}
.user-role {
font-size: 12px;
color: var(--color-text-muted);
}
/* ==================== 动画样式 ==================== */
.rotate-icon {
transition: transform 0.3s ease;
}
.rotate-180 {
transform: rotate(180deg);
}
</style>响应式侧边栏
通过响应式设计,侧边栏可以根据屏幕尺寸自动调整其行为,支持折叠和展开功能。
设置 enableResponsive 属性为 true 即可启用响应式侧边栏。
v-model:defaultOpen="active" 绑定侧边栏展开/收起状态,当屏幕宽度小于 breakpoint 时,侧边栏会自动折叠,active 值会自动设置为 false。
v-model:isMobile="isMobile" 判定是为移动设备,当屏幕宽度小于 breakpoint-mobile 时,isMobile 值会自动设置为 true。
设置 breakpoint 属性为屏幕宽度阈值,当屏幕宽度小于该值时,active 值会自动设置为 false。
设置 breakpoint-mobile 属性为移动设备屏幕宽度阈值,当屏幕宽度小于该值时,isMobile 值会自动设置为 true。
首页
Create Project
Deploy your new project in one-click.vue
<script setup lang="ts">
import { ref, computed, defineAsyncComponent } from 'vue';
const CardDemo1 = defineAsyncComponent(() => import('../Card/CardDemo1.vue'));
const TabsDemo1 = defineAsyncComponent(() => import('../Tabs/TabsDemo1.vue'));
const GridDemo1 = defineAsyncComponent(() => import('../Grid/GridDemo1.vue'));
const FormDemo1 = defineAsyncComponent(() => import('../Form/FormDemo1.vue'));
const FormDemo2 = defineAsyncComponent(() => import('../Form/FormDemo2.vue'));
const ModalDemo1 = defineAsyncComponent(
() => import('../Modal/ModalDemo1.vue')
);
const DrawerDemo1 = defineAsyncComponent(
() => import('../Drawer/DrawerDemo.vue')
);
/**
* 侧边栏组件演示
* 包含侧边栏展开/收起、多级菜单、动态组件加载等功能
*/
// ==================== 状态管理 ====================
const active = ref(false); // 侧边栏展开/收起状态
const mainMenuActive = ref(true); // 主导航组展开状态
const settingsMenuActive = ref(false); // 设置导航组展开状态
const activeItem = ref('home'); // 当前激活的菜单项
const isMobile = ref(false); // 是否为移动设备
// ==================== 菜单配置数据 ====================
/**
* 主导航菜单项配置
*/
const mainMenuItems = [
{ id: 'home', label: '首页', icon: 'house', component: CardDemo1 },
{
id: 'dashboard',
label: '仪表盘',
icon: 'chart-area',
component: TabsDemo1,
},
{ id: 'users', label: '用户管理', icon: 'users', component: GridDemo1 },
{ id: 'search', label: '搜索', icon: 'search', component: FormDemo1 },
];
/**
* 设置导航菜单项配置
*/
const settingsMenuItems = [
{ id: 'settings', label: '设置', icon: 'settings', component: FormDemo2 },
{
id: 'help',
label: '帮助中心',
icon: 'hand-helping',
component: ModalDemo1,
},
{ id: 'logout', label: '退出登录', icon: 'log-out', component: DrawerDemo1 },
];
// ==================== 计算属性 ====================
/**
* 获取当前激活菜单项的显示标签
*/
const currentActiveLabel = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.label ||
settingsMenuItems.find(item => item.id === activeItem.value)?.label
);
});
/**
* 获取当前激活菜单项对应的组件
*/
const currentActiveComponent = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.component ||
settingsMenuItems.find(item => item.id === activeItem.value)?.component
);
});
// ==================== 事件处理函数 ====================
/**
* 切换侧边栏展开/收起状态,默认组件内已更新active值
*/
const toggleSidebar = () => {
console.log('toggleSidebar', active.value);
};
const BREAKPOINT = 1024;
const BREAKPOINT_MOBILE = 768;
/**
* 设置当前激活的菜单项
* @param itemId - 菜单项ID
*/
const setActiveItem = (itemId: string) => {
activeItem.value = itemId;
};
</script>
<template>
<div class="demo-container">
<SidebarProvider
v-model:defaultOpen="active"
v-model:isMobile="isMobile"
:breakpoint="BREAKPOINT"
:breakpoint-mobile="BREAKPOINT_MOBILE"
enableResponsive="true"
>
<div class="demo-content">
<!-- 侧边栏 -->
<Sidebar :collapsed-width="49" :width="236" v-if="!isMobile">
<!-- 侧边栏头部 -->
<SidebarHeader>
<div class="sidebar-title">{{ active ? 'Zui' : 'zui' }}</div>
</SidebarHeader>
<!-- 侧边栏内容 -->
<SidebarContent>
<SidebarMenu>
<!-- 主导航组 -->
<SidebarGroup v-model:default-open="mainMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="layout-grid" :size="18" />
<span>主导航</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': mainMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<Popover placement="right-start" :offset="20" :arrow="false">
<div class="group-label">
<Icon name="layout-grid" :size="18" />
</div>
<template #content>
<SidebarMenuItem
v-for="item in mainMenuItems"
:key="item.id"
>
<SidebarMenuButton
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="margin: 6px 0px"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</template>
</Popover>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem v-for="item in mainMenuItems" :key="item.id">
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
<!-- 设置导航组 -->
<SidebarGroup v-model:default-open="settingsMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="codepen" :size="18" />
<span>系统</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': settingsMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<Popover placement="right-start" :offset="20" :arrow="false">
<div class="group-label">
<Icon name="codepen" :size="18" />
</div>
<template #content>
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<SidebarMenuButton
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="margin: 6px 0px"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</template>
</Popover>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
</SidebarMenu>
</SidebarContent>
<!-- 侧边栏底部 -->
<SidebarFooter>
<div class="user-info" :class="{ collapsed: !active }">
<div class="user-avatar">
<Icon name="user" :size="24" />
</div>
<div v-if="active" class="user-details">
<div class="user-name">用户名称</div>
<div class="user-role">管理员</div>
</div>
</div>
</SidebarFooter>
</Sidebar>
<Sidebar
:collapsed-width="49"
:width="236"
v-else-if="isMobile && active"
>
<!-- 侧边栏头部 -->
<SidebarHeader>
<div class="sidebar-title">{{ active ? 'Zui' : 'zui' }}</div>
</SidebarHeader>
<!-- 侧边栏内容 -->
<SidebarContent v-if="active">
<SidebarMenu>
<!-- 主导航组 -->
<SidebarGroup v-model:default-open="mainMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="layout-grid" :size="18" />
<span>主导航</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': mainMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<Popover placement="right-start" :offset="20" :arrow="false">
<div class="group-label">
<Icon name="layout-grid" :size="18" />
</div>
<template #content placement="right">
<SidebarMenuItem
v-for="item in mainMenuItems"
:key="item.id"
>
<SidebarMenuButton
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="margin: 6px 0px"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</template>
</Popover>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem v-for="item in mainMenuItems" :key="item.id">
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
<!-- 设置导航组 -->
<SidebarGroup v-model:default-open="settingsMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="codepen" :size="18" />
<span>系统</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': settingsMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<Popover placement="right-start" :offset="20" :arrow="false">
<div class="group-label">
<Icon name="codepen" :size="18" />
</div>
<template #content placement="right">
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<SidebarMenuButton
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="margin: 6px 0px"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</template>
</Popover>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
</SidebarMenu>
</SidebarContent>
<!-- 侧边栏底部 -->
<SidebarFooter v-if="active">
<div class="user-info" :class="{ collapsed: !active }">
<div class="user-avatar">
<Icon name="user" :size="24" />
</div>
<div v-if="active" class="user-details">
<div class="user-name">用户名称</div>
<div class="user-role">管理员</div>
</div>
</div>
</SidebarFooter>
</Sidebar>
<!-- 主内容区域 -->
<main class="main-content">
<div class="main-header">
<SidebarTrigger asChild @click="toggleSidebar">
<Icon :name="active ? 'panel-right' : 'panel-left'" :size="20" />
</SidebarTrigger>
<span>{{ currentActiveLabel }}</span>
</div>
<div class="main-body">
<component
v-if="currentActiveComponent"
:is="currentActiveComponent"
/>
</div>
</main>
</div>
</SidebarProvider>
</div>
</template>
<style scoped>
/* ==================== 布局样式 ==================== */
.demo-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.demo-content {
display: flex;
width: 100%;
min-height: 600px;
overflow: hidden;
}
/* ==================== 侧边栏样式 ==================== */
.sidebar-title {
font-size: 18px;
font-weight: 600;
text-align: center;
padding: var(--space-3) 0px;
color: var(--color-default);
}
/* ==================== 主内容区域样式 ==================== */
.main-content {
flex: 1;
overflow: hidden;
/* display: flex;
flex-direction: column; */
}
.main-header {
display: flex;
align-items: center;
padding: var(--padding-3);
border-bottom: 1px solid var(--color-border-1);
gap: var(--space-2);
width: 100%;
}
.main-body {
flex: 1;
padding: var(--padding-3);
height: calc(100% - var(--sidebar-header-height));
overflow: auto;
}
/* ==================== 菜单相关样式 ==================== */
.menu-icon {
height: 22px;
font-size: 16px;
}
.menu-icon-collapsed {
/* width: 100%; */
/* padding: var(--padding-1); */
display: flex;
align-items: center;
justify-content: center;
/* height: 22px; */
/* font-size: 18px; */
}
.group-label {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
/* ==================== 用户信息样式 ==================== */
.user-info {
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
padding: 8px 0;
width: 100%;
transition: opacity 0.9s ease;
}
.user-info.collapsed {
justify-content: center;
}
.user-avatar {
border: 1px solid var(--color-border-1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-primary);
width: 32px;
height: 32px;
}
.user-details {
transition: opacity 0.9s ease;
}
.user-name {
font-size: 14px;
font-weight: 900;
}
.user-role {
font-size: 12px;
color: var(--color-text-muted);
}
/* ==================== 动画样式 ==================== */
.rotate-icon {
transition: transform 0.3s ease;
}
.rotate-180 {
transform: rotate(180deg);
}
</style>嵌套导航侧边栏
通过侧边栏的分组功能,可以创建带有层级结构的嵌套导航菜单,适用于复杂的应用导航场景。
首页
Create Project
Deploy your new project in one-click.vue
<script setup lang="ts">
import { ref, computed, defineAsyncComponent } from 'vue';
const CardDemo1 = defineAsyncComponent(() => import('../Card/CardDemo1.vue'));
const TabsDemo1 = defineAsyncComponent(() => import('../Tabs/TabsDemo1.vue'));
const GridDemo1 = defineAsyncComponent(() => import('../Grid/GridDemo1.vue'));
const FormDemo1 = defineAsyncComponent(() => import('../Form/FormDemo1.vue'));
const FormDemo2 = defineAsyncComponent(() => import('../Form/FormDemo2.vue'));
const ModalDemo1 = defineAsyncComponent(
() => import('../Modal/ModalDemo1.vue')
);
const DrawerDemo1 = defineAsyncComponent(
() => import('../Drawer/DrawerDemo.vue')
);
/**
* 侧边栏组件演示
* 包含侧边栏展开/收起、多级菜单、动态组件加载等功能
*/
// ==================== 状态管理 ====================
const active = ref(false); // 侧边栏展开/收起状态
const mainMenuActive = ref(true); // 主导航组展开状态
const settingsMenuActive = ref(false); // 设置导航组展开状态
const activeItem = ref('home'); // 当前激活的菜单项
const isMobile = ref(false); // 是否为移动设备
// ==================== 菜单配置数据 ====================
/**
* 主导航菜单项配置
*/
const mainMenuItems = [
{ id: 'home', label: '首页', icon: 'house', component: CardDemo1 },
{
id: 'dashboard',
label: '仪表盘',
icon: 'chart-area',
component: TabsDemo1,
},
{ id: 'users', label: '用户管理', icon: 'users', component: GridDemo1 },
{ id: 'search', label: '搜索', icon: 'search', component: FormDemo1 },
];
/**
* 设置导航菜单项配置
*/
const settingsMenuItems = [
{ id: 'settings', label: '设置', icon: 'settings', component: FormDemo2 },
{
id: 'help',
label: '帮助中心',
icon: 'hand-helping',
component: ModalDemo1,
},
{ id: 'logout', label: '退出登录', icon: 'log-out', component: DrawerDemo1 },
];
// ==================== 计算属性 ====================
/**
* 获取当前激活菜单项的显示标签
*/
const currentActiveLabel = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.label ||
settingsMenuItems.find(item => item.id === activeItem.value)?.label
);
});
/**
* 获取当前激活菜单项对应的组件
*/
const currentActiveComponent = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.component ||
settingsMenuItems.find(item => item.id === activeItem.value)?.component
);
});
// ==================== 事件处理函数 ====================
/**
* 切换侧边栏展开/收起状态,默认组件内已更新active值
*/
const toggleSidebar = () => {
console.log('toggleSidebar', active.value);
};
const BREAKPOINT = 1024;
const BREAKPOINT_MOBILE = 768;
/**
* 设置当前激活的菜单项
* @param itemId - 菜单项ID
*/
const setActiveItem = (itemId: string) => {
activeItem.value = itemId;
};
</script>
<template>
<div class="demo-container">
<SidebarProvider
v-model:defaultOpen="active"
v-model:isMobile="isMobile"
:breakpoint="BREAKPOINT"
:breakpoint-mobile="BREAKPOINT_MOBILE"
enableResponsive="true"
>
<div class="demo-content">
<!-- 侧边栏 -->
<Sidebar :collapsed-width="49" :width="236" v-if="!isMobile">
<!-- 侧边栏头部 -->
<SidebarHeader>
<div class="sidebar-title">{{ active ? 'Zui' : 'zui' }}</div>
</SidebarHeader>
<!-- 侧边栏内容 -->
<SidebarContent>
<SidebarMenu>
<!-- 主导航组 -->
<SidebarGroup v-model:default-open="mainMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="layout-grid" :size="18" />
<span>主导航</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': mainMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<Popover placement="right-start" :offset="20" :arrow="false">
<div class="group-label">
<Icon name="layout-grid" :size="18" />
</div>
<template #content placement="right">
<SidebarMenuItem
v-for="item in mainMenuItems"
:key="item.id"
>
<SidebarMenuButton
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="margin: 6px 0px"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</template>
</Popover>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem v-for="item in mainMenuItems" :key="item.id">
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
<!-- 设置导航组 -->
<SidebarGroup v-model:default-open="settingsMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="codepen" :size="18" />
<span>系统</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': settingsMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<Popover placement="right-start" :offset="20" :arrow="false">
<div class="group-label">
<Icon name="codepen" :size="18" />
</div>
<template #content placement="right">
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<SidebarMenuButton
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="margin: 6px 0px"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</template>
</Popover>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
</SidebarMenu>
</SidebarContent>
<!-- 侧边栏底部 -->
<SidebarFooter>
<div class="user-info" :class="{ collapsed: !active }">
<div class="user-avatar">
<Icon name="user" :size="24" />
</div>
<div v-if="active" class="user-details">
<div class="user-name">用户名称</div>
<div class="user-role">管理员</div>
</div>
</div>
</SidebarFooter>
</Sidebar>
<Sidebar
:collapsed-width="49"
:width="236"
v-else-if="isMobile && active"
>
<!-- 侧边栏头部 -->
<SidebarHeader>
<div class="sidebar-title">{{ active ? 'Zui' : 'zui' }}</div>
</SidebarHeader>
<!-- 侧边栏内容 -->
<SidebarContent v-if="active">
<SidebarMenu>
<!-- 主导航组 -->
<SidebarGroup v-model:default-open="mainMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="layout-grid" :size="18" />
<span>主导航</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': mainMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<Popover placement="right-start" :offset="20" :arrow="false">
<div class="group-label">
<Icon name="layout-grid" :size="18" />
</div>
<template #content placement="right">
<SidebarMenuItem
v-for="item in mainMenuItems"
:key="item.id"
>
<SidebarMenuButton
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="margin: 6px 0px"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</template>
</Popover>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem v-for="item in mainMenuItems" :key="item.id">
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
<!-- 设置导航组 -->
<SidebarGroup v-model:default-open="settingsMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="codepen" :size="18" />
<span>系统</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': settingsMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<Popover placement="right-start" :offset="20" :arrow="false">
<div class="group-label">
<Icon name="codepen" :size="18" />
</div>
<template #content placement="right">
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<SidebarMenuButton
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="margin: 6px 0px"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</template>
</Popover>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
</SidebarMenu>
</SidebarContent>
<!-- 侧边栏底部 -->
<SidebarFooter v-if="active">
<div class="user-info" :class="{ collapsed: !active }">
<div class="user-avatar">
<Icon name="user" :size="24" />
</div>
<div v-if="active" class="user-details">
<div class="user-name">用户名称</div>
<div class="user-role">管理员</div>
</div>
</div>
</SidebarFooter>
</Sidebar>
<!-- 主内容区域 -->
<main class="main-content">
<div class="main-header">
<SidebarTrigger asChild @click="toggleSidebar">
<Icon :name="active ? 'panel-right' : 'panel-left'" :size="20" />
</SidebarTrigger>
<span>{{ currentActiveLabel }}</span>
</div>
<div class="main-body">
<component
v-if="currentActiveComponent"
:is="currentActiveComponent"
/>
</div>
</main>
</div>
</SidebarProvider>
</div>
</template>
<style scoped>
/* ==================== 布局样式 ==================== */
.demo-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.demo-content {
display: flex;
width: 100%;
min-height: 600px;
overflow: hidden;
}
/* ==================== 侧边栏样式 ==================== */
.sidebar-title {
font-size: 18px;
font-weight: 600;
text-align: center;
padding: var(--space-3) 0px;
color: var(--color-default);
}
/* ==================== 主内容区域样式 ==================== */
.main-content {
flex: 1;
overflow: hidden;
/* display: flex;
flex-direction: column; */
}
.main-header {
display: flex;
align-items: center;
padding: var(--padding-3);
border-bottom: 1px solid var(--color-border-1);
gap: var(--space-2);
width: 100%;
}
.main-body {
flex: 1;
padding: var(--padding-3);
height: calc(100% - var(--sidebar-header-height));
overflow: auto;
}
/* ==================== 菜单相关样式 ==================== */
.menu-icon {
height: 22px;
font-size: 16px;
}
.menu-icon-collapsed {
/* width: 100%; */
/* padding: var(--padding-1); */
display: flex;
align-items: center;
justify-content: center;
/* height: 22px; */
/* font-size: 18px; */
}
.group-label {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
/* ==================== 用户信息样式 ==================== */
.user-info {
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
padding: 8px 0;
width: 100%;
transition: opacity 0.9s ease;
}
.user-info.collapsed {
justify-content: center;
}
.user-avatar {
border: 1px solid var(--color-border-1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-primary);
width: 32px;
height: 32px;
}
.user-details {
transition: opacity 0.9s ease;
}
.user-name {
font-size: 14px;
font-weight: 900;
}
.user-role {
font-size: 12px;
color: var(--color-text-muted);
}
/* ==================== 动画样式 ==================== */
.rotate-icon {
transition: transform 0.3s ease;
}
.rotate-180 {
transform: rotate(180deg);
}
</style>自定义样式侧边栏
侧边栏支持丰富的样式自定义选项,可以根据品牌需求定制不同的主题风格和视觉效果。
首页
Create Project
Deploy your new project in one-click.vue
<script setup lang="ts">
import { ref, computed, defineAsyncComponent } from 'vue';
const CardDemo1 = defineAsyncComponent(() => import('../Card/CardDemo1.vue'));
const TabsDemo1 = defineAsyncComponent(() => import('../Tabs/TabsDemo1.vue'));
const GridDemo1 = defineAsyncComponent(() => import('../Grid/GridDemo1.vue'));
const FormDemo1 = defineAsyncComponent(() => import('../Form/FormDemo1.vue'));
const FormDemo2 = defineAsyncComponent(() => import('../Form/FormDemo2.vue'));
const ModalDemo1 = defineAsyncComponent(
() => import('../Modal/ModalDemo1.vue')
);
const DrawerDemo1 = defineAsyncComponent(
() => import('../Drawer/DrawerDemo.vue')
);
/**
* 侧边栏组件演示
* 包含侧边栏展开/收起、多级菜单、动态组件加载等功能
*/
// ==================== 状态管理 ====================
const active = ref(true); // 侧边栏展开/收起状态
const mainMenuActive = ref(true); // 主导航组展开状态
const settingsMenuActive = ref(false); // 设置导航组展开状态
const activeItem = ref('home'); // 当前激活的菜单项
// ==================== 菜单配置数据 ====================
/**
* 主导航菜单项配置
*/
const mainMenuItems = [
{ id: 'home', label: '首页', icon: 'house', component: CardDemo1 },
{
id: 'dashboard',
label: '仪表盘',
icon: 'chart-area',
component: TabsDemo1,
},
{ id: 'users', label: '用户管理', icon: 'users', component: GridDemo1 },
{ id: 'search', label: '搜索', icon: 'search', component: FormDemo1 },
];
/**
* 设置导航菜单项配置
*/
const settingsMenuItems = [
{ id: 'settings', label: '设置', icon: 'settings', component: FormDemo2 },
{
id: 'help',
label: '帮助中心',
icon: 'hand-helping',
component: ModalDemo1,
},
{ id: 'logout', label: '退出登录', icon: 'log-out', component: DrawerDemo1 },
];
// ==================== 计算属性 ====================
/**
* 获取当前激活菜单项的显示标签
*/
const currentActiveLabel = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.label ||
settingsMenuItems.find(item => item.id === activeItem.value)?.label
);
});
/**
* 获取当前激活菜单项对应的组件
*/
const currentActiveComponent = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.component ||
settingsMenuItems.find(item => item.id === activeItem.value)?.component
);
});
// ==================== 事件处理函数 ====================
/**
* 切换侧边栏展开/收起状态,默认组件内已更新active值
*/
const toggleSidebar = () => {
console.log('toggleSidebar', active.value);
};
/**
* 设置当前激活的菜单项
* @param itemId - 菜单项ID
*/
const setActiveItem = (itemId: string) => {
activeItem.value = itemId;
};
</script>
<template>
<div class="demo-container">
<SidebarProvider v-model:defaultOpen="active">
<div class="demo-content">
<!-- 侧边栏 -->
<Sidebar
:collapsed-width="49"
:width="236"
style="
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
background-color: var(--color-default-text-1);
"
>
<!-- 侧边栏头部 -->
<SidebarHeader>
<div class="sidebar-title">{{ active ? 'Zui' : 'zui' }}</div>
</SidebarHeader>
<!-- 侧边栏内容 -->
<SidebarContent>
<SidebarMenu>
<!-- 主导航组 -->
<SidebarGroup v-model:default-open="mainMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="layout-grid" :size="18" />
<span>主导航</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': mainMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<div class="group-label">
<!-- <div class="group-icon-no-active"> -->
<Icon name="layout-grid" :size="18" />
<!-- </div> -->
</div>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem v-for="item in mainMenuItems" :key="item.id">
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
<SidebarMenuButton
v-else
asChild
center
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="
width: 100%;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
"
>
<Icon
class="menu-icon-collapsed"
:name="item.icon"
size="16"
/>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
<!-- 设置导航组 -->
<SidebarGroup v-model:default-open="settingsMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div
style="
display: flex;
align-items: center;
gap: 8px;
margin-top: var(--space-1);
"
>
<Icon name="codepen" :size="18" />
<span>系统</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': settingsMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="codepen" :size="18" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
<!-- 收起状态下的菜单按钮 -->
<SidebarMenuButton
v-else
asChild
center
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="
width: 100%;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
"
>
<Icon
class="menu-icon-collapsed"
:name="item.icon"
size="16"
/>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
</SidebarMenu>
</SidebarContent>
<!-- 侧边栏底部 -->
<SidebarFooter>
<div class="user-info" :class="{ collapsed: !active }">
<div class="user-avatar">
<Icon name="user" :size="24" />
</div>
<div v-if="active" class="user-details">
<div class="user-name">用户名称</div>
<div class="user-role">管理员</div>
</div>
</div>
</SidebarFooter>
</Sidebar>
<!-- 主内容区域 -->
<main class="main-content">
<div class="main-header">
<SidebarTrigger asChild @click="toggleSidebar">
<Icon
:name="active ? 'panel-right' : 'panel-left'"
:size="20"
color="var(--color-default-text-1)"
/>
</SidebarTrigger>
<span>{{ currentActiveLabel }}</span>
</div>
<div class="main-body">
<component
v-if="currentActiveComponent"
:is="currentActiveComponent"
/>
</div>
</main>
</div>
</SidebarProvider>
</div>
</template>
<style scoped>
/* ==================== 布局样式 ==================== */
.demo-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.demo-content {
display: flex;
width: 100%;
min-height: 600px;
overflow: hidden;
}
/* ==================== 侧边栏样式 ==================== */
.sidebar-title {
font-size: 18px;
font-weight: 600;
text-align: center;
padding: var(--space-3) 0px;
color: var(--color-default-text-1);
background-color: var(--color-primary);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* ==================== 主内容区域样式 ==================== */
.main-content {
flex: 1;
overflow: hidden;
/* display: flex;
flex-direction: column; */
}
.main-header {
display: flex;
align-items: center;
padding: var(--padding-3);
/* border-bottom: 1px solid var(--color-border-1); */
color: var(--color-default-text-1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
gap: var(--space-2);
width: 100%;
background-color: var(--color-primary);
}
.main-body {
flex: 1;
padding: var(--padding-3);
height: calc(100% - var(--sidebar-header-height));
overflow: auto;
}
/* ==================== 菜单相关样式 ==================== */
.menu-icon {
height: 22px;
font-size: 16px;
}
.menu-icon-collapsed {
/* width: 100%; */
/* padding: var(--padding-1); */
display: flex;
align-items: center;
justify-content: center;
/* height: 22px; */
/* font-size: 18px; */
}
.group-label {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
/* ==================== 用户信息样式 ==================== */
.user-info {
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
padding: 8px 0;
width: 100%;
transition: opacity 0.9s ease;
}
.user-info.collapsed {
justify-content: center;
}
.user-avatar {
border: 1px solid var(--color-border-1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-primary);
width: 32px;
height: 32px;
}
.user-details {
transition: opacity 0.9s ease;
}
.user-name {
font-size: 14px;
font-weight: 900;
}
.user-role {
font-size: 12px;
color: var(--color-text-muted);
}
/* ==================== 动画样式 ==================== */
.rotate-icon {
transition: transform 0.3s ease;
}
.rotate-180 {
transform: rotate(180deg);
}
</style>Create Project
Deploy your new project in one-click.vue
<script setup lang="ts">
import { ref, computed, defineAsyncComponent } from 'vue';
const CardDemo1 = defineAsyncComponent(() => import('../Card/CardDemo1.vue'));
const TabsDemo1 = defineAsyncComponent(() => import('../Tabs/TabsDemo1.vue'));
const GridDemo1 = defineAsyncComponent(() => import('../Grid/GridDemo1.vue'));
const FormDemo1 = defineAsyncComponent(() => import('../Form/FormDemo1.vue'));
const FormDemo2 = defineAsyncComponent(() => import('../Form/FormDemo2.vue'));
const ModalDemo1 = defineAsyncComponent(
() => import('../Modal/ModalDemo1.vue')
);
const DrawerDemo1 = defineAsyncComponent(
() => import('../Drawer/DrawerDemo.vue')
);
/**
* 侧边栏组件演示
* 包含侧边栏展开/收起、多级菜单、动态组件加载等功能
*/
// ==================== 状态管理 ====================
const active = ref(true); // 侧边栏展开/收起状态
const mainMenuActive = ref(true); // 主导航组展开状态
const settingsMenuActive = ref(false); // 设置导航组展开状态
const activeItem = ref('home'); // 当前激活的菜单项
// ==================== 菜单配置数据 ====================
/**
* 主导航菜单项配置
*/
const mainMenuItems = [
{ id: 'home', label: '首页', icon: 'house', component: CardDemo1 },
{
id: 'dashboard',
label: '仪表盘',
icon: 'chart-area',
component: TabsDemo1,
},
{ id: 'users', label: '用户管理', icon: 'users', component: GridDemo1 },
{ id: 'search', label: '搜索', icon: 'search', component: FormDemo1 },
];
/**
* 设置导航菜单项配置
*/
const settingsMenuItems = [
{ id: 'settings', label: '设置', icon: 'settings', component: FormDemo2 },
{
id: 'help',
label: '帮助中心',
icon: 'hand-helping',
component: ModalDemo1,
},
{ id: 'logout', label: '退出登录', icon: 'log-out', component: DrawerDemo1 },
];
// ==================== 计算属性 ====================
/**
* 获取当前激活菜单项的显示标签
*/
const currentActiveLabel = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.label ||
settingsMenuItems.find(item => item.id === activeItem.value)?.label
);
});
/**
* 获取当前激活菜单项对应的组件
*/
const currentActiveComponent = computed(() => {
return (
mainMenuItems.find(item => item.id === activeItem.value)?.component ||
settingsMenuItems.find(item => item.id === activeItem.value)?.component
);
});
// ==================== 事件处理函数 ====================
/**
* 切换侧边栏展开/收起状态,默认组件内已更新active值
*/
const toggleSidebar = () => {
console.log('toggleSidebar', active.value);
};
/**
* 设置当前激活的菜单项
* @param itemId - 菜单项ID
*/
const setActiveItem = (itemId: string) => {
activeItem.value = itemId;
};
</script>
<template>
<div class="demo-container">
<SidebarProvider v-model:defaultOpen="active" style="border: none">
<div class="demo-content">
<!-- 侧边栏 -->
<Sidebar
:collapsed-width="49"
:width="236"
style="
border: none;
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); */
/* background-color: var(--color-default-text-1); */
"
>
<!-- 侧边栏头部 -->
<!-- 侧边栏内容 -->
<SidebarContent
style="
border-radius: 0px 18px 18px 0px;
/* padding: 12px; */
margin: 12px 12px 12px 0px;
border: 1px solid var(--color-border-1);
background-color: var(--color-text-bg);
"
>
<SidebarHeader>
<div class="sidebar-title">
<SidebarTrigger asChild @click="toggleSidebar">
<Icon
:name="active ? 'panel-right' : 'panel-left'"
:size="20"
color="var(--color-text-1)"
/>
</SidebarTrigger>
{{ active ? 'Zui' : 'zui' }}
</div>
</SidebarHeader>
<SidebarMenu>
<!-- 主导航组 -->
<SidebarGroup v-model:default-open="mainMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="layout-grid" :size="18" />
<span>主导航</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': mainMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<div class="group-label">
<!-- <div class="group-icon-no-active"> -->
<Icon name="layout-grid" :size="16" />
<!-- </div> -->
</div>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem v-for="item in mainMenuItems" :key="item.id">
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
<SidebarMenuButton
v-else
asChild
center
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="
width: 100%;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
"
>
<Icon
class="menu-icon-collapsed"
:name="item.icon"
size="16"
/>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
<!-- 设置导航组 -->
<SidebarGroup v-model:default-open="settingsMenuActive">
<SidebarGroupLabel v-if="active">
<div class="group-label">
<div
style="
display: flex;
align-items: center;
gap: 8px;
margin-top: var(--space-1);
"
>
<Icon name="codepen" :size="18" />
<span>系统</span>
</div>
<div
class="rotate-icon"
:class="{ 'rotate-180': settingsMenuActive }"
>
<Icon name="chevron-down" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupLabel v-if="!active">
<div class="group-label">
<div style="display: flex; align-items: center; gap: 8px">
<Icon name="codepen" :size="18" />
</div>
</div>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenuItem
v-for="item in settingsMenuItems"
:key="item.id"
>
<!-- 展开状态下的菜单按钮 -->
<SidebarMenuButton
v-if="active"
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
>
<Icon class="menu-icon" :name="item.icon" size="16" />
<span>{{ item.label }}</span>
</SidebarMenuButton>
<!-- 收起状态下的菜单按钮 -->
<SidebarMenuButton
v-else
asChild
center
:active="activeItem === item.id"
@click="setActiveItem(item.id)"
style="
width: 100%;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
"
>
<Icon
class="menu-icon-collapsed"
:name="item.icon"
size="16"
/>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
</SidebarMenu>
</SidebarContent>
<!-- 侧边栏底部 -->
<SidebarFooter> </SidebarFooter>
</Sidebar>
<!-- 主内容区域 -->
<main class="main-content">
<!-- <div class="main-header">
<SidebarTrigger asChild @click="toggleSidebar">
<Icon
:name="active ? 'panel-right' : 'panel-left'"
:size="20"
color="var(--color-default-text-1)"
/>
</SidebarTrigger>
<span>{{ currentActiveLabel }}</span>
</div> -->
<div class="main-body">
<component
v-if="currentActiveComponent"
:is="currentActiveComponent"
/>
</div>
</main>
</div>
</SidebarProvider>
</div>
</template>
<style scoped>
/* ==================== 布局样式 ==================== */
.demo-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.demo-content {
display: flex;
width: 100%;
min-height: 600px;
overflow: hidden;
}
/* ==================== 侧边栏样式 ==================== */
.sidebar-title {
font-size: 18px;
font-weight: 600;
text-align: center;
padding: var(--space-3) 0px;
color: var(--color-text-1);
/* background-color: var(--color-primary); */
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); */
}
/* ==================== 主内容区域样式 ==================== */
.main-content {
flex: 1;
overflow: hidden;
border-radius: 18px 0px 0px 18px;
background-color: var(--color-default-text-1);
border: 1px solid var(--color-border-1);
margin: 12px 0px 12px 0px;
}
.main-header {
display: flex;
align-items: center;
padding: var(--padding-3);
/* border-bottom: 1px solid var(--color-border-1); */
color: var(--color-default-text-1);
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); */
gap: var(--space-2);
width: 100%;
/* background-color: var(--color-primary); */
}
.main-body {
flex: 1;
padding: var(--padding-3);
height: calc(100% - var(--sidebar-header-height));
overflow: auto;
}
/* ==================== 菜单相关样式 ==================== */
.menu-icon {
height: 22px;
font-size: 16px;
}
.menu-icon-collapsed {
/* width: 100%; */
/* padding: var(--padding-1); */
display: flex;
align-items: center;
justify-content: center;
/* height: 22px; */
/* font-size: 18px; */
}
.group-label {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
/* ==================== 用户信息样式 ==================== */
.user-info {
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
padding: 8px 0;
width: 100%;
transition: opacity 0.9s ease;
}
.user-info.collapsed {
justify-content: center;
}
.user-avatar {
border: 1px solid var(--color-border-1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-primary);
width: 32px;
height: 32px;
}
.user-details {
transition: opacity 0.9s ease;
}
.user-name {
font-size: 14px;
font-weight: 900;
}
.user-role {
font-size: 12px;
color: var(--color-text-muted);
}
/* ==================== 动画样式 ==================== */
.rotate-icon {
transition: transform 0.3s ease;
}
.rotate-180 {
transform: rotate(180deg);
}
</style>侧边栏提供简单模式,只需配置。
vue
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const menuConfig: any[] = [
{
id: 'home',
label: '首页',
icon: 'house',
component: defineAsyncComponent(() => import('../Card/CardDemo1.vue'))
},
{
id: 'dashboard',
label: '仪表盘',
icon: 'chart-area',
component: defineAsyncComponent(() => import('../Tabs/TabsDemo1.vue'))
},
{
id: 'system',
label: '系统',
icon: 'codepen',
children: [
{
id: 'settings',
label: '设置',
icon: 'settings',
component: defineAsyncComponent(() => import('../Grid/GridDemo1.vue'))
},
{
id: 'help',
label: '帮助',
icon: 'hand-helping',
component: defineAsyncComponent(() => import('../Grid/GridDemo2.vue'))
}
]
}
]
</script>
<template>
<SidebarLayout v-model:collapsed="isCollapsed" :menuConfig="menuConfig" defaultActiveId="home" />
</template>组件组成
Sidebar 组件由以下多个子组件组成,它们共同协作提供完整的侧边栏功能:
| 组件名称 | 说明 |
|---|---|
| SidebarProvider | 侧边栏提供者,管理侧边栏的展开/折叠状态 |
| Sidebar | 侧边栏容器,控制侧边栏的布局和样式 |
| SidebarTrigger | 侧边栏触发器,用于切换侧边栏的展开/折叠状态 |
| SidebarHeader | 侧边栏头部,用于放置标题和 logo |
| SidebarContent | 侧边栏内容区域,支持自定义滚动条 |
| SidebarFooter | 侧边栏底部,可用于显示用户信息等 |
| SidebarGroup | 侧边栏分组,用于组织菜单项 |
| SidebarGroupLabel | 侧边栏分组标签,显示分组名称 |
| SidebarGroupContent | 侧边栏分组内容,包含具体的菜单项 |
| SidebarMenu | 侧边栏菜单容器 |
| SidebarMenuItem | 侧边栏菜单项 |
| SidebarMenuButton | 侧边栏菜单按钮,支持激活状态 |
布局方式
侧边栏支持多种布局方式,包括:
- 固定宽度布局:侧边栏保持固定宽度,内容区域占据剩余空间
- 可折叠布局:侧边栏可以折叠为图标模式,节省空间
- 响应式布局:在移动设备上,侧边栏可以切换为覆盖层模式
使用指南
基本结构
使用 Sidebar 组件的基本结构如下:
vue
<SidebarProvider>
<div class="container">
<Sidebar>
<SidebarHeader>
<!-- 头部内容 -->
</SidebarHeader>
<SidebarContent>
<!-- 侧边栏内容 -->
<SidebarMenu>
<!-- 菜单项 -->
</SidebarMenu>
</SidebarContent>
<SidebarFooter>
<!-- 底部内容 -->
</SidebarFooter>
</Sidebar>
<!-- 主内容区域 -->
<main>
<!-- 主内容 -->
</main>
</div>
</SidebarProvider>创建菜单项
可以使用 SidebarMenu、SidebarMenuItem 和 SidebarMenuButton 组件创建菜单项:
vue
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton active>
<Icon />
<span>首页</span>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<Icon />
<span>设置</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>分组菜单
使用 SidebarGroup、SidebarGroupLabel 和 SidebarGroupContent 组件可以对菜单项进行分组:
vue
<SidebarGroup>
<SidebarGroupLabel>主导航</SidebarGroupLabel>
<SidebarGroupContent>
<!-- 菜单项 -->
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>设置</SidebarGroupLabel>
<SidebarGroupContent>
<!-- 菜单项 -->
</SidebarGroupContent>
</SidebarGroup>CSS 变量
侧边栏组件使用以下 CSS 变量控制样式,可以在全局或局部进行自定义:
css
:root {
--sidebar-background: var(--color-background);
--sidebar-foreground: var(--color-text);
--sidebar-primary: var(--color-primary);
--sidebar-primary-foreground: var(--color-primary-text-1);
--sidebar-accent: var(--color-bg-hover-1);
--sidebar-accent-foreground: var(--color-text);
--sidebar-border: var(--color-border);
--sidebar-ring: var(--color-primary);
--sidebar-width: 240px;
--sidebar-collapsed-width: 72px;
}注意事项
- 确保 SidebarProvider 组件包裹在 Sidebar 和触发其状态的组件之外
- 在移动设备上,建议使用触发按钮来控制侧边栏的显示和隐藏
- 使用 SVG 图标时,确保图标尺寸一致,以获得最佳视觉效果
- 可以根据项目需要自定义侧边栏的宽度、背景色等样式