381 lines
8.9 KiB
Vue
381 lines
8.9 KiB
Vue
<template>
|
|
<div class="userbar">
|
|
<!-- 菜单搜索 -->
|
|
<a-tooltip :title="$t('common.search')">
|
|
<a-button type="text" @click="showSearch" class="action-btn">
|
|
<SearchOutlined />
|
|
</a-button>
|
|
</a-tooltip>
|
|
|
|
<!-- 消息通知 -->
|
|
<a-dropdown :trigger="['click']" placement="bottomRight">
|
|
<a-badge :count="messageCount" :offset="[-5, 5]">
|
|
<a-button type="text" class="action-btn">
|
|
<BellOutlined />
|
|
</a-button>
|
|
</a-badge>
|
|
<template #overlay>
|
|
<a-card class="dropdown-card" :title="$t('common.messages')" :bordered="false">
|
|
<template #extra>
|
|
<a @click="clearMessages">{{ $t('common.clearAll') }}</a>
|
|
</template>
|
|
<div class="message-list">
|
|
<div v-for="msg in messages" :key="msg.id" class="message-item" :class="{ unread: !msg.read }">
|
|
<div class="message-content">
|
|
<div class="message-title">{{ msg.title }}</div>
|
|
<div class="message-time">{{ msg.time }}</div>
|
|
</div>
|
|
<a-badge v-if="!msg.read" dot />
|
|
</div>
|
|
<a-empty v-if="messages.length === 0" :description="$t('common.noMessages')" />
|
|
</div>
|
|
</a-card>
|
|
</template>
|
|
</a-dropdown>
|
|
|
|
<!-- 任务列表 -->
|
|
<a-tooltip :title="$t('common.taskCenter')">
|
|
<a-badge :count="taskCount" :offset="[-5, 5]">
|
|
<a-button type="text" @click="taskVisible = true" class="action-btn">
|
|
<CheckSquareOutlined />
|
|
</a-button>
|
|
</a-badge>
|
|
</a-tooltip>
|
|
|
|
<!-- 语言切换 -->
|
|
<a-dropdown :trigger="['click']" placement="bottomRight">
|
|
<a-button type="text" class="action-btn">
|
|
<GlobalOutlined />
|
|
</a-button>
|
|
<template #overlay>
|
|
<a-menu @click="handleLanguageChange">
|
|
<a-menu-item v-for="locale in i18nStore.availableLocales" :key="locale.value"
|
|
:disabled="i18nStore.currentLocale === locale.value">
|
|
<span>{{ locale.label }}</span>
|
|
</a-menu-item>
|
|
</a-menu>
|
|
</template>
|
|
</a-dropdown>
|
|
|
|
<!-- 全屏 -->
|
|
<a-tooltip :title="$t('common.fullscreen')">
|
|
<a-button type="text" @click="toggleFullscreen" class="action-btn">
|
|
<FullscreenOutlined v-if="!isFullscreen" />
|
|
<FullscreenExitOutlined v-else />
|
|
</a-button>
|
|
</a-tooltip>
|
|
|
|
<!-- 用户信息 -->
|
|
<a-dropdown :trigger="['click']">
|
|
<div class="user-info">
|
|
<a-avatar :size="32" :src="userStore.user?.avatar || ''">
|
|
{{ userStore.user?.username?.charAt(0)?.toUpperCase() || 'U' }}
|
|
</a-avatar>
|
|
<span class="username">{{ userStore.user?.username || 'Admin' }}</span>
|
|
<DownOutlined />
|
|
</div>
|
|
<template #overlay>
|
|
<a-menu @click="handleMenuClick">
|
|
<a-menu-item key="profile">
|
|
<UserOutlined />
|
|
<span>{{ $t('common.personalCenter') }}</span>
|
|
</a-menu-item>
|
|
<a-menu-item key="settings">
|
|
<SettingOutlined />
|
|
<span>{{ $t('common.systemSettings') }}</span>
|
|
</a-menu-item>
|
|
<a-menu-item key="clearCache">
|
|
<DeleteOutlined />
|
|
<span>{{ $t('common.clearCache') }}</span>
|
|
</a-menu-item>
|
|
<a-menu-divider />
|
|
<a-menu-item key="logout">
|
|
<LogoutOutlined />
|
|
<span>{{ $t('common.logout') }}</span>
|
|
</a-menu-item>
|
|
</a-menu>
|
|
</template>
|
|
</a-dropdown>
|
|
</div>
|
|
|
|
<!-- 菜单搜索弹窗 -->
|
|
<search v-model:visible="searchVisible" />
|
|
|
|
<!-- 任务抽屉 -->
|
|
<task v-model:visible="taskVisible" v-model:tasks="tasks" />
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { message, Modal } from 'ant-design-vue'
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
import { useI18nStore } from '@/stores/modules/i18n'
|
|
import { DownOutlined, UserOutlined, LogoutOutlined, FullscreenOutlined, FullscreenExitOutlined, BellOutlined, CheckSquareOutlined, GlobalOutlined, SearchOutlined, SettingOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import search from './search.vue'
|
|
import task from './task.vue'
|
|
|
|
// 定义组件名称(多词命名)
|
|
defineOptions({
|
|
name: 'UserBar',
|
|
})
|
|
|
|
const { t } = useI18n()
|
|
const router = useRouter()
|
|
const userStore = useUserStore()
|
|
const i18nStore = useI18nStore()
|
|
|
|
const isFullscreen = ref(false)
|
|
const searchVisible = ref(false)
|
|
const taskVisible = ref(false)
|
|
|
|
// 消息数据
|
|
const messages = ref([
|
|
{ id: 1, title: '系统通知:新版本已发布', time: '10分钟前', read: false },
|
|
{ id: 2, title: '任务提醒:请完成待审核的用户', time: '30分钟前', read: false },
|
|
{ id: 3, title: '安全警告:检测到异常登录', time: '1小时前', read: true },
|
|
{ id: 4, title: '数据备份已完成', time: '2小时前', read: true },
|
|
])
|
|
|
|
const messageCount = computed(() => messages.value.filter((m) => !m.read).length)
|
|
|
|
// 任务数据
|
|
const tasks = ref([
|
|
{ id: 1, title: '完成用户审核', priority: 'high', completed: false, time: '今天' },
|
|
{ id: 2, title: '更新系统文档', priority: 'medium', completed: false, time: '明天' },
|
|
{ id: 3, title: '优化数据库查询', priority: 'low', completed: true, time: '昨天' },
|
|
])
|
|
|
|
const taskCount = computed(() => tasks.value.filter((t) => !t.completed).length)
|
|
|
|
// 切换全屏
|
|
const toggleFullscreen = () => {
|
|
if (!document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen()
|
|
isFullscreen.value = true
|
|
} else {
|
|
document.exitFullscreen()
|
|
isFullscreen.value = false
|
|
}
|
|
}
|
|
|
|
// 监听全屏变化
|
|
const handleFullscreenChange = () => {
|
|
isFullscreen.value = !!document.fullscreenElement
|
|
}
|
|
|
|
onMounted(() => {
|
|
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
|
})
|
|
|
|
// 显示搜索功能
|
|
const showSearch = () => {
|
|
searchVisible.value = true
|
|
}
|
|
|
|
// 清除消息
|
|
const clearMessages = () => {
|
|
messages.value = []
|
|
message.success(t('common.cleared'))
|
|
}
|
|
|
|
// 显示任务抽屉
|
|
const showTasks = () => {
|
|
taskVisible.value = true
|
|
}
|
|
|
|
// 切换语言
|
|
const handleLanguageChange = ({ key }) => {
|
|
i18nStore.setLocale(key)
|
|
message.success(t('common.languageChanged'))
|
|
}
|
|
|
|
// 处理菜单点击
|
|
const handleMenuClick = ({ key }) => {
|
|
switch (key) {
|
|
case 'profile':
|
|
router.push('/ucenter')
|
|
break
|
|
case 'settings':
|
|
router.push('/system/setting')
|
|
break
|
|
case 'clearCache':
|
|
handleClearCache()
|
|
break
|
|
case 'logout':
|
|
handleLogout()
|
|
break
|
|
}
|
|
}
|
|
|
|
// 清除缓存
|
|
const handleClearCache = () => {
|
|
Modal.confirm({
|
|
title: t('common.confirmClearCache'),
|
|
content: t('common.clearCacheConfirm'),
|
|
okText: t('common.confirm'),
|
|
cancelText: t('common.cancel'),
|
|
onOk: () => {
|
|
try {
|
|
// 清除 localStorage
|
|
localStorage.clear()
|
|
// 清除 sessionStorage
|
|
sessionStorage.clear()
|
|
// 清除所有缓存
|
|
if ('caches' in window) {
|
|
caches.keys().then(names => {
|
|
names.forEach(name => {
|
|
caches.delete(name)
|
|
})
|
|
})
|
|
}
|
|
message.success(t('common.cacheCleared'))
|
|
// 延迟刷新页面以应用缓存清除
|
|
setTimeout(() => {
|
|
window.location.reload()
|
|
}, 1000)
|
|
} catch (error) {
|
|
message.error(t('common.clearCacheFailed'))
|
|
console.error('清除缓存失败:', error)
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
// 退出登录
|
|
const handleLogout = () => {
|
|
Modal.confirm({
|
|
title: t('common.confirmLogout'),
|
|
content: t('common.logoutConfirm'),
|
|
okText: t('common.confirm'),
|
|
cancelText: t('common.cancel'),
|
|
onOk: async () => {
|
|
try {
|
|
await userStore.logout()
|
|
message.success(t('common.logoutSuccess'))
|
|
router.push('/login')
|
|
} catch {
|
|
message.error(t('common.logoutFailed'))
|
|
}
|
|
},
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.userbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
|
|
.search-input {
|
|
width: 240px;
|
|
margin-right: 8px;
|
|
|
|
:deep(.ant-input) {
|
|
background-color: rgba(255, 255, 255, 0.9);
|
|
}
|
|
}
|
|
|
|
.user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
padding: 0 12px;
|
|
border-radius: 4px;
|
|
transition: background-color 0.3s;
|
|
|
|
&:hover {
|
|
background-color: rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
.username {
|
|
font-size: 14px;
|
|
color: #333;
|
|
}
|
|
}
|
|
|
|
.action-btn {
|
|
font-size: 16px;
|
|
padding: 4px 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
|
|
.lang-text {
|
|
font-size: 14px;
|
|
}
|
|
|
|
&:hover {
|
|
color: #1890ff;
|
|
}
|
|
}
|
|
|
|
.dropdown-card {
|
|
width: 320px;
|
|
max-height: 400px;
|
|
overflow: auto;
|
|
|
|
:deep(.ant-card-head) {
|
|
padding: 12px 16px;
|
|
min-height: auto;
|
|
}
|
|
|
|
:deep(.ant-card-body) {
|
|
padding: 12px 16px;
|
|
max-height: 320px;
|
|
overflow-y: auto;
|
|
}
|
|
}
|
|
|
|
.message-list,
|
|
.task-list {
|
|
|
|
.message-item,
|
|
.task-item {
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
position: relative;
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
&.unread {
|
|
background-color: rgba(24, 144, 255, 0.04);
|
|
padding: 10px;
|
|
margin: 0 -10px;
|
|
border-radius: 4px;
|
|
}
|
|
}
|
|
|
|
.message-content {
|
|
.message-title {
|
|
font-size: 14px;
|
|
color: #333;
|
|
margin-bottom: 4px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.message-time {
|
|
font-size: 12px;
|
|
color: #999;
|
|
}
|
|
}
|
|
|
|
.task-item {
|
|
.completed {
|
|
text-decoration: line-through;
|
|
color: #999;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|