124 lines
3.1 KiB
Plaintext
124 lines
3.1 KiB
Plaintext
<template>
|
||
<view class="sub-sider" v-if="groups && groups.length > 0">
|
||
<view class="sub-header">
|
||
<text class="sub-title">{{ activeMenuTitle }}</text>
|
||
<view class="sub-collapse" @click="collapsed = !collapsed">
|
||
<text class="sub-collapse-text">{{ collapsed ? '›' : '‹' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view class="sub-body" scroll-y="true">
|
||
<view v-for="(g, gi) in groups" :key="gi" class="group">
|
||
<view class="group-title" @click="toggleGroup(g.title)">
|
||
<text class="group-title-text">{{ g.title }}</text>
|
||
<text class="group-arrow">{{ isGroupOpen(g.title) ? '˄' : '˅' }}</text>
|
||
</view>
|
||
|
||
<view v-if="isGroupOpen(g.title)">
|
||
<view
|
||
v-for="c in g.children"
|
||
:key="c.id"
|
||
class="sub-item"
|
||
:class="{ active: activeSubId === c.id }"
|
||
@click="$emit('sub-click', c)"
|
||
>
|
||
<text class="sub-item-text" :class="{ activeText: activeSubId === c.id }">
|
||
{{ c.title }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref } from 'vue'
|
||
import type { MenuGroup, MenuChild } from '../types.uts'
|
||
|
||
const props = defineProps<{
|
||
activeMenuTitle: string
|
||
groups: MenuGroup[]
|
||
activeSubId: string
|
||
}>()
|
||
|
||
defineEmits<{
|
||
(e:'sub-click', child: MenuChild): void
|
||
}>()
|
||
|
||
const collapsed = ref(false)
|
||
const openGroups = ref<string[]>([])
|
||
|
||
const isGroupOpen = (title: string): boolean => {
|
||
// 默认展开第一个分组 + 当前高亮分组(简单策略)
|
||
if (openGroups.value.length === 0 && props.groups && props.groups.length > 0) return props.groups[0].title === title
|
||
return openGroups.value.includes(title)
|
||
}
|
||
|
||
const toggleGroup = (title: string) => {
|
||
if (openGroups.value.includes(title)) {
|
||
openGroups.value = openGroups.value.filter(t => t !== title)
|
||
} else {
|
||
openGroups.value = [...openGroups.value, title]
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.sub-sider{
|
||
width: 240px;
|
||
background:#ffffff;
|
||
border-right: 1px solid #e5e7eb;
|
||
height: 100vh;
|
||
position: fixed;
|
||
left: 96px; /* 紧贴主侧边栏 */
|
||
top: 0;
|
||
bottom: 0;
|
||
z-index: 900;
|
||
}
|
||
.sub-header{
|
||
height: 56px;
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content: space-between;
|
||
padding: 0 16px;
|
||
border-bottom: 1px solid #eef2f7;
|
||
}
|
||
.sub-title{ font-size:16px; font-weight:600; color:#111827; }
|
||
.sub-collapse{ width:28px; height:28px; display:flex; align-items:center; justify-content:center; }
|
||
.sub-collapse-text{ color:#6b7280; }
|
||
|
||
.sub-body{ height: calc(100vh - 56px); }
|
||
|
||
.group{ padding: 8px 0; }
|
||
.group-title{
|
||
height: 44px;
|
||
padding: 0 16px;
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content: space-between;
|
||
color:#111827;
|
||
}
|
||
.group-title-text{ font-size:15px; font-weight:600; }
|
||
.group-arrow{ color:#6b7280; }
|
||
|
||
.sub-item{
|
||
height: 44px;
|
||
padding: 0 16px;
|
||
display:flex;
|
||
align-items:center;
|
||
background: transparent;
|
||
}
|
||
.sub-item.active{
|
||
background: #eaf2ff;
|
||
}
|
||
.sub-item-text{
|
||
font-size:14px;
|
||
color:#111827;
|
||
}
|
||
.sub-item-text.activeText{
|
||
color:#1677ff;
|
||
font-weight:600;
|
||
}
|
||
</style>
|