Layout尽可能地不使用vuex(除未读消息、用户信息)

路由守卫代码分离
修复多页签中关闭全部时页面未刷新的问题
master
toesbieya 6 years ago
parent a3ce8e86d7
commit 47c1de01c0
  1. 11
      vue/src/layout/component/Header.vue
  2. 17
      vue/src/layout/component/Main/index.vue
  3. 15
      vue/src/layout/component/Navbar/component/RootMenu.vue
  4. 11
      vue/src/layout/component/Navbar/component/SettingDrawer.vue
  5. 14
      vue/src/layout/component/Navbar/index.vue
  6. 17
      vue/src/layout/component/Sidebar/index.vue
  7. 53
      vue/src/layout/component/TagsView/index.vue
  8. 15
      vue/src/layout/index.vue
  9. 3
      vue/src/layout/mixin/actionOnSelectMenu.js
  10. 3
      vue/src/layout/mixin/decideRouterTransition.js
  11. 5
      vue/src/layout/mixin/resize.js
  12. 35
      vue/src/layout/store/iframe.js
  13. 66
      vue/src/layout/store/main.js
  14. 62
      vue/src/layout/store/setting.js
  15. 85
      vue/src/layout/store/tagsView.js
  16. 16
      vue/src/router/guard/avoidReuse.js
  17. 24
      vue/src/router/guard/iframe.js
  18. 13
      vue/src/router/guard/index.js
  19. 17
      vue/src/router/guard/nprogress.js
  20. 43
      vue/src/router/guard/security.js
  21. 20
      vue/src/router/guard/setInfo.js
  22. 125
      vue/src/router/index.js
  23. 10
      vue/src/store/module/app.js
  24. 38
      vue/src/store/module/iframe.js
  25. 48
      vue/src/store/module/resource.js
  26. 82
      vue/src/store/module/tagsView.js
  27. 3
      vue/src/store/module/user.js
  28. 2
      vue/src/util/index.js
  29. 36
      vue/src/util/observable.js
  30. 7
      vue/src/util/route.js
  31. 5
      vue/src/view/app/login/index.vue

