From 77712048d486a29a2881a113822c1689a5cdfe3f Mon Sep 17 00:00:00 2001 From: van Date: Thu, 30 Apr 2026 17:30:42 +0800 Subject: [PATCH] 1 --- src/components/MobileBottomNav/index.vue | 2 +- src/layout/index.vue | 9 +++-- src/router/index.js | 44 +++++++++++++++--------- src/store/modules/permission.js | 17 ++++++++- src/utils/route-nav.js | 35 +++++++++++++++++++ src/views/login.vue | 9 ++--- 6 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 src/utils/route-nav.js diff --git a/src/components/MobileBottomNav/index.vue b/src/components/MobileBottomNav/index.vue index 30cd18e..9806f1e 100644 --- a/src/components/MobileBottomNav/index.vue +++ b/src/components/MobileBottomNav/index.vue @@ -59,7 +59,7 @@ export default { return mainRoutes } return [ - { path: '/sloworder/index', label: '首页', icon: 'el-icon-s-home' } + { path: '/user/profile', label: '我的', icon: 'el-icon-s-home' } ] } }, diff --git a/src/layout/index.vue b/src/layout/index.vue index 7d5db35..f5fbf35 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -26,6 +26,8 @@ import ResizeMixin from './mixin/ResizeHandler' import { mapState, mapGetters } from 'vuex' import variables from '@/assets/styles/variables.scss' +import auth from '@/plugins/auth' + export default { name: 'Layout', components: { @@ -55,13 +57,14 @@ export default { return `${len}-${firstPath}` }, mobileNavItems() { - return [ - { path: '/sloworder/index', label: '慢单', icon: 'el-icon-tickets' }, - { path: '/jd-instruction/index', label: '中控', icon: 'el-icon-s-operation' }, + const items = [ + { path: '/sloworder/index', label: '慢单', icon: 'el-icon-tickets', permissions: ['jdorder:sloworder:list'] }, + { path: '/jd-instruction/index', label: '中控', icon: 'el-icon-s-operation', permissions: ['jdorder:instruction:list'] }, { path: '/mobile/fadan', label: '发单', icon: 'el-icon-edit-outline' }, { path: '/mobile/xianyu-publish', label: '发品', icon: 'el-icon-goods' }, { path: '/mobile/zhongcao', label: '种草', icon: 'el-icon-chat-dot-round' } ] + return items.filter(it => !it.permissions || auth.hasPermiOr(it.permissions)) }, classObj() { return { diff --git a/src/router/index.js b/src/router/index.js index 74cd5f5..9e736c9 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -63,7 +63,16 @@ export const constantRoutes = [ }, { path: '/', - redirect: '/sloworder/index' + redirect: () => { + const store = require('@/store').default + const { firstLeafPathFromSidebarRoutes } = require('@/utils/route-nav') + const roles = store.getters.roles + if (roles && roles.length > 0) { + const p = firstLeafPathFromSidebarRoutes(store.getters.sidebarRouters) + if (p) return p + } + return '/user/profile' + } }, { path: '/user', @@ -103,22 +112,6 @@ export const constantRoutes = [ component: () => import('@/views/public/order-submit/index'), hidden: true }, - // 慢单管理(移到公共路由,无需权限) - { - path: '/sloworder', - component: Layout, - redirect: 'noredirect', - name: 'SlowOrder', - meta: { title: '下好的慢单', icon: 'list' }, - children: [ - { - path: 'index', - component: () => import('@/views/system/jdorder/orderList'), - name: 'SlowOrderIndex', - meta: { title: '下好的慢单', icon: 'list' } - } - ] - }, // 移动端专用入口(隐藏菜单,底部导航直达) { path: '/mobile', @@ -149,6 +142,23 @@ export const constantRoutes = [ // 动态路由,基于用户权限动态去加载 export const dynamicRoutes = [ + // 下好的慢单(需菜单/按钮权限 jdorder:sloworder:list) + { + path: '/sloworder', + component: Layout, + redirect: 'noredirect', + name: 'SlowOrder', + meta: { title: '下好的慢单', icon: 'list' }, + permissions: ['jdorder:sloworder:list'], + children: [ + { + path: 'index', + component: () => import('@/views/system/jdorder/orderList'), + name: 'SlowOrderIndex', + meta: { title: '下好的慢单', icon: 'list' } + } + ] + }, // 京粉订单管理 { path: '/order', diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js index b549ef0..280be38 100644 --- a/src/store/modules/permission.js +++ b/src/store/modules/permission.js @@ -5,6 +5,19 @@ import Layout from '@/layout/index' import ParentView from '@/components/ParentView' import InnerLink from '@/layout/components/InnerLink' +/** 按顶级 path 去重合并,保留先出现的项 */ +function mergeDistinctTopLevelRoutes(primary, secondary) { + const paths = new Set(primary.map(r => r.path)) + const out = primary.slice() + secondary.forEach(r => { + if (r && r.path != null && r.path !== '' && !paths.has(r.path)) { + paths.add(r.path) + out.push(r) + } + }) + return out +} + const permission = { state: { routes: [], @@ -42,7 +55,9 @@ const permission = { rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) router.addRoutes(asyncRoutes) commit('SET_ROUTES', rewriteRoutes) - commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) + let sidebarMerged = mergeDistinctTopLevelRoutes([...constantRoutes], asyncRoutes) + sidebarMerged = mergeDistinctTopLevelRoutes(sidebarMerged, sidebarRoutes) + commit('SET_SIDEBAR_ROUTERS', sidebarMerged) commit('SET_DEFAULT_ROUTES', sidebarRoutes) commit('SET_TOPBAR_ROUTES', sidebarRoutes) resolve(rewriteRoutes) diff --git a/src/utils/route-nav.js b/src/utils/route-nav.js new file mode 100644 index 0000000..62fbec1 --- /dev/null +++ b/src/utils/route-nav.js @@ -0,0 +1,35 @@ +/** 扁平化侧边栏用语义路径,顺序与侧边栏自上而下一致(与 MobileBottomNav 逻辑对齐) */ + +export function flattenNavigableRoutes(routes, parentPath = '') { + if (!routes || !Array.isArray(routes)) return [] + const result = [] + routes.forEach(route => { + if (route.hidden) return + let fullPath = (route.path || '').trim() + if (parentPath) { + if (!fullPath.startsWith('/')) { + const base = parentPath.endsWith('/') ? parentPath.slice(0, -1) : parentPath + fullPath = `${base}/${fullPath}`.replace(/\/+/g, '/') + } + } + if (fullPath && !fullPath.startsWith('/')) fullPath = '/' + fullPath + if (route.children && route.children.length > 0) { + result.push(...flattenNavigableRoutes(route.children, fullPath || parentPath)) + } else if (route.meta && route.meta.title && fullPath && fullPath !== '/') { + result.push({ path: fullPath, meta: route.meta }) + } + }) + return result +} + +/** 侧边栏路由中首个可打开的叶子路径(用于默认首页);无则 null */ +export function firstLeafPathFromSidebarRoutes(sidebarRoutes) { + const flat = flattenNavigableRoutes(sidebarRoutes) + const exclude = ['/redirect', '/login', '/register', '/404', '/401', '/user/profile'] + for (const { path } of flat) { + if (!path) continue + if (exclude.some(p => path === p || path.startsWith(p + '/'))) continue + return path + } + return null +} diff --git a/src/views/login.vue b/src/views/login.vue index e835c70..cfa6f50 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -67,6 +67,8 @@ import { getCodeImg } from "@/api/login" import Cookies from "js-cookie" import { encrypt, decrypt } from '@/utils/jsencrypt' +import store from '@/store' +import { firstLeafPathFromSidebarRoutes } from '@/utils/route-nav' export default { name: "Login", @@ -147,11 +149,10 @@ export default { // 先获取用户信息和生成路由,然后再跳转 this.$store.dispatch('GetInfo').then(() => { this.$store.dispatch('GenerateRoutes').then(() => { - // 使用 replace 而不是 push,避免路由历史问题 - const redirectPath = this.redirect || "/sloworder/index" + const fallback = firstLeafPathFromSidebarRoutes(store.getters.sidebarRouters) || '/user/profile' + const redirectPath = this.redirect || fallback this.$router.replace(redirectPath).catch(() => { - // 如果目标路由不存在,跳转到默认路由 - this.$router.replace("/sloworder/index") + this.$router.replace(fallback) }) }) })