HeaderLayout.vue
1 <script setup lang="ts"> 2 import { watchEffect, computed, ref } from 'vue' 3 import { useRoute, useRouter } from 'vue-router' 4 import { useLayoutStore, getScreenFromPath } from '@/renderer/store/layout' 5 // import { useTheme } from 'vuetify' 6 import { useDisplay } from 'vuetify' 7 import LocaleBtn from '@/renderer/components/common/LocaleBtn.vue' 8 import { useRouteFeatures } from '@/renderer/composables/useRouteFeatures' 9 import { getAppInfo } from '@/renderer/utils' 10 // import { useMcpStore } from '@/renderer/store/mcp' 11 import { useI18n } from 'vue-i18n' 12 const { t } = useI18n() 13 14 const { titleKey, hasComponent } = useRouteFeatures() 15 16 const { xs, smAndUp } = useDisplay() 17 18 // const { hasComponent } = useRouteFeatures() 19 20 // const mcpStore = useMcpStore() 21 22 const layoutStore = useLayoutStore() 23 24 const router = useRouter() 25 const route = useRoute() 26 // const theme = useTheme() 27 28 const handleRoute = (path: string): void => { 29 router.push(path) 30 return 31 } 32 33 watchEffect(() => { 34 layoutStore.screen = getScreenFromPath(route.path) 35 }) 36 37 const platform = ref('') 38 39 watchEffect(() => { 40 getAppInfo() 41 .then((info) => { 42 platform.value = info.platform 43 }) 44 .catch((error) => { 45 console.error('Failed to fetch app info:', error) 46 platform.value = '' 47 }) 48 }) 49 50 const items = computed(() => { 51 return [ 52 { title: t('title.main'), testid: 'btn-menu-mcp', route: '/', icon: 'mdi-view-dashboard' }, 53 { 54 title: t('title.chat'), 55 testid: 'btn-menu-chat', 56 route: '/chat', 57 icon: 'mdi-comment-text-outline' 58 }, 59 { 60 title: t('title.agent'), 61 testid: 'btn-menu-agent', 62 route: '/agent', 63 icon: 'mdi-account-multiple' 64 }, 65 { 66 title: t('title.setting'), 67 testid: 'btn-menu-setting', 68 route: '/setting', 69 icon: 'mdi-cog-transfer-outline' 70 } 71 ] 72 }) 73 </script> 74 <template> 75 <v-app-bar 76 class="drag gradient-main text-white title-bar" 77 block 78 :order="-1" 79 color="primary" 80 height="36" 81 > 82 <v-app-bar-nav-icon 83 class="ml-2 no-drag" 84 density="compact" 85 rounded="lg" 86 :disabled="!hasComponent('sideDrawer').value" 87 @click.stop="layoutStore.sidebar = !layoutStore.sidebar" 88 > 89 </v-app-bar-nav-icon> 90 <LocaleBtn class="no-drag ml-3" data-testid="select-language" /> 91 <v-app-bar-title class="title text-uppercase text-title-medium">{{ 92 $t(titleKey.toString()) 93 }}</v-app-bar-title> 94 95 <v-btn-toggle 96 v-if="smAndUp" 97 v-model="layoutStore.screen" 98 class="no-drag ml-2" 99 data-testid="main-menu" 100 mandatory 101 variant="text" 102 base-color="white" 103 > 104 <v-btn 105 v-for="(item, index) in items" 106 :key="index" 107 :data-testid="item.testid" 108 @click="handleRoute(item.route)" 109 > 110 <v-icon> {{ item.icon }} </v-icon> 111 </v-btn> 112 </v-btn-toggle> 113 <v-menu v-if="xs"> 114 <template #activator="{ props }"> 115 <v-btn color="white" v-bind="props" class="no-drag ml-2" icon="mdi-apps" rounded="lg"> 116 </v-btn> 117 </template> 118 <v-list nav> 119 <v-list-item 120 v-for="(item, index) in items" 121 :key="index" 122 :value="index" 123 slim 124 :title="item.title" 125 @click="handleRoute(item.route)" 126 > 127 <template #prepend> 128 <v-icon :icon="item.icon" color="secondary"></v-icon> 129 </template> 130 </v-list-item> 131 </v-list> 132 </v-menu> 133 134 <v-spacer></v-spacer> 135 136 <template #append> 137 <v-col style="flex: 0 0 100px"></v-col> 138 </template> 139 </v-app-bar> 140 </template> 141 <style scoped> 142 .drag { 143 app-region: drag; 144 } 145 146 .no-drag { 147 app-region: no-drag; 148 } 149 150 .title { 151 display: block; 152 } 153 154 @media (max-width: 600px) { 155 .title { 156 display: none; 157 } 158 } 159 160 .title-bar { 161 padding-left: env(titlebar-area-x, 0); 162 } 163 164 .gradient-main { 165 background: linear-gradient(to right, #01579b, #344767 calc(100% - 120px), #344767) !important; 166 background-blend-mode: normal; 167 } 168 </style>