@ -1,7 +1,9 @@
<script type="text/jsx">
import {mapState} from 'vuex'
import VNavbar from './Navbar'
import TagsView from './TagsView'
import {mutations as mainMutations} from "@/layout/store/main"
import {getters as settingGetters} from "@/layout/store/setting"
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
export default {
name: "Header",
@ -17,7 +19,8 @@ export default {
},
computed: {
...mapState('setting', ['useTagsView', 'headerAutoHidden']),
useTagsView: () => settingGetters.useTagsView,
headerAutoHidden: () => settingGetters.headerAutoHidden,
hideHeader() {
return this.mouseOutside
@ -30,11 +33,11 @@ export default {
watch: {
//
useTagsView(v) {
!v && this.$store.dispatch('tagsView/delAllViews')
!v && tagsViewMutations.delAllViews()
},
hideHeader(v) {
this.$store.commit('app/hasNav', !v)
mainMutations.hasNav(!v)
v ? this.addEvent() : this.removeEvent()
}
},

@ -22,7 +22,9 @@
</template>
<script>
import {mapState} from 'vuex'
import {getters as iframeGetters} from "@/layout/store/iframe"
import {getters as settingGetters} from "@/layout/store/setting"
import {getters as tagsViewGetters} from "@/layout/store/tagsView"
import BackToTop from "./component/BackToTop"
import PageView from "./component/PageView"
import PageFooter from "./component/PageFooter"
@ -33,15 +35,14 @@ export default {
components: {BackToTop, PageView, PageFooter},
computed: {
...mapState('setting', ['showBackToTop']),
showBackToTop:()=>settingGetters.showBackToTop,
...mapState('tagsView', ['cachedViews', 'transitionName']),
cachedViews:()=>tagsViewGetters.cachedViews,
transitionName:()=>tagsViewGetters.transitionName,
...mapState('iframe', {
showIframe: state => state.show,
currentIframe: state => state.current,
iframeList: state => state.list
})
showIframe: () => iframeGetters.show,
currentIframe: () => iframeGetters.current,
iframeList: () => iframeGetters.list,
}
}
</script>

@ -4,6 +4,7 @@
*/
import ClickOutside from 'element-ui/lib/utils/clickoutside'
import actionOnSelectMenuMixin from "@/layout/mixin/actionOnSelectMenu"
import {mutations as mainMutations} from "@/layout/store/main"
export default {
name: "RootMenu",
@ -145,6 +146,20 @@ export default {
}
},
watch: {
$route: {
immediate: true,
handler(to) {
const {path, matched} = to
//使/redirect
if (path.startsWith('/redirect') || matched.length === 0) {
return
}
mainMutations.activeRootMenu(matched[0].path || '/')
}
}
},
mounted() {
this.setChildrenWidth()

@ -18,11 +18,12 @@
</template>
<script>
import ColorCheckBox from "@/component/ColorCheckBox"
import ColorCheckboxGroup from "@/component/ColorCheckBox/group"
import {isDev} from '@/config'
import client from 'webpack-theme-color-replacer/client'
import forElementUI from 'webpack-theme-color-replacer/forElementUI'
import {isDev} from '@/config'
import ColorCheckBox from "@/component/ColorCheckBox"
import ColorCheckboxGroup from "@/component/ColorCheckBox/group"
import {getters, mutations} from "@/layout/store/setting"
const settings = [
{name: '显示logo', key: 'showLogo'},
@ -58,10 +59,10 @@ export default {
isDev && client.changer.changeColor({newColors: forElementUI.getElementUISeries(color)})
},
getValue(key) {
return this.$store.state.setting[key]
return getters[key]
},
setValue(value, key) {
this.$store.commit(`setting/${key}`, value)
mutations[key](value)
}
}
}

@ -55,6 +55,8 @@ import Breadcrumb from './component/Breadcrumb'
import Hamburger from './component/Hamburger'
import RootMenu from "./component/RootMenu"
import SettingDrawer from './component/SettingDrawer'
import {getters as mainGetters} from "@/layout/store/main"
import {getters as settingGetters, mutations as settingMutations} from "@/layout/store/setting"
import {elConfirm} from "@/util/message"
export default {
@ -67,13 +69,15 @@ export default {
data: () => ({settingDrawer: false}),
computed: {
...mapState('app', ['device']),
...mapState('resource', ['activeRootMenu', 'menus']),
device: () => mainGetters.device,
activeRootMenu: () => mainGetters.activeRootMenu,
menus: () => mainGetters.menus,
...mapState('user', ['avatar', 'name', 'prepareLogout']),
...mapState('setting', ['sidebarAutoHidden', 'sidebarCollapse', 'showBreadcrumb']),
sidebarAutoHidden: () => settingGetters.sidebarAutoHidden,
sidebarCollapse: () => settingGetters.sidebarCollapse,
showBreadcrumb: () => settingGetters.showBreadcrumb,
renderHamburger() {
return !(this.device === 'pc' && this.sidebarAutoHidden)
@ -82,7 +86,7 @@ export default {
methods: {
sidebarCtrl() {
this.$store.commit('setting/sidebarCollapse', !this.sidebarCollapse)
settingMutations.sidebarCollapse(!this.sidebarCollapse)
},
refresh() {

@ -1,6 +1,7 @@
<script type="text/jsx">
import {mapState} from 'vuex'
import actionOnSelectMenuMixin from "@/layout/mixin/actionOnSelectMenu"
import {getters as mainGetters} from "@/layout/store/main"
import {getters as settingGetters,mutations as settingMutations} from "@/layout/store/setting"
import Logo from './component/Logo'
import SidebarItem from './component/SidebarItem'
@ -23,11 +24,15 @@ export default {
},
computed: {
...mapState('app', ['device']),
device: () => mainGetters.device,
activeRootMenu: () => mainGetters.activeRootMenu,
menus: () => mainGetters.menus,
...mapState('resource', ['activeRootMenu', 'menus']),
...mapState('setting', ['showLogo', 'sidebarCollapse', 'sidebarUniqueOpen', 'sidebarShowParent', 'sidebarAutoHidden']),
showLogo: () => settingGetters.showLogo,
sidebarCollapse: () => settingGetters.sidebarCollapse,
sidebarUniqueOpen: () => settingGetters.sidebarUniqueOpen,
sidebarShowParent: () => settingGetters.sidebarShowParent,
sidebarAutoHidden: () => settingGetters.sidebarAutoHidden,
//truefalsepc
collapse() {
@ -120,7 +125,7 @@ export default {
}
//mobile
this.device === 'mobile' && this.$store.commit('setting/sidebarCollapse', true)
this.device === 'mobile' && settingMutations.sidebarCollapse(true)
jump && this.actionOnSelectMenu(index)
}

@ -31,6 +31,8 @@
import shortcutMixin from '@/layout/mixin/shortcut'
import decideRouterTransitionMixin from '@/layout/mixin/decideRouterTransition'
import {route as routeConfig} from '@/config'
import {getters as mainGetters} from "@/layout/store/main"
import {getters as tagsViewGetters, mutations as tagsViewMutations} from "@/layout/store/tagsView"
import ContextMenu from "@/component/ContextMenu"
import ContextMenuItem from "@/component/ContextMenu/item"
import ScrollPane from './ScrollPane'
@ -54,18 +56,15 @@ export default {
},
computed: {
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
menus() {
return this.$store.state.resource.menus
}
visitedViews: () => tagsViewGetters.visitedViews,
menus: () => mainGetters.menus
},
watch: {
$route(to, from) {
this.decideRouteTransition && this.decideRouteTransition(to, from)
this.addTags(to).then(() => this.moveToCurrentTag())
this.addTags(to)
this.moveToCurrentTag()
},
'contextmenu.show'(v) {
this.$emit('menu-show', v)
@ -102,13 +101,13 @@ export default {
initTags() {
this.affixTags = this.filterAffixTags(this.menus)
for (const tag of this.affixTags) {
this.$store.commit('tagsView/addVisitedView', tag)
tagsViewMutations.addVisitedView(tag)
}
},
//meta.titletab
addTags(to = this.$route) {
return to.meta.title ? this.$store.dispatch('tagsView/addView', to) : Promise.resolve()
to.meta.title && tagsViewMutations.addView(to)
},
//tab
@ -124,34 +123,36 @@ export default {
* 刷新所选关闭所选关闭其他关闭所有
* */
refreshSelectedTag(view) {
this.$store.commit('tagsView/delCachedView', view)
tagsViewMutations.delCachedView(view)
this.$nextTick(() => this.$router.replace({path: `/redirect${view.fullPath}`}))
},
closeSelectedTag(view) {
if (this.isAffix(view)) return
this.$store
.dispatch('tagsView/delView', view)
.then(() => this.isActive(view) && this.gotoLastView())
tagsViewMutations.delView(view)
this.isActive(view) && this.gotoLastView()
},
closeOthersTags() {
this.$store
.dispatch('tagsView/delOthersViews', this.selectedTag)
.then(() => {
if (this.selectedTag.path !== this.$route.path) {
return this.$router.push(this.selectedTag)
}
})
tagsViewMutations.delOthersViews(this.selectedTag)
//
if (this.selectedTag.path !== this.$route.path) {
return this.$router.push(this.selectedTag)
}
},
closeAllTags() {
this.$store
.dispatch('tagsView/delAllViews')
.then(() => this.gotoLastView())
tagsViewMutations.delAllViews()
this.gotoLastView()
},
gotoLastView() {
if (this.visitedViews.length === 0) return this.$router.push('/')
if (this.visitedViews.length === 0) {
return this.$router.push('/')
}
const latest = this.visitedViews[this.visitedViews.length - 1]
if (this.$route.path !== latest.path) this.$router.push(latest.path)
//
if (this.$route.path !== latest.path) {
this.$router.push(latest.path)
}
else this.$router.replace(`/redirect${this.$route.fullPath}`)
},
openMenu(tag, e) {
@ -171,7 +172,7 @@ export default {
beforeDestroy() {
//fade
this.$store.commit('tagsView/transitionName', routeConfig.animate.default)
tagsViewMutations.transitionName(routeConfig.animate.default)
},
mounted() {

@ -15,12 +15,13 @@
</template>
<script>
import {mapState} from 'vuex'
import offlineMixin from './mixin/offline'
import resizeMixin from './mixin/resize'
import VMain from './component/Main'
import VHeader from './component/Header'
import VSidebar from './component/Sidebar'
import offlineMixin from "@/layout/mixin/offline"
import resizeMixin from './mixin/resize'
import {getters as mainGetters} from "@/layout/store/main"
import {getters as settingGetters, mutations as settingMutations} from "@/layout/store/setting"
export default {
name: 'Layout',
@ -30,9 +31,11 @@ export default {
components: {VMain, VSidebar, VHeader},
computed: {
...mapState('app', ['device', 'hasNav']),
device: () => mainGetters.device,
hasNav: () => mainGetters.hasNav,
...mapState('setting', ['useTagsView', 'sidebarCollapse']),
useTagsView: () => settingGetters.useTagsView,
sidebarCollapse: () => settingGetters.sidebarCollapse,
containerClass() {
return {
@ -50,7 +53,7 @@ export default {
methods: {
collapseSidebar() {
this.$store.commit('setting/sidebarCollapse', true)
settingMutations.sidebarCollapse(true)
}
}
}

@ -1,3 +1,4 @@
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
import {isExternal} from "@/util/validate"
export default {
@ -17,7 +18,7 @@ export default {
//触发的菜单路径是当前路由时,根据参数判断是否进行刷新
if (this.$route.path === fullPath) {
if (!refreshWhenSame) return
this.$store.commit('tagsView/delCachedView', this.$route)
tagsViewMutations.delCachedView(this.$route)
this.$nextTick(() => this.$router.replace({path: '/redirect' + this.$route.fullPath}))
}
else this.$router.push(fullPath)

@ -1,4 +1,5 @@
import {route as routeConfig} from '@/config'
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
const {animate} = routeConfig
@ -14,7 +15,7 @@ export default {
//新开tab也认为顺序高于上一个tab
if (toIndex === -1 || fromIndex < toIndex) transitionName = animate.next
this.$store.commit('tagsView/transitionName', transitionName)
tagsViewMutations.transitionName(transitionName)
},
}
}

@ -1,3 +1,4 @@
import {mutations as mainMutations} from "@/layout/store/main"
import {debounce} from '@/util'
import {isMobile} from "@/util/browser"
@ -6,8 +7,8 @@ export default {
$_resizeHandler() {
if (!document.hidden) {
const mobile = isMobile()
this.$store.commit('app/device', mobile ? 'mobile' : 'pc')
mobile && this.$store.commit('setting/sidebarCollapse', true)
mainMutations.device(mobile ? 'mobile' : 'pc')
mobile && this.collapseSidebar()
}
}
},

@ -0,0 +1,35 @@
import Vue from 'vue'
import {createGetters} from "@/util/observable"
const state = {
show: false,
current: '',
list: []
}
const store = Vue.observable(state)
function add(src) {
!store.list.includes(src) && store.list.push(src)
}
function del(src) {
const index = store.list.findIndex(i => i === src)
index > -1 && store.list.splice(index, 1)
}
function open({src}) {
store.show = true
store.current = src
add(src)
}
function close({src, del: needDelete}) {
store.show = false
store.current = ''
needDelete && del(src)
}
export const getters = createGetters(store)
export const mutations = {del, open, close}

@ -0,0 +1,66 @@
import Vue from 'vue'
import {isEmpty} from "@/util"
import {isMobile} from "@/util/browser"
import {createGetters, createMutations} from "@/util/observable"
import {getLocalPersonalSettings} from "@/util/storage"
const localSettings = getLocalPersonalSettings()
const state = {
//区分pc和移动端(mobile)
device: isMobile() ? 'mobile' : 'pc',
//右侧块是否含有导航栏,这里初始值是为了转为boolean类型
hasNav: !!!localSettings.headerAutoHidden,
//当前激活的顶部菜单的fullPath
activeRootMenu: '',
//所有的树形菜单,每个元素为顶部菜单,顶部菜单的子级(如果有)为侧边栏菜单
menus: [],
}
const store = Vue.observable(state)
//菜单排序
function sort(routes) {
const getSortValue = item => {
const sort = deepTap(item)
return isEmpty(sort) ? 10000 : sort
}
const deepTap = item => {
const {name, children = [], meta: {title, hidden, sort} = {}} = item
if (hidden) return null
if (!isEmpty(sort)) return sort
//如果是类似首页那样的路由层级
if (isEmpty(name, title) && children.length === 1) {
return deepTap(children[0])
}
return null
}
routes.sort((pre, next) => {
pre = getSortValue(pre)
next = getSortValue(next)
if (pre < next) return -1
else if (pre === next) return 0
else return 1
})
routes.forEach(route => {
const {children} = route
if (children && children.length) {
sort(children)
}
})
}
export const getters = createGetters(store)
export const mutations = {
...createMutations(store),
menus(v) {
sort(v)
store.menus = v
}
}

@ -1,8 +1,6 @@
/*
* 个性化设置
* */
import Vue from 'vue'
import {isEmpty} from "@/util"
import {createGetters} from "@/util/observable"
import {getLocalPersonalSettings, setLocalPersonalSettings} from "@/util/storage"
const state = {
@ -40,39 +38,35 @@ Object.keys(state).forEach(key => {
if (!isEmpty(localSettings[key])) state[key] = localSettings[key]
})
const mutations = {
...createMutations(state),
sidebarCollapse(state, v) {
if (v && state.sidebarAutoHidden) {
state.sidebarAutoHidden = false
}
state.sidebarCollapse = v
setLocalPersonalSettings(state)
},
sidebarAutoHidden(state, v) {
if (v && state.sidebarCollapse) {
state.sidebarCollapse = false
}
state.sidebarAutoHidden = v
setLocalPersonalSettings(state)
}
}
export default {
namespaced: true,
state,
mutations
}
const store = Vue.observable(state)
//每次修改个人设置时,同步到localStorage
function createMutations(state) {
return Object.keys(state).reduce((mutations, key) => {
mutations[key] = (ref, data) => {
ref[key] = data
setLocalPersonalSettings(ref)
function createMutations(store) {
return Object.keys(store).reduce((mutations, key) => {
mutations[key] = data => {
store[key] = data
setLocalPersonalSettings(store)
}
return mutations
}, {})
}
export const getters = createGetters(store)
export const mutations = {
...createMutations(store),
sidebarCollapse(v) {
//当启用侧边栏折叠时,停用自动隐藏侧边栏
if (v) store.sidebarAutoHidden = false
store.sidebarCollapse = v
setLocalPersonalSettings(store)
},
sidebarAutoHidden(v) {
//当启用自动隐藏侧边栏时,停用侧边栏折叠
if (v) store.sidebarCollapse = false
store.sidebarAutoHidden = v
setLocalPersonalSettings(store)
}
}

@ -0,0 +1,85 @@
import Vue from 'vue'
import {route as routeConfig} from "@/config"
import {mutations as iframeMutations} from "@/layout/store/iframe"
import {createGetters, createMutations} from "@/util/observable"
const state = {
//显示的页签,{...route,title:route.meta.title}对象数组
visitedViews: [],
//缓存的页签,用于<keep-router-view-alive/>:include
cachedViews: [],
//路由过渡动画名称
transitionName: routeConfig.animate.default
}
const store = Vue.observable(state)
function getCachedViewKey(view) {
const {usePathKey, useFullPathKey} = view.meta || {}
return usePathKey ? view.path : useFullPathKey ? view.fullPath : view.name
}
function addVisitedView(view) {
const {title = '暂无标题'} = view.meta || {}
if (store.visitedViews.some(v => v.path === view.path)) return
store.visitedViews.push({...view, title})
}
function addCachedView(view) {
const {noCache, iframe, usePathKey, useFullPathKey} = view.meta || {}
if (noCache || iframe || !view.name && !usePathKey && !useFullPathKey) return
const key = getCachedViewKey(view)
if (store.cachedViews.includes(key)) return
store.cachedViews.push(key)
}
function delVisitedView(view) {
const index = store.visitedViews.findIndex(i => i.path === view.path)
index > -1 && store.visitedViews.splice(index, 1)
}
function delCachedView(view) {
const key = getCachedViewKey(view)
const index = store.cachedViews.indexOf(key)
index > -1 && store.cachedViews.splice(index, 1)
}
export const getters = createGetters(store)
export const mutations = {
...createMutations(store),
addVisitedView,
addCachedView,
addView(view) {
addVisitedView(view)
addCachedView(view)
},
delVisitedView,
delCachedView,
delView(view) {
delVisitedView(view)
delCachedView(view)
iframeMutations.del(view.meta ? view.meta.iframe : null)
},
delOthersViews(view) {
const visitedViews = store.visitedViews.filter(v => v.meta.affix || v.path === view.path)
const name = store.cachedViews.find(name => name === getCachedViewKey(view))
store.visitedViews = visitedViews
store.cachedViews = name ? [name] : []
},
delAllViews() {
store.visitedViews = store.visitedViews.filter(tag => tag.meta && tag.meta.affix)
store.cachedViews = []
}
}

@ -0,0 +1,16 @@
import {isEmpty} from "@/util"
const beforeEach = (to, from, next) => {
//若是共用组件的路由页面之间的跳转,借助redirect避免组件复用
const a = to.meta.commonModule, b = from.meta.commonModule
const isComponentReuse = !isEmpty(a) && a === b
if (isComponentReuse) {
//这里vue-router会报redirect错误,已在main.js中忽略
return next(`/redirect${to.fullPath}`)
}
next()
}
export default function (router) {
router.beforeEach(beforeEach)
}

@ -0,0 +1,24 @@
import {mutations as iframeMutations} from "@/layout/store/iframe"
const beforeEach = (to, from, next) => {
//从iframe页面离开时,判断是否需要删除iframe
if (from.meta.iframe) {
let del = false
//如果设置了无缓存或是进行了刷新,那么移除iframe
if (from.meta.noCache || to.path === `/redirect${from.path}`) {
del = true
}
iframeMutations.close({src: from.meta.iframe, del})
}
//跳转至iframe页面时,打开iframe
if (to.meta.iframe) {
iframeMutations.open({src: to.meta.iframe})
}
next()
}
export default function (router) {
router.beforeEach(beforeEach)
}

@ -0,0 +1,13 @@
import nprogress from './nprogress'
import avoidReuse from './avoidReuse'
import setInfo from './setInfo'
import security from './security'
import iframe from './iframe'
export default function (router) {
nprogress(router)
avoidReuse(router)
setInfo(router)
security(router)
iframe(router)
}

@ -0,0 +1,17 @@
import NProgress from 'nprogress'
NProgress.configure({showSpinner: false})
const beforeEach = (to, from, next) => {
//使用redirect进行跳转时不显示进度条
!to.path.startsWith('/redirect') && NProgress.start()
next()
}
const afterEach = () => NProgress.done()
export default function (router) {
router.beforeEach(beforeEach)
router.afterEach(afterEach)
}

@ -0,0 +1,43 @@
import {pathToRegexp} from 'path-to-regexp'
import store from "@/store"
import {auth, needAuth} from "@/util/auth"
import {isUserExist} from "@/util/storage"
const whiteList = ['/login', '/register', '/403', '/404', '/500'].map(url => pathToRegexp(url))
const beforeEach = async (to, from, next) => {
//白名单内不需要进行权限控制
if (whiteList.some(reg => reg.test(to.path))) {
return next()
}
//未登录时返回登录页
if (!isUserExist()) {
return next({path: '/login', query: {redirect: to.fullPath}})
}
//初始化路由和菜单权限
if (!store.state.resource.init) {
await store.dispatch('resource/init', {...store.state.user, addRoutes: true})
return next({...to, replace: true})
}
//页面不需要鉴权或有访问权限时通过
if (!needAuth(to) || auth(getAuthorizedPath(to))) {
return next()
}
//用户无权限访问时的动作
next('/403')
}
//获取进行权限验证的路由地址,使用了动态路由的会用到
function getAuthorizedPath(route) {
const {params, path, matched} = route
const isDynamic = Object.values(params).some(v => path.includes(v))
return isDynamic ? matched[matched.length - 1].path : path
}
export default function (router) {
router.beforeEach(beforeEach)
}

@ -0,0 +1,20 @@
import {title} from "@/config"
const beforeEach = (to, from, next) => {
const {path, meta} = to
if (!path.startsWith('/redirect')) {
if (typeof meta.dynamicTitle === 'function') {
meta.title = meta.dynamicTitle(to, from)
}
const pageTitle = meta.title
document.title = pageTitle ? `${pageTitle} - ${title}` : title
}
next()
}
export default function (router) {
router.beforeEach(beforeEach)
}

@ -13,22 +13,13 @@
* */
import Vue from 'vue'
import Router from 'vue-router'
import {pathToRegexp} from 'path-to-regexp'
import NProgress from 'nprogress'
import {route as routeConfig, title} from '@/config'
import defaultRoutes from '@/router/define'
import store from "@/store"
import {route as routeConfig} from '@/config'
import {stringifyRoutes, parseRoutes, generateRoutes} from './util'
import {isEmpty} from "@/util"
import {auth, needAuth} from "@/util/auth"
import {isUserExist} from "@/util/storage"
import defaultRoutes from './define'
import registerGuard from './guard'
Vue.use(Router)
NProgress.configure({showSpinner: false})
const whiteList = transformWhiteList(['/login', '/register', '/403', '/404', '/500'])
const router = new Router({
base: process.env.BASE_URL,
mode: routeConfig.mode,
@ -36,114 +27,12 @@ const router = new Router({
routes: defaultRoutes
})
router.beforeEach(async (to, from, next) => {
//使用redirect进行跳转时不显示进度条
!to.path.startsWith('/redirect') && NProgress.start()
if (needExtraRedirect(to, from)) {
//这里vue-router会报redirect错误,已在main.js中忽略
return next(`/redirect${to.fullPath}`)
}
specifyRouteTitle(to, from)
setPageTitle(to)
//白名单内不需要进行权限控制
if (whiteList.some(reg => reg.test(to.path))) {
return next()
}
//未登录时返回登录页
if (!isUserExist()) {
return next({path: '/login', query: {redirect: to.fullPath}})
}
setActiveRootMenu(to)
//初始化路由和菜单权限
if (!store.state.resource.init) {
await store.dispatch('resource/init', {...store.state.user, addRoutes: true})
return next({...to, replace: true})
}
//页面不需要鉴权或有访问权限时通过
if (!needAuth(to) || auth(getAuthorizedPath(to))) {
iframeControl(to, from)
return next()
}
//用户无权限访问时的动作
next('/403')
})
router.afterEach(() => NProgress.done())
//将给定的白名单url转换为正则
function transformWhiteList(list) {
return list.map(pathToRegexp)
}
//拼接页面标题
function setPageTitle(route) {
const pageTitle = route.meta.title
document.title = pageTitle ? `${pageTitle} - ${title}` : title
}
//确定路由的标题
function specifyRouteTitle(to, from) {
const {meta} = to
if (typeof meta.dynamicTitle === 'function') {
meta.title = meta.dynamicTitle(to, from)
}
}
//判断是否需要一次额外的redirect跳转
function needExtraRedirect(to, from) {
//若是共用组件的路由页面之间的跳转,借助redirect避免组件复用
const a = to.meta.commonModule, b = from.meta.commonModule
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) {
const {params, path, matched} = route
const isDynamic = Object.values(params).some(v => path.includes(v))
return isDynamic ? matched[matched.length - 1].path : path
}
//判断是否需要打开iframe
function iframeControl(to, from) {
//从iframe页面离开时,判断是否需要删除iframe
if (from.meta.iframe) {
let del = false
//如果设置了无缓存或是进行了刷新,那么移除iframe
if (from.meta.noCache || to.path === `/redirect${from.path}`) {
del = true
}
store.dispatch('iframe/close', {src: from.meta.iframe, del})
}
//跳转至iframe页面时,打开iframe
if (to.meta.iframe) {
store.dispatch('iframe/open', {src: to.meta.iframe})
}
}
registerGuard(router)
export default router
export function addDynamicRoutes(json) {
json = parseRoutes(stringifyRoutes(json))
export function addDynamicRoutes(jsonTree) {
jsonTree = parseRoutes(stringifyRoutes(jsonTree))
const endRoute = {path: '*', redirect: '/404'}
router.addRoutes([...generateRoutes(json), endRoute])
router.addRoutes([...generateRoutes(jsonTree), endRoute])
}

@ -1,18 +1,8 @@
import {getLocalPersonalSettings} from "@/util/storage"
import {createMutations} from "@/util"
import {isMobile} from "@/util/browser"
const localSettings = getLocalPersonalSettings()
const state = {
//区分pc和移动端(mobile)
device: isMobile() ? 'mobile' : 'pc',
//登陆页背景动画
loginBackgroundAnimation: 'sparkRain',
//右侧块是否含有导航栏,这里初始值是为了转为boolean类型
hasNav: !!!localSettings.headerAutoHidden
}
const mutations = createMutations(state)

@ -1,38 +0,0 @@
import {createMutations} from "@/util"
const state = {
show: false,
current: '',
list: []
}
const mutations = {
...createMutations(state),
add(state, src) {
!state.list.includes(src) && state.list.push(src)
},
del(state, src) {
const index = state.list.findIndex(i => i === src)
index > -1 && state.list.splice(index, 1)
}
}
const actions = {
open({commit}, {src}) {
commit('show', true)
commit('current', src)
commit('add', src)
},
close({commit}, {src, del}) {
commit('show', false)
commit('current', '')
del && commit('del', src)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}

@ -1,5 +1,6 @@
import path from 'path'
import {route as routeConfig} from '@/config'
import {mutations as mainMutations} from "@/layout/store/main"
import {addDynamicRoutes} from '@/router'
import {getDynamicRoutes} from '@/router/define'
import {parseRoutes, metaExtend} from "@/router/util"
@ -10,12 +11,6 @@ import {createTree} from "@/util/tree"
import {isExternal} from "@/util/validate"
const state = {
//当前激活的顶部菜单的fullPath
activeRootMenu: '',
//所有的树形菜单,每个元素为顶部菜单,顶部菜单的子级(如果有)为侧边栏菜单
menus: [],
//后端返回的原始权限数据
//权限表:<权限路径,权限id>,用于util.auth
resourceMap: {},
@ -27,13 +22,6 @@ const state = {
}
const mutations = {
activeRootMenu(state, activeRootMenu) {
state.activeRootMenu = activeRootMenu
},
menus(state, menus) {
sort(menus)
state.menus = menus
},
resource(state, {data, admin}) {
state.resourceMap = generateResourceMap(data)
@ -73,7 +61,7 @@ const actions = {
//获取经过权限过滤后的菜单
const menus = getAuthorizedMenus({resources, admin}, routes)
commit('menus', menus)
mainMutations.menus(menus)
commit('resource', {data: data || [], admin})
//设置初始化完成的标志
@ -169,38 +157,6 @@ function clean(menus, cleanHidden = true) {
}
}
//菜单排序
function sort(routes) {
const getSortValue = item => {
const sort = deepTap(item)
return isEmpty(sort) ? 10000 : sort
}
const deepTap = item => {
const {name, children = [], meta: {title, hidden, sort} = {}} = item
if (hidden) return null
if (!isEmpty(sort)) return sort
//如果是类似首页那样的路由层级
if (isEmpty(name, title) && children.length === 1) {
return deepTap(children[0])
}
return null
}
routes.sort((pre, next) => {
pre = getSortValue(pre)
next = getSortValue(next)
if (pre < next) return -1
else if (pre === next) return 0
else return 1
})
routes.forEach(route => {
const {children} = route
if (children && children.length) {
sort(children)
}
})
}
//根据权限列表生成哈希表:<权限路径,权限id>
function generateResourceMap(resources) {
if (!Array.isArray(resources) || resources.length <= 0) {

@ -1,82 +0,0 @@
import {route as routeConfig} from '@/config'
import {createMutations} from "@/util"
const state = {
//显示的页签,{...route,title:route.meta.title}对象数组
visitedViews: [],
//缓存的页签,用于<keep-router-view-alive/>:include
cachedViews: [],
//路由过渡动画名称
transitionName: routeConfig.animate.default
}
const mutations = {
...createMutations(state),
addVisitedView(state, view) {
const {title = '暂无标题'} = view.meta || {}
if (state.visitedViews.some(v => v.path === view.path)) return
state.visitedViews.push({...view, title})
},
addCachedView(state, view) {
const {noCache, iframe, usePathKey, useFullPathKey} = view.meta || {}
if (noCache || iframe || !view.name && !usePathKey && !useFullPathKey) return
const key = getCachedViewKey(view)
if (state.cachedViews.includes(key)) return
state.cachedViews.push(key)
},
delVisitedView(state, view) {
const index = state.visitedViews.findIndex(i => i.path === view.path)
index > -1 && state.visitedViews.splice(index, 1)
},
delCachedView(state, view) {
const key = getCachedViewKey(view)
const index = state.cachedViews.indexOf(key)
index > -1 && state.cachedViews.splice(index, 1)
}
}
const actions = {
addView({commit}, view) {
commit('addVisitedView', view)
commit('addCachedView', view)
},
delView({commit}, view) {
commit('delVisitedView', view)
commit('delCachedView', view)
commit('iframe/del', view.meta ? view.meta.iframe : null, {root: true})
},
delOthersViews({state, commit}, view) {
const visitedViews = state.visitedViews.filter(v => v.meta.affix || v.path === view.path)
const name = state.cachedViews.find(name => name === getCachedViewKey(view))
commit('visitedViews', visitedViews)
commit('cachedViews', name ? [name] : [])
},
delAllViews({state, commit}) {
commit('visitedViews', state.visitedViews.filter(tag => tag.meta && tag.meta.affix))
commit('cachedViews', [])
}
}
function getCachedViewKey(view) {
const {usePathKey, useFullPathKey} = view.meta || {}
return usePathKey ? view.path : useFullPathKey ? view.fullPath : view.name
}
export default {
namespaced: true,
state,
mutations,
actions
}

@ -1,6 +1,7 @@
import {createMutations, emptyOrDefault} from "@/util"
import {autoCompleteUrl} from "@/util/file"
import {getUser, setUser} from "@/util/storage"
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
import {login, logout} from '@/api/account'
//刷新时从本地存储中获取用户信息
@ -48,7 +49,7 @@ const actions = {
return Promise.all([
dispatch('socket/close', null, {root: true}),
dispatch('removeUser'),
dispatch('tagsView/delAllViews', null, {root: true})
tagsViewMutations.delAllViews()
])
})
.then(() => {

@ -177,7 +177,7 @@ export function waitUntilSuccess(success, callback, interval = 1000, maxTryTime
})
}
//store中根据state批量生成对应的mutation
//vuex中根据state批量生成对应的mutation
export function createMutations(state, all = false) {
const keys = Object.keys(state)
const obj = {}

@ -0,0 +1,36 @@
import {getInitialValue} from "@/util"
//为Vue.observer返回的对象设置getter
export function createGetters(store) {
const getters = Object.create({})
Object.defineProperties(
getters,
Object.keys(store).reduce((obj, key) => {
obj[key] = {
enumerable: true,
get() {
return store[key]
}
}
return obj
}, {})
)
return getters
}
//设置mutation
export function createMutations(store, all = false) {
const keys = Object.keys(store)
const obj = {}
keys.forEach(key => {
obj[key] = v => store[key] = v
})
if (all) {
obj['$all'] = v => {
keys.forEach(key => {
store[key] = v && v[key] || getInitialValue(store[key])
})
}
}
return obj
}

@ -1,9 +1,8 @@
import {isEmpty} from "@/util"
import router from '@/router'
import store from '@/store'
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
export function closeCurrentPage(next) {
return store
.dispatch('tagsView/delView', router.currentRoute)
.then(() => !isEmpty(next) && router.replace(next))
tagsViewMutations.delView(router.currentPage)
!isEmpty(next) && router.replace(next)
}

@ -20,6 +20,7 @@ import SetAnimation from "./SetAnimation"
import LoginForm from "./LoginForm"
import RegisterForm from "./RegisterForm"
import {isEmpty} from "@/util"
import {isMobile} from "@/util/browser"
export default {
name: 'login',
@ -29,7 +30,7 @@ export default {
data: () => ({title}),
computed: {
...mapState('app', ['device', 'loginBackgroundAnimation']),
...mapState('app', ['loginBackgroundAnimation']),
component() {
const formType = this.$route.path.substring(1)
@ -57,7 +58,7 @@ export default {
mounted() {
this.animationInstance = null
//
this.device !== 'mobile' && this.setAnimation(this.loginBackgroundAnimation)
!isMobile() && this.setAnimation(this.loginBackgroundAnimation)
},
beforeDestroy() {

Loading…
Cancel
Save