246 lines
5.3 KiB
Vue
246 lines
5.3 KiB
Vue
<template>
|
||
<div class="app-header">
|
||
<div class="header-left">
|
||
<n-button
|
||
quaternary
|
||
circle
|
||
@click="$emit('toggle-sidebar')"
|
||
class="sidebar-toggle"
|
||
>
|
||
<template #icon>
|
||
<Icon icon="tabler:menu-2" />
|
||
</template>
|
||
</n-button>
|
||
|
||
<n-breadcrumb class="breadcrumb">
|
||
<n-breadcrumb-item>
|
||
<router-link to="/">{{ appName }}</router-link>
|
||
</n-breadcrumb-item>
|
||
<template v-for="(item, index) in breadcrumbItems" :key="index">
|
||
<n-breadcrumb-item v-if="item.href">
|
||
<router-link :to="item.href">{{ item.label }}</router-link>
|
||
</n-breadcrumb-item>
|
||
<n-breadcrumb-item v-else>
|
||
{{ item.label }}
|
||
</n-breadcrumb-item>
|
||
</template>
|
||
</n-breadcrumb>
|
||
</div>
|
||
|
||
<div class="header-right">
|
||
<n-space>
|
||
<!-- 搜索框 - 使用Naive UI的响应式属性,仅System User显示 -->
|
||
<n-input
|
||
v-if="isSystemUser"
|
||
v-model:value="searchQuery"
|
||
:placeholder="t('Search...')"
|
||
clearable
|
||
class="search-input"
|
||
@keyup.enter="handleSearch"
|
||
@clear="handleSearchClear"
|
||
>
|
||
<template #prefix>
|
||
<Icon icon="tabler:search" />
|
||
</template>
|
||
</n-input>
|
||
|
||
<!-- 通知 -->
|
||
<n-button quaternary circle>
|
||
<template #icon>
|
||
<Icon icon="tabler:bell" />
|
||
</template>
|
||
</n-button>
|
||
|
||
<!-- 用户菜单 -->
|
||
<UserMenu />
|
||
</n-space>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
import { NButton, NBreadcrumb, NBreadcrumbItem, NSpace, NInput } from 'naive-ui'
|
||
import { Icon } from '@iconify/vue'
|
||
import { useAuthStore } from '../../shared/stores/auth'
|
||
import { t } from '../../shared/i18n'
|
||
import UserMenu from '../../shared/components/UserMenu.vue'
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const authStore = useAuthStore()
|
||
|
||
// 搜索相关状态
|
||
const searchQuery = ref('')
|
||
|
||
// 从 localStorage 读取应用名称
|
||
const appName = computed(() => localStorage.getItem('appName') || 'Jingrow')
|
||
|
||
const isSystemUser = computed(() => authStore.user?.user_type === 'System User')
|
||
|
||
const breadcrumbItems = computed(() => {
|
||
const items: Array<{ label: string; href?: string }> = []
|
||
|
||
// 根据路由名称和参数生成面包屑
|
||
if (route.name === 'PageTypeList') {
|
||
const entity = route.params.entity as string
|
||
if (entity) {
|
||
items.push({
|
||
label: entity,
|
||
href: `/app/${entity}`
|
||
})
|
||
}
|
||
} else if (route.name === 'PageTypeDetail') {
|
||
const entity = route.params.entity as string
|
||
const id = route.params.id as string
|
||
if (entity) {
|
||
items.push({
|
||
label: entity,
|
||
href: `/app/${entity}`
|
||
})
|
||
}
|
||
if (id && id !== 'new') {
|
||
items.push({
|
||
label: id === 'new' ? t('Create') : id
|
||
})
|
||
}
|
||
} else {
|
||
// 其他页面的标题映射
|
||
const map: Record<string, string> = {
|
||
Dashboard: t('Dashboard'),
|
||
AgentList: t('Agents'),
|
||
AgentDetail: t('Agent Detail'),
|
||
NodeList: t('Node Management'),
|
||
NodeDetail: t('Node Detail'),
|
||
LocalJobList: t('Local Jobs'),
|
||
LocalJobDetail: t('Local Job Detail'),
|
||
FlowBuilder: t('Flow Builder'),
|
||
ScheduledJobList: t('Scheduled Jobs'),
|
||
ScheduledJobDetail: t('Scheduled Job Detail'),
|
||
MenuManager: t('Menu Manager'),
|
||
Settings: t('Settings'),
|
||
SearchResults: t('Search Results')
|
||
}
|
||
const title = map[route.name as string]
|
||
if (title) {
|
||
items.push({ label: title })
|
||
}
|
||
}
|
||
|
||
return items
|
||
})
|
||
|
||
|
||
// 搜索处理函数
|
||
const handleSearch = () => {
|
||
if (searchQuery.value.trim()) {
|
||
router.push({
|
||
name: 'SearchResults',
|
||
query: { q: searchQuery.value.trim() }
|
||
})
|
||
}
|
||
}
|
||
|
||
const handleSearchClear = () => {
|
||
searchQuery.value = ''
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.app-header {
|
||
height: 64px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 24px;
|
||
background: white;
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
min-width: 0; /* 允许flex子项收缩 */
|
||
}
|
||
|
||
.header-right {
|
||
display: flex;
|
||
align-items: center;
|
||
min-width: 0; /* 允许flex子项收缩 */
|
||
}
|
||
|
||
|
||
/* 搜索框响应式样式 */
|
||
.search-input {
|
||
width: 280px;
|
||
}
|
||
|
||
/* 面包屑响应式样式 */
|
||
.breadcrumb {
|
||
min-width: 0; /* 允许收缩 */
|
||
}
|
||
|
||
/* 平板端样式 (768px - 1024px) */
|
||
@media (max-width: 1024px) {
|
||
.app-header {
|
||
padding: 0 16px;
|
||
}
|
||
|
||
.search-input {
|
||
width: 200px;
|
||
}
|
||
|
||
.header-left {
|
||
gap: 12px;
|
||
}
|
||
}
|
||
|
||
/* 移动端样式 (最大768px) */
|
||
@media (max-width: 768px) {
|
||
.app-header {
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.header-left {
|
||
gap: 8px;
|
||
}
|
||
|
||
/* 移动端隐藏面包屑 */
|
||
.breadcrumb {
|
||
display: none;
|
||
}
|
||
|
||
/* 移动端隐藏搜索框 */
|
||
.search-input {
|
||
display: none;
|
||
}
|
||
|
||
/* 移动端隐藏用户名文字 */
|
||
.username {
|
||
display: none;
|
||
}
|
||
|
||
/* 移动端调整按钮间距 */
|
||
.header-right :deep(.n-space) {
|
||
gap: 4px;
|
||
}
|
||
}
|
||
|
||
/* 小屏移动端样式 (最大480px) */
|
||
@media (max-width: 480px) {
|
||
.app-header {
|
||
padding: 0 8px;
|
||
}
|
||
|
||
.header-left {
|
||
gap: 4px;
|
||
}
|
||
|
||
/* 小屏移动端进一步压缩间距 */
|
||
.header-right :deep(.n-space) {
|
||
gap: 2px;
|
||
}
|
||
}
|
||
</style>
|