|
|
|
@ -1,19 +1,23 @@ |
|
|
|
<script setup> |
|
|
|
// 导航栏在这 |
|
|
|
import {computed, onMounted, onUnmounted, ref} from 'vue' |
|
|
|
import {computed, onMounted, 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 bell from '@/assets/SvgIcons/bell.svg' |
|
|
|
import noMessage from '@/assets/images/no-message.svg' |
|
|
|
import goTop from '@/assets/SvgIcons/go-top.svg' |
|
|
|
|
|
|
|
import {getOrderPage} from '@/utils/goToCheck.js' |
|
|
|
import {groupMessages} from "@/utils/getMessage.js" |
|
|
|
|
|
|
|
// 使用import.meta.glob导入所有SVG图标(修复版本) |
|
|
|
import {useMessageStore} from '@/store/index.js' |
|
|
|
|
|
|
|
// ------------------ ICONS ------------------ |
|
|
|
const icons = import.meta.glob('@/assets/SvgIcons/*.svg', {eager: true}) |
|
|
|
|
|
|
|
const menuNameMap = { |
|
|
|
@ -25,124 +29,83 @@ const menuNameMap = { |
|
|
|
'权限管理': '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) { |
|
|
|
|
|
|
|
if (response && response.code === 200) { |
|
|
|
const currentRoute = route.fullPath; |
|
|
|
router.replace('/blank'); // 临时跳转到一个空页面 |
|
|
|
setTimeout(() => { |
|
|
|
router.replace(currentRoute); // 跳转回原页面 |
|
|
|
}, 10); |
|
|
|
|
|
|
|
router.replace('/blank'); |
|
|
|
setTimeout(() => router.replace(currentRoute), 10); |
|
|
|
ElMessage.success('数据刷新成功'); |
|
|
|
} else { |
|
|
|
ElMessage.error('数据刷新失败:' + (response?.msg || '未知错误')); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('数据刷新异常:', error); |
|
|
|
console.error(error) |
|
|
|
ElMessage.error('数据刷新异常,请重试'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 存储接口返回的菜单数据 |
|
|
|
const menuList = ref([]) |
|
|
|
// ------------------ 菜单逻辑 ------------------ |
|
|
|
const route = useRoute() |
|
|
|
const router = useRouter() |
|
|
|
|
|
|
|
// 获取仓库实例 |
|
|
|
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() |
|
|
|
const menuList = ref(filterMenu(menuTree.value)) |
|
|
|
|
|
|
|
// 通用函数:从菜单树中递归找出最匹配的 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) |
|
|
|
} |
|
|
|
if (item.children?.length) traverse(item.children) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
traverse(menuList) |
|
|
|
return bestMatch || path // fallback 到当前路径 |
|
|
|
return bestMatch || path |
|
|
|
} |
|
|
|
|
|
|
|
// 响应式高亮菜单 |
|
|
|
const activeMenu = computed(() => { |
|
|
|
return findBestMatch(menuList.value, route.path) |
|
|
|
}) |
|
|
|
const router = useRouter() |
|
|
|
const messageVisible = ref(false) |
|
|
|
const activeMenu = computed(() => findBestMatch(menuList.value, route.path)) |
|
|
|
|
|
|
|
// 查看个人信息弹出框 |
|
|
|
const openMessage = function () { |
|
|
|
messageVisible.value = true |
|
|
|
} |
|
|
|
// 关闭个人信息 |
|
|
|
const closeMessage = function () { |
|
|
|
messageVisible.value = false |
|
|
|
} |
|
|
|
const message = function () { |
|
|
|
openMessage() |
|
|
|
} |
|
|
|
// ------------------ 用户信息 / 密码修改 ------------------ |
|
|
|
const messageVisible = ref(false) |
|
|
|
const openMessage = () => (messageVisible.value = true) |
|
|
|
const closeMessage = () => (messageVisible.value = false) |
|
|
|
|
|
|
|
// 显示修改密码弹窗 |
|
|
|
const showPasswordDialog = ref(false) |
|
|
|
const pwdRef = ref() |
|
|
|
|
|
|
|
//打开修改密码弹窗 |
|
|
|
const openChangePassword = () => { |
|
|
|
showPasswordDialog.value = true |
|
|
|
} |
|
|
|
|
|
|
|
//关闭后清空密码表单 |
|
|
|
function onPwdDialogClosed() { |
|
|
|
// 调用子组件暴露的 resetFields |
|
|
|
pwdRef.value?.resetFields() |
|
|
|
} |
|
|
|
const openChangePassword = () => (showPasswordDialog.value = true) |
|
|
|
const onPwdDialogClosed = () => pwdRef.value?.resetFields() |
|
|
|
|
|
|
|
// ------------------ 退出登录 ------------------ |
|
|
|
function logout() { |
|
|
|
const machineId = localStorage.getItem('machineId') |
|
|
|
localStorage.removeItem('token') |
|
|
|
@ -151,124 +114,117 @@ function logout() { |
|
|
|
ElMessage.success('退出成功') |
|
|
|
} |
|
|
|
|
|
|
|
// 切换员工数据开关状态 |
|
|
|
// ------------------ 员工数据开关 ------------------ |
|
|
|
const toggleFlag = () => { |
|
|
|
const newFlag = flag.value === 1 ? 0 : 1 |
|
|
|
adminStore.setFlag(newFlag) |
|
|
|
ElMessage.success(newFlag === 1 ? '员工数据已隐藏' : '员工数据已显示') |
|
|
|
console.log('flag', newFlag) |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------ 消息中心(完全修复版) ------------------ |
|
|
|
const messageStore = useMessageStore() |
|
|
|
const {messages} = storeToRefs(messageStore) |
|
|
|
|
|
|
|
// 消息推送逻辑 |
|
|
|
// 控制 MessageDialog 显示状态 |
|
|
|
// 获取消息 |
|
|
|
const getMessage = async () => { |
|
|
|
try { |
|
|
|
const res = await API({ |
|
|
|
url: '/getMessage', |
|
|
|
method: 'POST', |
|
|
|
data: {} |
|
|
|
}); |
|
|
|
|
|
|
|
if (res?.data) { |
|
|
|
const cleanList = res.data.filter(i => i.flag !== 1) |
|
|
|
messageStore.setMessages(cleanList) |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.error("getMessage error:", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const messageNum = ref(0) |
|
|
|
// 点击铃铛 → 打开弹窗并刷新 |
|
|
|
const showMessageDialog = ref(false) |
|
|
|
// 消息小红点状态 |
|
|
|
const messageDot = ref(true) |
|
|
|
|
|
|
|
// 点击铃铛图标时打开 |
|
|
|
const openMessageDialog = () => { |
|
|
|
const openMessageDialog = async () => { |
|
|
|
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: 2, title: '现金管理—收款明细', desc: 'XXX(用户)的收款单被驳回', time: '1小时前', 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); |
|
|
|
}); |
|
|
|
await getMessage() // 等待消息更新 |
|
|
|
} |
|
|
|
|
|
|
|
// 控制是否显示全部 |
|
|
|
const showAll = ref(false); |
|
|
|
// 关闭消息窗口 |
|
|
|
const closeMessageDialog = () => showMessageDialog.value = false |
|
|
|
|
|
|
|
// 小红点(完全响应式) |
|
|
|
const messageDot = computed(() => messages.value.length > 0) |
|
|
|
|
|
|
|
// 计算属性:根据showAll状态返回需要显示的消息 |
|
|
|
const displayMessages = computed(() => { |
|
|
|
if (showAll.value) { |
|
|
|
return groupedMessages.value; |
|
|
|
} |
|
|
|
// 消息数量(完全响应式) |
|
|
|
const messageNum = computed(() => messages.value.length) |
|
|
|
|
|
|
|
// 按日期分组(computed) |
|
|
|
const messageList = computed(() => groupMessages(messages.value)) |
|
|
|
|
|
|
|
// 默认只展示前两条消息(跨分组) |
|
|
|
const limitedMessages = {}; |
|
|
|
let count = 0; |
|
|
|
// 按日期生成最终结构(computed) |
|
|
|
const groupedMessages = computed(() => { |
|
|
|
const result = {} |
|
|
|
messageList.value.forEach(item => { |
|
|
|
if (!result[item.group]) result[item.group] = [] |
|
|
|
result[item.group].push(item) |
|
|
|
}) |
|
|
|
return result |
|
|
|
}) |
|
|
|
|
|
|
|
// 显示全部 or 显示前两条 |
|
|
|
const showAll = ref(false) |
|
|
|
|
|
|
|
// 保持分组顺序(今天 -> 昨天 -> 更早) |
|
|
|
const groupOrder = ['今天', '昨天', '更早']; |
|
|
|
const displayMessages = computed(() => { |
|
|
|
if (showAll.value) return groupedMessages.value |
|
|
|
|
|
|
|
for (const groupName of groupOrder) { |
|
|
|
const group = groupedMessages.value[groupName]; |
|
|
|
if (!group) continue; |
|
|
|
let count = 0 |
|
|
|
const limited = {} |
|
|
|
const groupOrder = ['今天', '昨天', '更早'] |
|
|
|
|
|
|
|
limitedMessages[groupName] = []; |
|
|
|
for (const g of groupOrder) { |
|
|
|
const group = groupedMessages.value[g] |
|
|
|
if (!group) continue |
|
|
|
|
|
|
|
limited[g] = [] |
|
|
|
for (const item of group) { |
|
|
|
if (count < 2) { |
|
|
|
limitedMessages[groupName].push(item); |
|
|
|
count++; |
|
|
|
} else { |
|
|
|
break; |
|
|
|
limited[g].push(item) |
|
|
|
count++ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 如果该分组没有数据了就删除 |
|
|
|
if (limitedMessages[groupName].length === 0) { |
|
|
|
delete limitedMessages[groupName]; |
|
|
|
} |
|
|
|
|
|
|
|
if (count >= 2) break; |
|
|
|
if (limited[g].length === 0) delete limited[g] |
|
|
|
if (count >= 2) break |
|
|
|
} |
|
|
|
return limited |
|
|
|
}) |
|
|
|
|
|
|
|
return limitedMessages; |
|
|
|
}); |
|
|
|
|
|
|
|
// 切换显示全部/收起 |
|
|
|
const toggleShowAll = () => { |
|
|
|
showAll.value = !showAll.value; |
|
|
|
}; |
|
|
|
|
|
|
|
// 控制返回顶部按钮状态 |
|
|
|
const toggleShowAll = () => showAll.value = !showAll.value |
|
|
|
|
|
|
|
// 返回顶部 |
|
|
|
const scrollContainer = ref(null) |
|
|
|
const scrollToTop = () => { |
|
|
|
scrollContainer.value?.scrollTo({top: 0, behavior: 'smooth'}) |
|
|
|
const scrollToTop = () => scrollContainer.value?.scrollTo({top: 0, behavior: 'smooth'}) |
|
|
|
|
|
|
|
// 点击消息 → 已读 + 跳转 |
|
|
|
const handleMessageClick = async (item) => { |
|
|
|
const res = await API({ |
|
|
|
url: '/getMessage/update', |
|
|
|
method: 'POST', |
|
|
|
data: {id: item.id} |
|
|
|
}); |
|
|
|
|
|
|
|
if (res.code === 200) { |
|
|
|
closeMessageDialog() |
|
|
|
await router.push(getOrderPage(item.status)) |
|
|
|
await getMessage() |
|
|
|
ElMessage.success('跳转成功') |
|
|
|
} else { |
|
|
|
ElMessage.error('跳转失败') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
import goTop from '@/assets/SvgIcons/go-top.svg' |
|
|
|
|
|
|
|
onMounted(() => getMessage()) |
|
|
|
</script> |
|
|
|
|
|
|
|
<template> |
|
|
|
<div class="main-container"> |
|
|
|
<!-- 背景毛玻璃层(作为内容容器) --> |
|
|
|
@ -353,7 +309,7 @@ import goTop from '@/assets/SvgIcons/go-top.svg' |
|
|
|
</span> |
|
|
|
<template #dropdown> |
|
|
|
<el-dropdown-menu> |
|
|
|
<el-dropdown-item @click="refreshData()">数据刷新</el-dropdown-item> |
|
|
|
<!-- <el-dropdown-item @click="refreshData()">数据刷新</el-dropdown-item>--> |
|
|
|
<!-- 员工数据开关 --> |
|
|
|
<el-dropdown-item @click="toggleFlag()"> |
|
|
|
{{ flag === 1 ? '显示员工数据' : '隐藏员工数据' }} |
|
|
|
@ -432,7 +388,7 @@ import goTop from '@/assets/SvgIcons/go-top.svg' |
|
|
|
消息中心 ({{ messageNum }}) |
|
|
|
</div> |
|
|
|
<!-- todo 这是为了样式显示 一定要改逻辑--> |
|
|
|
<div v-if="messageNum !== 0"> |
|
|
|
<div v-if="messageNum === 0"> |
|
|
|
<div class="no-message"> |
|
|
|
<el-image :src="noMessage"></el-image> |
|
|
|
<p class="no-message-text">暂无未办消息,快去处理工作吧~</p> |
|
|
|
@ -461,13 +417,19 @@ import goTop from '@/assets/SvgIcons/go-top.svg' |
|
|
|
<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 |
|
|
|
class="message-time" |
|
|
|
:style="{ color: item.czTime.includes('分钟') ? 'red' : '' }" |
|
|
|
> |
|
|
|
{{ item.czTime }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<p class="message-desc">{{ item.desc }}</p> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
style="margin: 0 auto; display: block;" |
|
|
|
@click="handleMessageClick(item)" |
|
|
|
> |
|
|
|
前往查看 |
|
|
|
</el-button> |
|
|
|
@ -481,6 +443,7 @@ import goTop from '@/assets/SvgIcons/go-top.svg' |
|
|
|
<div> |
|
|
|
<el-button |
|
|
|
type="text" |
|
|
|
v-if="messageNum > 2" |
|
|
|
class="view-all" |
|
|
|
@click="toggleShowAll" |
|
|
|
> |
|
|
|
|