|
|
<script setup>// 导航栏在这
import {computed, onMounted, onUnmounted, ref} from 'vue'import {useRoute, useRouter} from 'vue-router'import {ElMessage} from 'element-plus'import ChangePassword from '@/components/dialogs/changePassword.vue'import {useAdminStore} from '@/store'import {storeToRefs} from 'pinia'import {filterMenu, getRoutePath} from "@/utils/menuUtils.js";// 刷新数据功能
import API from '@/util/http.js'import bell from '@/assets/SvgIcons/bell.svg'
import noMessage from '@/assets/images/no-message.svg'
// 使用import.meta.glob导入所有SVG图标(修复版本)
const icons = import.meta.glob('@/assets/SvgIcons/*.svg', {eager: true})
const menuNameMap = { '工作台': 'workbench', '金币管理': 'gold-management', '现金管理': 'cash-management', '活动管理': 'activity-management', '频道管理': 'channel-management', '权限管理': 'permission-management',}
// 创建获取图标路径的函数(修复版本)
const getIconPath = (menuName) => { const englishName = menuNameMap[menuName] || menuName;
// 构建可能的key格式
const possibleKeys = [ `@/assets/SvgIcons/${englishName}.svg`, `./SvgIcons/${englishName}.svg`, `/src/assets/SvgIcons/${englishName}.svg` ]
// 在icons对象中查找对应的图标
for (const key of possibleKeys) { if (icons[key]) { const iconModule = icons[key] // 返回模块的default属性或直接返回值
return iconModule.default || iconModule } }}
// 数据刷新功能实现
const refreshData = async () => { try { // 显示加载提示,duration保持打开提示不自动关闭,showclose显示未×,点击可关闭提示
ElMessage({message: '数据刷新中,请稍候...', type: 'info'});
// 调用Mysql接口获取最新数据
const response = await API({url: '/Mysql', method: 'POST', data: {}}); if (response && response.code === 200) {
const currentRoute = route.fullPath; router.replace('/blank'); // 临时跳转到一个空页面
setTimeout(() => { router.replace(currentRoute); // 跳转回原页面
}, 10);
ElMessage.success('数据刷新成功'); } else { ElMessage.error('数据刷新失败:' + (response?.msg || '未知错误')); } } catch (error) { console.error('数据刷新异常:', error); ElMessage.error('数据刷新异常,请重试'); }}
// 存储接口返回的菜单数据
const menuList = ref([])
// 获取仓库实例
const adminStore = useAdminStore()// 解构状态(保持响应式) 获得 adminData(用户信息) 和 menuTree(菜单树)
const {adminData, menuTree, flag} = storeToRefs(adminStore)
// 筛选权限菜单 ,menuTree 是组件通信拿的
menuList.value = filterMenu(menuTree.value)
console.log("menuList", menuList.value)// 获取当前路由
const route = useRoute()
// 通用函数:从菜单树中递归找出最匹配的 index
function findBestMatch(menuList, path) { let bestMatch = ''
function traverse(menus) { for (const item of menus) { const itemPath = getRoutePath(item) // 如果当前菜单的 path 是当前路径的前缀,可能是候选项
if (path.startsWith(itemPath) && itemPath.length > bestMatch.length) { bestMatch = itemPath }
if (item.children && item.children.length > 0) { traverse(item.children) } } }
traverse(menuList) return bestMatch || path // fallback 到当前路径
}
// 响应式高亮菜单
const activeMenu = computed(() => { return findBestMatch(menuList.value, route.path)})const router = useRouter()const messageVisible = ref(false)
// 查看个人信息弹出框
const openMessage = function () { messageVisible.value = true}// 关闭个人信息
const closeMessage = function () { messageVisible.value = false}const message = function () { openMessage()}
// 显示修改密码弹窗
const showPasswordDialog = ref(false)const pwdRef = ref()
//打开修改密码弹窗
const openChangePassword = () => { showPasswordDialog.value = true}
//关闭后清空密码表单
function onPwdDialogClosed() { // 调用子组件暴露的 resetFields
pwdRef.value?.resetFields()}
function logout() { const machineId = localStorage.getItem('machineId') localStorage.removeItem('token') adminStore.clearState() router.push('/login?machineId=' + machineId) ElMessage.success('退出成功')}
// 切换员工数据开关状态
const toggleFlag = () => { const newFlag = flag.value === 1 ? 0 : 1 adminStore.setFlag(newFlag) ElMessage.success(newFlag === 1 ? '员工数据已隐藏' : '员工数据已显示') console.log('flag', newFlag)}
// 消息推送逻辑
// 控制 MessageDialog 显示状态
const messageNum = ref(0)const showMessageDialog = ref(false)// 消息小红点状态
const messageDot = ref(true)
// 点击铃铛图标时打开
const openMessageDialog = () => { showMessageDialog.value = true messageDot.value = false
}// 点击关闭时关闭
const closeMessageDialog = () => { showMessageDialog.value = false}
// 模拟消息数据
const messageList = ref([ {id: 1, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2分钟前', group: '今天'}, {id: 2, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '1小时前', group: '今天'}, {id: 3, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '昨天 09:30', group: '昨天'}, {id: 4, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '昨天 08:30', group: '昨天'}, {id: 5, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 6, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 7, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 8, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 9, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 10, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 11, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 12, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 13, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 14, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 15, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'}, {id: 16, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '2025-11-05 10:05:20', group: '更早'},]);
// 按时间分组
const groupedMessages = ref({});messageList.value.forEach(item => { if (!groupedMessages.value[item.group]) { groupedMessages.value[item.group] = []; } groupedMessages.value[item.group].push(item);});
// 控制是否显示全部
const showAll = ref(false);
// 计算属性:根据showAll状态返回需要显示的消息
const displayMessages = computed(() => { if (showAll.value) { return groupedMessages.value; }
// 默认只展示前两条消息(跨分组)
const limitedMessages = {}; let count = 0;
// 保持分组顺序(今天 -> 昨天 -> 更早)
const groupOrder = ['今天', '昨天', '更早'];
for (const groupName of groupOrder) { const group = groupedMessages.value[groupName]; if (!group) continue;
limitedMessages[groupName] = []; for (const item of group) { if (count < 2) { limitedMessages[groupName].push(item); count++; } else { break; } }
// 如果该分组没有数据了就删除
if (limitedMessages[groupName].length === 0) { delete limitedMessages[groupName]; }
if (count >= 2) break; }
return limitedMessages;});
// 切换显示全部/收起
const toggleShowAll = () => { showAll.value = !showAll.value;};
// 控制返回顶部按钮状态
const scrollContainer = ref(null)const scrollToTop = () => { scrollContainer.value?.scrollTo({top: 0, behavior: 'smooth'})}
</script>
<template> <div class="main-container"> <iframe src="http://192.168.40.8:8081/Money/ceshi" style="display:none"></iframe> <!-- 背景毛玻璃层(作为内容容器) --> <div class="background-glass"> <!-- 侧边栏 --> <div class="sidebar-container"> <el-aside class="sidebar-layout"> <div class="logo"> <img src="../assets/新logo.png" alt="logo" style="width: 9vh; height: 9vh"/> </div>
<div class="menu-scroll-container"> <el-menu :router="true" :default-active="activeMenu" style="min-height: 80vh;border:none;"> <!-- 递归渲染菜单层级 --> <template v-for="menu in menuList" :key="menu.id"> <!-- 有子菜单的父级菜单(menuType=2 且存在children) --> <el-sub-menu v-if="menu.children && menu.children.length > 0" :index="menu.id.toString()"> <template #title> <img :src="getIconPath(menu.menuName)" :alt="`${menu.menuName}图标`" style="width: 4vh; height: 4vh; margin-right: 4px;" >
<span>{{ menu.menuName }}</span> </template> <!-- 子菜单 --> <template v-for="child in menu.children" :key="child.id"> <!-- 子菜单为叶子节点(无children) --> <el-menu-item v-if="!child.children || child.children.length === 0" :index="getRoutePath(child)"> <el-icon style="margin-right: 4px;"> <Folder/> </el-icon> <span>{{ child.menuName }}</span> </el-menu-item>
<!-- 子菜单有下级 --> <el-sub-menu v-else :index="child.id.toString()"> <template #title> <el-icon style="margin-right: 4px;"> <Folder/> </el-icon> <span>{{ child.menuName }}</span> </template> <!-- 递归 下一级--> <template v-for="grandChild in child.children" :key="grandChild.id"> <el-menu-item :index="getRoutePath(grandChild)"> <el-icon style="margin-right: 4px;"> <Folder/> </el-icon> <span>{{ grandChild.menuName }}</span> </el-menu-item> </template> </el-sub-menu> </template> </el-sub-menu>
<!-- 无子菜单的一级菜单 --> <el-menu-item v-else :index="getRoutePath(menu)"> <img :src="getIconPath(menu.menuName)" :alt="`${menu.menuName}图标`" style="width: 4vh; height: 4vh; margin-right: 4px;" > <span>{{ menu.menuName }}</span> </el-menu-item> </template> </el-menu> </div>
<div style="display: flex"> <!-- 底部固定的设置中心 --> <div class="settings-container"> <el-dropdown placement="top-start"> <span class="el-dropdown-link"> <!-- 暂时使用静态路径,确保设置图标正常显示 --> <img src="@/assets/SvgIcons/设置.svg" alt="设置" style="width: 4vh; height: 4vh"/> <span>设置中心</span> <el-icon class="arrow-icon"> <ArrowUp/> </el-icon> </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item @click="refreshData()">数据刷新</el-dropdown-item> <!-- 员工数据开关 --> <el-dropdown-item @click="toggleFlag()"> {{ flag === 1 ? '显示员工数据' : '隐藏员工数据' }} </el-dropdown-item> <el-dropdown-item @click="message()">查看个人信息</el-dropdown-item> <el-dropdown-item @click="openChangePassword">修改密码</el-dropdown-item> <el-dropdown-item @click="logout">退出登录</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> <!-- 消息提示 这里的 小红点不用el-badge 他在切换状态会抽一下 应该是dom问题 --> <div class="message-container"> <div style="position: relative;"> <el-image :src="bell" style="width: 28px; height: 28px;" @click="openMessageDialog"></el-image> <span v-show="messageDot" class="dot"></span> </div> </div>
</div>
</el-aside> </div>
<!-- 右侧内容区域 --> <div class="content-container"> <!-- 头部 <el-header class="header"> </el-header> -->
<!-- 主内容区域 --> <div class="main-area"> <el-main> <router-view></router-view> </el-main> </div> </div> </div>
<!-- 查看个人信息 --> <el-dialog v-model="messageVisible" title="查看个人信息" width="500px"> <el-form :model="adminData"> <el-form-item label="用户姓名" label-width="100px" label-position="left"> <span class="message-font">{{ adminData.adminName }}</span> </el-form-item> <el-form-item label="精网号" label-width="100px" label-position="left"> <span class="message-font">{{ adminData.account }}</span> </el-form-item> <el-form-item label="地区" label-width="100px" label-position="left"> <span class="message-font">{{ adminData.markets }}</span> </el-form-item> <el-form-item label="注册时间" label-width="100px" label-position="left"> <span class="message-font">{{ adminData.createTime }}</span> </el-form-item> </el-form> <template #footer> <div> <el-button text @click="closeMessage()">关闭</el-button> </div> </template> </el-dialog>
<!-- 自定义密码修改弹窗组件 --> <el-dialog v-model="showPasswordDialog" :center="true" width="470px" @closed="onPwdDialogClosed"> <ChangePassword ref="pwdRef" @confirm="showPasswordDialog = false"/> </el-dialog>
<!--消息推送的弹窗--> <el-dialog style="background: #F3FAFE" v-model="showMessageDialog" title="" width="500px"> <div class="message-title"> <el-divider class="divider" direction="vertical" ></el-divider> 消息中心 ({{ messageNum }}) </div> <!-- todo 这是为了样式显示 一定要改逻辑--> <div v-if="messageNum !== 0"> <div class="no-message"> <el-image :src="noMessage"></el-image> <p class="no-message-text">暂无未办消息,快去处理工作吧~</p> </div> </div> <div v-else ref="scrollContainer" style="max-height: 60vh; overflow-y: auto;"> <!-- 按时间分组的消息列表 --> <div v-for="(group, time) in displayMessages" :key="time" style="margin-bottom: 16px;" > <div class="time-header"> {{ time }} <span class="little-dot"></span> <el-divider style="height: 2px; align-self: stretch;flex: 1;background: #CEE5FE; border: none;" ></el-divider> </div> <div v-for="item in group" :key="item.id" class="message-item" > <div style="display: flex; margin-bottom: 10px"> <span class="red-dot"></span> <span class="message-card-title">{{ item.title }}</span> <div class="message-time">{{ item.time }}</div> </div>
<p class="message-desc">{{ item.desc }}</p> <el-button type="primary" style="margin: 0 auto; display: block;" > 前往查看 </el-button> </div> </div>
<!-- 控制按钮 --> <div class="message-actions"> <el-button type="text" class="view-all" @click="toggleShowAll" > {{ showAll ? '收起' : '查看全部' }} </el-button>
<image src="@" @click="scrollToTop" style="width: 20px; height: 20px;"/> 返回顶部 </div> </div>
</el-dialog>
</div></template>
<style scoped>/* 主容器,设置背景图并居中 */.main-container { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-image: url('@/assets/backgroundBlue.png'); background-size: cover; background-position: center center; background-repeat: no-repeat; overflow: hidden;}
/* 背景毛玻璃层(作为内容容器) */.background-glass { position: absolute; top: 1vh; left: 1vh; right: 1vh; bottom: 1vh; background-image: url('@/assets/半透明background.png'); background-size: cover; z-index: 1; display: flex; flex-direction: row; padding: 10px; border-radius: 12px;}
/* 侧边栏容器 */.sidebar-container { flex-shrink: 0;
}
.logo { display: flex; align-items: center; justify-content: center; height: 12vh;
}
/* 中间可滚动菜单容器 */.menu-scroll-container { flex: 1; overflow-y: auto; padding: 10px 0;}
/* 底部设置中心样式 */.settings-container { padding: 10px 0 10px 20px; /* 上,右, 下,左 */ display: flex; align-items: center; /* 垂直居中 */}
/* 调整下拉菜单的样式,确保它向上弹出 */
.el-dropdown-link:focus { /* 移除底部的异常效果 */ outline: none; text-decoration: none;}
.el-dropdown-link { display: flex; align-items: center; cursor: pointer; gap: 10px; /* 图标和文字左右间距 */}
.sidebar-layout { width: 15vw; height: 100%; background: #E7F4FD; /* 浅蓝色背景 */ /* backdrop-filter: blur(5px); 毛玻璃效果 --消耗性能 */ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); /* 添加阴影增强层次感 */ border-radius: 12px; display: flex; flex-direction: column; position: relative; transition: all 0.3s ease;}
/* 内容区域容器 */.content-container { flex: 1; display: flex; flex-direction: column; margin-left: 5px; gap: 5px; height: 100%; overflow: hidden;}
/* 主内容区域容器 */.main-area { flex: 1; background: #E7F4FD; /* 半透明浅色背景 */ /* backdrop-filter: blur(5px); */ /* 毛玻璃效果 */ border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); /* 添加阴影增强层次感 */ overflow: hidden; display: flex; flex-direction: column;}
/* 主内容区域样式 */.el-main { height: 100%; padding: 1px 8px 10px 8px; background: transparent; overflow-y: auto; /* 应用自定义滚动条 */}
/* 确保el-menu撑满容器 */.sidebar-layout .el-menu { width: 100%;}
/* 侧边栏菜单样式 适配浅色背景 */.el-menu { background: transparent !important;}
/* 工作台,金币管理,现金管理 */::v-deep(.el-sub-menu__title:hover),::v-deep(.el-menu-item:hover) { background: #E5EBFE;}
/* 子菜单展开时和背景同色 */::v-deep(.el-sub-menu__title),::v-deep(.el-menu-item) { background: #E7F4FD;}
.message-font { /* 个人信息字体样式 */ font-size: 16px; font-weight: bold;}
/* 确保全局el-container适应容器 */:deep(.el-container) { /* vue3的深度选择器,用于覆盖element-plus的默认样式 */ min-height: 100%; width: 100%; background: transparent;}
/* 为侧边栏和主内容区域添加滚动条样式 */.menu-scroll-container,.el-main { scrollbar-width: thin; /* Firefox */ scrollbar-color: rgba(0, 0, 0, 0.3) rgba(255, 255, 255, 0.2); /* Firefox滑块和轨道颜色 */}
/* 小红点 */.dot { position: absolute; top: -2px; right: -2px; width: 8px; height: 8px; background: #F23C39; border-radius: 50%;}
/* 消息中心整体容器 */.message-container { padding: 10px 50px 10px 50px; /* 上,右, 下,左 */ display: flex; align-items: center; /* 垂直居中 */}
/* 消息中心标题 */.message-title { display: flex; font-size: 16px; color: black;
.divider { align-items: flex-start; gap: 36px; align-self: stretch; height: 20px; border-left: 3px solid #266EFF; }}
/* 无消息的样式 */.no-message { text-align: center; position: relative;
.no-message-text { position: absolute; top: 60%; left: 50%; /* 水平垂直居中 */ transform: translate(-50%, -50%); /* 文字样式 */ font-weight: bold; margin: 0; /* 可添加更多样式(如字体大小、阴影等) */ font-size: 14px; }}
/* 有消息的样式 */.time-header { font-size: 14px; color: #666; display: flex; align-items: center; gap: 8px;}
.message-item { height: 100px; padding: 10px 10px 10px 10px; border-radius: 4px; border: 1px solid #E5E5E5; background: #FCFEFF; margin-bottom: 10px;}
.message-card-title { font-weight: bold; margin-right: 4px;}
/* 圆点样式 */.red-dot { width: 6px; height: 6px; margin-right: 9px; border-radius: 50%; background-color: red;}
.message-desc { font-size: 13px; color: #666; margin: 4px 0 15px 15px;}
.message-time { margin-left: auto; font-size: 13px; color: #999;}
.view-all { display: block; margin: 0 auto 16px;}
.little-dot { display: inline-block; width: 8px; /* 圆点直径 */ height: 8px; border-radius: 50%; /* 圆形 */ background-color: #CEE5FE;;}
</style>
|