Refactor MasterDetailMntC.vue for improved readability and consistency
This commit is contained in:
@@ -35,9 +35,9 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
favoriteHeaderLabel: '我的最愛',
|
||||
favoriteItems: () => [],
|
||||
menuItems: () => [
|
||||
{ title: '首頁', path: '/' },
|
||||
{ title: '設定', path: '/settings' },
|
||||
],
|
||||
{ title: '首頁', path: '/' },
|
||||
{ title: '設定', path: '/settings' },
|
||||
],
|
||||
})
|
||||
|
||||
defineEmits<{
|
||||
|
||||
@@ -13,13 +13,16 @@
|
||||
import type { AdminLayoutMenuItem } from './sk-admin-layout/types'
|
||||
import SKAdminLayout from './SKAdminLayout.vue'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
systemTitle?: string
|
||||
menuItems?: AdminLayoutMenuItem[]
|
||||
}>(), {
|
||||
systemTitle: '簡潔模式',
|
||||
menuItems: () => [],
|
||||
})
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
systemTitle?: string
|
||||
menuItems?: AdminLayoutMenuItem[]
|
||||
}>(),
|
||||
{
|
||||
systemTitle: '簡潔模式',
|
||||
menuItems: () => [],
|
||||
}
|
||||
)
|
||||
|
||||
defineEmits<{
|
||||
select: [item: AdminLayoutMenuItem]
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
<template>
|
||||
<v-col
|
||||
v-if="features.showBreadcrumb && breadcrumbBarVisible && !isMobile"
|
||||
class="d-flex align-center justify-space-between pr-2 pl-3 py-1 bg-surface">
|
||||
v-if="features.showBreadcrumb && breadcrumbBarVisible && !isMobile"
|
||||
class="d-flex align-center justify-space-between pr-2 pl-3 py-1 bg-surface"
|
||||
>
|
||||
<v-breadcrumbs class="pa-0" density="compact" :items="breadcrumbItems">
|
||||
<template #prepend>
|
||||
<v-btn
|
||||
v-if="features.showFavorites && !showFavoritesBar" class="mr-2" color="primary" size="small"
|
||||
variant="outlined" @click="emit('toggle-favorites-bar', true)">
|
||||
v-if="features.showFavorites && !showFavoritesBar"
|
||||
class="mr-2"
|
||||
color="primary"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
@click="emit('toggle-favorites-bar', true)"
|
||||
>
|
||||
常用
|
||||
</v-btn>
|
||||
</template>
|
||||
<template #item="{ item }">
|
||||
<div class="d-flex align-center ga-1">
|
||||
<v-icon v-if="getBreadcrumbIcon(item)" class="mr-1" size="14" :icon="getBreadcrumbIcon(item)" />
|
||||
<v-icon
|
||||
v-if="getBreadcrumbIcon(item)"
|
||||
class="mr-1"
|
||||
size="14"
|
||||
:icon="getBreadcrumbIcon(item)"
|
||||
/>
|
||||
<span class="text-caption text-no-wrap">{{ getBreadcrumbTitle(item) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -57,7 +68,7 @@ const emit = defineEmits<{
|
||||
'toggle-favorites-bar': [value: boolean]
|
||||
}>()
|
||||
|
||||
function getBreadcrumbItem (item: unknown): AdminLayoutBreadcrumbItem | null {
|
||||
function getBreadcrumbItem(item: unknown): AdminLayoutBreadcrumbItem | null {
|
||||
if (typeof item !== 'object' || item === null) return null
|
||||
|
||||
if ('title' in item) {
|
||||
@@ -70,11 +81,11 @@ function getBreadcrumbItem (item: unknown): AdminLayoutBreadcrumbItem | null {
|
||||
return raw as AdminLayoutBreadcrumbItem
|
||||
}
|
||||
|
||||
function getBreadcrumbIcon (item: unknown) {
|
||||
function getBreadcrumbIcon(item: unknown) {
|
||||
return getBreadcrumbItem(item)?.icon
|
||||
}
|
||||
|
||||
function getBreadcrumbTitle (item: unknown) {
|
||||
function getBreadcrumbTitle(item: unknown) {
|
||||
return getBreadcrumbItem(item)?.title ?? ''
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
<template>
|
||||
<v-col
|
||||
v-if="features.showFavorites && showFavoritesBar && !isMobile"
|
||||
class="d-flex align-center pr-2 pl-3 py-1 bg-surface">
|
||||
v-if="features.showFavorites && showFavoritesBar && !isMobile"
|
||||
class="d-flex align-center pr-2 pl-3 py-1 bg-surface"
|
||||
>
|
||||
<div class="favorites-label text-body-2 text-no-wrap pe-2">
|
||||
{{ favoritesConfig.label }}
|
||||
</div>
|
||||
<div class="favorites-list flex-grow-1 d-flex flex-wrap ga-2">
|
||||
<transition-group class="d-flex flex-wrap ga-2" name="favorite-list" tag="div">
|
||||
<v-chip
|
||||
v-for="item in favoriteItems" :key="item.path ?? item.title" class="favorite-item" closable
|
||||
color="secondary" size="small" variant="outlined" @click="emit('select', item)"
|
||||
@click:close="emit('remove-favorite', item)">
|
||||
v-for="item in favoriteItems"
|
||||
:key="item.path ?? item.title"
|
||||
class="favorite-item"
|
||||
closable
|
||||
color="secondary"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
@click="emit('select', item)"
|
||||
@click:close="emit('remove-favorite', item)"
|
||||
>
|
||||
<v-icon v-if="item.icon" class="me-1" size="16" :icon="item.icon" />
|
||||
<span class="text-caption">{{ item.title }}</span>
|
||||
</v-chip>
|
||||
</transition-group>
|
||||
<v-btn
|
||||
v-if="favoritesConfig.showAdd" class="favorite-add" color="primary" size="small" variant="outlined"
|
||||
@click="emit('add-favorite')">
|
||||
v-if="favoritesConfig.showAdd"
|
||||
class="favorite-add"
|
||||
color="primary"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
@click="emit('add-favorite')"
|
||||
>
|
||||
<v-icon class="mr-1" size="16" :icon="mdiPlus" />
|
||||
<span class="text-caption">{{ favoritesConfig.addLabel }}</span>
|
||||
</v-btn>
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
<template>
|
||||
<v-col class="d-flex align-center bg-surface pr-2 pl-2 pl-m-3 py-1">
|
||||
<v-btn v-if="isMobile" :icon="mdiMenu" size="small" variant="text" @click="emit('toggle-drawer')"></v-btn>
|
||||
<v-btn
|
||||
v-if="isMobile"
|
||||
:icon="mdiMenu"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('toggle-drawer')"
|
||||
></v-btn>
|
||||
|
||||
<div v-if="features.showSearch" class="search-input-wrapper">
|
||||
<span id="admin-layout-search-label" class="sr-only">{{ searchConfig.label }}</span>
|
||||
<v-text-field
|
||||
v-model="searchValueModel" aria-labelledby="admin-layout-search-label" :aria-label="searchConfig.label"
|
||||
class="search-input" density="compact" hide-details name="layout-search"
|
||||
:placeholder="searchConfig.placeholder" variant="outlined"
|
||||
@keyup.enter="triggerSearch">
|
||||
v-model="searchValueModel"
|
||||
aria-labelledby="admin-layout-search-label"
|
||||
:aria-label="searchConfig.label"
|
||||
class="search-input"
|
||||
density="compact"
|
||||
hide-details
|
||||
name="layout-search"
|
||||
:placeholder="searchConfig.placeholder"
|
||||
variant="outlined"
|
||||
@keyup.enter="triggerSearch"
|
||||
>
|
||||
<template v-if="!isMobile" #prepend-inner>
|
||||
<v-icon size="small" :icon="mdiMagnify" />
|
||||
</template>
|
||||
<template #append-inner>
|
||||
<v-btn :aria-label="searchConfig.label" color="primary" size="small" variant="text" @click="triggerSearch">
|
||||
<v-btn
|
||||
:aria-label="searchConfig.label"
|
||||
color="primary"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="triggerSearch"
|
||||
>
|
||||
開始搜尋
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -22,16 +41,24 @@ v-model="searchValueModel" aria-labelledby="admin-layout-search-label" :aria-lab
|
||||
|
||||
<div v-if="features.showToolbarActions" class="top-actions">
|
||||
<slot name="actions">
|
||||
|
||||
<!-- 通知 -->
|
||||
<v-tooltip location="bottom" :text="toolbarActions.notificationsLabel">
|
||||
<template #activator="{ props: activatorProps }">
|
||||
<v-btn
|
||||
v-bind="activatorProps" :aria-label="toolbarActions.notificationsLabel" icon size="small" variant="text"
|
||||
@click="emit('action', 'notifications')">
|
||||
v-bind="activatorProps"
|
||||
:aria-label="toolbarActions.notificationsLabel"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('action', 'notifications')"
|
||||
>
|
||||
<v-badge
|
||||
v-if="toolbarCounts.notifications" color="error" :content="toolbarCounts.notifications"
|
||||
offset-x="4" offset-y="-2">
|
||||
v-if="toolbarCounts.notifications"
|
||||
color="error"
|
||||
:content="toolbarCounts.notifications"
|
||||
offset-x="4"
|
||||
offset-y="-2"
|
||||
>
|
||||
<v-icon :icon="mdiBellOutline" />
|
||||
</v-badge>
|
||||
<v-icon v-else :icon="mdiBellOutline" />
|
||||
@@ -43,11 +70,20 @@ v-if="toolbarCounts.notifications" color="error" :content="toolbarCounts.notific
|
||||
<v-tooltip location="bottom" :text="toolbarActions.messagesLabel">
|
||||
<template #activator="{ props: activatorProps }">
|
||||
<v-btn
|
||||
v-bind="activatorProps" :aria-label="toolbarActions.messagesLabel" icon size="small" variant="text"
|
||||
@click="emit('action', 'messages')">
|
||||
v-bind="activatorProps"
|
||||
:aria-label="toolbarActions.messagesLabel"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('action', 'messages')"
|
||||
>
|
||||
<v-badge
|
||||
v-if="toolbarCounts.messages" color="warning" :content="toolbarCounts.messages" offset-x="4"
|
||||
offset-y="-2">
|
||||
v-if="toolbarCounts.messages"
|
||||
color="warning"
|
||||
:content="toolbarCounts.messages"
|
||||
offset-x="4"
|
||||
offset-y="-2"
|
||||
>
|
||||
<v-icon :icon="mdiMessageTextOutline" />
|
||||
</v-badge>
|
||||
<v-icon v-else :icon="mdiMessageTextOutline" />
|
||||
@@ -59,8 +95,13 @@ v-if="toolbarCounts.messages" color="warning" :content="toolbarCounts.messages"
|
||||
<v-tooltip v-if="false" location="bottom" :text="toolbarActions.helpLabel">
|
||||
<template #activator="{ props: activatorProps }">
|
||||
<v-btn
|
||||
v-bind="activatorProps" :aria-label="toolbarActions.helpLabel" icon size="small" variant="text"
|
||||
@click="emit('action', 'help')">
|
||||
v-bind="activatorProps"
|
||||
:aria-label="toolbarActions.helpLabel"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('action', 'help')"
|
||||
>
|
||||
<v-icon :icon="mdiHelp" />
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -72,8 +113,12 @@ v-bind="activatorProps" :aria-label="toolbarActions.helpLabel" icon size="small"
|
||||
<v-tooltip location="bottom" :text="toolbarActions.settingsLabel">
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<v-btn
|
||||
v-bind="{ ...menuProps, ...tooltipProps }" :aria-label="toolbarActions.settingsLabel" icon size="small"
|
||||
variant="text">
|
||||
v-bind="{ ...menuProps, ...tooltipProps }"
|
||||
:aria-label="toolbarActions.settingsLabel"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
>
|
||||
<v-icon :icon="mdiCogOutline" />
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -82,16 +127,26 @@ v-bind="{ ...menuProps, ...tooltipProps }" :aria-label="toolbarActions.settingsL
|
||||
<v-list density="compact" width="180">
|
||||
<v-list-subheader class="text-subtitle-1 py-2">顯示設定</v-list-subheader>
|
||||
<v-list-item>
|
||||
<v-switch v-model="showFavoritesBarModel" color="primary" density="comfortable" hide-details>
|
||||
<v-switch
|
||||
v-model="showFavoritesBarModel"
|
||||
color="primary"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
>
|
||||
<template #label>
|
||||
<span class="text-body-2" style="width: 8ch;">常用功能</span>
|
||||
<span class="text-body-2" style="width: 8ch">常用功能</span>
|
||||
</template>
|
||||
</v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="breadcrumbBarVisibleModel" color="primary" density="comfortable" hide-details>
|
||||
<v-switch
|
||||
v-model="breadcrumbBarVisibleModel"
|
||||
color="primary"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
>
|
||||
<template #label>
|
||||
<span class="text-body-2" style="width: 8ch;">路徑</span>
|
||||
<span class="text-body-2" style="width: 8ch">路徑</span>
|
||||
</template>
|
||||
</v-switch>
|
||||
</v-list-item>
|
||||
@@ -101,7 +156,14 @@ v-bind="{ ...menuProps, ...tooltipProps }" :aria-label="toolbarActions.settingsL
|
||||
<!-- 登出 -->
|
||||
<v-tooltip location="bottom" :text="logoutLabel">
|
||||
<template #activator="{ props: activatorProps }">
|
||||
<v-btn v-bind="activatorProps" :aria-label="logoutLabel" icon size="small" variant="text" @click="emit('logout')">
|
||||
<v-btn
|
||||
v-bind="activatorProps"
|
||||
:aria-label="logoutLabel"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('logout')"
|
||||
>
|
||||
<v-icon :icon="mdiLogout" />
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -109,7 +171,13 @@ v-bind="{ ...menuProps, ...tooltipProps }" :aria-label="toolbarActions.settingsL
|
||||
|
||||
<v-tooltip v-if="features.showThemeToggle" location="bottom" :text="themeToggleLabel">
|
||||
<template #activator="{ props: activatorProps }">
|
||||
<v-btn v-bind="activatorProps" :aria-label="themeToggleLabel" icon variant="text" @click="emit('toggle-theme')">
|
||||
<v-btn
|
||||
v-bind="activatorProps"
|
||||
:aria-label="themeToggleLabel"
|
||||
icon
|
||||
variant="text"
|
||||
@click="emit('toggle-theme')"
|
||||
>
|
||||
<v-icon :icon="mdiPaletteOutline" />
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -120,8 +188,23 @@ v-bind="{ ...menuProps, ...tooltipProps }" :aria-label="toolbarActions.settingsL
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AdminLayoutActionType, AdminLayoutFeatures, AdminLayoutSearchConfig, AdminLayoutToolbarActions, AdminLayoutToolbarCounts } from './types'
|
||||
import { mdiBellOutline, mdiCogOutline, mdiHelp, mdiLogout, mdiMagnify, mdiMenu, mdiMessageTextOutline, mdiPaletteOutline } from '@mdi/js'
|
||||
import type {
|
||||
AdminLayoutActionType,
|
||||
AdminLayoutFeatures,
|
||||
AdminLayoutSearchConfig,
|
||||
AdminLayoutToolbarActions,
|
||||
AdminLayoutToolbarCounts,
|
||||
} from './types'
|
||||
import {
|
||||
mdiBellOutline,
|
||||
mdiCogOutline,
|
||||
mdiHelp,
|
||||
mdiLogout,
|
||||
mdiMagnify,
|
||||
mdiMenu,
|
||||
mdiMessageTextOutline,
|
||||
mdiPaletteOutline,
|
||||
} from '@mdi/js'
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
@@ -194,7 +277,7 @@ const breadcrumbBarVisibleModel = computed({
|
||||
set: (value) => emit('update:breadcrumb-bar-visible', value),
|
||||
})
|
||||
|
||||
function triggerSearch () {
|
||||
function triggerSearch() {
|
||||
emit('search')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,23 +1,42 @@
|
||||
<template>
|
||||
<v-list v-model:opened="openedModel" color="primary" density="compact" prepend-gap="8">
|
||||
<template v-for="item in menuItems" :key="item.path ?? item.title">
|
||||
<v-list-group v-if="item.subItems?.length" :id="getGroupId(item)" :value="getGroupValue(item)">
|
||||
<v-list-group
|
||||
v-if="item.subItems?.length"
|
||||
:id="getGroupId(item)"
|
||||
:value="getGroupValue(item)"
|
||||
>
|
||||
<template #activator="{ props: activatorProps }">
|
||||
<v-list-item
|
||||
v-bind="isShrink ? undefined : activatorProps" :class="{ 'px-0': isShrink }"
|
||||
:link="isNavigable(item) && !!item.path" :to="isNavigable(item) ? item.path : undefined" @click="emitSelect(item)">
|
||||
v-bind="isShrink ? undefined : activatorProps"
|
||||
:class="{ 'px-0': isShrink }"
|
||||
:link="isNavigable(item) && !!item.path"
|
||||
:to="isNavigable(item) ? item.path : undefined"
|
||||
@click="emitSelect(item)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon v-if="item.icon" size="20" :icon="item.icon" />
|
||||
<v-btn v-if="isShrink && !item.icon" class="" rounded size="36" spaced="start" variant="text">{{
|
||||
item.title?.charAt(0) }}</v-btn>
|
||||
<v-btn
|
||||
v-if="isShrink && !item.icon"
|
||||
class=""
|
||||
rounded
|
||||
size="36"
|
||||
spaced="start"
|
||||
variant="text"
|
||||
>{{ item.title?.charAt(0) }}</v-btn
|
||||
>
|
||||
</template>
|
||||
<template #title>
|
||||
<span v-if="!isShrink" class="text-body-2 nav-text-overflow">{{ item.title }}</span>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-chip
|
||||
v-if="!isShrink && getItemCount(item) > 0" class="menu-count" color="secondary" size="x-small"
|
||||
variant="tonal">
|
||||
v-if="!isShrink && getItemCount(item) > 0"
|
||||
class="menu-count"
|
||||
color="secondary"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
>
|
||||
{{ getItemCount(item) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
@@ -26,21 +45,29 @@ v-if="!isShrink && getItemCount(item) > 0" class="menu-count" color="secondary"
|
||||
|
||||
<template v-for="subItem in item.subItems" :key="subItem.path ?? subItem.title">
|
||||
<v-list-group
|
||||
v-if="subItem.subItems?.length"
|
||||
v-if="subItem.subItems?.length"
|
||||
:id="getGroupId(subItem, getGroupId(item))"
|
||||
:value="getGroupValue(subItem, getGroupValue(item))">
|
||||
:value="getGroupValue(subItem, getGroupValue(item))"
|
||||
>
|
||||
<template #activator="{ props: subProps }">
|
||||
<v-list-item
|
||||
v-bind="subProps" :link="isNavigable(subItem)"
|
||||
:prepend-icon="subItem.icon || mdiMenuRight" :to="isNavigable(subItem) ? subItem.path : undefined"
|
||||
@click="emitSelect(subItem)">
|
||||
v-bind="subProps"
|
||||
:link="isNavigable(subItem)"
|
||||
:prepend-icon="subItem.icon || mdiMenuRight"
|
||||
:to="isNavigable(subItem) ? subItem.path : undefined"
|
||||
@click="emitSelect(subItem)"
|
||||
>
|
||||
<template #title>
|
||||
<span class="text-body-2 nav-text-overflow">{{ subItem.title }}</span>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-chip
|
||||
v-if="getItemCount(subItem) > 0" class="menu-count" color="secondary" size="x-small"
|
||||
variant="tonal">
|
||||
v-if="getItemCount(subItem) > 0"
|
||||
class="menu-count"
|
||||
color="secondary"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
>
|
||||
{{ getItemCount(subItem) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
@@ -48,13 +75,19 @@ v-if="getItemCount(subItem) > 0" class="menu-count" color="secondary" size="x-sm
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
v-for="subSubItem in subItem.subItems" :key="subSubItem.path ?? subSubItem.title"
|
||||
:link="!!subSubItem.path" :prepend-icon="mdiCircleSmall" :to="subSubItem.path"
|
||||
@click="emitSelect(subSubItem)">
|
||||
v-for="subSubItem in subItem.subItems"
|
||||
:key="subSubItem.path ?? subSubItem.title"
|
||||
:link="!!subSubItem.path"
|
||||
:prepend-icon="mdiCircleSmall"
|
||||
:to="subSubItem.path"
|
||||
@click="emitSelect(subSubItem)"
|
||||
>
|
||||
<template #title>
|
||||
<v-tooltip location="end" :text="subSubItem.title">
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{ subSubItem.title }}</span>
|
||||
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
|
||||
subSubItem.title
|
||||
}}</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
@@ -62,12 +95,18 @@ v-for="subSubItem in subItem.subItems" :key="subSubItem.path ?? subSubItem.title
|
||||
</v-list-group>
|
||||
|
||||
<v-list-item
|
||||
v-else :link="!!subItem.path" :prepend-icon="subItem.icon || mdiMenuRight" :to="subItem.path"
|
||||
@click="emitSelect(subItem)">
|
||||
v-else
|
||||
:link="!!subItem.path"
|
||||
:prepend-icon="subItem.icon || mdiMenuRight"
|
||||
:to="subItem.path"
|
||||
@click="emitSelect(subItem)"
|
||||
>
|
||||
<template #title>
|
||||
<v-tooltip location="end" :text="subItem.title">
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{ subItem.title }}</span>
|
||||
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
|
||||
subItem.title
|
||||
}}</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
@@ -75,16 +114,31 @@ v-else :link="!!subItem.path" :prepend-icon="subItem.icon || mdiMenuRight" :to="
|
||||
</template>
|
||||
</v-list-group>
|
||||
|
||||
<v-list-item v-else :class="{ 'px-0': isShrink }" :link="!!item.path" :to="item.path" @click="emitSelect(item)">
|
||||
<v-list-item
|
||||
v-else
|
||||
:class="{ 'px-0': isShrink }"
|
||||
:link="!!item.path"
|
||||
:to="item.path"
|
||||
@click="emitSelect(item)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon v-if="item.icon" size="20" :icon="item.icon" />
|
||||
<v-btn v-if="isShrink && !item.icon" class="" rounded size="36" spaced="start" variant="text">{{
|
||||
item.title?.charAt(0) }}</v-btn>
|
||||
<v-btn
|
||||
v-if="isShrink && !item.icon"
|
||||
class=""
|
||||
rounded
|
||||
size="36"
|
||||
spaced="start"
|
||||
variant="text"
|
||||
>{{ item.title?.charAt(0) }}</v-btn
|
||||
>
|
||||
</template>
|
||||
<template #title>
|
||||
<v-tooltip v-if="!isShrink" location="end" :text="item.title">
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{ item.title }}</span>
|
||||
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
|
||||
item.title
|
||||
}}</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
@@ -126,15 +180,18 @@ const openedModel = computed({
|
||||
})
|
||||
|
||||
// 當側邊欄收縮時,自動收起所有展開的子選單
|
||||
watch(() => props.isShrink, (newVal) => {
|
||||
if (newVal) {
|
||||
openedModel.value = []
|
||||
watch(
|
||||
() => props.isShrink,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
openedModel.value = []
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const isNavigable = (item: AdminLayoutMenuItem) => item?.navigable !== false
|
||||
|
||||
function emitSelect (item: AdminLayoutMenuItem) {
|
||||
function emitSelect(item: AdminLayoutMenuItem) {
|
||||
// 收縮狀態下點擊選單項目時,先解除收縮再進行選擇
|
||||
// 這樣可以讓使用者看到完整的選單結構和導航結果
|
||||
if (props.isShrink) {
|
||||
@@ -143,7 +200,7 @@ function emitSelect (item: AdminLayoutMenuItem) {
|
||||
emit('select', item)
|
||||
}
|
||||
|
||||
function getItemCount (item: AdminLayoutMenuItem) {
|
||||
function getItemCount(item: AdminLayoutMenuItem) {
|
||||
if (!item?.subItems?.length) return 0
|
||||
const countLeaf = (list: AdminLayoutMenuItem[]): number =>
|
||||
(list || []).reduce((total: number, current: AdminLayoutMenuItem) => {
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
<v-sheet class="mobile-favorites-panel d-flex flex-column" color="surface">
|
||||
<v-list class="px-2 py-2 flex-grow-1 overflow-auto" density="comfortable">
|
||||
<v-list-item
|
||||
v-for="item in favoriteItems" :key="item.path ?? item.title" class="mb-1" rounded="lg"
|
||||
@click="emit('select', item)">
|
||||
v-for="item in favoriteItems"
|
||||
:key="item.path ?? item.title"
|
||||
class="mb-1"
|
||||
rounded="lg"
|
||||
@click="emit('select', item)"
|
||||
>
|
||||
<v-list-item-title class="text-body-2">{{ item.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -13,11 +17,14 @@ v-for="item in favoriteItems" :key="item.path ?? item.title" class="mb-1" rounde
|
||||
<script setup lang="ts">
|
||||
import type { AdminLayoutMenuItem } from './types'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
favoriteItems?: AdminLayoutMenuItem[]
|
||||
}>(), {
|
||||
favoriteItems: () => [],
|
||||
})
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
favoriteItems?: AdminLayoutMenuItem[]
|
||||
}>(),
|
||||
{
|
||||
favoriteItems: () => [],
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [item: AdminLayoutMenuItem]
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
<v-sheet class="mobile-menu-panel d-flex flex-column" color="surface">
|
||||
<v-list class="px-2 py-2 flex-grow-1 overflow-auto" density="comfortable">
|
||||
<v-list-item
|
||||
v-for="item in mobileCurrentItems" :key="item.path ?? item.title" class="mb-1" rounded="lg"
|
||||
@click="emit('item-click', item)">
|
||||
v-for="item in mobileCurrentItems"
|
||||
:key="item.path ?? item.title"
|
||||
class="mb-1"
|
||||
rounded="lg"
|
||||
@click="emit('item-click', item)"
|
||||
>
|
||||
<v-list-item-title class="text-body-2">{{ item.title }}</v-list-item-title>
|
||||
<template #append>
|
||||
<v-icon size="18" :icon="item.subItems?.length ? mdiChevronRight : mdiArrowTopRight" />
|
||||
@@ -17,11 +21,14 @@ v-for="item in mobileCurrentItems" :key="item.path ?? item.title" class="mb-1" r
|
||||
import type { AdminLayoutMenuItem } from './types'
|
||||
import { mdiArrowTopRight, mdiChevronRight } from '@mdi/js'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
mobileCurrentItems?: AdminLayoutMenuItem[]
|
||||
}>(), {
|
||||
mobileCurrentItems: () => [],
|
||||
})
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
mobileCurrentItems?: AdminLayoutMenuItem[]
|
||||
}>(),
|
||||
{
|
||||
mobileCurrentItems: () => [],
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'item-click': [item: AdminLayoutMenuItem]
|
||||
|
||||
@@ -65,7 +65,4 @@ export interface AdminLayoutDrawerConfig {
|
||||
railWidth: number
|
||||
}
|
||||
|
||||
export type AdminLayoutActionType =
|
||||
| 'notifications'
|
||||
| 'messages'
|
||||
| 'help'
|
||||
export type AdminLayoutActionType = 'notifications' | 'messages' | 'help'
|
||||
|
||||
Reference in New Issue
Block a user