Sidebar 侧边栏
介绍
Sidebar 侧边栏是一个垂直展示的导航组件,用于在不同的内容区域之间进行切换。该组件常用于商品分类、设置页面、内容列表等场景,提供清晰的层级导航和快速切换能力。组件支持双模式使用、图标展示、徽标提示、禁用控制等丰富功能,并具有独特的激活项圆角设计,视觉效果优雅。
核心特性:
- 双模式支持 - 提供 Items 数组模式和 SidebarItem 子组件模式两种使用方式,满足不同场景需求
- 图标集成 - 支持为每个侧边栏项添加图标,提升视觉识别度
- 徽标提示 - 内置 Badge 徽标组件,支持数字、圆点、自定义内容等多种形式
- 禁用控制 - 支持禁用单个侧边栏项,禁用项不可点击且样式置灰
- 前置钩子 - 提供 beforeChange 钩子函数,可在切换前执行异步验证或确认操作
- 圆角设计 - 激活项的前后相邻项具有独特的圆角效果,视觉层次分明
- 自定义插槽 - 支持自定义侧边栏项内容和图标,可实现复杂的自定义布局
- 暗色主题 - 内置暗色模式支持,自动适配深色界面风格
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:1-63
基本用法
子组件模式
使用 wd-sidebar-item 子组件定义侧边栏项,这是最常见的使用方式。
<template>
<view class="demo-sidebar">
<wd-sidebar v-model="activeKey">
<wd-sidebar-item label="分类1" value="1" />
<wd-sidebar-item label="分类2" value="2" />
<wd-sidebar-item label="分类3" value="3" />
<wd-sidebar-item label="分类4" value="4" />
<wd-sidebar-item label="分类5" value="5" />
</wd-sidebar>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const activeKey = ref('1')
</script>
<style lang="scss" scoped>
.demo-sidebar {
display: flex;
height: 600rpx;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
使用说明:
v-model双向绑定当前激活的 value 值- 每个
wd-sidebar-item必须设置label和value属性 value值必须唯一,用于标识侧边栏项- 默认激活 value 与 v-model 绑定值匹配的项
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:41-43, 124-125, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:50-53
Items 数组模式
通过 items 数组配置侧边栏项,适合动态数据场景。
<template>
<view class="demo-sidebar">
<wd-sidebar v-model="activeKey" :items="items" />
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const items: SidebarItem[] = [
{ label: '分类1', value: '1' },
{ label: '分类2', value: '2' },
{ label: '分类3', value: '3' },
{ label: '分类4', value: '4' },
{ label: '分类5', value: '5' },
]
</script>
<style lang="scss" scoped>
.demo-sidebar {
display: flex;
height: 600rpx;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
使用说明:
- Items 模式通过
items数组传入侧边栏项配置 - 每个 item 必须包含
label和value属性 value必须唯一- 适合从后端获取数据或动态生成侧边栏项的场景
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:5-39, 68-96, 122-123
带图标
为侧边栏项添加图标,提升视觉识别度。
<template>
<view class="demo-sidebar">
<wd-text title="子组件模式" />
<wd-sidebar v-model="activeKey1">
<wd-sidebar-item label="首页" value="1" icon="home" />
<wd-sidebar-item label="分类" value="2" icon="category" />
<wd-sidebar-item label="购物车" value="3" icon="cart" />
<wd-sidebar-item label="我的" value="4" icon="user" />
</wd-sidebar>
<wd-text title="Items 模式" custom-style="margin-top: 32rpx" />
<wd-sidebar v-model="activeKey2" :items="items" />
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey1 = ref('1')
const activeKey2 = ref('1')
const items: SidebarItem[] = [
{ label: '首页', value: '1', icon: 'home' },
{ label: '分类', value: '2', icon: 'category' },
{ label: '购物车', value: '3', icon: 'cart' },
{ label: '我的', value: '4', icon: 'user' },
]
</script>
<style lang="scss" scoped>
.demo-sidebar {
padding: 32rpx;
}
.demo-sidebar wd-sidebar {
height: 400rpx;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
使用说明:
- 子组件模式通过
iconprop 设置图标 - Items 模式在 item 对象中设置
icon属性 - 图标名称来自项目配置的图标库
- 图标显示在文字左侧
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:31-33, 78, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:9-11, 59
带徽标
使用徽标显示未读消息、待处理事项等提示信息。
<template>
<view class="demo-sidebar">
<wd-text title="数字徽标" />
<wd-sidebar v-model="activeKey1">
<wd-sidebar-item label="消息" value="1" :badge="5" />
<wd-sidebar-item label="通知" value="2" :badge="10" />
<wd-sidebar-item label="待办" value="3" :badge="100" :max="99" />
<wd-sidebar-item label="已读" value="4" />
</wd-sidebar>
<wd-text title="圆点徽标" custom-style="margin-top: 32rpx" />
<wd-sidebar v-model="activeKey2">
<wd-sidebar-item label="消息" value="1" :is-dot="true" />
<wd-sidebar-item label="通知" value="2" :is-dot="true" />
<wd-sidebar-item label="待办" value="3" />
<wd-sidebar-item label="已读" value="4" />
</wd-sidebar>
<wd-text title="Items 模式" custom-style="margin-top: 32rpx" />
<wd-sidebar v-model="activeKey3" :items="items" />
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey1 = ref('1')
const activeKey2 = ref('1')
const activeKey3 = ref('1')
const items: SidebarItem[] = [
{ label: '消息', value: '1', badge: 5 },
{ label: '通知', value: '2', badge: '99+' },
{ label: '待办', value: '3', isDot: true },
{ label: '已读', value: '4' },
]
</script>
<style lang="scss" scoped>
.demo-sidebar {
padding: 32rpx;
}
.demo-sidebar wd-sidebar {
height: 400rpx;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
使用说明:
badge属性设置徽标显示的数字或文字isDot属性设置为圆点徽标max属性设置数字徽标的最大值,超过显示为${max}+- 徽标显示在文字右上角
- 支持所有 Badge 组件的属性,通过
badgeProps透传
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:34-36, 74-83, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:12-14, 54-56, 60-63, 94-107
禁用项
禁用特定的侧边栏项,禁用项不可点击。
<template>
<view class="demo-sidebar">
<wd-text title="子组件模式" />
<wd-sidebar v-model="activeKey1">
<wd-sidebar-item label="分类1" value="1" />
<wd-sidebar-item label="分类2(禁用)" value="2" :disabled="true" />
<wd-sidebar-item label="分类3" value="3" />
<wd-sidebar-item label="分类4(禁用)" value="4" :disabled="true" />
<wd-sidebar-item label="分类5" value="5" />
</wd-sidebar>
<wd-text title="Items 模式" custom-style="margin-top: 32rpx" />
<wd-sidebar v-model="activeKey2" :items="items" />
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey1 = ref('1')
const activeKey2 = ref('1')
const items: SidebarItem[] = [
{ label: '分类1', value: '1' },
{ label: '分类2(禁用)', value: '2', disabled: true },
{ label: '分类3', value: '3' },
{ label: '分类4(禁用)', value: '4', disabled: true },
{ label: '分类5', value: '5' },
]
</script>
<style lang="scss" scoped>
.demo-sidebar {
padding: 32rpx;
}
.demo-sidebar wd-sidebar {
height: 500rpx;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
使用说明:
- 子组件模式通过
disabledprop 禁用侧边栏项 - Items 模式在 item 对象中设置
disabled: true - 禁用项样式置灰且不可点击
- 点击禁用项不会触发任何事件
- 禁用项不能被选中
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:213, 249, 84, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:65, 81, 153-154
事件监听
监听侧边栏项的切换和点击事件。
<template>
<view class="demo-sidebar">
<wd-sidebar
v-model="activeKey"
@change="handleChange"
@item-click="handleItemClick"
>
<wd-sidebar-item
v-for="i in 5"
:key="i"
:label="`分类${i}`"
:value="`${i}`"
@itemclick="handleItemClickSub"
/>
</wd-sidebar>
<view class="event-log">
<view class="log-title">事件日志:</view>
<view v-for="(log, index) in eventLogs" :key="index" class="log-item">
{{ log }}
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const eventLogs = ref<string[]>([])
const addLog = (message: string) => {
const time = new Date().toLocaleTimeString()
eventLogs.value.unshift(`[${time}] ${message}`)
if (eventLogs.value.length > 10) {
eventLogs.value.pop()
}
}
const handleChange = (event: { value: string | number; label: string }) => {
addLog(`change 事件 - 切换到: ${event.label} (value: ${event.value})`)
}
const handleItemClick = (item: SidebarItem, index: number) => {
addLog(`item-click 事件 - 点击: ${item.label} (索引: ${index})`)
}
const handleItemClickSub = () => {
addLog('子组件 itemclick 事件触发')
}
</script>
<style lang="scss" scoped>
.demo-sidebar {
display: flex;
height: 600rpx;
}
.event-log {
flex: 1;
margin-left: 32rpx;
padding: 24rpx;
background: #f5f5f5;
border-radius: 8rpx;
overflow-y: auto;
.log-title {
margin-bottom: 16rpx;
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.log-item {
padding: 12rpx 0;
font-size: 24rpx;
color: #666;
border-bottom: 1rpx solid #e0e0e0;
&:last-child {
border-bottom: none;
}
}
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
使用说明:
change事件:侧边栏项切换时触发,参数包含value和labelitem-click事件:点击侧边栏项时触发(Items 模式),参数包含item和indexitemclick事件:子组件上的点击事件(子组件模式)- 事件触发顺序:
item-click/itemclick→change→update:modelValue
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:133-140, 189-192, 248-256, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:71-74, 153-161
高级用法
前置钩子
使用 beforeChange 钩子在切换前执行验证或确认操作。
<template>
<view class="demo-sidebar">
<wd-text title="切换前确认" />
<wd-sidebar v-model="activeKey" :before-change="handleBeforeChange">
<wd-sidebar-item label="首页" value="1" />
<wd-sidebar-item label="个人中心" value="2" />
<wd-sidebar-item label="设置" value="3" />
<wd-sidebar-item label="退出登录" value="4" />
</wd-sidebar>
<view class="tip">点击"退出登录"会弹出确认对话框</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarBeforeChangeOption } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const handleBeforeChange = (option: SidebarBeforeChangeOption) => {
const { value, resolve } = option
// 退出登录需要确认
if (value === '4') {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
resolve(true) // 允许切换
// 这里可以执行退出登录逻辑
uni.showToast({
title: '已退出登录',
icon: 'success',
})
} else {
resolve(false) // 取消切换
}
},
})
} else {
resolve(true) // 其他项直接允许切换
}
}
</script>
<style lang="scss" scoped>
.demo-sidebar {
padding: 32rpx;
}
.demo-sidebar wd-sidebar {
height: 400rpx;
}
.tip {
margin-top: 16rpx;
font-size: 24rpx;
color: #999;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
异步验证示例:
<template>
<view class="demo-sidebar">
<wd-text title="异步验证" />
<wd-sidebar v-model="activeKey" :before-change="handleBeforeChange">
<wd-sidebar-item label="公开内容" value="1" />
<wd-sidebar-item label="VIP 内容" value="2" />
<wd-sidebar-item label="专属内容" value="3" />
</wd-sidebar>
<view class="tip">切换到 VIP/专属内容会进行权限验证</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarBeforeChangeOption } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const handleBeforeChange = async (option: SidebarBeforeChangeOption) => {
const { value, resolve } = option
// VIP 和专属内容需要验证权限
if (value === '2' || value === '3') {
uni.showLoading({ title: '验证中...' })
try {
// 模拟异步权限验证
await new Promise((resolveAsync) => setTimeout(resolveAsync, 1000))
// 模拟权限验证结果
const hasPermission = Math.random() > 0.5
uni.hideLoading()
if (hasPermission) {
resolve(true)
uni.showToast({
title: '验证通过',
icon: 'success',
})
} else {
resolve(false)
uni.showToast({
title: '权限不足',
icon: 'none',
})
}
} catch (error) {
uni.hideLoading()
resolve(false)
uni.showToast({
title: '验证失败',
icon: 'none',
})
}
} else {
resolve(true)
}
}
</script>
<style lang="scss" scoped>
.demo-sidebar {
padding: 32rpx;
}
.demo-sidebar wd-sidebar {
height: 400rpx;
}
.tip {
margin-top: 16rpx;
font-size: 24rpx;
color: #999;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
使用说明:
beforeChange是一个函数,接收option参数option包含value(目标值)和resolve(回调函数)- 调用
resolve(true)允许切换,resolve(false)取消切换 - 可以在 beforeChange 中执行异步操作(如网络请求、权限验证等)
- 常用于需要用户确认、权限验证、数据保存提示等场景
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:101-112, 127, 169-182
自定义内容插槽
使用插槽自定义侧边栏项的内容。
<template>
<view class="demo-sidebar">
<wd-text title="自定义图标插槽" />
<wd-sidebar v-model="activeKey1">
<wd-sidebar-item label="首页" value="1">
<template #icon>
<view class="custom-icon" style="background: #ff6b6b;">
<wd-icon name="home" color="#fff" size="32" />
</view>
</template>
</wd-sidebar-item>
<wd-sidebar-item label="分类" value="2">
<template #icon>
<view class="custom-icon" style="background: #4ecb73;">
<wd-icon name="category" color="#fff" size="32" />
</view>
</template>
</wd-sidebar-item>
<wd-sidebar-item label="购物车" value="3">
<template #icon>
<view class="custom-icon" style="background: #ffa940;">
<wd-icon name="cart" color="#fff" size="32" />
</view>
</template>
</wd-sidebar-item>
</wd-sidebar>
<wd-text title="Items 模式自定义内容" custom-style="margin-top: 32rpx" />
<wd-sidebar v-model="activeKey2" :items="items">
<template #item-0="{ item, index, active }">
<view class="custom-item" :class="{ 'is-active': active }">
<wd-icon name="home" size="40" />
<text class="label">{{ item.label }}</text>
<text class="desc">首页导航</text>
</view>
</template>
<template #item-1="{ item, index, active }">
<view class="custom-item" :class="{ 'is-active': active }">
<wd-icon name="category" size="40" />
<text class="label">{{ item.label }}</text>
<text class="desc">分类浏览</text>
</view>
</template>
</wd-sidebar>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey1 = ref('1')
const activeKey2 = ref('1')
const items: SidebarItem[] = [
{ label: '首页', value: '1', useSlot: true },
{ label: '分类', value: '2', useSlot: true },
{ label: '购物车', value: '3' },
{ label: '我的', value: '4' },
]
</script>
<style lang="scss" scoped>
.demo-sidebar {
padding: 32rpx;
}
.demo-sidebar wd-sidebar {
height: 400rpx;
}
.custom-icon {
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 60rpx;
margin-right: 12rpx;
border-radius: 50%;
}
.custom-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 16rpx;
.label {
margin-top: 8rpx;
font-size: 28rpx;
color: #333;
}
.desc {
margin-top: 4rpx;
font-size: 22rpx;
color: #999;
}
&.is-active {
.label {
color: #4d80f0;
font-weight: bold;
}
}
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
使用说明:
- 子组件模式支持
icon插槽自定义图标 - Items 模式设置
useSlot: true启用自定义内容插槽 - 默认插槽名为
item-${index},可通过slotName自定义 - 插槽接收
item、index、active参数 - 自定义图标插槽可通过
useIconSlot和iconSlotName实现
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:13-38, 85-92, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:8-11
配合内容区域使用
Sidebar 常用于左侧导航,配合右侧内容区域使用。
<template>
<view class="demo-sidebar">
<wd-sidebar v-model="activeCategory" :items="categories" />
<view class="content-area">
<view class="content-header">
<text class="title">{{ currentCategory?.label }}</text>
</view>
<scroll-view class="content-body" scroll-y>
<view v-for="item in currentProducts" :key="item.id" class="product-item">
<image class="product-image" :src="item.image" mode="aspectFill" />
<view class="product-info">
<text class="product-name">{{ item.name }}</text>
<text class="product-price">¥{{ item.price }}</text>
</view>
</view>
<view v-if="currentProducts.length === 0" class="empty">
暂无商品
</view>
</scroll-view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeCategory = ref('1')
const categories: SidebarItem[] = [
{ label: '手机数码', value: '1', badge: 10 },
{ label: '电脑办公', value: '2', badge: 5 },
{ label: '家用电器', value: '3' },
{ label: '服饰鞋包', value: '4', badge: 20 },
{ label: '食品饮料', value: '5' },
{ label: '图书音像', value: '6', badge: 3 },
]
// 模拟商品数据
const products = {
'1': [
{ id: 1, name: 'iPhone 15 Pro', price: 7999, image: '/static/phone.jpg' },
{ id: 2, name: 'iPad Pro', price: 6999, image: '/static/ipad.jpg' },
],
'2': [
{ id: 3, name: 'MacBook Pro', price: 12999, image: '/static/laptop.jpg' },
{ id: 4, name: 'Dell XPS', price: 8999, image: '/static/laptop2.jpg' },
],
'3': [
{ id: 5, name: '小米电视', price: 3999, image: '/static/tv.jpg' },
],
'4': [
{ id: 6, name: '运动鞋', price: 299, image: '/static/shoes.jpg' },
{ id: 7, name: 'T恤', price: 99, image: '/static/tshirt.jpg' },
],
'5': [],
'6': [
{ id: 8, name: '编程书籍', price: 79, image: '/static/book.jpg' },
],
}
const currentCategory = computed(() => {
return categories.find(cat => cat.value === activeCategory.value)
})
const currentProducts = computed(() => {
return products[activeCategory.value as keyof typeof products] || []
})
</script>
<style lang="scss" scoped>
.demo-sidebar {
display: flex;
height: 100vh;
}
.content-area {
flex: 1;
display: flex;
flex-direction: column;
background: #f5f5f5;
}
.content-header {
padding: 32rpx;
background: #fff;
border-bottom: 1rpx solid #e0e0e0;
.title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
}
.content-body {
flex: 1;
padding: 32rpx;
}
.product-item {
display: flex;
padding: 24rpx;
margin-bottom: 16rpx;
background: #fff;
border-radius: 8rpx;
&:last-child {
margin-bottom: 0;
}
}
.product-image {
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
background: #f0f0f0;
}
.product-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 24rpx;
.product-name {
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
}
.product-price {
font-size: 32rpx;
color: #ff6b6b;
font-weight: bold;
}
}
.empty {
padding: 128rpx 32rpx;
text-align: center;
font-size: 28rpx;
color: #999;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
使用说明:
- Sidebar 固定宽度,内容区域占据剩余空间
- 通过 v-model 绑定当前分类
- 根据选中的分类动态显示对应内容
- 适用于商品分类、文章分类、设置页面等场景
动态侧边栏
根据数据动态生成侧边栏项,支持增删改。
<template>
<view class="demo-sidebar">
<wd-sidebar v-model="activeKey" :items="items" />
<view class="actions">
<wd-button size="small" type="primary" @click="addItem">
添加分类
</wd-button>
<wd-button size="small" type="danger" @click="removeItem">
删除当前分类
</wd-button>
<wd-button size="small" type="warning" @click="updateItem">
更新当前分类
</wd-button>
<wd-button size="small" type="success" @click="toggleBadge">
切换徽标
</wd-button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const items = ref<SidebarItem[]>([
{ label: '分类1', value: '1' },
{ label: '分类2', value: '2' },
{ label: '分类3', value: '3' },
])
let itemCounter = 3
const addItem = () => {
itemCounter++
items.value.push({
label: `分类${itemCounter}`,
value: `${itemCounter}`,
})
activeKey.value = `${itemCounter}`
}
const removeItem = () => {
if (items.value.length <= 1) {
uni.showToast({
title: '至少保留一个分类',
icon: 'none',
})
return
}
const index = items.value.findIndex(item => item.value === activeKey.value)
if (index !== -1) {
items.value.splice(index, 1)
// 删除后选中第一个
activeKey.value = items.value[0].value
}
}
const updateItem = () => {
const index = items.value.findIndex(item => item.value === activeKey.value)
if (index !== -1) {
items.value[index].label = `分类${items.value[index].value}(已更新)`
}
}
const toggleBadge = () => {
const index = items.value.findIndex(item => item.value === activeKey.value)
if (index !== -1) {
const currentItem = items.value[index]
if (currentItem.badge) {
delete currentItem.badge
} else {
currentItem.badge = Math.floor(Math.random() * 100)
}
}
}
</script>
<style lang="scss" scoped>
.demo-sidebar {
display: flex;
flex-direction: column;
height: 600rpx;
}
.demo-sidebar wd-sidebar {
flex: 1;
}
.actions {
display: flex;
gap: 16rpx;
padding: 32rpx;
background: #f5f5f5;
border-top: 1rpx solid #e0e0e0;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
使用说明:
- Items 模式支持动态修改,响应式更新
- 添加/删除侧边栏项时需要管理 activeKey
- 删除当前选中项后应切换到其他项
- 动态修改 label、badge 等属性会立即生效
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:160-162
API
Sidebar Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| customStyle | 自定义根节点样式 | string | '' |
| customClass | 自定义根节点样式类 | string | '' |
| items | 侧边栏项数据数组(Items 模式) | SidebarItem[] | [] |
| modelValue / v-model | 当前导航项的值 | number | string | 0 |
| beforeChange | 在改变前执行的钩子函数 | SidebarBeforeChange | - |
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:116-128, 143-148
SidebarItem Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| customStyle | 自定义根节点样式 | string | '' |
| customClass | 自定义根节点样式类 | string | '' |
| label | 当前选项标题 | string | - |
| value | 当前选项的值,唯一标识 | number | string | - |
| badge | 徽标显示值 | string | number | null | null |
| badgeProps | 徽标属性,透传给 Badge 组件 | Record<string, any> | - |
| icon | 图标名称 | string | - |
| isDot | 是否点状徽标 | boolean | false |
| max | 徽标最大值 | number | 99 |
| disabled | 是否禁用 | boolean | false |
参考: src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:44-66, 77-82
SidebarItem 类型(Items 模式)
/**
* 侧边栏项配置接口
*/
export interface SidebarItem {
/** 当前选项标题 */
label: string
/** 当前选项的值,唯一标识 */
value: number | string
/** 徽标显示值 */
badge?: string | number | null
/** 徽标属性,透传给 Badge 组件 */
badgeProps?: Record<string, any>
/** 图标 */
icon?: string
/** 是否点状徽标 */
isDot?: boolean
/** 徽标最大值 */
max?: number
/** 是否禁用 */
disabled?: boolean
/** 是否使用自定义内容插槽 */
useSlot?: boolean
/** 自定义内容插槽名称,默认为 item-{index} */
slotName?: string
/** 是否使用图标插槽 */
useIconSlot?: boolean
/** 图标插槽名称,默认为 icon-{index} */
iconSlotName?: string
/** 自定义数据,点击时会传递 */
[key: string]: any
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:68-96
Sidebar Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 绑定值变化时触发 | value: number | string |
| change | 选中项变化时触发 | { value: number | string, label: string } |
| item-click | 点击侧边栏项时触发(Items 模式) | item: SidebarItem, index: number |
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:133-140, 189-192, 248-256
SidebarItem Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| itemclick | 点击侧边栏项时触发 | - |
参考: src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:71-74, 157
Sidebar Slots
| 插槽名 | 说明 | 参数 |
|---|---|---|
| default | 默认插槽,子组件模式下放置 wd-sidebar-item 组件 | - |
| item-$ | Items 模式下的自定义内容插槽,${index} 为侧边栏项索引 | { item: SidebarItem, index: number, active: boolean } |
| icon-$ | Items 模式下的自定义图标插槽 | { item: SidebarItem, index: number } |
| [自定义插槽名] | Items 模式下通过 slotName / iconSlotName 指定的自定义插槽 | { item: SidebarItem, index: number, active?: boolean } |
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:13-38
SidebarItem Slots
| 插槽名 | 说明 | 参数 |
|---|---|---|
| default | 侧边栏项内容(包含徽标) | - |
| icon | 自定义图标 | - |
参考: src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:8-11
类型定义
/**
* 侧边栏项配置接口
*/
export interface SidebarItem {
/** 当前选项标题 */
label: string
/** 当前选项的值,唯一标识 */
value: number | string
/** 徽标显示值 */
badge?: string | number | null
/** 徽标属性,透传给 Badge 组件 */
badgeProps?: Record<string, any>
/** 图标 */
icon?: string
/** 是否点状徽标 */
isDot?: boolean
/** 徽标最大值 */
max?: number
/** 是否禁用 */
disabled?: boolean
/** 是否使用自定义内容插槽 */
useSlot?: boolean
/** 自定义内容插槽名称,默认为 item-{index} */
slotName?: string
/** 是否使用图标插槽 */
useIconSlot?: boolean
/** 图标插槽名称,默认为 icon-{index} */
iconSlotName?: string
/** 自定义数据,点击时会传递 */
[key: string]: any
}
/**
* Sidebar切换前的选项接口
*/
export interface SidebarBeforeChangeOption {
/** 目标值 */
value: number | string
resolve: (pass: boolean) => void
}
/**
* Sidebar切换前的钩子函数类型
*/
export type SidebarBeforeChange = (option: SidebarBeforeChangeOption) => void
/**
* 侧边栏组件属性接口
*/
interface WdSidebarProps {
/** 自定义根节点样式 */
customStyle?: string
/** 自定义根节点样式类 */
customClass?: string
/** 侧边栏项数据数组 */
items?: SidebarItem[]
/** 当前导航项的索引 */
modelValue?: number | string
/** 在改变前执行的函数 */
beforeChange?: SidebarBeforeChange
}
/**
* 侧边栏组件事件接口
*/
interface WdSidebarEmits {
/** 更新选中值 */
'update:modelValue': [value: number | string]
/** 选中项变化时触发 */
change: [event: { value: number | string; label: string }]
/** 点击侧边栏项时触发 */
'item-click': [item: SidebarItem, index: number]
}
/**
* 侧边栏项组件属性接口
*/
interface WdSidebarItemProps {
/** 自定义根节点样式 */
customStyle?: string
/** 自定义根节点样式类 */
customClass?: string
/** 当前选项标题 */
label: string
/** 当前选项的值,唯一标识 */
value: number | string
/** 徽标显示值 */
badge?: string | number | null
/** 徽标属性,透传给 Badge 组件 */
badgeProps?: Record<string, any>
/** 图标 */
icon?: string
/** 是否点状徽标 */
isDot?: boolean
/** 徽标最大值 */
max?: number
/** 是否禁用 */
disabled?: boolean
}
/**
* 侧边栏项组件事件接口
*/
interface WdSidebarItemEmits {
/** 点击侧边栏项时触发 */
itemclick: []
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:68-140, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:44-74
主题定制
CSS 变量
Sidebar 组件提供了以下 CSS 变量用于主题定制:
// 侧边栏容器
$-sidebar-width: 210rpx; // 侧边栏宽度
$-sidebar-height: 100%; // 侧边栏高度
$-sidebar-bg: #f5f7fa; // 侧边栏背景色
// 侧边栏项
$-sidebar-font-size: 28rpx; // 侧边栏项字体大小
$-sidebar-color: #323233; // 侧边栏项文字颜色
$-sidebar-item-height: 88rpx; // 侧边栏项最小高度
$-sidebar-item-line-height: 40rpx; // 侧边栏项行高
$-sidebar-hover-bg: #e8e8e8; // 侧边栏项悬停背景色
$-sidebar-disabled-color: #c8c9cc; // 侧边栏项禁用颜色
// 激活项
$-sidebar-active-bg: #fff; // 激活项背景色
$-sidebar-active-color: #4d80f0; // 激活项文字颜色
$-sidebar-active-border-width: 6rpx; // 激活项边框宽度
$-sidebar-active-border-height: 32rpx; // 激活项边框高度
$-sidebar-border-radius: 24rpx; // 前后缀圆角大小
// 图标
$-sidebar-icon-size: 32rpx; // 图标大小
// 暗色主题
.wot-theme-dark {
$-dark-background: #1a1a1a;
$-dark-background2: #232323;
$-dark-background4: #383838;
$-dark-color: #e5e5e5;
$-dark-color-gray: #555;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:259-379, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:164-262
自定义样式
基础样式定制:
<template>
<view class="custom-sidebar">
<wd-sidebar
v-model="activeKey"
:items="items"
custom-class="my-sidebar"
/>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const items: SidebarItem[] = [
{ label: '分类1', value: '1' },
{ label: '分类2', value: '2' },
{ label: '分类3', value: '3' },
]
</script>
<style lang="scss" scoped>
.custom-sidebar {
height: 600rpx;
// 自定义侧边栏宽度和背景色
:deep(.my-sidebar) {
width: 240rpx;
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
}
// 自定义侧边栏项样式
:deep(.wd-sidebar-item) {
color: rgba(255, 255, 255, 0.7);
background: transparent;
&.wd-sidebar-item--active {
color: #fff;
background: rgba(255, 255, 255, 0.2);
border-radius: 16rpx;
&::before {
display: none; // 隐藏左侧边框
}
}
}
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
深色主题:
<template>
<view class="dark-sidebar wot-theme-dark">
<wd-sidebar v-model="activeKey" :items="items" />
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const items: SidebarItem[] = [
{ label: '分类1', value: '1' },
{ label: '分类2', value: '2' },
{ label: '分类3', value: '3' },
]
</script>
<style lang="scss" scoped>
.dark-sidebar {
height: 600rpx;
background: #1a1a1a;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
扁平风格:
<template>
<view class="flat-sidebar">
<wd-sidebar
v-model="activeKey"
:items="items"
custom-class="flat-style"
/>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const items: SidebarItem[] = [
{ label: '分类1', value: '1', icon: 'home' },
{ label: '分类2', value: '2', icon: 'category' },
{ label: '分类3', value: '3', icon: 'cart' },
]
</script>
<style lang="scss" scoped>
.flat-sidebar {
height: 600rpx;
:deep(.flat-style) {
.wd-sidebar-item {
background: #fff;
border-bottom: 1rpx solid #e0e0e0;
&.wd-sidebar-item--active {
background: #4d80f0;
color: #fff;
&::before {
display: none;
}
}
&.wd-sidebar-item--prefix,
&.wd-sidebar-item--suffix {
border-radius: 0;
}
}
}
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:259-379
最佳实践
1. 选择合适的使用模式
推荐做法:
<!-- ✅ 静态侧边栏使用子组件模式 -->
<wd-sidebar v-model="activeKey">
<wd-sidebar-item label="首页" value="1" icon="home" />
<wd-sidebar-item label="分类" value="2" icon="category" />
<wd-sidebar-item label="购物车" value="3" icon="cart" />
</wd-sidebar>
<!-- ✅ 动态侧边栏使用 Items 模式 -->
<wd-sidebar v-model="activeCategory" :items="categories" />2
3
4
5
6
7
8
9
不推荐做法:
<!-- ❌ 动态数据使用子组件模式,难以维护 -->
<wd-sidebar v-model="activeKey">
<wd-sidebar-item
v-for="item in categories"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</wd-sidebar>
<!-- ❌ 静态内容使用 Items 模式,代码冗余 -->
<wd-sidebar v-model="activeKey" :items="staticItems" />2
3
4
5
6
7
8
9
10
11
12
说明:
- 子组件模式适合侧边栏项固定、数量较少的场景
- Items 模式适合数据动态变化、从后端获取的场景
- 根据实际需求选择合适的模式
2. 合理使用 beforeChange 钩子
推荐做法:
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarBeforeChangeOption } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
// ✅ 在需要确认、验证的场景使用 beforeChange
const handleBeforeChange = (option: SidebarBeforeChangeOption) => {
const { value, resolve } = option
// 需要保存数据时提示用户
if (hasUnsavedData.value) {
uni.showModal({
title: '提示',
content: '有未保存的数据,是否切换?',
success: (res) => {
resolve(res.confirm)
},
})
} else {
resolve(true)
}
}
</script>
<template>
<wd-sidebar v-model="activeKey" :before-change="handleBeforeChange">
<!-- ... -->
</wd-sidebar>
</template>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
不推荐做法:
<script lang="ts" setup>
// ❌ 简单切换使用 beforeChange,增加复杂度
const handleBeforeChange = (option: SidebarBeforeChangeOption) => {
option.resolve(true) // 直接放行,没有实际作用
}
// ❌ 在 beforeChange 中执行耗时操作但不显示加载状态
const handleBeforeChange = async (option: SidebarBeforeChangeOption) => {
// 没有 loading 提示
await someAsyncOperation()
option.resolve(true)
}
</script>2
3
4
5
6
7
8
9
10
11
12
13
说明:
- beforeChange 用于需要用户确认、权限验证、数据保存等场景
- 简单切换无需使用 beforeChange
- 异步操作应显示加载状态
3. 徽标使用规范
推荐做法:
<script lang="ts" setup>
import { ref, computed } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
// ✅ 动态计算徽标值
const unreadCounts = ref({
message: 5,
notification: 10,
todo: 3,
})
const items = computed<SidebarItem[]>(() => [
{
label: '消息',
value: '1',
badge: unreadCounts.value.message || null, // 0 不显示
},
{
label: '通知',
value: '2',
badge: unreadCounts.value.notification,
},
{
label: '待办',
value: '3',
badge: unreadCounts.value.todo || null,
},
])
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
不推荐做法:
<script lang="ts" setup>
// ❌ 徽标值写死,无法动态更新
const items: SidebarItem[] = [
{ label: '消息', value: '1', badge: 5 },
{ label: '通知', value: '2', badge: 10 },
]
// ❌ 使用0作为徽标值
const items: SidebarItem[] = [
{ label: '消息', value: '1', badge: 0 }, // 应该不显示徽标
]
</script>2
3
4
5
6
7
8
9
10
11
12
说明:
- 徽标值应该动态计算
- 值为 0 或 null 时不显示徽标
- 超过 max 值显示
${max}+
4. 配合路由使用
推荐做法:
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const activeKey = ref('1')
// ✅ 页面加载时从路由获取当前分类
onLoad((options) => {
if (options.category) {
activeKey.value = options.category
}
})
// ✅ 切换分类时更新路由
watch(activeKey, (newValue) => {
uni.navigateTo({
url: `/pages/category/index?category=${newValue}`,
})
})
</script>
<template>
<wd-sidebar v-model="activeKey" :items="categories" />
</template>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
不推荐做法:
<script lang="ts" setup>
// ❌ 切换后不更新路由,刷新页面后状态丢失
const activeKey = ref('1')
// ❌ 直接在点击事件中跳转,绕过 v-model
const handleClick = (item: SidebarItem) => {
uni.navigateTo({
url: `/pages/category/index?category=${item.value}`,
})
}
</script>2
3
4
5
6
7
8
9
10
11
说明:
- 侧边栏状态应该与路由保持同步
- 支持浏览器前进后退
- 支持页面刷新后恢复状态
5. 性能优化
推荐做法:
<script lang="ts" setup>
import { ref, shallowRef } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
// ✅ 静态侧边栏使用常量
const SIDEBAR_ITEMS: SidebarItem[] = [
{ label: '分类1', value: '1' },
{ label: '分类2', value: '2' },
{ label: '分类3', value: '3' },
]
// ✅ 使用 shallowRef 减少响应式开销
const items = shallowRef(SIDEBAR_ITEMS)
const activeKey = ref('1')
</script>
<template>
<wd-sidebar v-model="activeKey" :items="items" />
</template>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
不推荐做法:
<script lang="ts" setup>
// ❌ 内联创建数组,每次渲染都重新创建
</script>
<template>
<wd-sidebar
v-model="activeKey"
:items="[
{ label: '分类1', value: '1' },
{ label: '分类2', value: '2' },
]"
/>
</template>2
3
4
5
6
7
8
9
10
11
12
13
说明:
- 静态数据使用常量
- 动态数据使用 ref 或 shallowRef
- 避免内联创建复杂对象
常见问题
1. 为什么切换侧边栏项后内容没有更新?
问题原因:
- v-model 绑定值未正确更新
- 内容组件没有监听侧边栏变化
- 数据未重新加载
解决方案:
<template>
<view class="page">
<wd-sidebar v-model="activeCategory" :items="categories" @change="handleChange" />
<view class="content">
<!-- 使用 key 强制重新渲染 -->
<CategoryContent :key="activeCategory" :category-id="activeCategory" />
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeCategory = ref('1')
const categories: SidebarItem[] = [
{ label: '分类1', value: '1' },
{ label: '分类2', value: '2' },
]
const handleChange = (event: { value: string | number; label: string }) => {
console.log('切换到:', event)
// 在这里可以执行数据加载等操作
loadCategoryData(event.value)
}
const loadCategoryData = (categoryId: string | number) => {
// 加载分类数据
}
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
说明:
- 监听
change事件处理分类切换 - 为内容组件添加
key强制重新渲染 - 检查 v-model 绑定是否正确
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:189-192
2. 如何实现侧边栏的二级菜单?
问题原因:
- Sidebar 组件本身不支持多级菜单
- 需要自定义实现展开/收起逻辑
解决方案:
<template>
<view class="page">
<wd-sidebar v-model="activeKey" :items="flatMenus">
<template #item-0="{ item, active }">
<view class="menu-item" :class="{ 'is-active': active }">
<text>{{ item.label }}</text>
</view>
</template>
<!-- 其他自定义项 -->
</wd-sidebar>
</view>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const expandedKeys = ref<string[]>([])
// 原始多级菜单数据
const menus = [
{
label: '数码产品',
value: '1',
children: [
{ label: '手机', value: '1-1' },
{ label: '电脑', value: '1-2' },
],
},
{
label: '服装鞋包',
value: '2',
children: [
{ label: '男装', value: '2-1' },
{ label: '女装', value: '2-2' },
],
},
]
// 展开的菜单
const flatMenus = computed<SidebarItem[]>(() => {
const result: SidebarItem[] = []
menus.forEach((menu) => {
// 添加一级菜单
result.push({
label: menu.label,
value: menu.value,
useSlot: true,
})
// 如果展开,添加二级菜单
if (expandedKeys.value.includes(menu.value)) {
menu.children.forEach((child) => {
result.push({
label: ` ${child.label}`, // 缩进显示
value: child.value,
})
})
}
})
return result
})
const toggleExpand = (key: string) => {
const index = expandedKeys.value.indexOf(key)
if (index !== -1) {
expandedKeys.value.splice(index, 1)
} else {
expandedKeys.value.push(key)
}
}
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
说明:
- Sidebar 不直接支持多级菜单
- 可以通过动态生成扁平数据实现伪二级菜单
- 或者使用其他多级菜单组件
3. 为什么禁用的侧边栏项还能点击?
问题原因:
- 自定义插槽中的元素拦截了点击事件
- 事件冒泡被阻止
- 禁用状态未正确传递
解决方案:
<template>
<wd-sidebar v-model="activeKey" :items="items">
<template #item-0="{ item, active }">
<view
class="custom-item"
:class="{ 'is-disabled': item.disabled }"
@click.stop="handleCustomClick(item)"
>
{{ item.label }}
</view>
</template>
</wd-sidebar>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const items: SidebarItem[] = [
{ label: '分类1', value: '1', disabled: true, useSlot: true },
{ label: '分类2', value: '2' },
]
const handleCustomClick = (item: SidebarItem) => {
// 自定义插槽中需要手动检查禁用状态
if (item.disabled) {
return
}
// 处理点击逻辑
}
</script>
<style lang="scss" scoped>
.custom-item {
&.is-disabled {
color: #c8c9cc;
cursor: not-allowed;
// 阻止点击
pointer-events: none;
}
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
说明:
- 使用自定义插槽时需要手动处理禁用状态
- 通过
pointer-events: none阻止点击 - 或在点击事件中检查
disabled属性
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:249, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:153-154
4. 如何动态修改侧边栏项的样式?
问题原因:
- 需要根据不同条件显示不同样式
- 静态样式无法满足需求
解决方案:
<template>
<wd-sidebar v-model="activeKey" :items="items">
<template #item-0="{ item, active }">
<view class="custom-item" :style="getItemStyle(item, active)">
<wd-icon v-if="item.icon" :name="item.icon" :color="getIconColor(item, active)" />
<text>{{ item.label }}</text>
</view>
</template>
<template #item-1="{ item, active }">
<view class="custom-item" :style="getItemStyle(item, active)">
<wd-icon v-if="item.icon" :name="item.icon" :color="getIconColor(item, active)" />
<text>{{ item.label }}</text>
</view>
</template>
</wd-sidebar>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const items: SidebarItem[] = [
{
label: '重要',
value: '1',
icon: 'warning',
useSlot: true,
color: '#ff6b6b',
},
{
label: '普通',
value: '2',
icon: 'info',
useSlot: true,
color: '#4d80f0',
},
]
const getItemStyle = (item: SidebarItem, active: boolean) => {
if (active && item.color) {
return {
background: `${item.color}20`,
borderLeft: `4rpx solid ${item.color}`,
}
}
return {}
}
const getIconColor = (item: SidebarItem, active: boolean) => {
return active && item.color ? item.color : '#666'
}
</script>
<style lang="scss" scoped>
.custom-item {
padding: 24rpx;
display: flex;
align-items: center;
gap: 12rpx;
transition: all 0.3s;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
说明:
- 使用自定义插槽实现动态样式
- 通过
:style绑定动态计算的样式 - 将样式数据存储在 item 的自定义属性中
5. 如何实现侧边栏的滚动定位?
问题原因:
- 侧边栏项过多时,选中的项可能不在可视区域
- 需要自动滚动到选中项
解决方案:
<template>
<scroll-view
:scroll-y="true"
:scroll-into-view="scrollIntoView"
class="sidebar-scroll"
>
<wd-sidebar v-model="activeKey" :items="items" @change="handleChange" />
</scroll-view>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import type { SidebarItem } from '@/wd/components/wd-sidebar/wd-sidebar.vue'
const activeKey = ref('1')
const scrollIntoView = ref('')
const items: SidebarItem[] = Array.from({ length: 20 }, (_, i) => ({
label: `分类${i + 1}`,
value: `${i + 1}`,
}))
const handleChange = (event: { value: string | number }) => {
// 滚动到选中项
scrollIntoView.value = `item-${event.value}`
}
// 监听 activeKey 变化,自动滚动
watch(activeKey, (newValue) => {
scrollIntoView.value = `item-${newValue}`
})
</script>
<style lang="scss" scoped>
.sidebar-scroll {
height: 100vh;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
说明:
- 使用
scroll-view包裹侧边栏 - 通过
scroll-into-view属性滚动到指定元素 - 需要为每个侧边栏项设置 id
注意事项
- value 唯一性:每个侧边栏项的
value必须唯一,否则会导致选中状态异常。重复的 value 会导致无法正确识别当前选中项。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:72, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:53
- label 必填:侧边栏项必须设置
label属性,这是显示给用户的文字。如果使用自定义插槽,可以不设置 label,但需要在插槽中提供内容。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:70, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:51
- beforeChange 异步处理:在
beforeChange钩子中执行异步操作时,必须等待操作完成后再调用resolve,否则可能导致切换时机不正确。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:169-182
- 禁用项不触发事件:禁用的侧边栏项点击后不会触发
change事件,也不会更新modelValue,但会触发item-click事件(Items 模式)。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:249, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:153-154
- 徽标值为 0:当
badge值为0时,徽标会显示为 0。如果不想显示,应该设置为null或不设置 badge 属性。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:74, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:55, 80
- 插槽使用限制:子组件模式只支持
icon插槽。Items 模式支持自定义内容插槽和图标插槽,但需要设置useSlot或useIconSlot为true。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:85-92, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:8
- 圆角效果:激活项的前一项和后一项会自动添加圆角效果(前缀项右下圆角,后缀项右上圆角),这是组件的默认设计。如果不需要,可以通过样式覆盖。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:218-220, 352-358
- 高度设置:Sidebar 组件本身高度为 100%,需要为父容器设置具体高度,否则侧边栏可能无法正常显示。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:302
- 动态修改 items:动态修改
items数组后,组件会响应式更新。但如果修改的是当前选中项,需要确保新的 items 中仍然存在该 value,否则会自动选中第一项。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:160-162
- badgeProps 优先级:当同时设置
badgeProps和badge、isDot、max属性时,badgeProps 中的相同属性优先级更高。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:229-241, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:94-107
- 事件触发顺序:点击侧边栏项时,事件触发顺序为:
item-click/itemclick→ beforeChange(如果设置)→change→update:modelValue。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:248-256, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:153-161
- 样式隔离:组件使用
styleIsolation: 'shared'模式,自定义样式时需要注意样式作用域和优先级。使用:deep()深度选择器可以修改组件内部样式。
参考: src/wd/components/wd-sidebar/wd-sidebar.vue:61, src/wd/components/wd-sidebar-item/wd-sidebar-item.vue:32
总结
Sidebar 侧边栏组件是一个功能完善、使用灵活的垂直导航组件。通过双模式支持、图标集成、徽标提示、前置钩子等特性,可以满足各类侧边导航场景的需求。
使用建议:
- 静态侧边栏优先使用子组件模式
- 动态侧边栏使用 Items 模式
- 重要操作使用 beforeChange 钩子
- 合理使用徽标提示未读消息
- 配合路由实现状态持久化
适用场景:
- 商品分类导航
- 设置页面菜单
- 文章/内容分类
- 多Tab页面切换
- 筛选条件侧边栏
性能优化:
- 静态数据使用常量定义
- 动态数据使用 ref 或 shallowRef
- 避免频繁修改 items 数组
- 合理使用插槽,避免过度自定义
最佳体验:
- 提供清晰的视觉层次
- 禁用状态给予明确反馈
- 徽标信息及时更新
- 配合内容区域提供完整体验
- 响应路由变化保持状态同步
