Initial commit: One Pipe System
完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
383
src/components/core/layouts/art-header-bar/index.vue
Normal file
383
src/components/core/layouts/art-header-bar/index.vue
Normal file
@@ -0,0 +1,383 @@
|
||||
<template>
|
||||
<div class="layout-top-bar" :class="[tabStyle]" :style="{ width: topBarWidth() }">
|
||||
<div class="menu">
|
||||
<div class="left" style="display: flex">
|
||||
<!-- 系统信息 -->
|
||||
<div class="top-header" @click="toHome" v-if="isTopMenu">
|
||||
<ArtLogo class="logo" />
|
||||
<p v-if="width >= 1400">{{ AppConfig.systemInfo.name }}</p>
|
||||
</div>
|
||||
|
||||
<ArtLogo class="logo2" @click="toHome" />
|
||||
|
||||
<!-- 菜单按钮 -->
|
||||
<div class="btn-box" v-if="isLeftMenu && showMenuButton">
|
||||
<div class="btn menu-btn">
|
||||
<i class="iconfont-sys" @click="visibleMenu"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 刷新按钮 -->
|
||||
<div class="btn-box" v-if="showRefreshButton">
|
||||
<div class="btn refresh-btn" :style="{ marginLeft: !isLeftMenu ? '10px' : '0' }">
|
||||
<i class="iconfont-sys" @click="reload()">  </i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速入口 -->
|
||||
<ArtFastEnter v-if="width >= 1200" />
|
||||
|
||||
<!-- 面包屑 -->
|
||||
<ArtBreadcrumb
|
||||
v-if="(showCrumbs && isLeftMenu) || (showCrumbs && isDualMenu)"
|
||||
:style="{ paddingLeft: !showRefreshButton && !showMenuButton ? '10px' : '0' }"
|
||||
/>
|
||||
|
||||
<!-- 顶部菜单 -->
|
||||
<ArtHorizontalMenu v-if="isTopMenu" :list="menuList" :width="menuTopWidth" />
|
||||
|
||||
<!-- 混合菜单-顶部 -->
|
||||
<ArtMixedMenu v-if="isTopLeftMenu" :list="menuList" :width="menuTopWidth" />
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<!-- 搜索 -->
|
||||
<div class="search-wrap">
|
||||
<div class="search-input" @click="openSearchDialog">
|
||||
<div class="left">
|
||||
<i class="iconfont-sys"></i>
|
||||
<span>{{ $t('topBar.search.title') }}</span>
|
||||
</div>
|
||||
<div class="search-keydown">
|
||||
<i class="iconfont-sys" v-if="isWindows"></i>
|
||||
<i class="iconfont-sys" v-else></i>
|
||||
<span>k</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 全屏按钮 -->
|
||||
<div class="btn-box screen-box" @click="toggleFullScreen">
|
||||
<div
|
||||
class="btn"
|
||||
:class="{ 'full-screen-btn': !isFullscreen, 'exit-full-screen-btn': isFullscreen }"
|
||||
>
|
||||
<i class="iconfont-sys">{{ isFullscreen ? '' : '' }}</i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 通知 -->
|
||||
<div class="btn-box notice-btn" @click="visibleNotice">
|
||||
<div class="btn notice-button">
|
||||
<i class="iconfont-sys notice-btn"></i>
|
||||
<span class="count notice-btn"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 聊天 -->
|
||||
<div class="btn-box chat-btn" @click="openChat">
|
||||
<div class="btn chat-button">
|
||||
<i class="iconfont-sys"></i>
|
||||
<span class="dot"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 语言 -->
|
||||
<div class="btn-box" v-if="showLanguage">
|
||||
<el-dropdown @command="changeLanguage" popper-class="langDropDownStyle">
|
||||
<div class="btn language-btn">
|
||||
<i class="iconfont-sys"></i>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<div v-for="item in languageOptions" :key="item.value" class="lang-btn-item">
|
||||
<el-dropdown-item
|
||||
:command="item.value"
|
||||
:class="{ 'is-selected': locale === item.value }"
|
||||
>
|
||||
<span class="menu-txt">{{ item.label }}</span>
|
||||
<i v-if="locale === item.value" class="iconfont-sys"></i>
|
||||
</el-dropdown-item>
|
||||
</div>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<!-- 设置 -->
|
||||
<div class="btn-box" @click="openSetting">
|
||||
<el-popover :visible="showSettingGuide" placement="bottom-start" :width="190" :offset="0">
|
||||
<template #reference>
|
||||
<div class="btn setting-btn">
|
||||
<i class="iconfont-sys"></i>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<p
|
||||
>点击这里查看<span :style="{ color: systemThemeColor }"> 主题风格 </span>、
|
||||
<span :style="{ color: systemThemeColor }"> 开启顶栏菜单 </span>等更多配置
|
||||
</p>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<!-- 切换主题 -->
|
||||
<div class="btn-box" @click="themeAnimation">
|
||||
<div class="btn theme-btn">
|
||||
<i class="iconfont-sys">{{ isDark ? '' : '' }}</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户菜单 -->
|
||||
<div class="user">
|
||||
<el-popover
|
||||
ref="userMenuPopover"
|
||||
placement="bottom-end"
|
||||
:width="240"
|
||||
:hide-after="0"
|
||||
:offset="10"
|
||||
trigger="hover"
|
||||
:show-arrow="false"
|
||||
popper-class="user-menu-popover"
|
||||
popper-style="border: 1px solid var(--art-border-dashed-color); border-radius: calc(var(--custom-radius) / 2 + 4px); padding: 5px 16px; 5px 16px;"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="user-info-display">
|
||||
<div class="avatar">{{ getUserAvatar }}</div>
|
||||
<div class="info">
|
||||
<div class="username">{{ userInfo.username || '用户' }}</div>
|
||||
<div class="user-type">{{ userInfo.user_type_name || '' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="user-menu-box">
|
||||
<div class="user-head">
|
||||
<div class="avatar-large">{{ getUserAvatar }}</div>
|
||||
<div class="user-wrap">
|
||||
<span class="name">{{ userInfo.username }}</span>
|
||||
<span class="email">{{ userInfo.phone }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="user-menu">
|
||||
<li @click="goPage('/system/user-center')">
|
||||
<i class="menu-icon iconfont-sys"></i>
|
||||
<span class="menu-txt">{{ $t('topBar.user.userCenter') }}</span>
|
||||
</li>
|
||||
<li @click="lockScreen()">
|
||||
<i class="menu-icon iconfont-sys"></i>
|
||||
<span class="menu-txt">{{ $t('topBar.user.lockScreen') }}</span>
|
||||
</li>
|
||||
<div class="line"></div>
|
||||
<div class="logout-btn" @click="loginOut">
|
||||
{{ $t('topBar.user.logout') }}
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ArtWorkTab />
|
||||
|
||||
<art-notification v-model:value="showNotice" ref="notice" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LanguageEnum, MenuTypeEnum, MenuWidth } from '@/enums/appEnum'
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { HOME_PAGE } from '@/router/routesAlias'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { mittBus } from '@/utils/sys'
|
||||
import { useMenuStore } from '@/store/modules/menu'
|
||||
import AppConfig from '@/config'
|
||||
import { languageOptions } from '@/locales'
|
||||
const isWindows = navigator.userAgent.includes('Windows')
|
||||
const { locale } = useI18n()
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
showMenuButton,
|
||||
showRefreshButton,
|
||||
showLanguage,
|
||||
menuOpen,
|
||||
showCrumbs,
|
||||
systemThemeColor,
|
||||
showSettingGuide,
|
||||
menuType,
|
||||
isDark,
|
||||
tabStyle
|
||||
} = storeToRefs(settingStore)
|
||||
|
||||
const { language, getUserInfo: userInfo } = storeToRefs(userStore)
|
||||
|
||||
const { menuList } = storeToRefs(useMenuStore())
|
||||
|
||||
const showNotice = ref(false)
|
||||
const notice = ref(null)
|
||||
const userMenuPopover = ref()
|
||||
|
||||
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)
|
||||
|
||||
import { useCommon } from '@/composables/useCommon'
|
||||
import { WEB_LINKS } from '@/utils/constants'
|
||||
import { themeAnimation } from '@/utils/theme/animation'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { width } = useWindowSize()
|
||||
|
||||
const menuTopWidth = computed(() => {
|
||||
return width.value * 0.5
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取用户头像显示文字
|
||||
* 英文:取第一个字母并大写
|
||||
* 中文:取第一个汉字
|
||||
*/
|
||||
const getUserAvatar = computed(() => {
|
||||
const username = userInfo.value.username
|
||||
if (!username) return 'U'
|
||||
|
||||
const firstChar = username.charAt(0)
|
||||
// 检查是否为中文字符(Unicode 范围:\u4e00-\u9fa5)
|
||||
const isChinese = /[\u4e00-\u9fa5]/.test(firstChar)
|
||||
|
||||
return isChinese ? firstChar : firstChar.toUpperCase()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
initLanguage()
|
||||
document.addEventListener('click', bodyCloseNotice)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', bodyCloseNotice)
|
||||
})
|
||||
|
||||
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen()
|
||||
|
||||
const toggleFullScreen = () => {
|
||||
toggleFullscreen()
|
||||
}
|
||||
|
||||
const topBarWidth = (): string => {
|
||||
const { TOP, DUAL_MENU, TOP_LEFT } = MenuTypeEnum
|
||||
const { getMenuOpenWidth } = settingStore
|
||||
const { isFirstLevel } = router.currentRoute.value.meta
|
||||
const type = menuType.value
|
||||
const isMenuOpen = menuOpen.value
|
||||
|
||||
const isTopLayout = type === TOP || (type === TOP_LEFT && isFirstLevel)
|
||||
|
||||
if (isTopLayout) {
|
||||
return '100%'
|
||||
}
|
||||
|
||||
if (type === DUAL_MENU) {
|
||||
return isFirstLevel ? 'calc(100% - 80px)' : `calc(100% - 80px - ${getMenuOpenWidth})`
|
||||
}
|
||||
|
||||
return isMenuOpen ? `calc(100% - ${getMenuOpenWidth})` : `calc(100% - ${MenuWidth.CLOSE})`
|
||||
}
|
||||
|
||||
const visibleMenu = () => {
|
||||
settingStore.setMenuOpen(!menuOpen.value)
|
||||
}
|
||||
|
||||
const goPage = (path: string) => {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
const toHome = () => {
|
||||
router.push(HOME_PAGE)
|
||||
}
|
||||
|
||||
const loginOut = () => {
|
||||
closeUserMenu()
|
||||
setTimeout(() => {
|
||||
ElMessageBox.confirm(t('common.logOutTips'), t('common.tips'), {
|
||||
confirmButtonText: t('common.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'login-out-dialog'
|
||||
}).then(() => {
|
||||
userStore.logOut()
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
|
||||
const reload = (time: number = 0) => {
|
||||
setTimeout(() => {
|
||||
useCommon().refresh()
|
||||
}, time)
|
||||
}
|
||||
|
||||
const initLanguage = () => {
|
||||
locale.value = language.value
|
||||
}
|
||||
|
||||
const changeLanguage = (lang: LanguageEnum) => {
|
||||
if (locale.value === lang) return
|
||||
locale.value = lang
|
||||
userStore.setLanguage(lang)
|
||||
reload(50)
|
||||
}
|
||||
|
||||
const openSetting = () => {
|
||||
mittBus.emit('openSetting')
|
||||
|
||||
// 隐藏设置引导
|
||||
if (showSettingGuide.value) {
|
||||
settingStore.hideSettingGuide()
|
||||
}
|
||||
// 打开设置引导
|
||||
// settingStore.openSettingGuide()
|
||||
}
|
||||
|
||||
const openSearchDialog = () => {
|
||||
mittBus.emit('openSearchDialog')
|
||||
}
|
||||
|
||||
const bodyCloseNotice = (e: any) => {
|
||||
let { className } = e.target
|
||||
|
||||
if (showNotice.value) {
|
||||
if (typeof className === 'object') {
|
||||
showNotice.value = false
|
||||
return
|
||||
}
|
||||
if (className.indexOf('notice-btn') === -1) {
|
||||
showNotice.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const visibleNotice = () => {
|
||||
showNotice.value = !showNotice.value
|
||||
}
|
||||
|
||||
const openChat = () => {
|
||||
mittBus.emit('openChat')
|
||||
}
|
||||
|
||||
const lockScreen = () => {
|
||||
mittBus.emit('openLockScreen')
|
||||
}
|
||||
|
||||
const closeUserMenu = () => {
|
||||
setTimeout(() => {
|
||||
userMenuPopover.value.hide()
|
||||
}, 100)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use './style';
|
||||
@use './mobile';
|
||||
</style>
|
||||
Reference in New Issue
Block a user