119 lines
3.5 KiB
Vue
119 lines
3.5 KiB
Vue
<template>
|
|
<v-app>
|
|
<v-app-bar color="surface">
|
|
<v-toolbar-title>{{ systemTitle }}</v-toolbar-title>
|
|
<v-spacer />
|
|
<v-menu
|
|
v-model="menuOpen"
|
|
:close-on-content-click="false"
|
|
:location="menuLocation"
|
|
:max-height="menuMaxHeight"
|
|
offset="8"
|
|
scroll-strategy="reposition"
|
|
:width="menuWidth"
|
|
>
|
|
<template #activator="{ props }">
|
|
<v-btn v-bind="props" icon="mdi-dots-vertical" />
|
|
</template>
|
|
|
|
<v-list :density="menuDensity">
|
|
<template v-for="(item, index) in menuItems" :key="item?.key ?? item?.path ?? index">
|
|
<v-list-group
|
|
v-if="smAndDown && item?.subItems?.length"
|
|
:value="item?.key ?? item?.path ?? index"
|
|
>
|
|
<template #activator="{ props }">
|
|
<v-list-item v-bind="props" :prepend-icon="item.icon" :title="item.title" />
|
|
</template>
|
|
|
|
<v-list-item
|
|
v-for="(subItem, subIndex) in item.subItems"
|
|
:key="subItem?.key ?? subItem?.path ?? subIndex"
|
|
class="pl-6"
|
|
:prepend-icon="subItem.icon"
|
|
:title="subItem.title"
|
|
@click="handleSelect(subItem)"
|
|
/>
|
|
</v-list-group>
|
|
|
|
<v-menu
|
|
v-else-if="item?.subItems?.length"
|
|
close-delay="120"
|
|
:close-on-content-click="false"
|
|
location="end top"
|
|
offset="0"
|
|
open-delay="80"
|
|
:open-on-hover="submenuOpenOnHover"
|
|
origin="start top"
|
|
scroll-strategy="reposition"
|
|
submenu
|
|
>
|
|
<template #activator="{ props }">
|
|
<v-list-item
|
|
v-bind="props"
|
|
append-icon="mdi-chevron-right"
|
|
:prepend-icon="item.icon"
|
|
:title="item.title"
|
|
/>
|
|
</template>
|
|
|
|
<v-list :density="menuDensity">
|
|
<v-list-item
|
|
v-for="(subItem, subIndex) in item.subItems"
|
|
:key="subItem?.key ?? subItem?.path ?? subIndex"
|
|
:prepend-icon="subItem.icon"
|
|
:title="subItem.title"
|
|
@click="handleSelect(subItem)"
|
|
/>
|
|
</v-list>
|
|
</v-menu>
|
|
|
|
<v-list-item
|
|
v-else
|
|
:prepend-icon="item.icon"
|
|
:title="item.title"
|
|
@click="handleSelect(item)"
|
|
/>
|
|
</template>
|
|
</v-list>
|
|
</v-menu>
|
|
</v-app-bar>
|
|
|
|
<v-main>
|
|
<v-container fluid height="100%">
|
|
<slot></slot>
|
|
</v-container>
|
|
</v-main>
|
|
</v-app>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref } from 'vue'
|
|
import { useDisplay } from 'vuetify'
|
|
|
|
const emit = defineEmits(['select'])
|
|
|
|
defineProps({
|
|
systemTitle: { type: String, default: '簡潔模式' },
|
|
menuItems: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
})
|
|
|
|
const menuOpen = ref(false)
|
|
|
|
const { smAndDown } = useDisplay()
|
|
|
|
const menuDensity = computed(() => (smAndDown.value ? 'default' : 'compact'))
|
|
const menuWidth = computed(() => (smAndDown.value ? 280 : 240))
|
|
const menuMaxHeight = computed(() => (smAndDown.value ? 420 : 360))
|
|
const menuLocation = computed(() => (smAndDown.value ? 'bottom end' : 'bottom end'))
|
|
const submenuOpenOnHover = computed(() => !smAndDown.value)
|
|
|
|
function handleSelect (item) {
|
|
menuOpen.value = false
|
|
emit('select', item)
|
|
}
|
|
</script>
|