You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

779 lines
23 KiB

<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>