2b69686795
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引 - 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理 - 实现/article、/case、/product及/page动态路由兼容列表与详情展示 - 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置 - 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持 - 模板增强支持CMS单页内容加载及SEO信息动态设置 - 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
159 lines
7.7 KiB
Vue
159 lines
7.7 KiB
Vue
<template>
|
||
<header class="sticky top-0 z-50">
|
||
<!-- 上层:白底 Logo + 移动端菜单按钮 / 服务热线 -->
|
||
<div class="bg-white border-b border-[var(--t8-border)]">
|
||
<div class="container mx-auto px-4 h-24 flex items-center justify-between">
|
||
<SiteBrand
|
||
link-class="min-w-0 shrink"
|
||
:logo="siteLogo"
|
||
:icon="siteIcon"
|
||
:name="siteName"
|
||
logo-class="h-10 w-auto max-w-[70vw] object-contain sm:h-14 sm:max-w-[220px] lg:h-[82px] lg:max-w-[420px]"
|
||
icon-class="h-8 w-auto object-contain"
|
||
name-class="font-bold text-[var(--t8-primary)] truncate max-w-[200px]"
|
||
/>
|
||
<div class="flex items-center gap-2">
|
||
<!-- 服务热线(桌面端显示) -->
|
||
<div class="hidden sm:flex items-center gap-2 text-[var(--t8-text)]">
|
||
<span class="w-9 h-9 rounded-full bg-[var(--t8-primary-soft)] flex items-center justify-center flex-shrink-0">
|
||
<svg class="w-5 h-5 text-[var(--t8-primary)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||
</svg>
|
||
</span>
|
||
<div class="leading-tight">
|
||
<div class="text-xs text-[var(--t8-text-secondary)]">服务热线</div>
|
||
<div class="font-semibold text-[var(--t8-primary)]">{{ phone || '400-000-0000' }}</div>
|
||
</div>
|
||
</div>
|
||
<!-- 移动端菜单按钮(放在上层右侧,与 logo 同高居中) -->
|
||
<button
|
||
class="md:hidden p-2 rounded-lg hover:bg-gray-100 flex items-center"
|
||
@click="mobileMenuOpen = !mobileMenuOpen"
|
||
>
|
||
<svg v-if="!mobileMenuOpen" class="w-6 h-6 text-[var(--t8-text)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||
</svg>
|
||
<svg v-else class="w-6 h-6 text-[var(--t8-text)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 下层:金棕渐变导航条(移动端隐藏) -->
|
||
<div :style="{ backgroundImage: 'var(--t8-navbar-bg)' }" class="hidden md:block">
|
||
<div class="container mx-auto px-4 flex items-center justify-between h-14">
|
||
<!-- 桌面端导航(居左) -->
|
||
<nav class="hidden md:flex items-center gap-3">
|
||
<template v-for="item in navItems" :key="item.navigationId || item.path">
|
||
<!-- 有子菜单 -->
|
||
<div v-if="item.children && item.children.length > 0" class="relative group h-full flex items-center">
|
||
<button
|
||
class="relative h-full px-5 text-white/90 hover:text-white hover:bg-white/5 transition-colors flex items-center gap-1 text-lg font-medium
|
||
after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:h-[2px] after:w-0 after:bg-[var(--t8-accent)] after:transition-all after:duration-200 group-hover:after:w-[70%]"
|
||
>
|
||
{{ item.title }}
|
||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||
</svg>
|
||
</button>
|
||
<div class="absolute left-0 top-full pt-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all">
|
||
<div class="bg-white rounded-lg shadow-lg border border-[var(--t8-border)] py-2 min-w-[160px]">
|
||
<NuxtLink
|
||
v-for="child in item.children"
|
||
:key="child.navigationId"
|
||
:to="getChildPath(child)"
|
||
class="block px-4 py-2 text-[15px] text-[var(--t8-text)] hover:text-[var(--t8-primary)] hover:bg-[var(--t8-primary-soft)] transition-colors whitespace-nowrap"
|
||
>
|
||
{{ child.title }}
|
||
</NuxtLink>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 无子菜单 -->
|
||
<NuxtLink
|
||
v-else
|
||
:to="getNavLink(item)"
|
||
:target="item.target === '_blank' ? '_blank' : undefined"
|
||
class="relative h-full px-5 flex items-center text-white/90 hover:text-white hover:bg-white/5 transition-colors text-lg font-medium
|
||
after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:h-[2px] after:w-0 after:bg-[var(--t8-accent)] after:transition-all after:duration-200 hover:after:w-[70%]"
|
||
active-class="!text-white bg-white/15"
|
||
>
|
||
{{ item.title }}
|
||
</NuxtLink>
|
||
</template>
|
||
</nav>
|
||
|
||
<!-- 搜索框(后台 setting.searchBtn 控制是否显示) -->
|
||
<SiteSearchBox
|
||
v-if="showHeaderSearch"
|
||
variant="light"
|
||
accent="var(--t8-primary)"
|
||
placeholder="请输入关键词..."
|
||
class="hidden md:flex items-center"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 移动端菜单 -->
|
||
<div
|
||
v-if="mobileMenuOpen"
|
||
class="md:hidden bg-white border-t border-[var(--t8-border)]"
|
||
>
|
||
<div class="container mx-auto px-4 py-4 space-y-1">
|
||
<template v-for="item in navItems" :key="item.navigationId || item.path">
|
||
<div v-if="item.children && item.children.length > 0">
|
||
<div class="px-4 py-3 text-base font-semibold text-[var(--t8-primary)]">
|
||
{{ item.title }}
|
||
</div>
|
||
<NuxtLink
|
||
v-for="child in item.children"
|
||
:key="child.navigationId"
|
||
:to="getChildPath(child)"
|
||
class="block px-8 py-2 text-sm text-[var(--t8-text-secondary)] hover:text-[var(--t8-primary)] hover:bg-[var(--t8-primary-soft)] rounded-lg transition-colors"
|
||
@click="mobileMenuOpen = false"
|
||
>
|
||
{{ child.title }}
|
||
</NuxtLink>
|
||
</div>
|
||
<NuxtLink
|
||
v-else
|
||
:to="getNavLink(item)"
|
||
:target="item.target === '_blank' ? '_blank' : undefined"
|
||
class="block px-4 py-3 text-base font-medium text-[var(--t8-text)] hover:text-[var(--t8-primary)] hover:bg-[var(--t8-primary-soft)] rounded-lg transition-colors"
|
||
active-class="text-[var(--t8-primary)] bg-[var(--t8-primary-soft)]"
|
||
@click="mobileMenuOpen = false"
|
||
>
|
||
{{ item.title }}
|
||
</NuxtLink>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
/**
|
||
* 模板 8 - Header 组件(金棕企业风,对齐 gxhrtc 截图)
|
||
* 双层结构:上层白底(Logo + 服务热线),下层金棕渐变导航条(菜单居左 + 搜索居右)
|
||
*/
|
||
import type { CmsNavigation } from '~/types'
|
||
|
||
const { siteInfo, siteName, siteLogo, siteIcon, navigations, phone, showHeaderSearch, fetchSiteInfo } = useSite()
|
||
const mobileMenuOpen = ref(false)
|
||
|
||
/** 子导航链接统一走 app/utils 的 getNavLink */
|
||
function getChildPath(child: CmsNavigation): string {
|
||
return getNavLink(child)
|
||
}
|
||
|
||
// 获取站点信息(含导航数据)
|
||
await fetchSiteInfo()
|
||
|
||
/** 导航项(从 useSite 的 navigations 获取,已合并 topNavs + bottomNavs 并过滤 top===1) */
|
||
const navItems = computed<CmsNavigation[]>(() => {
|
||
return navigations.value || []
|
||
})
|
||
</script>
|