This commit is contained in:
2026-01-16 13:10:36 +08:00
parent 7a4ca4275e
commit b6bb8a6deb
7 changed files with 564 additions and 120 deletions

View File

@@ -1,18 +1,87 @@
<template>
<div class="userbar">
<a-tooltip title="全屏">
<!-- 菜单搜索 -->
<a-input-search v-model:value="searchKeyword" :placeholder="$t('common.searchMenu')" enter-button
@search="handleSearch" class="search-input" allow-clear>
<template #prefix>
<SearchOutlined />
</template>
</a-input-search>
<!-- 消息通知 -->
<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-dropdown :trigger="['click']" placement="bottomRight">
<a-badge :count="taskCount" :offset="[-5, 5]">
<a-button type="text" class="action-btn">
<CheckSquareOutlined />
</a-button>
</a-badge>
<template #overlay>
<a-card class="dropdown-card" :title="$t('common.tasks')" :bordered="false">
<template #extra>
<a @click="clearTasks">{{ $t('common.clearAll') }}</a>
</template>
<div class="task-list">
<div v-for="task in tasks" :key="task.id" class="task-item">
<a-checkbox :checked="task.completed" @change="toggleTask(task)">
<span :class="{ completed: task.completed }">{{ task.title }}</span>
</a-checkbox>
</div>
<a-empty v-if="tasks.length === 0" :description="$t('common.noTasks')" />
</div>
</a-card>
</template>
</a-dropdown>
<!-- 语言切换 -->
<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-tooltip title="布局设置">
<a-button type="text" @click="showSetting = true" class="action-btn">
<SettingOutlined />
</a-button>
</a-tooltip>
<!-- 用户信息 -->
<a-dropdown :trigger="['click']">
<div class="user-info">
<a-avatar :size="32" :src="userStore.user?.avatar || ''">
@@ -25,82 +94,69 @@
<a-menu @click="handleMenuClick">
<a-menu-item key="profile">
<UserOutlined />
<span>个人中心</span>
<span>{{ $t('common.personalCenter') }}</span>
</a-menu-item>
<a-menu-item key="settings">
<SettingOutlined />
<span>系统设置</span>
<span>{{ $t('common.systemSettings') }}</span>
</a-menu-item>
<a-menu-divider />
<a-menu-item key="logout">
<LogoutOutlined />
<span>退出登录</span>
<span>{{ $t('common.logout') }}</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<!-- 布局设置抽屉 -->
<a-drawer v-model:open="showSetting" title="布局设置" placement="right" :width="280">
<div class="setting-content">
<div class="setting-item">
<div class="setting-title">布局模式</div>
<a-radio-group v-model:value="layoutStore.layoutMode" @change="handleLayoutChange">
<a-radio value="default">默认布局</a-radio>
<a-radio value="menu">菜单布局</a-radio>
<a-radio value="top">顶部布局</a-radio>
</a-radio-group>
</div>
<div class="setting-item">
<div class="setting-title">主题颜色</div>
<div class="color-list">
<div v-for="color in themeColors" :key="color" class="color-item"
:class="{ active: themeColor === color }" :style="{ backgroundColor: color }"
@click="changeThemeColor(color)">
<CheckOutlined v-if="themeColor === color" />
</div>
</div>
</div>
</div>
</a-drawer>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
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 { useLayoutStore } from '@/stores/modules/layout'
import { useI18nStore } from '@/stores/modules/i18n'
import {
DownOutlined,
UserOutlined,
SettingOutlined,
LogoutOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
CheckOutlined
BellOutlined,
CheckSquareOutlined,
GlobalOutlined,
SearchOutlined,
SettingOutlined
} from '@ant-design/icons-vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const router = useRouter()
const userStore = useUserStore()
const layoutStore = useLayoutStore()
const i18nStore = useI18nStore()
const isFullscreen = ref(false)
const showSetting = ref(false)
const themeColor = ref('#1890ff')
const searchKeyword = ref('')
const themeColors = [
'#1890ff',
'#f5222d',
'#fa541c',
'#faad14',
'#13c2c2',
'#52c41a',
'#2f54eb',
'#722ed1'
]
// 消息数据
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: '完成用户审核', completed: false },
{ id: 2, title: '更新系统文档', completed: false },
{ id: 3, title: '优化数据库查询', completed: true }
])
const taskCount = computed(() => tasks.value.filter((t) => !t.completed).length)
// 切换全屏
const toggleFullscreen = () => {
@@ -126,6 +182,40 @@ onUnmounted(() => {
document.removeEventListener('fullscreenchange', handleFullscreenChange)
})
// 菜单搜索
const handleSearch = (value) => {
if (!value.trim()) {
message.warning(t('common.searchEmpty'))
return
}
// 这里可以实现实际的搜索逻辑,比如跳转到搜索页面或显示搜索结果
message.info(t('common.searching') + value)
searchKeyword.value = ''
}
// 清除消息
const clearMessages = () => {
messages.value = []
message.success(t('common.cleared'))
}
// 清除任务
const clearTasks = () => {
tasks.value = []
message.success(t('common.cleared'))
}
// 切换任务状态
const toggleTask = (task) => {
task.completed = !task.completed
}
// 切换语言
const handleLanguageChange = ({ key }) => {
i18nStore.setLocale(key)
message.success(t('common.languageChanged'))
}
// 处理菜单点击
const handleMenuClick = ({ key }) => {
switch (key) {
@@ -133,7 +223,8 @@ const handleMenuClick = ({ key }) => {
router.push('/profile')
break
case 'settings':
showSetting.value = true
// 系统设置功能暂未实现
message.info(t('common.settingsDeveloping'))
break
case 'logout':
handleLogout()
@@ -144,34 +235,21 @@ const handleMenuClick = ({ key }) => {
// 退出登录
const handleLogout = () => {
Modal.confirm({
title: '确认退出',
content: '确定要退出登录吗?',
okText: '确定',
cancelText: '取消',
title: t('common.confirmLogout'),
content: t('common.logoutConfirm'),
okText: t('common.confirm'),
cancelText: t('common.cancel'),
onOk: async () => {
try {
await userStore.logout()
message.success('退出成功')
message.success(t('common.logoutSuccess'))
router.push('/login')
} catch (error) {
message.error('退出失败')
message.error(t('common.logoutFailed'))
}
}
})
}
// 切换布局
const handleLayoutChange = (e) => {
layoutStore.setLayoutMode(e.target.value)
message.success(`已切换到${e.target.value === 'default' ? '默认' : e.target.value === 'menu' ? '菜单' : '顶部'}布局`)
}
// 切换主题颜色
const changeThemeColor = (color) => {
themeColor.value = color
// 这里可以实现主题切换逻辑
message.success('主题颜色已更新')
}
</script>
<style scoped lang="scss">
@@ -180,6 +258,15 @@ const changeThemeColor = (color) => {
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;
@@ -202,48 +289,75 @@ const changeThemeColor = (color) => {
.action-btn {
font-size: 16px;
padding: 4px 8px;
display: flex;
align-items: center;
gap: 4px;
.lang-text {
font-size: 14px;
}
&:hover {
color: #1890ff;
}
}
}
.setting-content {
.setting-item {
margin-bottom: 24px;
.dropdown-card {
width: 320px;
max-height: 400px;
overflow: auto;
.setting-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 12px;
color: #333;
:deep(.ant-card-head) {
padding: 12px 16px;
min-height: auto;
}
.color-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
:deep(.ant-card-body) {
padding: 12px 16px;
max-height: 320px;
overflow-y: auto;
}
}
.color-item {
width: 32px;
height: 32px;
.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;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s;
}
}
&:hover {
transform: scale(1.1);
}
.message-content {
.message-title {
font-size: 14px;
color: #333;
margin-bottom: 4px;
line-height: 1.4;
}
&.active {
.anticon {
color: #fff;
}
}
.message-time {
font-size: 12px;
color: #999;
}
}
.task-item {
.completed {
text-decoration: line-through;
color: #999;
}
}
}