Industrial-image-management.../src/layout/components/HeaderRightBar/index.vue

246 lines
6.5 KiB
Vue
Raw Normal View History

2025-07-30 09:13:52 +08:00
<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">
2025-08-08 16:37:36 +08:00
import { Modal, Notification } from '@arco-design/web-vue'
2025-07-30 09:13:52 +08:00
import { useFullscreen } from '@vueuse/core'
2025-08-08 16:37:36 +08:00
import { onMounted, ref, nextTick, onBeforeUnmount } from 'vue'
2025-07-30 09:13:52 +08:00
import Message from './Message.vue'
import SettingDrawer from './SettingDrawer.vue'
import Search from './Search.vue'
2025-07-30 09:13:52 +08:00
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)
}
2025-07-30 09:13:52 +08:00
initTimer = setTimeout(() => {
// 如果已有连接,先关闭
if (socket) {
socket.close()
socket = null
}
2025-07-30 09:13:52 +08:00
try {
2025-08-08 16:37:36 +08:00
const wsUrl = import.meta.env.VITE_API_WS_URL || 'ws://localhost:8888'
console.log('正在连接WebSocket:', `${wsUrl}/websocket?token=${token}`)
socket = new WebSocket(`${wsUrl}/websocket?token=${token}`)
2025-07-30 09:13:52 +08:00
socket.onopen = () => {
2025-08-08 16:37:36 +08:00
console.log('WebSocket连接成功')
2025-07-30 09:13:52 +08:00
}
socket.onmessage = (event) => {
2025-08-08 16:37:36 +08:00
console.log('收到WebSocket消息:', event.data)
try {
const data = JSON.parse(event.data)
// 处理通知消息
if (data.type && data.title && data.content) {
console.log('处理通知消息:', data)
// 显示通知
Notification.info({
title: data.title,
content: data.content,
duration: 5000,
closable: true,
position: 'topRight'
})
// 增加未读消息计数
unreadMessageCount.value++
} else {
// 处理简单的数字消息(兼容旧版本)
const count = Number.parseInt(event.data)
if (!isNaN(count)) {
unreadMessageCount.value = count
}
}
} catch (error) {
console.error('解析WebSocket消息失败:', error)
// 尝试解析为数字(兼容旧版本)
const count = Number.parseInt(event.data)
if (!isNaN(count)) {
unreadMessageCount.value = count
}
2025-07-30 09:13:52 +08:00
}
}
2025-08-08 16:37:36 +08:00
socket.onerror = (error) => {
console.error('WebSocket连接错误:', error)
2025-07-30 09:13:52 +08:00
}
2025-08-08 16:37:36 +08:00
socket.onclose = (event) => {
console.log('WebSocket连接关闭:', event.code, event.reason)
2025-07-30 09:13:52 +08:00
socket = null
}
} catch (error) {
2025-08-08 16:37:36 +08:00
console.error('创建WebSocket连接失败:', error)
2025-07-30 09:13:52 +08:00
}
2025-07-30 09:13:52 +08:00
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>