ant design vue pro 1.7.8 自定义渲染菜单,多页签,keep-alive 详细教程 vue2.x版
目的
为了实现多级菜单,并且都是可以缓存的;缓存可控制。
一、重写左侧菜单渲染
创建如下文件 路径是(src/layoutes/SidebarMenu)
(1).LeftMenu.vue 代码
<template><a-menu:open-keys="openKeys":selectedKeys="menuSel"mode="inline"theme="dark"@openChange="onOpenChange"@select="onSelect"><template v-for="item in menu"><template v-if="!item.hidden"><a-menu-item v-if="!item.children" :key="item.path" ><router-link :to="item.path"><!-- <template v-if="item.meta.icon"><a-icon v-if=" typeof item.meta.icon =='string'" :type="item.meta.icon"></a-icon><template v-else><a-icon :component=" item.meta.icon" /></template></template> --><MenuIcon :icon="item.meta.icon"/>{{ item.meta.title }}</router-link>></a-menu-item><sub-menu :menu-info="item" v-else-if="item.children" :key="item.path"></sub-menu></template></template></a-menu>
</template>
<script>
import SubMenu from './SubMenu.vue'
import MenuIcon from './MenuIcon.vue'
export default {name: 'LeftMenu',components: {SubMenu,MenuIcon},props: {menu: {type: Array,required: true,default: () => []}},data () {return {menuSel: [],openKeys: []}},watch: {$route: {handler (newVal) {this.menuSel = [newVal.path]const openKey = []if (newVal.matched) {this.getParentPath(this.menu, newVal.path, openKey)}this.openKeys = openKey},immediate: true,deep: true}},methods: {getParentPath (data, path, paths) {for (var i = 0; i < data.length; i++) {if (data[i].path === path) {paths.push(data[i].path)return true} else if (data[i].children) {const p = this.getParentPath(data[i].children, path, paths)if (p) {paths.unshift(data[i].path)return true}}}return false},onOpenChange (openKeys) {top.window.router = this.menuconst t = []this.getParentPath(this.menu, openKeys[openKeys.length - 1], t)this.openKeys = t},onSelect (sel) {this.menuSel = sel.selectedKeys}}
}
</script>
(2).SubMenu.vue 代码
<!-- 子菜单 -->
<template><a-sub-menu :key="menuInfo.path || menuInfo.name" v-bind="$props" v-on="$listeners"><span slot="title"><MenuIcon :icon="menuInfo.meta.icon"/><span>{{ menuInfo.meta.title }}</span></span><template v-for="item in menuInfo.children"><a-menu-item v-if="!item.children" :key="item.path || item.name"><!-- <a-icon type="pie-chart" /> --><router-link :to="item.path"><MenuIcon :icon="item.meta.icon"/><span>{{ item.meta.title }}</span></router-link></a-menu-item><sub-menu v-else :key="item.path" :menu-info="item" /></template></a-sub-menu>
</template><script>
import { Menu } from 'ant-design-vue'
import MenuIcon from './MenuIcon.vue'
export default {name: 'SubMenu',components: {MenuIcon},// 必须添加isSubMenu: true,props: {...Menu.SubMenu.props,menuInfo: {type: Object,default: () => ({})}}}
</script>
(3).MenuIcon.vue 代码
<template><span v-if="icon"><a-icon v-if="typeof icon ==='string'" :type="icon"></a-icon><a-icon v-else :component="icon"></a-icon></span>
</template>
<script>
export default {name: 'LeftMenuIcon',props: {icon: {type: [String, Object],default: null}}}
</script>
二、改造 BasicLayout.vue(src/layouts/BasicLayout.vue)
关键部分代码已有注释
<template><pro-layout:collapsed="collapsed":mediaQuery="query":isMobile="isMobile":handleMediaQuery="handleMediaQuery":handleCollapse="handleCollapse":i18nRender="i18nRender"v-bind="settings"><a-layout-content v-if="multiTab" :style="{ height: '100%', margin: '0 0 10px 0', paddingTop: fixedHeader ? '64px' : '0' }"><!--多页签新增--><multi-tab v-if="multiTab"></multi-tab><transition name="page-transition"><!--引入--><RouterViewC></RouterViewC></transition></a-layout-content><!--自定义渲染左侧菜单--><template v-slot:menuRender><LeftMenu :menu="menus"/></template><template v-slot:menuHeaderRender><div><img :src="LogoUrl" v-if="LogoVisible" class="logo" alt="logo" /><!-- <logo-svg />--><h1 :style="{'font-size':title.length>8?'17px':'20px'}">{{ title }}</h1></div></template><setting-drawer :settings="settings" @change="handleSettingChange"> </setting-drawer><template v-slot:rightContentRender><right-content :top-menu="settings.layout === 'topmenu'" :is-mobile="isMobile" :theme="settings.theme" /></template><template v-if="IsShowFooter" v-slot:footerRender><global-footer /></template></pro-layout>
</template><script>import { SettingDrawer, updateTheme } from '@ant-design-vue/pro-layout'
import GlobalFooter from '@/components/GlobalFooter'
import { i18nRender } from '@/locales'
import { mapState } from 'vuex'
import RouterViewC from './RouteView.vue' //引入自定义routeview
// import { updateTheme } from '@ant-design-vue/pro-layout'
// import SettingDrawer from '@/components/SettingDrawer' // 自定义方式
import LeftMenu from './SidebarMenu/LeftMenu.vue' //引入自定义左侧菜单
import {CONTENT_WIDTH_TYPE,SIDEBAR_TYPE,TOGGLE_MOBILE_TYPE,TOGGLE_LAYOUT,TOGGLE_NAV_THEME,TOGGLE_COLOR,TOGGLE_FIXED_HEADER,TOGGLE_FIXED_SIDEBAR,TOGGLE_WEAK
} from '@/store/mutation-types'
import storage from 'store'
import defaultSettings from '@/config/defaultSettings'
import RightContent from '@/components/GlobalHeader/RightContent'
export default {name: 'BasicLayout',components: {SettingDrawer,RightContent,GlobalFooter,RouterViewC,//引入自定义routeviewLeftMenu//引入自定义左侧菜单},
三、新增RouteView.vue(src/layouts/RouteView.vue)
<template><keep-alive :include="cachedViews"><router-view :key="key" /></keep-alive>
</template>
<script>
export default {name: 'RouteView',computed: {cachedViews () {return this.$store.state.tagsView.cachedViews},key () {return this.$route.fullPath}},data () {return {}}}
</script>
四、新增tagviews.js(src/store/modules)
const state = {visitedViews: [],cachedViews: []
}const mutations = {ADD_VISITED_VIEW: (state, view) => {if (state.visitedViews.some(v => v.path === view.path)) returnstate.visitedViews.push(Object.assign({}, view, {title: view.meta.title || 'no-name'}))},ADD_CACHED_VIEW: (state, view) => {if (state.cachedViews.includes(view.name)) returnif (view.meta && view.meta.isCache) {state.cachedViews.push(view.name)}},DEL_VISITED_VIEW: (state, view) => {for (const [i, v] of state.visitedViews.entries()) {if (v.path === view.path) {state.visitedViews.splice(i, 1)break}}},DEL_CACHED_VIEW: (state, view) => {const index = state.cachedViews.indexOf(view.name)index > -1 && state.cachedViews.splice(index, 1)},DEL_OTHERS_VISITED_VIEWS: (state, view) => {state.visitedViews = state.visitedViews.filter(v => {return v.meta.affix || v.path === view.path})},DEL_OTHERS_CACHED_VIEWS: (state, view) => {const index = state.cachedViews.indexOf(view.name)if (index > -1) {state.cachedViews = state.cachedViews.slice(index, index + 1)} else {state.cachedViews = []}},DEL_ALL_VISITED_VIEWS: state => {// keep affix tagsconst affixTags = state.visitedViews.filter(tag => tag.meta.affix)state.visitedViews = affixTags},DEL_ALL_CACHED_VIEWS: state => {state.cachedViews = []},UPDATE_VISITED_VIEW: (state, view) => {for (let v of state.visitedViews) {if (v.path === view.path) {v = Object.assign(v, view)break}}},DEL_RIGHT_VIEWS: (state, view) => {const index = state.visitedViews.findIndex(v => v.path === view.path)if (index === -1) {return}state.visitedViews = state.visitedViews.filter((item, idx) => {if (idx <= index || (item.meta && item.meta.affix)) {return true}const i = state.cachedViews.indexOf(item.name)if (i > -1) {state.cachedViews.splice(i, 1)}return false})},DEL_LEFT_VIEWS: (state, view) => {const index = state.visitedViews.findIndex(v => v.path === view.path)if (index === -1) {return}state.visitedViews = state.visitedViews.filter((item, idx) => {if (idx >= index || (item.meta && item.meta.affix)) {return true}const i = state.cachedViews.indexOf(item.name)if (i > -1) {state.cachedViews.splice(i, 1)}return false})}
}const actions = {addView ({dispatch}, view) {dispatch('addVisitedView', view)dispatch('addCachedView', view)},addVisitedView ({commit}, view) {commit('ADD_VISITED_VIEW', view)},addCachedView ({commit}, view) {commit('ADD_CACHED_VIEW', view)},delView ({dispatch,state}, view) {return new Promise(resolve => {dispatch('delVisitedView', view)dispatch('delCachedView', view)resolve({visitedViews: [...state.visitedViews],cachedViews: [...state.cachedViews]})})},delVisitedView ({commit,state}, view) {return new Promise(resolve => {commit('DEL_VISITED_VIEW', view)resolve([...state.visitedViews])})},delCachedView ({commit,state}, view) {return new Promise(resolve => {commit('DEL_CACHED_VIEW', view)resolve([...state.cachedViews])})},delOthersViews ({dispatch,state}, view) {return new Promise(resolve => {dispatch('delOthersVisitedViews', view)dispatch('delOthersCachedViews', view)resolve({visitedViews: [...state.visitedViews],cachedViews: [...state.cachedViews]})})},delOthersVisitedViews ({commit,state}, view) {return new Promise(resolve => {commit('DEL_OTHERS_VISITED_VIEWS', view)resolve([...state.visitedViews])})},delOthersCachedViews ({commit,state}, view) {return new Promise(resolve => {commit('DEL_OTHERS_CACHED_VIEWS', view)resolve([...state.cachedViews])})},delAllViews ({dispatch,state}, view) {return new Promise(resolve => {dispatch('delAllVisitedViews', view)dispatch('delAllCachedViews', view)resolve({visitedViews: [...state.visitedViews],cachedViews: [...state.cachedViews]})})},delAllVisitedViews ({commit,state}) {return new Promise(resolve => {commit('DEL_ALL_VISITED_VIEWS')resolve([...state.visitedViews])})},delAllCachedViews ({commit,state}) {return new Promise(resolve => {commit('DEL_ALL_CACHED_VIEWS')resolve([...state.cachedViews])})},updateVisitedView ({commit}, view) {commit('UPDATE_VISITED_VIEW', view)},delRightTags ({commit}, view) {return new Promise(resolve => {commit('DEL_RIGHT_VIEWS', view)resolve([...state.visitedViews])})},delLeftTags ({commit}, view) {return new Promise(resolve => {commit('DEL_LEFT_VIEWS', view)resolve([...state.visitedViews])})}
}export default {namespaced: true,state,mutations,actions
}
五、getters.js 引入 tagview
只要增加 visitedViews 和 cachedViews 如下图
六、store/index.js 引入 tageviews(src/store/index.js)
如下图
七、生成路由位置permission.js(/src/store/modules/permission.js)
svg icon 参考下发 参考链接
八、改造MultiTab.vue(src/components/MultiTab/MultiTab.vue)
<script>
import events from './events'export default {name: 'MultiTab',data () {return {fullPathList: [],pages: [],activeKey: '',newTabIndex: 0}},created () {// bind eventevents.$on('open', (val) => {if (!val) {throw new Error(`multi-tab: open tab ${val} err`)}this.activeKey = val}).$on('close', (val) => {if (!val) {this.closeThat(this.activeKey)return}this.closeThat(val)}).$on('rename', ({ key, name }) => {try {const item = this.pages.find((item) => item.path === key)item.meta.customTitle = namethis.$forceUpdate()} catch (e) {}})this.pages.push(this.$route)this.fullPathList.push(this.$route.fullPath)this.selectedLastPath()},methods: {onEdit (targetKey, action) {this[action](targetKey)},remove (targetKey) {const newVal = this.getPage(targetKey)this.pages = this.pages.filter((page) => page.fullPath !== targetKey)this.fullPathList = this.fullPathList.filter((path) => path !== targetKey)if (newVal != null) {this.$store.dispatch('tagsView/delView', newVal)}// 判断当前标签是否关闭,若关闭则跳转到最后一个还存在的标签页if (!this.fullPathList.includes(this.activeKey)) {this.selectedLastPath()}},selectedLastPath () {this.activeKey = this.fullPathList[this.fullPathList.length - 1]},getPage (targetKey) {const newVal = this.pages.filter((c) => c.fullPath === targetKey)return newVal.length > 0 ? newVal[0] : null},// content menucloseThat (e) {// 判断是否为最后一个标签页,如果是最后一个,则无法被关闭if (this.fullPathList.length > 1) {this.remove(e)} else {this.$message.info('这是最后一个标签了, 无法被关闭')}},closeLeft (e) {const currentIndex = this.fullPathList.indexOf(e)if (currentIndex > 0) {this.fullPathList.forEach((item, index) => {if (index < currentIndex) {this.remove(item)}})} else {this.$message.info('左侧没有标签')}},closeRight (e) {const currentIndex = this.fullPathList.indexOf(e)if (currentIndex < this.fullPathList.length - 1) {this.fullPathList.forEach((item, index) => {if (index > currentIndex) {this.remove(item)}})} else {this.$message.info('右侧没有标签')}},closeAll (e) {const currentIndex = this.fullPathList.indexOf(e)this.fullPathList.forEach((item, index) => {if (index !== currentIndex) {this.remove(item)}})},refreshPage (e) {const currentIndex = this.fullPathList.indexOf(e)this.fullPathList.forEach((item, index) => {if (index === currentIndex) {let newVal = this.getPage(item)if (newVal != null) {const { path, query, matched } = newValmatched.forEach((m) => {if (m.components && m.components.default && m.components.default.name) {if (!['Layout', 'ParentView'].includes(m.components.default.name)) {newVal = { name: m.components.default.name, path: path, query: query }}}})this.$store.dispatch('tagsView/delCachedView', newVal).then((res) => {const { path, query } = newValthis.$router.replace({path: '/redirect' + path,query: query})})}}})},closeMenuClick (key, route) {this[key](route)},renderTabPaneMenu (e) {return (<a-menu{...{on: {click: ({ key, item, domEvent }) => {this.closeMenuClick(key, e)}}}}><a-menu-item key="closeThat">关闭当前标签</a-menu-item><a-menu-item key="closeRight">关闭右侧</a-menu-item><a-menu-item key="closeLeft">关闭左侧</a-menu-item><a-menu-item key="closeAll">关闭全部</a-menu-item><a-menu-item key="refreshPage">刷新标签</a-menu-item></a-menu>)},// renderrenderTabPane (title, keyPath) {const menu = this.renderTabPaneMenu(keyPath)return (<a-dropdown overlay={menu} trigger={['contextmenu']}><span style={{ userSelect: 'none' }}>{title}</span></a-dropdown>)},addtags () {const newVal = this.$routethis.$store.dispatch('tagsView/addView', newVal)}},mounted () {this.addtags()},watch: {$route: function (newVal) {this.activeKey = newVal.fullPaththis.addtags()if (this.fullPathList.indexOf(newVal.fullPath) < 0) {this.fullPathList.push(newVal.fullPath)this.pages.push(newVal)if (newVal.name && !this.$store.state.tagsView.cachedViews.includes(newVal.name)) {this.$store.commit('tagsView/ADD_CACHED_VIEW', newVal.name)}}},activeKey: function (newPathKey) {this.$router.push({ path: newPathKey })}},render () {const {onEdit,$data: { pages }} = thisconst panes = pages.map((page) => {return (<a-tab-panestyle={{ height: 0 }}tab={this.renderTabPane(page.meta.customTitle || page.meta.title, page.fullPath)}key={page.fullPath}closable={pages.length > 1}></a-tab-pane>)})return (<div class="ant-pro-multi-tab"><div class="ant-pro-multi-tab-wrapper"><a-tabshideAddtype={'editable-card'}v-model={this.activeKey}tabBarStyle={{ background: '#FFF', margin: 0, paddingLeft: '16px', paddingTop: '1px' }}{...{ on: { edit: onEdit } }}>{panes}</a-tabs></div></div>)}
}
</script>
九、改造permission.js(src/permission.js)
新增方法 handleKeepAlive
/*** 方法二:兼容<layout>按需加载* @param to*/
async function handleKeepAlive (to) {if (to.matched && to.matched.length > 2) {for (let i = 0; i < to.matched.length; i++) {const element = to.matched[i]if (element.components.default.name === 'RouteView') { // 注意这个RouteView 是继承路由那边的nameto.matched.splice(i, 1)await handleKeepAlive(to)}// 如果没有按需加载完成则等待加载if (typeof element.components.default === 'function') {await element.components.default()await handleKeepAlive(to)}}}
}
在路由守卫前插入以下代码
参考链接:
antd vue 左侧菜单使用svg图标。_ant design pro vue 怎么左侧导航图标使用本地svg-CSDN博客
Vue 使用 <keep-alive include> 实现多级 <router-view> 缓存,无限层次缓存_keepalive include-CSDN博客