Commit 7ae82c56 authored by 蔡伟's avatar 蔡伟

Merge branch 'feat/layout-style' into 'dev'

feat(*): 动态菜单

See merge request !17
parents 7afb6ad1 b047ace9
...@@ -11,9 +11,9 @@ ...@@ -11,9 +11,9 @@
:collapse-transition="false" :collapse-transition="false"
:router="true" :router="true"
> >
<template v-for="(item, index) in routerList" :key="item.path"> <template v-for="(item, index) in filteredRouterList" :key="item.path">
<el-sub-menu <el-sub-menu
:index="index + ''" :index="item.path"
v-if="item.children && item.children.length > 0" v-if="item.children && item.children.length > 0"
class="submenu-box" class="submenu-box"
> >
...@@ -22,16 +22,20 @@ ...@@ -22,16 +22,20 @@
<img class="menu-icon" :src="`/src/assets/menu/${item.meta.icon}.png`" alt=""> <img class="menu-icon" :src="`/src/assets/menu/${item.meta.icon}.png`" alt="">
<span>{{getMenuTitle(item)}}</span> <span>{{getMenuTitle(item)}}</span>
</template> </template>
<el-menu-item v-for="(item_son, index_son) in item.children" <el-menu-item
:key="item_son.path" v-for="(item_son, index_son) in item.children"
:index="item_son.path" :key="item_son.path"
@click="handleClick(item_son.path)" :index="item_son.path"
class="submenu-title-noDropdown" class="submenu-title-noDropdown"
> >
<span>{{getMenuTitle(item_son)}}</span> <span>{{getMenuTitle(item_son)}}</span>
</el-menu-item> </el-menu-item>
</el-sub-menu> </el-sub-menu>
<el-menu-item v-else :index="index + ''" class="submenu-box" @click="handleClick(item.path)"> <el-menu-item
v-else
:index="item.path"
class="submenu-box"
>
<!-- <el-icon><setting /></el-icon> --> <!-- <el-icon><setting /></el-icon> -->
<img class="menu-icon" :src="`/src/assets/menu/${item.meta.icon}.png`" alt=""> <img class="menu-icon" :src="`/src/assets/menu/${item.meta.icon}.png`" alt="">
<span>{{getMenuTitle(item)}}</span> <span>{{getMenuTitle(item)}}</span>
...@@ -43,7 +47,7 @@ ...@@ -43,7 +47,7 @@
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'; import { computed, ref, onMounted } from 'vue';
import { import {
Document, Document,
Menu as IconMenu, Menu as IconMenu,
...@@ -54,32 +58,59 @@ import {routes} from "../router/index.js"; ...@@ -54,32 +58,59 @@ import {routes} from "../router/index.js";
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { menuStore } from '@/pinia/menu.js'; import { menuStore } from '@/pinia/menu.js';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { sidebar } = storeToRefs(menuStore()); const { sidebar } = storeToRefs(menuStore());
const handleClick = (path) => {
console.log(path);
if (path.startsWith('/')) {
router.push(path) // 绝对路径,直接跳转
} else {
router.push('/' + path) // 相对路径,补全
}
}
const routerList = routes[0].children; const routerList = routes[0].children;
const menuListData = ref(JSON.parse(sessionStorage.getItem("menuList")) || []);
// 递归过滤路由
const filterRoutes = (routes) => {
if (!routes) return [];
return routes.filter(route => {
// 检查当前路由是否在菜单列表中
const isInMenuList = menuListData.value.some(menuItem =>
menuItem.name === route.meta?.title &&
menuItem.url === route.path
);
// 如果当前路由有子路由,递归过滤子路由
if (route.children && route.children.length > 0) {
const filteredChildren = filterRoutes(route.children);
// 如果子路由有匹配项,保留父路由并更新子路由
if (filteredChildren.length > 0) {
route.children = filteredChildren;
return true;
}
}
const activeMenu = computed(()=>{ // 如果当前路由在菜单列表中,或者有匹配的子路由,则保留
console.log('123--', route.path); return isInMenuList;
return route.path; });
};
// 过滤后的路由列表
const filteredRouterList = computed(() => {
if (!menuListData.value || menuListData.value.length === 0) {
return [];
}
return filterRoutes(routerList);
}); });
onMounted(() => {
});
const activeMenu = computed(() => route.path);
const handleOpen = (key, keyPath) => { const handleOpen = (key, keyPath) => {
console.log(key, keyPath);
}; };
const handleClose = (key, keyPath) => { const handleClose = (key, keyPath) => {
console.log(key, keyPath);
}; };
const getMenuTitle = (item) => { const getMenuTitle = (item) => {
return item.meta && item.meta.title || '未命名菜单' return item.meta && item.meta.title || '未命名菜单'
} }
...@@ -164,7 +195,7 @@ const getMenuTitle = (item) => { ...@@ -164,7 +195,7 @@ const getMenuTitle = (item) => {
} }
.el-menu-item { .el-menu-item {
background: #055F7CFF !important; // background: #055F7CFF !important;
} }
.submenu-box .is-active { .submenu-box .is-active {
......
...@@ -9,6 +9,8 @@ import "element-plus/dist/index.css"; ...@@ -9,6 +9,8 @@ import "element-plus/dist/index.css";
import * as ElementPlusIconsVue from "@element-plus/icons-vue"; import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import { createPinia } from "pinia"; import { createPinia } from "pinia";
import moment from 'moment'; import moment from 'moment';
import { startPermissionCheck } from '@/utils/permissionChecker';
const app = createApp(App); const app = createApp(App);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) { for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component); app.component(key, component);
...@@ -19,3 +21,8 @@ app ...@@ -19,3 +21,8 @@ app
.use(moment) .use(moment)
.use(createPinia()) .use(createPinia())
.mount("#app"); .mount("#app");
// 启动权限轮询检查,每5秒检查一次
router.isReady().then(() => {
startPermissionCheck(5000);
});
import { createWebHistory, createRouter } from 'vue-router' import { createWebHistory, createRouter } from 'vue-router'
import Dashboard from '../views/dashboard/index.vue'
import DustOverview from '../views/dustOverview/index.vue'
import AboutView from '../views/AboutView/AboutView.vue'
import CollectorList from '../views/collectorList/collectorList.vue'
import User from '../views/user.vue'
import Layout from '../layout/index.vue' import Layout from '../layout/index.vue'
import Login from '../views/login/index.vue'
import equipmentManagement from '../views/equipmentManagement/index.vue'
import suspendManagement from '../views/equipmentManagement/suspendManagement/suspendManagement.vue'
import dustMonitoring from '../views/dustMonitoring/index.vue'
import bagMonitoring from '../views/dustMonitoring/bag-monitoring.vue'
// 检查路由是否在授权菜单中的函数
const checkRoutePermission = (route, menuList) => {
// 检查当前路由
const hasPermission = menuList.some(menu =>
menu.name === route.meta?.title &&
menu.url === route.path
);
if (hasPermission) return true;
// 如果有子路由,递归检查
if (route.children) {
return route.children.some(child => checkRoutePermission(child, menuList));
}
return false;
};
// 递归查找匹配的路由配置
const findMatchedRoute = (path, routes) => {
for (const route of routes) {
if (route.path === path) return route;
if (route.children) {
const found = findMatchedRoute(path, route.children);
if (found) return found;
}
}
return null;
};
import myAgency from '@/views/closeManage/myAgency.vue';
import myDone from '@/views/closeManage/myDone.vue';
const routes = [ const routes = [
{ {
path: '/', path: '/',
...@@ -23,12 +40,12 @@ const routes = [ ...@@ -23,12 +40,12 @@ const routes = [
children: [ children: [
{ {
path: '/dashboard', path: '/dashboard',
component: Dashboard, component: () => import('../views/dashboard/index.vue'),
meta: { title: '首页', icon: 'dashboard', }, meta: { title: '首页', icon: 'dashboard', },
}, },
{ {
path: '/dust-overview', path: '/dust-overview',
component: DustOverview, component: () => import('../views/dustOverview/index.vue'),
meta: { title: '除尘器总览', icon: 'dustOverview' }, meta: { title: '除尘器总览', icon: 'dustOverview' },
}, },
{ {
...@@ -37,34 +54,34 @@ const routes = [ ...@@ -37,34 +54,34 @@ const routes = [
children: [ children: [
{ {
path: '/management/device-management', path: '/management/device-management',
component: equipmentManagement, component: () => import('../views/equipmentManagement/index.vue'),
meta: { title: '设备管理' }, meta: { title: '设备管理' },
}, },
{ {
path: '/management/suspend-management', path: '/management/suspend-management',
component: suspendManagement, component: () => import('../views/equipmentManagement/suspendManagement/suspendManagement.vue'),
meta: { title: '挂起设备管理' }, meta: { title: '挂起设备管理' },
}, },
] ]
}, },
{ {
path: '/monitor', path: '/monitor',
component: dustMonitoring, component: () => import('../views/dustMonitoring/index.vue'),
meta: { title: '除尘器监控', icon: 'monitor' }, meta: { title: '除尘器监控', icon: 'monitor' },
}, },
{ {
path: '/collectorList', path: '/collectorList',
component: CollectorList, component: () => import('../views/collectorList/collectorList.vue'),
meta: { title: '布袋周期', icon: 'collectorList' }, meta: { title: '布袋周期', icon: 'collectorList' },
}, },
{ {
path: '/bag-monitor', path: '/bag-monitor',
component: bagMonitoring, component: () => import('../views/dustMonitoring/bag-monitoring.vue'),
meta: { title: 'BME布袋监测', icon: 'collectorList' }, meta: { title: 'BME布袋监测', icon: 'collectorList' },
}, },
{ {
path: '/alerts', path: '/alerts',
component: AboutView, component: () => import('../views/AboutView/AboutView.vue'),
meta: { title: '告警总览', icon: 'warnning' }, meta: { title: '告警总览', icon: 'warnning' },
}, },
{ {
...@@ -73,12 +90,12 @@ const routes = [ ...@@ -73,12 +90,12 @@ const routes = [
children: [ children: [
{ {
path: '/my-loop/myAgency', path: '/my-loop/myAgency',
component: myAgency, component: () => import('@/views/closeManage/myAgency.vue'),
meta: { title: '我的待办' }, meta: { title: '我的待办' },
}, },
{ {
path: '/my-loop/myDone', path: '/my-loop/myDone',
component: myDone, component: () => import('@/views/closeManage/myDone.vue'),
meta: { title: '我的已办' }, meta: { title: '我的已办' },
}, },
] ]
...@@ -87,7 +104,7 @@ const routes = [ ...@@ -87,7 +104,7 @@ const routes = [
}, },
{ {
path: '/login', path: '/login',
component: Login, component: () => import('../views/login/index.vue'),
meta: { meta: {
title: '登录' title: '登录'
} }
...@@ -99,6 +116,36 @@ const router = createRouter({ ...@@ -99,6 +116,36 @@ const router = createRouter({
routes, routes,
}) })
// 添加全局路由守卫
router.beforeEach((to, from, next) => {
// 登录页面不需要权限验证
if (to.path === '/login') {
next();
return;
}
// 获取存储的菜单列表
const menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]');
// 如果没有菜单列表且不是登录页,跳转到登录页
if (!menuList.length) {
next('/login');
return;
}
// 查找目标路由的配置
const targetRoute = findMatchedRoute(to.path, routes[0].children || []);
// 如果找不到路由配置,或者路由没有权限,跳转到首页或显示无权限页面
if (!targetRoute || !checkRoutePermission(targetRoute, menuList)) {
// 可以根据需求跳转到不同页面,比如 403 页面或首页
next('/dashboard');
return;
}
next();
});
export { export {
routes, routes,
router router
......
...@@ -53,9 +53,9 @@ router.beforeEach(async (to, from, next) => { ...@@ -53,9 +53,9 @@ router.beforeEach(async (to, from, next) => {
// debugger // debugger
// get user info // get user info
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor'] // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
const roles = await store.getInfo() // const roles = await store.getInfo()
// generate accessible routes map based on roles // generate accessible routes map based on roles
const menuLimitsObj = await store.menuLimitsObj // const menuLimitsObj = await store.menuLimitsObj
// const accessRoutes = await store.dispatch('permission/generateRoutes', { roles, menuLimitsObj }) // const accessRoutes = await store.dispatch('permission/generateRoutes', { roles, menuLimitsObj })
// console.log("accessRoutes",accessRoutes) // console.log("accessRoutes",accessRoutes)
// dynamically add accessible routes // dynamically add accessible routes
......
import { getDataFun } from '@/request/method';
import { router } from '@/router';
import { ElMessage } from 'element-plus';
let permissionInterval = null;
let previousPermissionData = null;
/**
* 开始权限轮询检查
* @param {Number} interval - 轮询间隔,默认5000ms
*/
export function startPermissionCheck(interval = 10000) {
// 如果已经存在轮询,先清除
if (permissionInterval) {
clearInterval(permissionInterval);
}
// 初始化获取一次权限数据
checkUserPermission();
// 设置定时轮询
permissionInterval = setInterval(() => {
checkUserPermission();
}, interval);
}
/**
* 停止权限轮询检查
*/
export function stopPermissionCheck() {
if (permissionInterval) {
clearInterval(permissionInterval);
permissionInterval = null;
}
}
/**
* 检查用户权限
*/
async function checkUserPermission() {
try {
const response = await getDataFun('/management/admin/userDataPermission', {
userId: sessionStorage.getItem('userId') || 1
});
// 如果请求成功
if (response && response.code === 1) {
const currentPermissionData = JSON.stringify(response.data);
// 将当前权限数据存储到 sessionStorage
sessionStorage.setItem('userPermission', currentPermissionData);
// 如果之前有权限数据,比较是否有变化
if (previousPermissionData && previousPermissionData !== currentPermissionData) {
// 权限发生变化,退出登录
ElMessage({
type: 'warning',
message: '您的权限已变更,系统将自动退出登录',
duration: 2000,
onClose: () => {
logout();
}
});
}
// 更新之前的权限数据
previousPermissionData = currentPermissionData;
}
} catch (error) {
console.error('权限检查失败:', error);
}
}
/**
* 退出登录
*/
function logout() {
// 清除所有相关的存储数据
sessionStorage.removeItem('menuList');
sessionStorage.removeItem('userPermission');
// 可能还有其他需要清除的数据,如 token 等
// 跳转到登录页
router.push('/login');
}
export default {
startPermissionCheck,
stopPermissionCheck
};
\ No newline at end of file
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
<el-option label="全部" value="" /> <el-option label="全部" value="" />
<el-option <el-option
v-for="(item, index) in basicConfiguration.deviceList" v-for="(item, index) in basicConfiguration.deviceList"
index="item.id" :key="item.code"
:label="item.desc" :label="item.desc"
:value="item.code" :value="item.code"
> >
...@@ -901,9 +901,9 @@ const getIndex = (index) => { ...@@ -901,9 +901,9 @@ const getIndex = (index) => {
}; };
const basicConfiguration = reactive({ const basicConfiguration = reactive({
ticketEventName: [{ name: "全部", id: "" }], ticketEventName: [],
branchFactoryList: [{ branchFactory: "全部", branchFactoryId: "" }], branchFactoryList: [{ branchFactory: "全部", branchFactoryId: "" }],
deviceList: [{ name: "全部", id: "" }] deviceList: []
}); });
const workSheetDetail = ref(null); const workSheetDetail = ref(null);
......
...@@ -275,6 +275,7 @@ import { getData, getDataFun, postData } from "@/request/method"; ...@@ -275,6 +275,7 @@ import { getData, getDataFun, postData } from "@/request/method";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { MD5 } from "crypto-js"; import { MD5 } from "crypto-js";
import { encryptUtf8ToBase64, encryptBase64ToUtf8 } from "@/utils/tools.js"; import { encryptUtf8ToBase64, encryptBase64ToUtf8 } from "@/utils/tools.js";
import { startPermissionCheck } from '@/utils/permissionChecker';
export default { export default {
name: "Login", name: "Login",
data() { data() {
...@@ -930,27 +931,21 @@ export default { ...@@ -930,27 +931,21 @@ export default {
this.dialogPhoneBind = false; this.dialogPhoneBind = false;
} }
setToken("permissions", 0); setToken("permissions", 0);
if (homeFlag == 0) { this.getMenuList(data.data.id);
this.$router.push({
path: "/newHome/homePage",
});
} else {
this.$router.push({
path: this.redirect || "/",
query: this.otherQuery,
});
}
this.loading = false; this.loading = false;
}) })
.catch((data) => { .catch((data) => {
this.store.istrue = false; this.store.istrue = false;
this.$router.push({ this.getMenuList(1);
path: this.redirect || "/", // this.$router.push({
query: this.otherQuery, // path: this.redirect || "/",
}); // query: this.otherQuery,
// });
this.loading = false; this.loading = false;
}); });
}, },
getOtherQuery(query) { getOtherQuery(query) {
return Object.keys(query).reduce((acc, cur) => { return Object.keys(query).reduce((acc, cur) => {
if (cur !== "redirect" && cur !== "token") { if (cur !== "redirect" && cur !== "token") {
...@@ -1056,28 +1051,47 @@ export default { ...@@ -1056,28 +1051,47 @@ export default {
} }
document.onkeydown = undefined; document.onkeydown = undefined;
setToken("permissions", 0); setToken("permissions", 0);
if (homeFlag == 0) { this.getMenuList(data.data.id);
this.$router.push({ // if (homeFlag == 0) {
path: "/newHome/homePage", // this.$router.push({
}); // path: "/newHome/homePage",
} else { // });
this.$router.push({ // } else {
path: this.redirect || "/", // this.$router.push({
query: this.otherQuery, // path: this.redirect || "/",
}); // query: this.otherQuery,
} // });
// }
this.oldloading = false; this.oldloading = false;
}) })
.catch((data) => { .catch((data) => {
// this.store.dispatch("settings/changeSetting", false); // this.store.dispatch("settings/changeSetting", false);
this.$router.push({ this.getMenuList(1);
path: this.redirect || "/", // this.$router.push({
query: this.otherQuery, // path: this.redirect || "/",
}); // query: this.otherQuery,
// });
this.oldloading = false; this.oldloading = false;
}); });
}, },
getMenuList(userId) {
getData(
"/system/permission?appCode=bme-dc-tom-service&userId=" + userId,
true
)
.then((result) => {
sessionStorage.setItem("menuList", JSON.stringify(result.data));
this.$router.push({
path: this.redirect || "/",
query: this.otherQuery,
});
startPermissionCheck();
})
.catch((e) => {
console.log("error fetch verify code!!" + e);
});
},
getPermissionData(userId) { getPermissionData(userId) {
return new Promise((resolve) => { return new Promise((resolve) => {
const url = "/management/admin/userDataPermission"; const url = "/management/admin/userDataPermission";
...@@ -1152,9 +1166,9 @@ $cursor: #ccc; ...@@ -1152,9 +1166,9 @@ $cursor: #ccc;
width: 95%; width: 95%;
} }
.dialogChangePasswordform { .dialogChangePasswordform {
.el-input__wrapper { .el-input {
width: 80%; display: flex !important;
} }
} }
.captcha { .captcha {
...@@ -1168,9 +1182,12 @@ $cursor: #ccc; ...@@ -1168,9 +1182,12 @@ $cursor: #ccc;
border: none; border: none;
border-bottom: 1px solid #fff; border-bottom: 1px solid #fff;
background: #fff; background: #fff;
padding-right: 10px;
box-sizing: border-box;
} }
} }
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -1180,9 +1197,11 @@ $light_gray: #eee; ...@@ -1180,9 +1197,11 @@ $light_gray: #eee;
.captcha-btn { .captcha-btn {
position: absolute; position: absolute;
top: 1%; top: 1px;
right: 17%; right: 0;
height: 100%; height: calc(100% - 2px);
border-top: none;
border-bottom: none;
} }
.login-container { .login-container {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment