加入顶部菜单功能

修复一些已知问题
代码调整
master
toesbieya 6 years ago
parent 1fbf5b8bbc
commit 998543eda0
  1. 2
      java/cloud/common/src/main/java/cn/toesbieya/jxc/common/enumeration/ResourceTypeEnum.java
  2. 34
      java/cloud/web/web-modules/system/src/main/java/cn/toesbieya/jxc/system/service/ResourceService.java
  3. BIN
      java/db/my.rar
  4. 2
      java/local/src/main/java/cn/toesbieya/jxc/enumeration/ResourceTypeEnum.java
  5. 34
      java/local/src/main/java/cn/toesbieya/jxc/service/sys/SysResourceService.java
  6. 4
      vue/element-ui-personal/component/Menu/Submenu.vue
  7. 4
      vue/src/App.vue
  8. 2
      vue/src/asset/icon/index.js
  9. 3
      vue/src/component/Icon/index.vue
  10. 1
      vue/src/layout/component/Main.vue
  11. 80
      vue/src/layout/component/Navbar/component/RootMenu.vue
  12. 9
      vue/src/layout/component/Navbar/index.vue
  13. 4
      vue/src/layout/component/Sidebar/component/SidebarItem.vue
  14. 5
      vue/src/layout/component/Sidebar/component/SidebarItemContent.vue
  15. 57
      vue/src/layout/component/Sidebar/index.vue
  16. 4
      vue/src/layout/component/Sidebar/style.scss
  17. 4
      vue/src/layout/component/TagsView/index.vue
  18. 0
      vue/src/layout/mixin/decideRouterTransition.js
  19. 20
      vue/src/layout/mixin/jumpOnSelectMenu.js
  20. 0
      vue/src/layout/mixin/shortcut.js
  21. 14
      vue/src/router/define.js
  22. 18
      vue/src/router/index.js
  23. 28
      vue/src/router/module/app.js
  24. 18
      vue/src/router/module/defaultRoot/child/app.js
  25. 5
      vue/src/router/module/defaultRoot/child/example.js
  26. 3
      vue/src/router/module/defaultRoot/child/message.js
  27. 3
      vue/src/router/module/defaultRoot/child/purchase.js
  28. 3
      vue/src/router/module/defaultRoot/child/sell.js
  29. 3
      vue/src/router/module/defaultRoot/child/stock.js
  30. 3
      vue/src/router/module/defaultRoot/child/system.js
  31. 12
      vue/src/router/module/defaultRoot/index.js
  32. 4
      vue/src/router/module/docRoot/index.js
  33. 36
      vue/src/router/module/exampleRoot/child/components.js
  34. 16
      vue/src/router/module/exampleRoot/child/cool.js
  35. 5
      vue/src/router/module/exampleRoot/child/icon.js
  36. 14
      vue/src/router/module/exampleRoot/child/iframe.js
  37. 35
      vue/src/router/module/exampleRoot/child/test.js
  38. 12
      vue/src/router/module/exampleRoot/index.js
  39. 5
      vue/src/router/module/index.js
  40. 9
      vue/src/router/util.js
  41. 2
      vue/src/store/module/app.js
  42. 2
      vue/src/store/module/message.js
  43. 23
      vue/src/store/module/resource.js
  44. 0
      vue/src/view/example/test/cache/detailPage.vue
  45. 0
      vue/src/view/example/test/develop/component/ApiSpeedTest.vue
  46. 0
      vue/src/view/example/test/develop/component/ExportExcelTest.vue
  47. 0
      vue/src/view/example/test/develop/component/ImageCompressTest.vue
  48. 0
      vue/src/view/example/test/develop/component/WebsocketTest.vue
  49. 0
      vue/src/view/example/test/develop/indexPage.vue
  50. 4
      vue/src/view/message/user/MessageStream.vue
  51. 1
      vue/src/view/system/menu/common.js
  52. 29
      vue/src/view/system/menu/indexPage.vue

