| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- <!-- 顶部栏 -->
- <template>
- <div
- class="w-full bg-[var(--default-bg-color)]"
- :class="[
- tabStyle === 'tab-card' || tabStyle === 'tab-google' ? 'mb-5 max-sm:mb-3 !bg-box' : ''
- ]"
- >
- <div
- class="relative box-border flex-b items-center h-15 leading-15 select-none"
- :class="[
- tabStyle === 'tab-card' || tabStyle === 'tab-google'
- ? 'border-b border-[var(--art-card-border)]'
- : ''
- ]"
- >
- <div class="flex-c flex-1 min-w-0 leading-15" style="display: flex">
- <!-- 系统信息 -->
- <div class="flex-c c-p" @click="toHome" v-if="isTopMenu">
- <ArtLogo class="pl-4.5" />
- <p v-if="width >= 1400" class="my-0 mx-2 ml-2 text-lg">{{ AppConfig.systemInfo.name }}</p>
- </div>
- <ArtLogo
- class="!hidden pl-3.5 overflow-hidden align-[-0.15em] fill-current"
- @click="toHome"
- />
- <!-- 菜单按钮 -->
- <ArtIconButton
- v-if="isLeftMenu && shouldShowMenuButton"
- icon="ri:menu-2-fill"
- class="ml-3 max-sm:ml-[7px]"
- @click="visibleMenu"
- />
- <!-- 刷新按钮 - 已隐藏 -->
- <ArtIconButton
- v-if="shouldShowRefreshButton"
- icon="ri:refresh-line"
- class="!ml-3 refresh-btn max-sm:!hidden"
- :style="{ marginLeft: !isLeftMenu ? '10px' : '0' }"
- @click="reload"
- />
- <!-- 快速入口 - 已隐藏 -->
- <!-- <ArtFastEnter v-if="shouldShowFastEnter && width >= headerBarFastEnterMinWidth">
- <ArtIconButton icon="ri:function-line" class="ml-3" />
- </ArtFastEnter> -->
- <!-- 面包屑 -->
- <ArtBreadcrumb
- v-if="(shouldShowBreadcrumb && isLeftMenu) || (shouldShowBreadcrumb && isDualMenu)"
- />
- <!-- 顶部菜单 -->
- <ArtHorizontalMenu v-if="isTopMenu" :list="menuList" />
- <!-- 混合菜单-顶部 -->
- <ArtMixedMenu v-if="isTopLeftMenu" :list="menuList" />
- </div>
- <div class="flex-c gap-2.5 leading-none">
- <!-- 全屏按钮 -->
- <ArtIconButton
- v-if="shouldShowFullscreen"
- :icon="isFullscreen ? 'ri:fullscreen-exit-line' : 'ri:fullscreen-fill'"
- :class="[!isFullscreen ? 'full-screen-btn' : 'exit-full-screen-btn', 'ml-3']"
- class="max-md:!hidden"
- @click="toggleFullScreen"
- />
- <!-- 国际化按钮 - 已隐藏 -->
- <!-- <ElDropdown
- @command="changeLanguage"
- popper-class="langDropDownStyle"
- v-if="shouldShowLanguage"
- >
- <ArtIconButton icon="ri:translate-2" class="language-btn text-[19px]" />
- <template #dropdown>
- <ElDropdownMenu>
- <div v-for="item in languageOptions" :key="item.value" class="lang-btn-item">
- <ElDropdownItem
- :command="item.value"
- :class="{ 'is-selected': locale === item.value }"
- >
- <span class="menu-txt">{{ item.label }}</span>
- <ArtSvgIcon icon="ri:check-fill" v-if="locale === item.value" />
- </ElDropdownItem>
- </div>
- </ElDropdownMenu>
- </template>
- </ElDropdown> -->
- <!-- 通知按钮 - 已隐藏 -->
- <!-- <ArtIconButton
- v-if="shouldShowNotification"
- icon="ri:notification-2-line"
- class="notice-button relative"
- @click="visibleNotice"
- >
- <div class="absolute top-2 right-2 size-1.5 !bg-danger rounded-full"></div>
- </ArtIconButton> -->
- <!-- 聊天按钮 - 已隐藏 -->
- <!-- <ArtIconButton
- v-if="shouldShowChat"
- icon="ri:message-3-line"
- class="chat-button relative"
- @click="openChat"
- >
- <div class="breathing-dot absolute top-2 right-2 size-1.5 !bg-success rounded-full"></div>
- </ArtIconButton> -->
- <!-- 主题切换按钮 - 已隐藏 -->
- <!-- <ArtIconButton
- v-if="shouldShowThemeToggle"
- @click="themeAnimation"
- :icon="isDark ? 'ri:sun-fill' : 'ri:moon-line'"
- /> -->
- <!-- 用户头像、菜单 -->
- <ArtUserMenu />
- </div>
- </div>
- <!-- 标签页 -->
- <ArtWorkTab />
- <!-- 通知组件 - 已隐藏 -->
- <!-- <ArtNotification v-model:value="showNotice" ref="notice" /> -->
- </div>
- </template>
- <script setup lang="ts">
- import { useI18n } from 'vue-i18n'
- import { useRouter } from 'vue-router'
- import { storeToRefs } from 'pinia'
- import { useFullscreen, useWindowSize } from '@vueuse/core'
- import { LanguageEnum, MenuTypeEnum } from '@/enums/appEnum'
- import { useSettingStore } from '@/store/modules/setting'
- import { useUserStore } from '@/store/modules/user'
- import { useMenuStore } from '@/store/modules/menu'
- import AppConfig from '@/config'
- import { languageOptions } from '@/locales'
- import { mittBus } from '@/utils/sys'
- import { themeAnimation } from '@/utils/ui/animation'
- import { useCommon } from '@/hooks/core/useCommon'
- import { useHeaderBar } from '@/hooks/core/useHeaderBar'
- import ArtUserMenu from './widget/ArtUserMenu.vue'
- defineOptions({ name: 'ArtHeaderBar' })
- const router = useRouter()
- const { locale } = useI18n()
- const { width } = useWindowSize()
- const settingStore = useSettingStore()
- const userStore = useUserStore()
- const menuStore = useMenuStore()
- // 顶部栏功能配置
- const {
- shouldShowMenuButton,
- shouldShowRefreshButton,
- shouldShowFastEnter,
- shouldShowBreadcrumb,
- shouldShowFullscreen,
- shouldShowNotification,
- shouldShowChat,
- shouldShowLanguage,
- shouldShowThemeToggle,
- fastEnterMinWidth: headerBarFastEnterMinWidth
- } = useHeaderBar()
- const { menuOpen, menuType, isDark, tabStyle } = storeToRefs(settingStore)
- const { language } = storeToRefs(userStore)
- const { menuList } = storeToRefs(menuStore)
- const showNotice = ref(false)
- const notice = ref(null)
- // 菜单类型判断
- const isLeftMenu = computed(() => menuType.value === MenuTypeEnum.LEFT)
- const isDualMenu = computed(() => menuType.value === MenuTypeEnum.DUAL_MENU)
- const isTopMenu = computed(() => menuType.value === MenuTypeEnum.TOP)
- const isTopLeftMenu = computed(() => menuType.value === MenuTypeEnum.TOP_LEFT)
- const { isFullscreen, toggle: toggleFullscreen } = useFullscreen()
- onMounted(() => {
- initLanguage()
- document.addEventListener('click', bodyCloseNotice)
- })
- onUnmounted(() => {
- document.removeEventListener('click', bodyCloseNotice)
- })
- /**
- * 切换全屏状态
- */
- const toggleFullScreen = (): void => {
- toggleFullscreen()
- }
- /**
- * 切换菜单显示/隐藏状态
- */
- const visibleMenu = (): void => {
- settingStore.setMenuOpen(!menuOpen.value)
- }
- const { homePath } = useCommon()
- const { refresh } = useCommon()
- /**
- * 跳转到首页
- */
- const toHome = (): void => {
- router.push(homePath.value)
- }
- /**
- * 刷新页面
- * @param {number} time - 延迟时间,默认为0毫秒
- */
- const reload = (time: number = 0): void => {
- setTimeout(() => {
- refresh()
- }, time)
- }
- /**
- * 初始化语言设置
- */
- const initLanguage = (): void => {
- locale.value = language.value
- }
- /**
- * 切换系统语言
- * @param {LanguageEnum} lang - 目标语言类型
- */
- const changeLanguage = (lang: LanguageEnum): void => {
- if (locale.value === lang) return
- locale.value = lang
- userStore.setLanguage(lang)
- reload(50)
- }
- /**
- * 点击页面其他区域关闭通知面板
- * @param {Event} e - 点击事件对象
- */
- const bodyCloseNotice = (e: any): void => {
- if (!showNotice.value) return
- const target = e.target as HTMLElement
- // 检查是否点击了通知按钮或通知面板内部
- const isNoticeButton = target.closest('.notice-button')
- const isNoticePanel = target.closest('.art-notification-panel')
- if (!isNoticeButton && !isNoticePanel) {
- showNotice.value = false
- }
- }
- /**
- * 切换通知面板显示状态
- */
- const visibleNotice = (): void => {
- showNotice.value = !showNotice.value
- }
- /**
- * 打开聊天窗口
- */
- const openChat = (): void => {
- mittBus.emit('openChat')
- }
- </script>
- <style lang="scss" scoped>
- /* Custom animations */
- @keyframes rotate180 {
- 0% {
- transform: rotate(0);
- }
- 100% {
- transform: rotate(180deg);
- }
- }
- @keyframes shake {
- 0% {
- transform: rotate(0);
- }
- 25% {
- transform: rotate(-5deg);
- }
- 50% {
- transform: rotate(5deg);
- }
- 75% {
- transform: rotate(-5deg);
- }
- 100% {
- transform: rotate(0);
- }
- }
- @keyframes expand {
- 0% {
- transform: scale(1);
- }
- 50% {
- transform: scale(1.1);
- }
- 100% {
- transform: scale(1);
- }
- }
- @keyframes shrink {
- 0% {
- transform: scale(1);
- }
- 50% {
- transform: scale(0.9);
- }
- 100% {
- transform: scale(1);
- }
- }
- @keyframes moveUp {
- 0% {
- transform: translateY(0);
- }
- 50% {
- transform: translateY(-3px);
- }
- 100% {
- transform: translateY(0);
- }
- }
- @keyframes breathing {
- 0% {
- opacity: 0.4;
- transform: scale(0.9);
- }
- 50% {
- opacity: 1;
- transform: scale(1.1);
- }
- 100% {
- opacity: 0.4;
- transform: scale(0.9);
- }
- }
- /* Hover animation classes */
- .refresh-btn:hover :deep(.art-svg-icon) {
- animation: rotate180 0.5s;
- }
- .language-btn:hover :deep(.art-svg-icon) {
- animation: moveUp 0.4s;
- }
- .setting-btn:hover :deep(.art-svg-icon) {
- animation: rotate180 0.5s;
- }
- .full-screen-btn:hover :deep(.art-svg-icon) {
- animation: expand 0.6s forwards;
- }
- .exit-full-screen-btn:hover :deep(.art-svg-icon) {
- animation: shrink 0.6s forwards;
- }
- .notice-button:hover :deep(.art-svg-icon) {
- animation: shake 0.5s ease-in-out;
- }
- .chat-button:hover :deep(.art-svg-icon) {
- animation: shake 0.5s ease-in-out;
- }
- /* Breathing animation for chat dot */
- .breathing-dot {
- animation: breathing 1.5s ease-in-out infinite;
- }
- /* iPad breakpoint adjustments */
- @media screen and (width <= 768px) {
- .logo2 {
- display: block !important;
- }
- }
- @media screen and (width <= 640px) {
- .btn-box {
- width: 40px;
- }
- }
- </style>
|