218 lines
7.1 KiB
Vue
218 lines
7.1 KiB
Vue
<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>
|
|
|
|
<div v-if="features.showSearch" class="search-input-wrapper">
|
|
<v-text-field
|
|
v-model="searchValueModel" :aria-label="searchConfig.label" class="search-input" density="compact"
|
|
hide-details :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>
|
|
</template>
|
|
</v-text-field>
|
|
</div>
|
|
|
|
<div v-if="features.showToolbarActions" class="top-actions">
|
|
<slot name="actions">
|
|
|
|
<!-- 通知 -->
|
|
<v-tooltip location="bottom" :text="toolbarActions.notificationsLabel">
|
|
<template #activator="{ props }">
|
|
<v-btn
|
|
v-bind="props" :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-icon :icon="mdiBellOutline" />
|
|
</v-badge>
|
|
<v-icon v-else :icon="mdiBellOutline" />
|
|
</v-btn>
|
|
</template>
|
|
</v-tooltip>
|
|
|
|
<!-- 訊息 -->
|
|
<v-tooltip location="bottom" :text="toolbarActions.messagesLabel">
|
|
<template #activator="{ props }">
|
|
<v-btn
|
|
v-bind="props" :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-icon :icon="mdiMessageTextOutline" />
|
|
</v-badge>
|
|
<v-icon v-else :icon="mdiMessageTextOutline" />
|
|
</v-btn>
|
|
</template>
|
|
</v-tooltip>
|
|
|
|
<!-- 說明 -->
|
|
<v-tooltip v-if="false" location="bottom" :text="toolbarActions.helpLabel">
|
|
<template #activator="{ props }">
|
|
<v-btn
|
|
v-bind="props" :aria-label="toolbarActions.helpLabel" icon size="small" variant="text"
|
|
@click="emit('action', 'help')">
|
|
<v-icon :icon="mdiHelp" />
|
|
</v-btn>
|
|
</template>
|
|
</v-tooltip>
|
|
|
|
<!-- 設定 -->
|
|
<v-menu :close-on-content-click="false" location="bottom end">
|
|
<template #activator="{ props: menuProps }">
|
|
<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-icon :icon="mdiCogOutline" />
|
|
</v-btn>
|
|
</template>
|
|
</v-tooltip>
|
|
</template>
|
|
<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>
|
|
<template #label>
|
|
<span class="text-body-2" style="width: 8ch;">常用功能</span>
|
|
</template>
|
|
</v-switch>
|
|
</v-list-item>
|
|
<v-list-item>
|
|
<v-switch v-model="showBreadcrumbBarModel" color="primary" density="comfortable" hide-details>
|
|
<template #label>
|
|
<span class="text-body-2" style="width: 8ch;">路徑</span>
|
|
</template>
|
|
</v-switch>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
|
|
<!-- 登出 -->
|
|
<v-tooltip location="bottom" :text="logoutLabel">
|
|
<template #activator="{ props }">
|
|
<v-btn v-bind="props" :aria-label="logoutLabel" icon size="small" variant="text" @click="emit('logout')">
|
|
<v-icon :icon="mdiLogout" />
|
|
</v-btn>
|
|
</template>
|
|
</v-tooltip>
|
|
|
|
<v-tooltip v-if="features.showThemeToggle" location="bottom" :text="themeToggleLabel">
|
|
<template #activator="{ props }">
|
|
<v-btn v-bind="props" :aria-label="themeToggleLabel" icon variant="text" @click="emit('toggle-theme')">
|
|
<v-icon :icon="mdiPaletteOutline" />
|
|
</v-btn>
|
|
</template>
|
|
</v-tooltip>
|
|
</slot>
|
|
</div>
|
|
</v-col>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { mdiBellOutline, mdiCogOutline, mdiHelp, mdiLogout, mdiMagnify, mdiMenu, mdiMessageTextOutline, mdiPaletteOutline } from '@mdi/js'
|
|
import { computed } from 'vue'
|
|
import type { AdminLayoutActionType, AdminLayoutFeatures, AdminLayoutSearchConfig, AdminLayoutToolbarActions, AdminLayoutToolbarCounts } from './types'
|
|
|
|
interface Props {
|
|
isMobile?: boolean
|
|
features?: AdminLayoutFeatures
|
|
searchValue?: string
|
|
searchConfig?: AdminLayoutSearchConfig
|
|
toolbarActions?: AdminLayoutToolbarActions
|
|
toolbarCounts?: AdminLayoutToolbarCounts
|
|
logoutLabel?: string
|
|
themeToggleLabel?: string
|
|
showFavoritesBar?: boolean
|
|
showBreadcrumbBar?: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
isMobile: false,
|
|
features: () => ({
|
|
showThemeToggle: false,
|
|
showFavorites: true,
|
|
showBreadcrumb: true,
|
|
showSearch: true,
|
|
showToolbarActions: true,
|
|
showUserInfo: true,
|
|
}),
|
|
searchValue: '',
|
|
searchConfig: () => ({
|
|
placeholder: '',
|
|
label: '',
|
|
}),
|
|
toolbarActions: () => ({
|
|
notificationsLabel: '',
|
|
messagesLabel: '',
|
|
helpLabel: '',
|
|
settingsLabel: '',
|
|
}),
|
|
toolbarCounts: () => ({
|
|
notifications: 0,
|
|
messages: 0,
|
|
}),
|
|
logoutLabel: '',
|
|
themeToggleLabel: '',
|
|
showFavoritesBar: true,
|
|
showBreadcrumbBar: true,
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'toggle-drawer': []
|
|
'update:searchValue': [value: string]
|
|
search: []
|
|
action: [type: AdminLayoutActionType]
|
|
logout: []
|
|
'toggle-theme': []
|
|
'update:showFavoritesBar': [value: boolean]
|
|
'update:showBreadcrumbBar': [value: boolean]
|
|
}>()
|
|
|
|
const searchValueModel = computed({
|
|
get: () => props.searchValue,
|
|
set: (value) => emit('update:searchValue', value),
|
|
})
|
|
|
|
const showFavoritesBarModel = computed({
|
|
get: () => props.showFavoritesBar,
|
|
set: (value) => emit('update:showFavoritesBar', value),
|
|
})
|
|
|
|
const showBreadcrumbBarModel = computed({
|
|
get: () => props.showBreadcrumbBar,
|
|
set: (value) => emit('update:showBreadcrumbBar', value),
|
|
})
|
|
|
|
function triggerSearch () {
|
|
emit('search')
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.search-input-wrapper {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
:deep(.search-input-wrapper .v-field--appended) {
|
|
padding-inline-end: 4px;
|
|
}
|
|
|
|
.top-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
</style>
|