@ -1,7 +1,7 @@
package cn.toesbieya.jxc.common.enumeration; package cn.toesbieya.jxc.common.enumeration;
public enum ResourceTypeEnum { public enum ResourceTypeEnum {
FOLDER(1), LEAF(2), API(3); ROOT(0), FOLDER(1), LEAF(2), API(3);
private final int code; private final int code;

@ -43,7 +43,11 @@ public class ResourceService implements ResourceApi {
return Collections.emptyList(); return Collections.emptyList();
} }
return getEnableResourceTree(ids, i -> !i.getType().equals(ResourceTypeEnum.FOLDER.getCode())); return getEnableResourceTree(ids, i -> {
Integer type = i.getType();
return !type.equals(ResourceTypeEnum.ROOT.getCode())
&& !type.equals(ResourceTypeEnum.FOLDER.getCode());
});
} }
public R add(SysResource entity) { public R add(SysResource entity) {
@ -100,7 +104,7 @@ public class ResourceService implements ResourceApi {
} }
String path = r.getPath(); String path = r.getPath();
int index = path.indexOf("//"); int index = path.lastIndexOf("//");
if (index != -1 && !path.startsWith("http")) { if (index != -1 && !path.startsWith("http")) {
//去掉'//'前拼接的父节点的path //去掉'//'前拼接的父节点的path
r.setPath(path.substring(index + 1)); r.setPath(path.substring(index + 1));
@ -116,11 +120,9 @@ public class ResourceService implements ResourceApi {
Integer pid = entity.getPid(); Integer pid = entity.getPid();
Integer type = entity.getType(); Integer type = entity.getType();
boolean isApi = type.equals(ResourceTypeEnum.API.getCode()); //顶部菜单的pid必须为0
if (type.equals(ResourceTypeEnum.ROOT.getCode())) {
//根节点不能为数据接口 return pid.equals(0) ? null : "顶部菜单不能有父节点";
if (pid.equals(0)) {
return isApi ? "数据接口不能作为根节点" : null;
} }
//校验父节点 //校验父节点
@ -130,6 +132,7 @@ public class ResourceService implements ResourceApi {
} }
Integer parentType = parent.getType(); Integer parentType = parent.getType();
//1.待添加的节点为数据接口且父节点必须为叶子菜单 //1.待添加的节点为数据接口且父节点必须为叶子菜单
boolean isApi = type.equals(ResourceTypeEnum.API.getCode());
if (isApi) { if (isApi) {
if (!parentType.equals(ResourceTypeEnum.LEAF.getCode())) { if (!parentType.equals(ResourceTypeEnum.LEAF.getCode())) {
return "数据接口只能以页面菜单为父节点"; return "数据接口只能以页面菜单为父节点";
@ -144,22 +147,23 @@ public class ResourceService implements ResourceApi {
//数据转换 //数据转换
private void transform(SysResource entity) { private void transform(SysResource entity) {
Integer pid = entity.getPid();
Integer type = entity.getType(); Integer type = entity.getType();
boolean isRoot = type.equals(ResourceTypeEnum.ROOT.getCode());
boolean isFolder = type.equals(ResourceTypeEnum.FOLDER.getCode()); boolean isFolder = type.equals(ResourceTypeEnum.FOLDER.getCode());
boolean isLeaf = type.equals(ResourceTypeEnum.LEAF.getCode());
boolean isApi = type.equals(ResourceTypeEnum.API.getCode());
//能自定义component的只有叶子菜单 //能自定义component的只有叶子菜单
if (!type.equals(ResourceTypeEnum.LEAF.getCode())) { if (!isLeaf) {
//根节点的聚合菜单的组件只能为'Layout' //顶部菜单的组件只能为'Layout'
boolean isRootFolder = isFolder && pid.equals(0); entity.setComponent(isRoot ? "Layout" : null);
entity.setComponent(isRootFolder ? "Layout" : null);
} }
//聚合菜单不能设置name //顶部或聚合菜单不能设置name(此处的name是路由名称)
if (isFolder) { if (isRoot || isFolder) {
entity.setName(null); entity.setName(null);
} }
//数据接口不能设置meta //数据接口不能设置meta
if (type.equals(ResourceTypeEnum.API.getCode())) { if (isApi) {
entity.setMeta(null); entity.setMeta(null);
} }
} }

Binary file not shown.

@ -1,7 +1,7 @@
package cn.toesbieya.jxc.enumeration; package cn.toesbieya.jxc.enumeration;
public enum ResourceTypeEnum { public enum ResourceTypeEnum {
FOLDER(1), LEAF(2), API(3); ROOT(0), FOLDER(1), LEAF(2), API(3);
private final int code; private final int code;

@ -41,7 +41,11 @@ public class SysResourceService {
return Collections.emptyList(); return Collections.emptyList();
} }
return getEnableResourceTree(ids, i -> !i.getType().equals(ResourceTypeEnum.FOLDER.getCode())); return getEnableResourceTree(ids, i -> {
Integer type = i.getType();
return !type.equals(ResourceTypeEnum.ROOT.getCode())
&& !type.equals(ResourceTypeEnum.FOLDER.getCode());
});
} }
public R add(SysResource entity) { public R add(SysResource entity) {
@ -99,7 +103,7 @@ public class SysResourceService {
} }
String path = r.getPath(); String path = r.getPath();
int index = path.indexOf("//"); int index = path.lastIndexOf("//");
if (index != -1 && !path.startsWith("http")) { if (index != -1 && !path.startsWith("http")) {
//去掉'//'前拼接的父节点的path //去掉'//'前拼接的父节点的path
r.setPath(path.substring(index + 1)); r.setPath(path.substring(index + 1));
@ -115,11 +119,9 @@ public class SysResourceService {
Integer pid = entity.getPid(); Integer pid = entity.getPid();
Integer type = entity.getType(); Integer type = entity.getType();
boolean isApi = type.equals(ResourceTypeEnum.API.getCode()); //顶部菜单的pid必须为0
if (type.equals(ResourceTypeEnum.ROOT.getCode())) {
//根节点不能为数据接口 return pid.equals(0) ? null : "顶部菜单不能有父节点";
if (pid.equals(0)) {
return isApi ? "数据接口不能作为根节点" : null;
} }
//校验父节点 //校验父节点
@ -129,6 +131,7 @@ public class SysResourceService {
} }
Integer parentType = parent.getType(); Integer parentType = parent.getType();
//1.待添加的节点为数据接口且父节点必须为叶子菜单 //1.待添加的节点为数据接口且父节点必须为叶子菜单
boolean isApi = type.equals(ResourceTypeEnum.API.getCode());
if (isApi) { if (isApi) {
if (!parentType.equals(ResourceTypeEnum.LEAF.getCode())) { if (!parentType.equals(ResourceTypeEnum.LEAF.getCode())) {
return "数据接口只能以页面菜单为父节点"; return "数据接口只能以页面菜单为父节点";
@ -143,22 +146,23 @@ public class SysResourceService {
//数据转换 //数据转换
private void transform(SysResource entity) { private void transform(SysResource entity) {
Integer pid = entity.getPid();
Integer type = entity.getType(); Integer type = entity.getType();
boolean isRoot = type.equals(ResourceTypeEnum.ROOT.getCode());
boolean isFolder = type.equals(ResourceTypeEnum.FOLDER.getCode()); boolean isFolder = type.equals(ResourceTypeEnum.FOLDER.getCode());
boolean isLeaf = type.equals(ResourceTypeEnum.LEAF.getCode());
boolean isApi = type.equals(ResourceTypeEnum.API.getCode());
//能自定义component的只有叶子菜单 //能自定义component的只有叶子菜单
if (!type.equals(ResourceTypeEnum.LEAF.getCode())) { if (!isLeaf) {
//根节点的聚合菜单的组件只能为'Layout' //顶部菜单的组件只能为'Layout'
boolean isRootFolder = isFolder && pid.equals(0); entity.setComponent(isRoot ? "Layout" : null);
entity.setComponent(isRootFolder ? "Layout" : null);
} }
//聚合菜单不能设置name //顶部或聚合菜单不能设置name(此处的name是路由名称)
if (isFolder) { if (isRoot || isFolder) {
entity.setName(null); entity.setName(null);
} }
//数据接口不能设置meta //数据接口不能设置meta
if (type.equals(ResourceTypeEnum.API.getCode())) { if (isApi) {
entity.setMeta(null); entity.setMeta(null);
} }
} }

@ -272,7 +272,3 @@ export default {
} }
} }
</script> </script>
<style>
</style>

@ -1,8 +1,6 @@
<template> <template>
<div id="app"> <div id="app">
<keep-alive include="Layout"> <router-view/>
<router-view/>
</keep-alive>
</div> </div>
</template> </template>

@ -1,5 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import VIcon from '@/component/VIcon' import VIcon from '@/component/Icon'
Vue.component('v-icon', VIcon) Vue.component('v-icon', VIcon)

@ -19,7 +19,7 @@ export default {
) )
} }
return <i class={icon} {...data}/> return <i class={`icon ${icon}`} {...data}/>
} }
} }
</script> </script>
@ -29,6 +29,5 @@ export default {
width: 1em; width: 1em;
height: 1em; height: 1em;
fill: currentColor; fill: currentColor;
overflow: hidden;
} }
</style> </style>

@ -70,7 +70,6 @@ export default {
background-color: #f5f7f9; background-color: #f5f7f9;
.scroll-container { .scroll-container {
position: relative;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

@ -0,0 +1,80 @@
<script type="text/jsx">
import jumpOnSelectMenuMixin from "@/layout/mixin/jumpOnSelectMenu"
export default {
name: "RootMenu",
mixins: [jumpOnSelectMenuMixin],
props: {
//
activeMenu: String,
//
menu: Array,
//<=1
alwaysShow: Boolean
},
methods: {
onSelect(menu) {
const path = menu.fullPath
if (path === this.activeMenu) return
this.jumpOnSelectMenu(path, false)
}
},
render() {
//selectpayload
const {activeMenu, menu, alwaysShow} = this
if (!Array.isArray(menu) || menu.length <= 1 && !alwaysShow) return
return (
<ul class="root-menu">
{menu.map(i => {
const key = i.fullPath + i.meta.title
const className = {'root-menu-item': true, 'active': i.fullPath === activeMenu}
const icon = i.meta.icon
return (
<li key={key} class={className} on-click={() => this.onSelect(i)}>
{icon && <v-icon icon={icon}/>}
{i.meta.title}
</li>
)
})}
</ul>
)
}
}
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
.root-menu {
height: $nav-height;
line-height: $nav-height;
list-style: none;
margin: 0;
padding: 0;
font-size: 14px;
color: #515a6e;
&-item {
height: inherit;
border-bottom: 2px solid transparent;
float: left;
padding: 0 20px;
cursor: pointer;
transition: all .2s ease-in-out;
> .icon {
margin-right: 6px;
vertical-align: -0.125em;
}
&:hover, &.active {
color: $--color-primary;
border-bottom: 2px solid $--color-primary;
}
}
}
</style>

@ -4,6 +4,8 @@
<hamburger class="navbar-item" :active="!sidebarCollapse" @click="sidebarCtrl"/> <hamburger class="navbar-item" :active="!sidebarCollapse" @click="sidebarCtrl"/>
<breadcrumb v-if="showBreadcrumb"/> <breadcrumb v-if="showBreadcrumb"/>
<root-menu :active-menu="activeRootMenu" :menu="menus" always-show/>
</div> </div>
<div> <div>
@ -51,8 +53,8 @@ import GuideMixin from '@/mixin/guide'
import Bell from "./component/Bell" import Bell from "./component/Bell"
import Breadcrumb from './component/Breadcrumb' import Breadcrumb from './component/Breadcrumb'
import Hamburger from './component/Hamburger' import Hamburger from './component/Hamburger'
import RootMenu from "./component/RootMenu"
import SettingDrawer from './component/SettingDrawer' import SettingDrawer from './component/SettingDrawer'
import {auth} from "@/util/auth"
import {elConfirm} from "@/util/message" import {elConfirm} from "@/util/message"
export default { export default {
@ -60,11 +62,13 @@ export default {
mixins: [GuideMixin.navbar], mixins: [GuideMixin.navbar],
components: {Hamburger, Breadcrumb, Bell, SettingDrawer}, components: {Hamburger, Breadcrumb, Bell, RootMenu, SettingDrawer},
data: () => ({settingDrawer: false}), data: () => ({settingDrawer: false}),
computed: { computed: {
...mapState('resource', ['activeRootMenu', 'menus']),
...mapState('user', ['avatar', 'name', 'prepareLogout']), ...mapState('user', ['avatar', 'name', 'prepareLogout']),
...mapState('setting', ['sidebarCollapse', 'showBreadcrumb']) ...mapState('setting', ['sidebarCollapse', 'showBreadcrumb'])
@ -74,6 +78,7 @@ export default {
sidebarCtrl() { sidebarCtrl() {
this.$store.commit('setting/sidebarCollapse', !this.sidebarCollapse) this.$store.commit('setting/sidebarCollapse', !this.sidebarCollapse)
}, },
refresh() { refresh() {
this.$router.replace({path: `/redirect${this.$route.fullPath}`}) this.$router.replace({path: `/redirect${this.$route.fullPath}`})
}, },

@ -14,7 +14,7 @@ function getOnlyChild(menu) {
function renderSingleMenu(h, {index, icon, title}) { function renderSingleMenu(h, {index, icon, title}) {
return ( return (
<el-menu-item index={index}> <el-menu-item key={index} index={index}>
<SidebarItemContent icon={icon} title={title}/> <SidebarItemContent icon={icon} title={title}/>
</el-menu-item> </el-menu-item>
) )
@ -22,7 +22,7 @@ function renderSingleMenu(h, {index, icon, title}) {
function renderSubMenu(h, {index, icon, title, children}) { function renderSubMenu(h, {index, icon, title, children}) {
return ( return (
<el-submenu index={index} popper-append-to-body> <el-submenu key={index} index={index}>
<SidebarItemContent slot="title" icon={icon} title={title}/> <SidebarItemContent slot="title" icon={icon} title={title}/>
{children} {children}
</el-submenu> </el-submenu>

@ -4,10 +4,7 @@ export default {
functional: true, functional: true,
props: { props: {icon: String, title: String},
icon: String,
title: String
},
render(h, context) { render(h, context) {
const {icon, title} = context.props const {icon, title} = context.props

@ -1,31 +1,42 @@
<script type="text/jsx"> <script type="text/jsx">
import {mapState} from 'vuex' import {mapState} from 'vuex'
import jumpOnSelectMenuMixin from "@/layout/mixin/jumpOnSelectMenu"
import Logo from './component/Logo' import Logo from './component/Logo'
import SidebarItem from './component/SidebarItem' import SidebarItem from './component/SidebarItem'
import {isExternal} from "@/util/validate"
export default { export default {
name: 'sidebar', name: 'sidebar',
mixins: [jumpOnSelectMenuMixin],
components: {SidebarItem, Logo}, components: {SidebarItem, Logo},
data: () => ({mouseOutside: true, activeMenu: ''}), data() {
return {
//
mouseOutside: true,
//fullPath
activeMenu: '',
//children
sidebarMenus: []
}
},
computed: { computed: {
...mapState('app', ['device']), ...mapState('app', ['device']),
...mapState('resource', ['menus']), ...mapState('resource', ['activeRootMenu', 'menus']),
...mapState('setting', ['showLogo', 'sidebarCollapse', 'sidebarUniqueOpen', 'sidebarShowParent', 'sidebarAutoHidden']), ...mapState('setting', ['showLogo', 'sidebarCollapse', 'sidebarUniqueOpen', 'sidebarShowParent', 'sidebarAutoHidden']),
//pc //truefalsepc
collapse() { collapse() {
return this.sidebarCollapse && this.device === 'pc' return this.sidebarCollapse && this.device === 'pc'
}, },
// //
// //
// //
hideSidebar() { hideSidebar() {
return this.sidebarAutoHidden && this.mouseOutside return this.sidebarAutoHidden && this.mouseOutside
|| this.sidebarCollapse && this.device === 'mobile' || this.sidebarCollapse && this.device === 'mobile'
@ -62,6 +73,21 @@ export default {
} }
}, },
activeRootMenu: {
immediate: true,
handler(v) {
const {menus} = this
if (menus.length <= 0 || !v) return
const root = menus.find(i => i.path === v)
this.sidebarMenus = root ? root.children || [] : []
//el-menu
//el-menu
const menu = this.$refs.menu
menu && menu.updateActiveIndex()
}
},
// //
hideSidebar: { hideSidebar: {
immediate: true, immediate: true,
@ -96,18 +122,7 @@ export default {
//mobile //mobile
this.device === 'mobile' && this.$store.commit('setting/sidebarCollapse', true) this.device === 'mobile' && this.$store.commit('setting/sidebarCollapse', true)
if (!jump) return jump && this.jumpOnSelectMenu(index)
//
if (isExternal(index)) {
return window.open(index)
}
if (this.$route.path === index) {
this.$store.commit('tagsView/delCachedView', this.$route)
this.$nextTick(() => this.$router.replace({path: '/redirect' + this.$route.fullPath}))
}
else this.$router.push(index)
} }
}, },
@ -116,6 +131,9 @@ export default {
}, },
render() { render() {
//
if (this.sidebarMenus.length <= 0) return
const menu = ( const menu = (
<el-menu <el-menu
ref="menu" ref="menu"
@ -126,12 +144,11 @@ export default {
mode="vertical" mode="vertical"
on-select={this.select} on-select={this.select}
> >
{this.menus.map(menu => ( {this.sidebarMenus.map(m => (
<sidebar-item <sidebar-item
key={menu.path}
show-parent={this.sidebarShowParent} show-parent={this.sidebarShowParent}
collapse={this.sidebarCollapse} collapse={this.sidebarCollapse}
menu={menu} menu={m}
/> />
))} ))}
</el-menu> </el-menu>

@ -1,6 +1,6 @@
@import "~@/asset/style/variables.scss"; @import "~@/asset/style/variables.scss";
$iconSize: 17px; $iconSize: 16px;
.sidebar-container { .sidebar-container {
display: flex; display: flex;
@ -84,8 +84,6 @@ $iconSize: 17px;
.icon { .icon {
margin-right: 16px; margin-right: 16px;
width: $iconSize;
height: $iconSize;
} }
//菜单hover //菜单hover

@ -28,8 +28,8 @@
</template> </template>
<script type="text/jsx"> <script type="text/jsx">
import shortcutMixin from './mixin/shortcut' import shortcutMixin from '@/layout/mixin/shortcut'
import decideRouterTransitionMixin from './mixin/decideRouterTransition' import decideRouterTransitionMixin from '@/layout/mixin/decideRouterTransition'
import ContextMenu from "@/component/ContextMenu" import ContextMenu from "@/component/ContextMenu"
import ContextMenuItem from "@/component/ContextMenu/item" import ContextMenuItem from "@/component/ContextMenu/item"
import ScrollPane from './ScrollPane' import ScrollPane from './ScrollPane'

@ -0,0 +1,20 @@
import {isExternal} from "@/util/validate"
export default {
methods: {
jumpOnSelectMenu(fullPath, refreshWhenSame = true) {
//外部链接时打开新窗口
if (isExternal(fullPath)) {
return window.open(fullPath)
}
//触发的菜单路径是当前路由时,根据参数判断是否进行刷新
if (this.$route.path === fullPath) {
if (!refreshWhenSame) return
this.$store.commit('tagsView/delCachedView', this.$route)
this.$nextTick(() => this.$router.replace({path: '/redirect' + this.$route.fullPath}))
}
else this.$router.push(fullPath)
}
}
}

@ -3,19 +3,11 @@ import Redirect from "@/view/app/redirect"
import Login from "@/view/app/login/index" import Login from "@/view/app/login/index"
import Page404 from "@/view/app/404" import Page404 from "@/view/app/404"
import Page403 from "@/view/app/403" import Page403 from "@/view/app/403"
import {parseRoutes, stringifyRoutes, metaExtend} from "./util" import routes from './module'
import {parseRoutes, stringifyRoutes} from "./util"
const modulesFiles = require.context('./module', false, /\.js$/)
const dynamicRoutes = modulesFiles.keys().reduce((modules, modulePath) => {
const value = modulesFiles(modulePath).default
Array.isArray(value) ? modules.push(...value) : modules.push(value)
return modules
}, [])
metaExtend(dynamicRoutes)
export function getDynamicRoutes() { export function getDynamicRoutes() {
return parseRoutes(stringifyRoutes(dynamicRoutes)) return parseRoutes(stringifyRoutes(routes))
} }
//系统自有路由 //系统自有路由

@ -17,11 +17,11 @@ import {pathToRegexp} from 'path-to-regexp'
import NProgress from 'nprogress' import NProgress from 'nprogress'
import {contextPath, routerMode, title} from '@/config' import {contextPath, routerMode, title} from '@/config'
import defaultRoutes from '@/router/define' import defaultRoutes from '@/router/define'
import {auth, needAuth} from "@/util/auth"
import {isUserExist} from "@/util/storage"
import {stringifyRoutes, parseRoutes, generateRoutes} from './util'
import store from "@/store" import store from "@/store"
import {stringifyRoutes, parseRoutes, generateRoutes} from './util'
import {isEmpty} from "@/util" import {isEmpty} from "@/util"
import {auth, needAuth} from "@/util/auth"
import {isUserExist} from "@/util/storage"
Vue.use(Router) Vue.use(Router)
@ -59,6 +59,8 @@ router.beforeEach(async (to, from, next) => {
return next({path: '/login', query: {redirect: to.fullPath}}) return next({path: '/login', query: {redirect: to.fullPath}})
} }
setActiveRootMenu(to)
//初始化路由和菜单权限 //初始化路由和菜单权限
if (!store.state.resource.init) { if (!store.state.resource.init) {
await store.dispatch('resource/init', {...store.state.user, addRoutes: true}) await store.dispatch('resource/init', {...store.state.user, addRoutes: true})
@ -103,6 +105,16 @@ function needExtraRedirect(to, from) {
return !isEmpty(a) && a === b return !isEmpty(a) && a === b
} }
//设置顶部菜单的激活状态
function setActiveRootMenu(to) {
const {path, matched} = to
//使用/redirect跳转 或 无匹配路由 时跳过
if (path.startsWith('/redirect') || matched.length === 0) {
return
}
store.commit('resource/activeRootMenu', matched[0].path || '/')
}
//获取进行权限验证的路由地址,使用了动态路由的会用到 //获取进行权限验证的路由地址,使用了动态路由的会用到
function getAuthorizedPath(route) { function getAuthorizedPath(route) {
const {params, path, matched} = route const {params, path, matched} = route

@ -1,28 +0,0 @@
//路由表:系统页面
const router = [
{
path: '/',
component: 'Layout',
children: [
{
path: 'index',
component: 'index/',
name: 'index',
meta: {title: '首页', affix: true, icon: 'svg-home', noAuth: true, sort: 0}
},
{
path: 'user',
name: 'userCenter',
component: 'userCenter/',
meta: {title: '个人中心', noCache: true, icon: 'svg-user', noAuth: true, hidden: true},
}
]
},
{
path: 'https://doc.toesbieya.cn',
meta: {title: '文档', icon: 'svg-documentation', noAuth: true, sort: 1}
}
]
export default router

@ -0,0 +1,18 @@
//路由表:系统页面
const router = [
{
path: 'index',
component: 'index/',
name: 'index',
meta: {title: '首页', affix: true, icon: 'svg-home', noAuth: true, sort: 0}
},
{
path: 'user',
name: 'userCenter',
component: 'userCenter/',
meta: {title: '个人中心', noCache: true, icon: 'svg-user', noAuth: true, hidden: true},
}
]
export default router

@ -1,8 +1,7 @@
/*路由表:演示用例*/ /*路由表:演示用例*/
const router = { const router = {
path: '/example', path: 'example',
component: 'Layout',
meta: {title: '演示用例', icon: 'svg-show', noAuth: true, noCache: true}, meta: {title: '演示用例', icon: 'svg-show', noAuth: true, noCache: true},
children: [ children: [
{ {
@ -98,4 +97,4 @@ const router = {
] ]
} }
export default router export default []

@ -1,8 +1,7 @@
/*路由表:消息中心*/ /*路由表:消息中心*/
const router = { const router = {
path: '/message', path: 'message',
component: 'Layout',
meta: {title: '消息中心', icon: 'svg-message', alwaysShow: true}, meta: {title: '消息中心', icon: 'svg-message', alwaysShow: true},
children: [ children: [
{ {

@ -1,8 +1,7 @@
/*路由表:采购管理*/ /*路由表:采购管理*/
const router = { const router = {
path: '/purchase', path: 'purchase',
component: 'Layout',
meta: {title: '采购管理', icon: 'svg-shopping', alwaysShow: true}, meta: {title: '采购管理', icon: 'svg-shopping', alwaysShow: true},
children: [ children: [
{ {

@ -1,8 +1,7 @@
/*路由表:销售管理*/ /*路由表:销售管理*/
const router = { const router = {
path: '/sell', path: 'sell',
component: 'Layout',
meta: {title: '销售管理', icon: 'svg-sell', alwaysShow: true}, meta: {title: '销售管理', icon: 'svg-sell', alwaysShow: true},
children: [ children: [
{ {

@ -1,8 +1,7 @@
/*路由表:库存管理*/ /*路由表:库存管理*/
const router = { const router = {
path: '/stock', path: 'stock',
component: 'Layout',
meta: {title: '库存管理', icon: 'svg-stock', alwaysShow: true}, meta: {title: '库存管理', icon: 'svg-stock', alwaysShow: true},
children: [ children: [
{ {

@ -1,8 +1,7 @@
/*路由表:系统管理*/ /*路由表:系统管理*/
const router = { const router = {
path: '/system', path: 'system',
component: 'Layout',
meta: {title: '系统管理', icon: 'svg-system', alwaysShow: true}, meta: {title: '系统管理', icon: 'svg-system', alwaysShow: true},
children: [ children: [
{ {

@ -0,0 +1,12 @@
/*顶级菜单:默认*/
import {context2array} from '@/router/util'
const files = require.context('./child', false, /\.js$/)
export default {
path: '/',
component: 'Layout',
meta: {title: '后台管理', icon: 'svg-home', sort: 0},
children: context2array(files)
}

@ -0,0 +1,4 @@
export default {
path: 'https://doc.toesbieya.cn',
meta: {title: '文档', icon: 'svg-documentation', noAuth: true}
}

@ -0,0 +1,36 @@
export default {
path: 'component',
meta: {title: '组件', icon: 'el-icon-s-grid'},
children: [
{
path: 'uploadExample',
component: 'example/component/upload',
meta: {title: '上传文件'}
},
{
path: 'skeletonExample',
component: 'example/component/skeleton',
meta: {title: '骨架屏'}
},
{
path: 'rippleExample',
component: 'example/component/ripple',
meta: {title: '波纹'}
},
{
path: 'signatureExample',
component: 'example/component/signature',
meta: {title: '手写签名'}
},
{
path: 'regionSelectorExample',
component: 'example/component/regionSelector',
meta: {title: '行政区划选择'}
},
{
path: 'treeSelectExample',
component: 'example/component/treeSelect',
meta: {title: '树选择'}
}
]
}

@ -0,0 +1,16 @@
export default {
path: 'cool',
meta: {title: '好玩的东东', icon: 'el-icon-s-opportunity'},
children: [
{
path: 'fluid',
component: 'example/cool/fluid',
meta: {title: '流体动画'}
},
{
path: 'l2d',
component: 'example/cool/l2d',
meta: {title: '看板娘'}
},
]
}

@ -0,0 +1,5 @@
export default {
path: 'icon',
component: 'example/icon/',
meta: {title: '图 标', icon: 'el-icon-eleme'}
}

@ -0,0 +1,14 @@
export default {
path: 'iframe',
meta: {title: 'iframe', noCache: false, icon: 'el-icon-s-flag'},
children: [
{
path: 'taobao',
meta: {title: '淘宝', iframe: 'https://www.taobao.com'}
},
{
path: 'baidu',
meta: {title: '百度', iframe: 'https://www.baidu.com'}
}
]
}

@ -0,0 +1,35 @@
export default {
path: 'test',
meta: {title: '测试页面', icon: 'el-icon-warning'},
children: [
{
path: 'develop',
component: 'example/test/develop/',
meta: {title: '开发测试'}
},
{
path: 'cache',
meta: {title: '详情页缓存测试', noCache: false},
children: [
{
path: 'detailPage1',
component: 'example/test/cache/detail',
meta: {
title: '详情页1',
usePathKey: true,
commonModule: '@/view/example/test/cache/detailPage'
}
},
{
path: 'detailPage2',
component: 'example/test/cache/detail',
meta: {
title: '详情页2',
usePathKey: true,
commonModule: '@/view/example/test/cache/detailPage'
}
}
]
}
]
}

@ -0,0 +1,12 @@
/*顶级菜单:演示用例*/
import {context2array} from '@/router/util'
const files = require.context('./child', false, /\.js$/)
export default {
path: '/example',
component: 'Layout',
meta: {title: '演示用例', icon: 'svg-show', noAuth: true, noCache: true},
children: context2array(files)
}

@ -0,0 +1,5 @@
import defaultRoot from './defaultRoot'
import exampleRoot from './exampleRoot'
import docRoot from './docRoot'
export default [defaultRoot, exampleRoot, docRoot]

@ -139,3 +139,12 @@ export function metaExtend(routes, parentMeta) {
route.children && metaExtend(route.children, route.meta) route.children && metaExtend(route.children, route.meta)
}) })
} }
//将require.context导出的对象处理为数组
export function context2array(context) {
return context.keys().reduce((modules, modulePath) => {
const value = context(modulePath).default
Array.isArray(value) ? modules.push(...value) : modules.push(value)
return modules
}, [])
}

@ -11,7 +11,7 @@ const state = {
//登陆页背景动画 //登陆页背景动画
loginBackgroundAnimation: 'sparkRain', loginBackgroundAnimation: 'sparkRain',
//右侧块是否含有头部 //右侧块是否含有头部,这里初始值是为了转为boolean类型
hasHeader: !!!localSettings.headerAutoHidden hasHeader: !!!localSettings.headerAutoHidden
} }

@ -11,7 +11,7 @@ const actions = {
refresh({commit}) { refresh({commit}) {
search search
.request({page: 1, pageSize: 1, unread: true}) .request({page: 1, pageSize: 1, unread: true})
.then(({data}) => commit('unreadCount', data)) .then(({data}) => commit('unreadCount', data.total))
} }
} }

@ -3,20 +3,33 @@ import {useBackendRoute} from '@/config'
import {addDynamicRoutes} from '@/router' import {addDynamicRoutes} from '@/router'
import {getDynamicRoutes} from '@/router/define' import {getDynamicRoutes} from '@/router/define'
import {parseRoutes, metaExtend} from "@/router/util" import {parseRoutes, metaExtend} from "@/router/util"
import {needAuth} from "@/util/auth"
import {createTree} from "@/util/tree"
import {getAll} from "@/api/system/resource" import {getAll} from "@/api/system/resource"
import {isEmpty} from "@/util" import {isEmpty} from "@/util"
import {needAuth} from "@/util/auth"
import {createTree} from "@/util/tree"
import {isExternal} from "@/util/validate" import {isExternal} from "@/util/validate"
const state = { const state = {
//当前激活的顶部菜单的fullPath
activeRootMenu: '',
//所有的树形菜单,每个元素为顶部菜单,顶部菜单的子级(如果有)为侧边栏菜单
menus: [], menus: [],
//后端返回的原始权限数据
//权限表:<权限路径,权限id>,用于util.auth
resourceMap: {}, resourceMap: {},
//由原始权限数据生成的树(当用户非admin时过滤了admin专属权限)
resourceTree: [], resourceTree: [],
//判断权限是否已经初始化
init: false init: false
} }
const mutations = { const mutations = {
activeRootMenu(state, activeRootMenu) {
state.activeRootMenu = activeRootMenu
},
menus(state, menus) { menus(state, menus) {
sort(menus) sort(menus)
state.menus = menus state.menus = menus
@ -52,12 +65,18 @@ const actions = {
return getAll return getAll
.request() .request()
.then(({data}) => { .then(({data}) => {
//动态添加路由,这里不需要进行权限过滤
const routes = transformOriginRouteData(data) const routes = transformOriginRouteData(data)
metaExtend(routes) metaExtend(routes)
addRoutes && addDynamicRoutes(routes) addRoutes && addDynamicRoutes(routes)
//获取经过权限过滤后的菜单
const menus = getAuthorizedMenus({resources, admin}, routes) const menus = getAuthorizedMenus({resources, admin}, routes)
commit('menus', menus) commit('menus', menus)
commit('resource', {data: data || [], admin}) commit('resource', {data: data || [], admin})
//设置初始化完成的标志
commit('init', true) commit('init', true)
}) })
} }

@ -14,7 +14,7 @@
<el-timeline> <el-timeline>
<el-timeline-item <el-timeline-item
v-for="msg in tableData" v-for="msg in tableData"
:key="msg.index" :key="msg.id"
:timestamp="msg.time" :timestamp="msg.time"
placement="top" placement="top"
> >
@ -125,7 +125,7 @@ export default {
}, },
read(msg) { read(msg) {
if (msg.read) return if (this.mode === 'read' || msg.read) return
msg.read = true msg.read = true
this.$store.commit('message/unreadCount', this.unreadCount - 1) this.$store.commit('message/unreadCount', this.unreadCount - 1)
read.request(msg.id) read.request(msg.id)

@ -1,4 +1,5 @@
export const nodeType = [ export const nodeType = [
{label: '顶部菜单', value: 0, icon: 'el-icon-s-promotion'},
{label: '聚合菜单', value: 1, icon: 'el-icon-folder'}, {label: '聚合菜单', value: 1, icon: 'el-icon-folder'},
{label: '页面菜单', value: 2, icon: 'el-icon-document'}, {label: '页面菜单', value: 2, icon: 'el-icon-document'},
{label: '数据接口', value: 3, icon: 'el-icon-open'}, {label: '数据接口', value: 3, icon: 'el-icon-open'},

@ -26,7 +26,7 @@
<el-alert show-icon :closable="false"> <el-alert show-icon :closable="false">
<template v-slot:title> <template v-slot:title>
当前选择编辑 当前选择编辑
<el-tag v-if="curNode" size="mini" closable @close="() => curNode = null"> <el-tag v-if="curNode" size="mini" closable @close="curNode = null">
{{ getNodeTitle(curNode) }} {{ getNodeTitle(curNode) }}
</el-tag> </el-tag>
</template> </template>
@ -175,7 +175,7 @@ export default {
form: { form: {
id: null, id: null,
pid: null, pid: null,
type: 0, type: null,
name: null, name: null,
path: null, path: null,
component: null, component: null,
@ -213,24 +213,23 @@ export default {
return this.$store.state.resource.resourceTree return this.$store.state.resource.resourceTree
}, },
showForm() { showForm() {
return this.type === 'add' && this.form.pid === 0 || this.curNode return this.type === 'add' && this.form.type === 0 || this.curNode
}, },
showTypeSelector() { showTypeSelector() {
const isRoot = this.form.pid === 0 //0.
const isRoot = this.form.type === 0
//1. //1.
const noApiNode = this.form.type !== 3 const noApiNode = this.form.type !== 3
//2. //2.
const noChild = this.type === 'add' && isRoot const noChild = this.type === 'add' && isRoot
|| this.type === 'edit' && this.curNode.childNodes.length <= 0 || this.type === 'edit' && this.curNode.childNodes.length <= 0
//3. //3.
const noInitialNode = this.form.path !== '/' || isRoot
//4.
const parentIsFolderMenu = const parentIsFolderMenu =
isRoot isRoot
? true ? true
: this.type === 'add' && this.curNode.data.type === 1 : this.type === 'add' && [0, 1].includes(this.curNode.data.type)
|| this.type === 'edit' && this.curNode.parent.data.type === 1 || this.type === 'edit' && [0, 1].includes(this.curNode.parent.data.type)
return noApiNode && noChild && noInitialNode && parentIsFolderMenu return !isRoot && noApiNode && noChild && parentIsFolderMenu
}, },
showSaveBtn() { showSaveBtn() {
return this.type === 'add' && this.canAdd || this.type === 'edit' && this.canUpdate return this.type === 'add' && this.canAdd || this.type === 'edit' && this.canUpdate
@ -271,8 +270,8 @@ export default {
}, },
// //
typeChange(v) { typeChange(v) {
//component //component
if (v === 2 && this.form.type === 1) { if (v === 2 && [0, 1].includes(this.form.type)) {
this.form.component = null this.form.component = null
} }
this.form.type = v this.form.type = v
@ -284,7 +283,7 @@ export default {
this.type = 'add' this.type = 'add'
this.clearForm() this.clearForm()
this.form.pid = 0 this.form.pid = 0
this.form.type = 1 this.form.type = 0
}, },
addChild() { addChild() {
if (this.loading) return if (this.loading) return
@ -294,7 +293,7 @@ export default {
this.type = 'add' this.type = 'add'
this.clearForm() this.clearForm()
this.form.pid = id this.form.pid = id
// //
this.form.type = type === 2 ? 3 : 1 this.form.type = type === 2 ? 3 : 1
}, },
del() { del() {
@ -325,6 +324,7 @@ export default {
resetObj(this.form) resetObj(this.form)
this.form.id = null this.form.id = null
this.form.pid = null this.form.pid = null
this.form.type = null
this.form.metaStr = JSON.stringify({title: ''}, null, 2) this.form.metaStr = JSON.stringify({title: ''}, null, 2)
this.form.meta.sort = null this.form.meta.sort = null
this.form.enable = true this.form.enable = true
@ -344,6 +344,7 @@ export default {
.finally(() => this.loading = false) .finally(() => this.loading = false)
}) })
}, },
//this.formmetametaStr
transformForm() { transformForm() {
return {...this.form, meta: this.form.metaStr, metaStr: undefined} return {...this.form, meta: this.form.metaStr, metaStr: undefined}
} }

Loading…
Cancel
Save