动态路由调整
This commit is contained in:
parent
5052ab0e56
commit
df639d040a
|
|
@ -1,87 +1,60 @@
|
|||
// 模拟静态数据
|
||||
const mockMenus = [
|
||||
{ menuId: 1, menuName: '系统管理', parentId: 0, orderNum: 1, path: 'system', component: 'Layout', status: '0' },
|
||||
{ menuId: 2, menuName: '用户管理', parentId: 1, orderNum: 1, path: 'user', component: 'system/user/index', status: '0' },
|
||||
{ menuId: 3, menuName: '角色管理', parentId: 1, orderNum: 2, path: 'role', component: 'system/role/index', status: '0' },
|
||||
{ menuId: 4, menuName: '菜单管理', parentId: 1, orderNum: 3, path: 'menu', component: 'system/menu/index', status: '0' }
|
||||
];
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询菜单列表
|
||||
export function listMenu(query) {
|
||||
const { menuName, status } = query;
|
||||
let filteredMenus = [...mockMenus];
|
||||
|
||||
if (menuName) {
|
||||
filteredMenus = filteredMenus.filter(menu => menu.menuName.includes(menuName));
|
||||
}
|
||||
if (status) {
|
||||
filteredMenus = filteredMenus.filter(menu => menu.status === status);
|
||||
}
|
||||
|
||||
return Promise.resolve(filteredMenus);
|
||||
return request({
|
||||
url: '/system/menu/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询菜单详细
|
||||
export function getMenu(menuId) {
|
||||
const menu = mockMenus.find(m => m.menuId === parseInt(menuId));
|
||||
return Promise.resolve(menu || {});
|
||||
return request({
|
||||
url: '/system/menu/' + menuId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 查询菜单下拉树结构
|
||||
export function treeselect() {
|
||||
const buildTree = (parentId) => {
|
||||
const children = mockMenus.filter(m => m.parentId === parentId);
|
||||
return children.map(child => ({
|
||||
id: child.menuId,
|
||||
label: child.menuName,
|
||||
children: buildTree(child.menuId)
|
||||
}));
|
||||
};
|
||||
|
||||
return Promise.resolve(buildTree(0));
|
||||
return request({
|
||||
url: '/system/menu/treeselect',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 根据角色ID查询菜单下拉树结构
|
||||
export function roleMenuTreeselect(roleId) {
|
||||
const buildTree = (parentId) => {
|
||||
const children = mockMenus.filter(m => m.parentId === parentId);
|
||||
return children.map(child => ({
|
||||
id: child.menuId,
|
||||
label: child.menuName,
|
||||
children: buildTree(child.menuId)
|
||||
}));
|
||||
};
|
||||
|
||||
return Promise.resolve({
|
||||
menus: buildTree(0),
|
||||
checkedKeys: [1, 2, 3, 4] // 模拟已选中的菜单
|
||||
});
|
||||
return request({
|
||||
url: '/system/menu/roleMenuTreeselect/' + roleId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增菜单
|
||||
export function addMenu(data) {
|
||||
const newMenu = {
|
||||
menuId: mockMenus.length + 1,
|
||||
...data
|
||||
};
|
||||
mockMenus.push(newMenu);
|
||||
return Promise.resolve({ code: 200, msg: '新增成功' });
|
||||
return request({
|
||||
url: '/system/menu',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改菜单
|
||||
export function updateMenu(data) {
|
||||
const index = mockMenus.findIndex(m => m.menuId === data.menuId);
|
||||
if (index !== -1) {
|
||||
mockMenus[index] = { ...mockMenus[index], ...data };
|
||||
}
|
||||
return Promise.resolve({ code: 200, msg: '修改成功' });
|
||||
return request({
|
||||
url: '/system/menu',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除菜单
|
||||
export function delMenu(menuId) {
|
||||
const index = mockMenus.findIndex(m => m.menuId === parseInt(menuId));
|
||||
if (index !== -1) {
|
||||
mockMenus.splice(index, 1);
|
||||
}
|
||||
return Promise.resolve({ code: 200, msg: '删除成功' });
|
||||
return request({
|
||||
url: '/system/menu/' + menuId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
const url = ref('http://doc.ruoyi.vip/ruoyi-vue');
|
||||
const url = ref("http://doc.ruoyi.vip/ruoyi-vue");
|
||||
|
||||
function goto() {
|
||||
window.open(url.value)
|
||||
window.open(url.value);
|
||||
}
|
||||
</script>
|
||||
|
|
@ -22,6 +22,7 @@ import defaultSettings from '@/settings'
|
|||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
|
|
@ -68,7 +69,13 @@ function setLayout() {
|
|||
}
|
||||
|
||||
onMounted(() => {
|
||||
permissionStore.generateRoutes()
|
||||
// 如果路由未加载,则加载路由(避免与路由守卫中的调用冲突)
|
||||
if (permissionStore.addRoutes.length === 0) {
|
||||
const userStore = useUserStore()
|
||||
if (userStore.roles.length > 0) {
|
||||
permissionStore.generateRoutes(userStore.roles)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { isHttp, isPathMatch } from '@/utils/validate'
|
|||
import { isRelogin } from '@/utils/request'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
|
|
@ -32,18 +33,39 @@ router.beforeEach((to, from, next) => {
|
|||
// 判断当前用户是否已拉取完user_info信息
|
||||
useUserStore().getInfo().then(() => {
|
||||
isRelogin.show = false
|
||||
// 不再动态加载路由,直接使用静态路由
|
||||
// 动态加载路由
|
||||
const permissionStore = usePermissionStore()
|
||||
permissionStore.generateRoutes(useUserStore().roles).then(() => {
|
||||
// 动态路由加载完成后,重新跳转到目标路由
|
||||
next({ ...to, replace: true })
|
||||
}).catch((error) => {
|
||||
console.error('路由加载失败:', error)
|
||||
ElMessage.error(error?.message || '路由加载失败,请刷新页面重试')
|
||||
next({ path: '/' })
|
||||
})
|
||||
}).catch(err => {
|
||||
useUserStore().logOut().then(() => {
|
||||
ElMessage.error(err)
|
||||
next({ path: '/' })
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// 已获取用户信息,检查路由是否已加载
|
||||
const permissionStore = usePermissionStore()
|
||||
if (permissionStore.addRoutes.length === 0) {
|
||||
// 路由未加载,重新加载
|
||||
permissionStore.generateRoutes(useUserStore().roles).then(() => {
|
||||
next({ ...to, replace: true })
|
||||
}).catch((error) => {
|
||||
console.error('路由加载失败:', error)
|
||||
ElMessage.error(error?.message || '路由加载失败,请刷新页面重试')
|
||||
next({ path: '/' })
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 没有token
|
||||
if (isWhiteList(to.path)) {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export const constantRoutes = [
|
|||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
redirect: '/order/intention',
|
||||
redirect: 'index',
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
|
|
@ -85,109 +85,11 @@ export const constantRoutes = [
|
|||
meta: { title: '个人中心', icon: 'user' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/order',
|
||||
component: Layout,
|
||||
hidden: false,
|
||||
name: 'Order',
|
||||
meta: { title: '订单管理', icon: 'shopping' },
|
||||
children: [
|
||||
{
|
||||
path: 'intention',
|
||||
component: () => import('@/views/order/intention/index.vue'),
|
||||
name: 'Intention',
|
||||
meta: { title: '订单/意向单', icon: 'form' }
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/order/intention/create.vue'),
|
||||
name: 'IntentionCreate',
|
||||
meta: { title: '创建订单/意向单', icon: 'edit', hidden: true }
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
component: () => import('@/views/order/intention/search.vue'),
|
||||
name: 'IntentionSearch',
|
||||
meta: { title: '品号查询', icon: 'search', hidden: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
// 以下为原动态路由,现在直接合并到静态路由中
|
||||
{
|
||||
path: '/system/user-auth',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:user:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'role/:userId(\\d+)',
|
||||
component: () => import('@/views/system/user/authRole.vue'),
|
||||
name: 'AuthRole',
|
||||
meta: { title: '分配角色', activeMenu: '/system/user' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/system/role-auth',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:role:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'user/:roleId(\\d+)',
|
||||
component: () => import('@/views/system/role/authUser.vue'),
|
||||
name: 'AuthUser',
|
||||
meta: { title: '分配用户', activeMenu: '/system/role' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/system/dict-data',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:dict:list'],
|
||||
children: [
|
||||
{
|
||||
path: 'index/:dictId(\\d+)',
|
||||
component: () => import('@/views/system/dict/data.vue'),
|
||||
name: 'Data',
|
||||
meta: { title: '字典数据', activeMenu: '/system/dict' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/monitor/job-log',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['monitor:job:list'],
|
||||
children: [
|
||||
{
|
||||
path: 'index/:jobId(\\d+)',
|
||||
component: () => import('@/views/monitor/job/log.vue'),
|
||||
name: 'JobLog',
|
||||
meta: { title: '调度日志', activeMenu: '/monitor/job' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/tool/gen-edit',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['tool:gen:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'index/:tableId(\\d+)',
|
||||
component: () => import('@/views/tool/gen/editTable.vue'),
|
||||
name: 'GenEdit',
|
||||
meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
|
||||
}
|
||||
]
|
||||
}
|
||||
// 业务路由(订单管理、系统管理等)将从后端动态获取
|
||||
];
|
||||
|
||||
// 动态路由已合并到静态路由中,不再需要
|
||||
// export const dynamicRoutes = [ ... ];
|
||||
// 动态路由将从后端接口获取,不再在此处定义
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import auth from '@/plugins/auth'
|
||||
import router, { constantRoutes } from '@/router'
|
||||
// 不再需要动态路由,移除 dynamicRoutes 和 getRouters
|
||||
// import { getRouters } from '@/api/menu'
|
||||
import { getRouters } from '@/api/menu'
|
||||
import Layout from '@/layout/index'
|
||||
import ParentView from '@/components/ParentView'
|
||||
import InnerLink from '@/layout/components/InnerLink'
|
||||
import { isHttp } from '@/utils/validate'
|
||||
|
||||
// 匹配views里面所有的.vue文件
|
||||
const modules = import.meta.glob('./../../views/**/*.vue')
|
||||
|
|
@ -34,14 +34,52 @@ const usePermissionStore = defineStore(
|
|||
this.sidebarRouters = routes
|
||||
},
|
||||
generateRoutes(roles) {
|
||||
return new Promise(resolve => {
|
||||
// 不再调用后端接口,直接使用静态路由
|
||||
const staticRoutes = constantRoutes
|
||||
this.setRoutes(staticRoutes)
|
||||
this.setSidebarRouters(staticRoutes)
|
||||
this.setDefaultRoutes(staticRoutes)
|
||||
this.setTopbarRoutes(staticRoutes)
|
||||
resolve(staticRoutes)
|
||||
return new Promise((resolve, reject) => {
|
||||
// 调用后端接口获取动态路由
|
||||
getRouters().then(res => {
|
||||
// 确保 res.data 是数组
|
||||
if (!res.data || !Array.isArray(res.data)) {
|
||||
console.error('路由数据格式错误:', res.data)
|
||||
reject(new Error('路由数据格式错误,期望数组'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const sdata = JSON.parse(JSON.stringify(res.data))
|
||||
const rdata = JSON.parse(JSON.stringify(res.data))
|
||||
const defaultData = JSON.parse(JSON.stringify(res.data))
|
||||
const sidebarRoutes = filterAsyncRouter(sdata)
|
||||
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
|
||||
const defaultRoutes = filterAsyncRouter(defaultData)
|
||||
|
||||
// 过滤权限路由
|
||||
const accessedRoutes = filterDynamicRoutes(rewriteRoutes)
|
||||
|
||||
// 调试:打印路由信息
|
||||
console.log('动态路由加载成功,路由数量:', accessedRoutes.length)
|
||||
if (accessedRoutes.length > 0) {
|
||||
console.log('第一个路由示例:', accessedRoutes[0])
|
||||
}
|
||||
|
||||
this.setRoutes(accessedRoutes)
|
||||
this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
|
||||
this.setDefaultRoutes(constantRoutes.concat(defaultRoutes))
|
||||
this.setTopbarRoutes(constantRoutes.concat(sidebarRoutes))
|
||||
|
||||
// 动态添加路由到router
|
||||
accessedRoutes.forEach(route => {
|
||||
router.addRoute(route)
|
||||
})
|
||||
|
||||
resolve(accessedRoutes)
|
||||
} catch (error) {
|
||||
console.error('路由处理错误:', error)
|
||||
reject(error)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取路由失败:', error)
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -49,23 +87,63 @@ const usePermissionStore = defineStore(
|
|||
|
||||
// 遍历后台传来的路由字符串,转换为组件对象
|
||||
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
||||
return asyncRouterMap.filter(route => {
|
||||
if (type && route.children) {
|
||||
route.children = filterChildren(route.children)
|
||||
// 确保 asyncRouterMap 是数组
|
||||
if (!Array.isArray(asyncRouterMap)) {
|
||||
console.error('filterAsyncRouter: 期望数组,但收到:', asyncRouterMap)
|
||||
return []
|
||||
}
|
||||
|
||||
return asyncRouterMap.filter(route => {
|
||||
// 处理外部链接路径:如果 path 是外部链接,需要转换为有效的内部路径
|
||||
if (route.path && isHttp(route.path)) {
|
||||
// 确保 meta.link 保存实际的外部链接
|
||||
if (!route.meta) {
|
||||
route.meta = {}
|
||||
}
|
||||
// 保存原始的外部链接到 meta.link
|
||||
const originalPath = route.path
|
||||
route.meta.link = originalPath
|
||||
|
||||
// 将外部链接路径转换为内部路径
|
||||
// 使用简单的路径格式:/iframe/域名,避免路径冲突
|
||||
const domain = originalPath.replace(/^https?:\/\//, '').replace(/\/.*$/, '')
|
||||
const timestamp = Date.now()
|
||||
const encodedPath = '/iframe/' + encodeURIComponent(domain) + '-' + timestamp
|
||||
|
||||
// 将 path 转换为有效的内部路径
|
||||
route.path = encodedPath
|
||||
} else if (!lastRouter && route.path && !route.path.startsWith('/')) {
|
||||
// 只对顶级路由(lastRouter 为 false)确保路径以 "/" 开头
|
||||
// 子路由的路径会在 filterChildren 中处理
|
||||
route.path = '/' + route.path
|
||||
}
|
||||
|
||||
if (route.component) {
|
||||
// 检查是否有外部链接
|
||||
if (route.meta && route.meta.link && (route.meta.link.startsWith('http://') || route.meta.link.startsWith('https://'))) {
|
||||
route.component = InnerLink
|
||||
}
|
||||
// Layout ParentView 组件特殊处理
|
||||
if (route.component === 'Layout') {
|
||||
else if (route.component === 'Layout') {
|
||||
route.component = Layout
|
||||
} else if (route.component === 'ParentView') {
|
||||
route.component = ParentView
|
||||
} else if (route.component === 'InnerLink') {
|
||||
route.component = InnerLink
|
||||
} else {
|
||||
route.component = loadView(route.component)
|
||||
const component = loadView(route.component)
|
||||
if (!component) {
|
||||
console.warn('找不到组件:', route.component)
|
||||
}
|
||||
route.component = component || (() => import('@/views/error/404.vue'))
|
||||
}
|
||||
}
|
||||
if (route.children != null && route.children && route.children.length) {
|
||||
// 如果 type 为 true,先使用 filterChildren 处理子路由路径
|
||||
if (type) {
|
||||
route.children = filterChildren(route.children, route)
|
||||
}
|
||||
// 然后递归处理子路由
|
||||
route.children = filterAsyncRouter(route.children, route, type)
|
||||
} else {
|
||||
delete route['children']
|
||||
|
|
@ -78,7 +156,22 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
|||
function filterChildren(childrenMap, lastRouter = false) {
|
||||
var children = []
|
||||
childrenMap.forEach(el => {
|
||||
el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path
|
||||
if (lastRouter) {
|
||||
// 处理子路由路径:如果子路由路径以 / 开头,需要去掉,因为要拼接父路由路径
|
||||
let childPath = el.path || ''
|
||||
if (childPath.startsWith('/')) {
|
||||
childPath = childPath.substring(1)
|
||||
}
|
||||
// 拼接父路由路径和子路由路径
|
||||
const parentPath = lastRouter.path || ''
|
||||
// 确保父路径以 / 结尾(如果父路径不是根路径)
|
||||
const separator = parentPath.endsWith('/') ? '' : '/'
|
||||
el.path = parentPath + separator + childPath
|
||||
}
|
||||
// 确保最终路径以 / 开头(如果不是空路径)
|
||||
if (el.path && !el.path.startsWith('/')) {
|
||||
el.path = '/' + el.path
|
||||
}
|
||||
if (el.children && el.children.length && el.component === 'ParentView') {
|
||||
children = children.concat(filterChildren(el.children, el))
|
||||
} else {
|
||||
|
|
@ -100,20 +193,39 @@ export function filterDynamicRoutes(routes) {
|
|||
if (auth.hasRoleOr(route.roles)) {
|
||||
res.push(route)
|
||||
}
|
||||
} else {
|
||||
// 没有权限控制的路由,直接通过
|
||||
res.push(route)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
export const loadView = (view) => {
|
||||
let res;
|
||||
if (!view) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 处理外部链接
|
||||
if (view.startsWith('http://') || view.startsWith('https://')) {
|
||||
return null
|
||||
}
|
||||
|
||||
let res = null;
|
||||
for (const path in modules) {
|
||||
const dir = path.split('views/')[1].split('.vue')[0];
|
||||
if (dir === view) {
|
||||
const dir = path.split('views/')[1]?.split('.vue')[0];
|
||||
if (dir && dir === view) {
|
||||
res = () => modules[path]();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
console.warn(`loadView: 找不到组件 "${view}",请检查路径是否正确`)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export default usePermissionStore
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,12 @@ export function mergeRecursive(source, target) {
|
|||
* @param {*} children 孩子节点字段 默认 'children'
|
||||
*/
|
||||
export function handleTree(data, id, parentId, children) {
|
||||
// 检查 data 是否为数组
|
||||
if (!data || !Array.isArray(data)) {
|
||||
console.warn('handleTree: 期望数组,但收到:', data)
|
||||
return []
|
||||
}
|
||||
|
||||
let config = {
|
||||
id: id || 'id',
|
||||
parentId: parentId || 'parentId',
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@
|
|||
</template>
|
||||
|
||||
<script setup name="Menu">
|
||||
import { addMenu, delMenu, getMenu, listMenu, updateMenu } from "@/api/system/menu";
|
||||
import { addMenu, delMenu, getMenu, listMenu, treeselect, updateMenu } from "@/api/system/menu";
|
||||
import SvgIcon from "@/components/SvgIcon";
|
||||
import IconSelect from "@/components/IconSelect";
|
||||
|
||||
|
|
@ -333,7 +333,7 @@ function getList() {
|
|||
/** 查询菜单下拉树结构 */
|
||||
function getTreeselect() {
|
||||
menuOptions.value = [];
|
||||
listMenu().then(response => {
|
||||
treeselect().then(response => {
|
||||
const menu = { menuId: 0, menuName: "主类目", children: [] };
|
||||
menu.children = proxy.handleTree(response.data, "menuId");
|
||||
menuOptions.value.push(menu);
|
||||
|
|
|
|||
Loading…
Reference in New Issue