213 lines
5.3 KiB
Vue
213 lines
5.3 KiB
Vue
<template>
|
|
<a-row justify="end" align="center">
|
|
<a-space size="medium">
|
|
<!-- 搜索 -->
|
|
<Search v-if="isDesktop" />
|
|
<!-- 项目配置 -->
|
|
<a-tooltip content="项目配置" position="bl">
|
|
<a-button size="mini" class="gi_hover_btn" @click="SettingDrawerRef?.open">
|
|
<template #icon>
|
|
<icon-settings :size="18" />
|
|
</template>
|
|
</a-button>
|
|
</a-tooltip>
|
|
|
|
<!-- 消息通知 -->
|
|
<a-popover
|
|
position="bottom"
|
|
trigger="click"
|
|
:content-style="{ marginTop: '-5px', padding: 0, border: 'none' }"
|
|
:arrow-style="{ width: 0, height: 0 }"
|
|
>
|
|
<a-badge :count="unreadMessageCount" dot>
|
|
<a-button size="mini" class="gi_hover_btn">
|
|
<template #icon>
|
|
<icon-notification :size="18" />
|
|
</template>
|
|
</a-button>
|
|
</a-badge>
|
|
<template #content>
|
|
<Message @readall-success="getMessageCount" />
|
|
</template>
|
|
</a-popover>
|
|
|
|
<!-- 全屏切换组件 -->
|
|
<a-tooltip v-if="!['xs', 'sm'].includes(breakpoint)" content="全屏切换" position="bottom">
|
|
<a-button size="mini" class="gi_hover_btn" @click="toggle">
|
|
<template #icon>
|
|
<icon-fullscreen v-if="!isFullscreen" :size="18" />
|
|
<icon-fullscreen-exit v-else :size="18" />
|
|
</template>
|
|
</a-button>
|
|
</a-tooltip>
|
|
|
|
<!-- 暗黑模式切换 -->
|
|
<a-tooltip content="主题切换" position="bottom">
|
|
<GiThemeBtn></GiThemeBtn>
|
|
</a-tooltip>
|
|
|
|
<!-- 管理员账户 -->
|
|
<a-dropdown trigger="hover">
|
|
<a-row align="center" :wrap="false" class="user">
|
|
<!-- 管理员头像 -->
|
|
<Avatar :src="userStore.avatar" :name="userStore.nickname" :size="32" />
|
|
<span class="username">{{ userStore.nickname }}</span>
|
|
<icon-down />
|
|
</a-row>
|
|
<template #content>
|
|
<a-doption @click="router.push('/user/profile')">
|
|
<span>个人中心</span>
|
|
</a-doption>
|
|
<a-doption @click="router.push('/user/message')">
|
|
<span>消息中心</span>
|
|
</a-doption>
|
|
<a-divider :margin="0" />
|
|
<a-doption @click="logout">
|
|
<span>退出登录</span>
|
|
</a-doption>
|
|
</template>
|
|
</a-dropdown>
|
|
</a-space>
|
|
</a-row>
|
|
|
|
<SettingDrawer ref="SettingDrawerRef"></SettingDrawer>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { Modal } from '@arco-design/web-vue'
|
|
import { useFullscreen } from '@vueuse/core'
|
|
import { nextTick, onMounted, ref } from 'vue'
|
|
import Message from './Message.vue'
|
|
import SettingDrawer from './SettingDrawer.vue'
|
|
import Search from './Search.vue'
|
|
import { useUserStore } from '@/stores'
|
|
import { getToken } from '@/utils/auth'
|
|
import { useBreakpoint, useDevice } from '@/hooks'
|
|
|
|
defineOptions({ name: 'HeaderRight' })
|
|
|
|
const { isDesktop } = useDevice()
|
|
const { breakpoint } = useBreakpoint()
|
|
let socket: WebSocket | null = null
|
|
|
|
// 清理函数
|
|
onBeforeUnmount(() => {
|
|
if (socket) {
|
|
socket.close()
|
|
socket = null
|
|
}
|
|
})
|
|
|
|
const unreadMessageCount = ref(0)
|
|
|
|
// 初始化 WebSocket - 使用防抖避免重复连接
|
|
let initTimer: NodeJS.Timeout | null = null
|
|
const initWebSocket = (token: string) => {
|
|
if (initTimer) {
|
|
clearTimeout(initTimer)
|
|
}
|
|
|
|
initTimer = setTimeout(() => {
|
|
// 如果已有连接,先关闭
|
|
if (socket) {
|
|
socket.close()
|
|
socket = null
|
|
}
|
|
|
|
try {
|
|
socket = new WebSocket(`${import.meta.env.VITE_API_WS_URL}/websocket?token=${token}`)
|
|
|
|
socket.onopen = () => {
|
|
// console.log('WebSocket connection opened')
|
|
}
|
|
|
|
socket.onmessage = (event) => {
|
|
const count = Number.parseInt(event.data)
|
|
if (!isNaN(count)) {
|
|
unreadMessageCount.value = count
|
|
}
|
|
}
|
|
|
|
socket.onerror = () => {
|
|
// console.error('WebSocket error:', error)
|
|
}
|
|
|
|
socket.onclose = () => {
|
|
// console.log('WebSocket connection closed')
|
|
socket = null
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to create WebSocket connection:', error)
|
|
}
|
|
|
|
initTimer = null
|
|
}, 100) // 100ms防抖
|
|
}
|
|
|
|
// 查询未读消息数量
|
|
const getMessageCount = async () => {
|
|
try {
|
|
const token = getToken()
|
|
if (token && !socket) {
|
|
nextTick(() => {
|
|
initWebSocket(token)
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to get message count:', error)
|
|
}
|
|
}
|
|
|
|
const { isFullscreen, toggle } = useFullscreen()
|
|
|
|
const router = useRouter()
|
|
const userStore = useUserStore()
|
|
const SettingDrawerRef = ref<InstanceType<typeof SettingDrawer>>()
|
|
|
|
// 退出登录
|
|
const logout = () => {
|
|
Modal.warning({
|
|
title: '提示',
|
|
content: '确认退出登录?',
|
|
hideCancel: false,
|
|
closable: true,
|
|
onBeforeOk: async () => {
|
|
try {
|
|
await userStore.logout()
|
|
await router.replace('/login')
|
|
return true
|
|
} catch (error) {
|
|
return false
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
onMounted(() => {
|
|
nextTick(() => {
|
|
// getMessageCount()
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.arco-dropdown-open .arco-icon-down {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.user {
|
|
cursor: pointer;
|
|
color: var(--color-text-1);
|
|
|
|
.username {
|
|
margin-left: 10px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.arco-icon-down {
|
|
transition: all 0.3s;
|
|
margin-left: 2px;
|
|
}
|
|
}
|
|
</style>
|