qdt-admin-layout 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +22 -0
- package/package.json +45 -0
- package/src/component/Aside/DefaultSidebar/index.vue +354 -0
- package/src/component/Aside/DefaultSidebar/style.scss +32 -0
- package/src/component/Aside/index.vue +30 -0
- package/src/component/Aside/style.scss +23 -0
- package/src/component/Aside/theme-dark.scss +19 -0
- package/src/component/Aside/theme-light.scss +18 -0
- package/src/component/Breadcrumb/index.vue +111 -0
- package/src/component/Breadcrumb/style.scss +27 -0
- package/src/component/CachedRouterView/index.vue +162 -0
- package/src/component/ContextMenu/functionalUse.js +26 -0
- package/src/component/ContextMenu/index.vue +140 -0
- package/src/component/ContextMenu/style.scss +30 -0
- package/src/component/ElMenu/item.vue +66 -0
- package/src/component/ElMenu/mixin.js +36 -0
- package/src/component/ElMenu/sub.vue +118 -0
- package/src/component/Hamburger/index.vue +27 -0
- package/src/component/Hamburger/style.scss +3 -0
- package/src/component/Header/index.vue +131 -0
- package/src/component/Header/style.scss +74 -0
- package/src/component/Header/theme-dark.scss +31 -0
- package/src/component/Header/theme-light.scss +15 -0
- package/src/component/HorizontalResizableMenu/GhostMenu.vue +101 -0
- package/src/component/HorizontalResizableMenu/index.vue +280 -0
- package/src/component/HorizontalScroller/index.vue +91 -0
- package/src/component/HorizontalScroller/style.scss +12 -0
- package/src/component/Layout/index.vue +153 -0
- package/src/component/Layout/style.scss +42 -0
- package/src/component/LoadingSpinner/index.vue +17 -0
- package/src/component/Logo/index.vue +41 -0
- package/src/component/Logo/style.scss +26 -0
- package/src/component/NavMenu/index.vue +206 -0
- package/src/component/NavMenu/style.scss +159 -0
- package/src/component/NavMenu/theme-dark.scss +59 -0
- package/src/component/NavMenu/theme-light.scss +81 -0
- package/src/component/Page/content.vue +58 -0
- package/src/component/Page/iframe.vue +63 -0
- package/src/component/Page/index.vue +22 -0
- package/src/component/Page/style.scss +48 -0
- package/src/component/Redirect/index.vue +19 -0
- package/src/component/TagsView/index.vue +255 -0
- package/src/component/TagsView/style.scss +51 -0
- package/src/component/TagsView/util.js +67 -0
- package/src/config/const.js +24 -0
- package/src/config/defaultRoute.js +23 -0
- package/src/config/index.js +4 -0
- package/src/config/logic.js +53 -0
- package/src/helper.js +43 -0
- package/src/index.js +15 -0
- package/src/mixin/menu.js +72 -0
- package/src/store/app.js +132 -0
- package/src/store/aside.js +92 -0
- package/src/store/header.js +37 -0
- package/src/store/index.js +20 -0
- package/src/store/page.js +67 -0
- package/src/store/tagsView.js +186 -0
- package/src/store/util.js +35 -0
- package/src/style/index.scss +23 -0
- package/src/style/maxViewHeight.scss +65 -0
- package/src/style/transition.scss +71 -0
- package/src/style/var.scss +81 -0
- package/src/util.js +69 -0
- package/types/config.d.ts +12 -0
- package/types/helper.d.ts +10 -0
- package/types/index.d.ts +5 -0
- package/types/menu.d.ts +17 -0
- package/types/route.d.ts +15 -0
- package/types/store.d.ts +156 -0
- package/types/vue-router.d.ts +7 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* 页签栏
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
appGetters,
|
|
8
|
+
pageGetters,
|
|
9
|
+
pageMutations,
|
|
10
|
+
tagsViewGetters,
|
|
11
|
+
tagsViewMutations
|
|
12
|
+
} from '../../store'
|
|
13
|
+
import useContextMenu from '../../component/ContextMenu/functionalUse'
|
|
14
|
+
import HorizontalScroller from '../../component/HorizontalScroller'
|
|
15
|
+
import { refreshPage } from '../../helper'
|
|
16
|
+
import { getRouterKey, getRouterTitle, isRedirectRouter } from '../../config/logic'
|
|
17
|
+
import { isAffix, getAffixTagsFromMenuTree, renderDefaultStyleTag } from './util'
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
name: 'TagsView',
|
|
21
|
+
|
|
22
|
+
data() {
|
|
23
|
+
return {
|
|
24
|
+
// 当前激活的页签的key,随路由变化
|
|
25
|
+
activeKey: '',
|
|
26
|
+
|
|
27
|
+
// 当前选中的页签
|
|
28
|
+
selectedTag: undefined,
|
|
29
|
+
|
|
30
|
+
// 页签右键菜单的属性
|
|
31
|
+
contextMenu: {
|
|
32
|
+
show: false,
|
|
33
|
+
top: 0,
|
|
34
|
+
left: 0
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
computed: {
|
|
40
|
+
contextMenuItems() {
|
|
41
|
+
const { visitedViews } = tagsViewGetters
|
|
42
|
+
const closeable = visitedViews.length > 1 && !isAffix(this.selectedTag)
|
|
43
|
+
|
|
44
|
+
return [
|
|
45
|
+
{ content: '刷新', click: this.refreshSelectedTag },
|
|
46
|
+
{ content: closeable && '关闭', click: this.closeSelectedTag },
|
|
47
|
+
{ content: '关闭其他', click: this.closeOthersTags },
|
|
48
|
+
{ content: '关闭全部', click: this.closeAllTags }
|
|
49
|
+
].filter(i => i.content)
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// 避免HorizontalScroller在右键时触发$forceUpdate
|
|
53
|
+
// https://segmentfault.com/q/1010000040171066
|
|
54
|
+
tagsSlot() {
|
|
55
|
+
return this._u([{
|
|
56
|
+
key: 'default',
|
|
57
|
+
fn: this.renderTags,
|
|
58
|
+
proxy: true
|
|
59
|
+
}])
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
watch: {
|
|
64
|
+
$route(to, from) {
|
|
65
|
+
tagsViewGetters.enableChangeTransition && this.decideRouteTransition(to, from)
|
|
66
|
+
|
|
67
|
+
// 如果是刷新的话,不需要进行后续操作
|
|
68
|
+
if (isRedirectRouter(to)) return
|
|
69
|
+
|
|
70
|
+
this.setActiveKey(to)
|
|
71
|
+
this.addTag(to)
|
|
72
|
+
this.$nextTick(this.moveToCurrentTag)
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
methods: {
|
|
77
|
+
/**
|
|
78
|
+
* 根据路由设置当前激活的页签key
|
|
79
|
+
* @param route {import('vue-router').Route}
|
|
80
|
+
*/
|
|
81
|
+
setActiveKey(route) {
|
|
82
|
+
this.activeKey = getRouterKey(route)
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 根据访问的tab页的左右顺序来确定路由动画
|
|
87
|
+
* @param to {import('vue-router').Route}
|
|
88
|
+
* @param from {import('vue-router').Route}
|
|
89
|
+
*/
|
|
90
|
+
decideRouteTransition(to, from) {
|
|
91
|
+
const { next, prev } = pageGetters.transition
|
|
92
|
+
const { visitedViews } = tagsViewGetters
|
|
93
|
+
const fromKey = getRouterKey(from)
|
|
94
|
+
const toKey = getRouterKey(to)
|
|
95
|
+
|
|
96
|
+
// 这里认为页签数量不会太多,所以为了可读性使用两次循环查找
|
|
97
|
+
const fromIndex = visitedViews.findIndex(i => i.key === fromKey)
|
|
98
|
+
const toIndex = visitedViews.findIndex(i => i.key === toKey)
|
|
99
|
+
|
|
100
|
+
let transitionName = prev
|
|
101
|
+
|
|
102
|
+
// 新开tab也认为顺序高于上一个tab
|
|
103
|
+
if (toIndex === -1 || fromIndex < toIndex) {
|
|
104
|
+
transitionName = next
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pageMutations.transition({ curr: transitionName })
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// 初始化固定显示的页签
|
|
111
|
+
initTags() {
|
|
112
|
+
// TODO 如果页签栏初始化后菜单未加载,则固定页签会出问题
|
|
113
|
+
// 添加所有固定显示的页签
|
|
114
|
+
getAffixTagsFromMenuTree(this.$router, appGetters.menus).forEach(tagsViewMutations.addTagOnly)
|
|
115
|
+
|
|
116
|
+
// 将当前路由对象添加为页签
|
|
117
|
+
this.addTag(this.$route)
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* 尝试将路由对象添加为tab页
|
|
121
|
+
* @param route {import('vue-router').Route}
|
|
122
|
+
*/
|
|
123
|
+
addTag(route) {
|
|
124
|
+
tagsViewMutations.addTagAndCache({
|
|
125
|
+
...route,
|
|
126
|
+
meta: {
|
|
127
|
+
...route.meta,
|
|
128
|
+
title: getRouterTitle(route)
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
},
|
|
132
|
+
// 横向滚动条移动至当前tab
|
|
133
|
+
moveToCurrentTag() {
|
|
134
|
+
const { scroller } = this.$refs
|
|
135
|
+
const cur =
|
|
136
|
+
Array
|
|
137
|
+
.from(scroller.$el.children)
|
|
138
|
+
.find(el => el.classList.contains('active'))
|
|
139
|
+
cur && scroller.moveToTarget(cur)
|
|
140
|
+
},
|
|
141
|
+
/**
|
|
142
|
+
* 激活末尾页签
|
|
143
|
+
* @param refresh {boolean=} 目标路由是当前路由时是否需要刷新
|
|
144
|
+
* @return {Promise<Route>}
|
|
145
|
+
*/
|
|
146
|
+
gotoLastTag(refresh = false) {
|
|
147
|
+
const views = tagsViewGetters.visitedViews
|
|
148
|
+
const router = this.$router
|
|
149
|
+
|
|
150
|
+
if (views.length === 0) {
|
|
151
|
+
return router.push('/')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const latest = views[views.length - 1]
|
|
155
|
+
|
|
156
|
+
// 目标路由是当前路由时需要刷新,否则直接跳转
|
|
157
|
+
this.activeKey === latest.key
|
|
158
|
+
? refresh && refreshPage(router)
|
|
159
|
+
// 需要套一层$nextTick,否则tagsViewStore.visitedViews可能只会变动一次
|
|
160
|
+
: this.$nextTick(() => router.push(latest))
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 刷新所选页签
|
|
165
|
+
* @param view {View}
|
|
166
|
+
*/
|
|
167
|
+
refreshSelectedTag(view = this.selectedTag) {
|
|
168
|
+
refreshPage(this.$router, view)
|
|
169
|
+
},
|
|
170
|
+
/**
|
|
171
|
+
* 关闭所选页签
|
|
172
|
+
* @param view {VisitedView}
|
|
173
|
+
*/
|
|
174
|
+
closeSelectedTag(view = this.selectedTag) {
|
|
175
|
+
if (tagsViewGetters.visitedViews.length <= 1 || isAffix(view)) return
|
|
176
|
+
|
|
177
|
+
tagsViewMutations.delTagAndCache(view)
|
|
178
|
+
this.activeKey === view.key && this.gotoLastTag()
|
|
179
|
+
},
|
|
180
|
+
// 关闭除激活、固定页签以外的所有页签
|
|
181
|
+
closeOthersTags() {
|
|
182
|
+
tagsViewMutations.delOtherTagAndCache(this.selectedTag)
|
|
183
|
+
this.gotoLastTag()
|
|
184
|
+
},
|
|
185
|
+
// 关闭除固定页签以外的所有页签
|
|
186
|
+
closeAllTags() {
|
|
187
|
+
tagsViewMutations.delAllTagAndCache()
|
|
188
|
+
this.gotoLastTag(true)
|
|
189
|
+
},
|
|
190
|
+
/**
|
|
191
|
+
* 在页签上打开右键菜单
|
|
192
|
+
* @param tag {VisitedView}
|
|
193
|
+
* @param e {MouseEvent}
|
|
194
|
+
*/
|
|
195
|
+
openContextMenu(tag, e) {
|
|
196
|
+
e.preventDefault()
|
|
197
|
+
|
|
198
|
+
// 销毁之前的右键菜单实例
|
|
199
|
+
this.$contextmenu && this.$contextmenu()
|
|
200
|
+
|
|
201
|
+
this.selectedTag = tag
|
|
202
|
+
|
|
203
|
+
this.$contextmenu = useContextMenu(this.contextMenuItems, {
|
|
204
|
+
left: e.clientX + 15,
|
|
205
|
+
top: e.clientY + 5
|
|
206
|
+
})
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
renderTags() {
|
|
210
|
+
const { $createElement: h, $router, activeKey } = this
|
|
211
|
+
const { itemSlot, visitedViews } = tagsViewGetters
|
|
212
|
+
const renderFn = itemSlot || renderDefaultStyleTag
|
|
213
|
+
|
|
214
|
+
return visitedViews.map(view => {
|
|
215
|
+
const active = activeKey === view.key
|
|
216
|
+
const showClose = !isAffix(view) && visitedViews.length > 1
|
|
217
|
+
const on = {
|
|
218
|
+
contextmenu: e => this.openContextMenu(view, e)
|
|
219
|
+
}
|
|
220
|
+
const onClose = e => {
|
|
221
|
+
// 需要阻止事件冒泡,不然会触发tag的click事件
|
|
222
|
+
e.stopPropagation()
|
|
223
|
+
this.closeSelectedTag(view)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 非激活页签时,绑定点击事件,点击跳转到页签对应的路由
|
|
227
|
+
if (!active) {
|
|
228
|
+
on.click = () => $router.push(view, () => undefined)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return renderFn(h, {
|
|
232
|
+
key: view.key,
|
|
233
|
+
active,
|
|
234
|
+
on,
|
|
235
|
+
title: view.meta.title,
|
|
236
|
+
close: showClose && onClose
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
mounted() {
|
|
243
|
+
this.setActiveKey(this.$route)
|
|
244
|
+
this.initTags()
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
render() {
|
|
248
|
+
return (
|
|
249
|
+
<nav class="tags-view">
|
|
250
|
+
<HorizontalScroller ref="scroller" scopedSlots={this.tagsSlot}/>
|
|
251
|
+
</nav>
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
</script>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
.tags-view {
|
|
2
|
+
height: $tags-view-height;
|
|
3
|
+
background-color: $tags-view-background-color;
|
|
4
|
+
box-shadow: $tags-view-shadow;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
|
|
7
|
+
&-item {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
flex-shrink: 0;
|
|
11
|
+
border: $tags-view-item-border;
|
|
12
|
+
border-radius: $tags-view-item-border-radius;
|
|
13
|
+
padding: $tags-view-item-padding;
|
|
14
|
+
margin: $tags-view-item-between / 2;
|
|
15
|
+
font-size: $tags-view-item-font-size;
|
|
16
|
+
color: $--color-text-secondary;
|
|
17
|
+
background-color: $--color-white;
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
user-select: none;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
|
|
22
|
+
&:first-of-type {
|
|
23
|
+
margin-left: $tags-view-item-between;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&:last-of-type {
|
|
27
|
+
margin-right: $tags-view-item-between;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&.active {
|
|
31
|
+
color: $--color-primary;
|
|
32
|
+
|
|
33
|
+
.tags-view-item__dot {
|
|
34
|
+
background-color: $--color-primary;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&__dot {
|
|
39
|
+
background-color: #e8eaec;
|
|
40
|
+
width: $tags-view-item-font-size;
|
|
41
|
+
height: $tags-view-item-font-size;
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
margin-right: .5em;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.el-icon-close {
|
|
47
|
+
font-size: $tags-view-item-font-size + 2;
|
|
48
|
+
margin-left: 1em;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getRouterTitle } from '../../config/logic'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 判断页签是否固定
|
|
5
|
+
*
|
|
6
|
+
* @param view {View}
|
|
7
|
+
* @return {boolean} 是固定页签则返回true,否则false
|
|
8
|
+
*/
|
|
9
|
+
export function isAffix(view) {
|
|
10
|
+
return view ? view.meta.affix : false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 从菜单树中获取所有需要固定显示的页签
|
|
15
|
+
*
|
|
16
|
+
* @param router {VueRouter} - 路由实例
|
|
17
|
+
* @param menus {StoreMenuItem[]} - appStore中的菜单
|
|
18
|
+
* @return {View[]}
|
|
19
|
+
*/
|
|
20
|
+
export function getAffixTagsFromMenuTree(router, menus) {
|
|
21
|
+
return menus.reduce((affixTags, menu) => {
|
|
22
|
+
const { fullPath, children, meta } = menu
|
|
23
|
+
|
|
24
|
+
if (meta.affix === true) {
|
|
25
|
+
const { route } = router.resolve(fullPath)
|
|
26
|
+
|
|
27
|
+
affixTags.push({
|
|
28
|
+
...route,
|
|
29
|
+
meta: {
|
|
30
|
+
affix: true,
|
|
31
|
+
...route.meta,
|
|
32
|
+
title: getRouterTitle(route)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (children) {
|
|
38
|
+
const tempTags = getAffixTagsFromMenuTree(router, children)
|
|
39
|
+
tempTags.length && affixTags.push(...tempTags)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return affixTags
|
|
43
|
+
}, [])
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 渲染默认样式的页签
|
|
48
|
+
*
|
|
49
|
+
* @param h {import('vue').CreateElement} - 渲染函数
|
|
50
|
+
* @param key {string} - vnode的key值
|
|
51
|
+
* @param active {boolean=} - 是否激活
|
|
52
|
+
* @param on {{[k:string]:function}?} - 绑定的事件监听器
|
|
53
|
+
* @param title {string} - 页签文字
|
|
54
|
+
* @param close {function?} - 点击关闭按钮时触发的函数
|
|
55
|
+
* @return {import('vue').VNode}
|
|
56
|
+
*/
|
|
57
|
+
export function renderDefaultStyleTag(h, { key, active = false, on, title, close }) {
|
|
58
|
+
const className = `tags-view-item${active ? ' active' : ''}`
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div key={key} class={className} {...{ on }}>
|
|
62
|
+
<div class="tags-view-item__dot"/>
|
|
63
|
+
<span>{title}</span>
|
|
64
|
+
{close && <i class="el-icon-close" on-click={close}/>}
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一些可修改的常量
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
/**
|
|
7
|
+
* 移动端的最大宽度
|
|
8
|
+
* 默认为scss中的$max-mobile-width变量
|
|
9
|
+
* 该值的变动并不能影响样式,需要同时修改$max-mobile-width或覆盖相关样式
|
|
10
|
+
*/
|
|
11
|
+
maxMobileWidth: 500,
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* redirect的路径名
|
|
15
|
+
* 如果需要修改,则需要在injectDefaultRoute执行前修改
|
|
16
|
+
*/
|
|
17
|
+
redirectPath: '/redirect',
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 是否开启Layout组件的插槽功能
|
|
21
|
+
* 关闭后只能通过store使用render方式来自定义渲染内容,不过可以提高一丢丢性能
|
|
22
|
+
*/
|
|
23
|
+
enableLayoutSlot: true
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Const from './const'
|
|
2
|
+
import Redirect from '../component/Redirect'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 注入默认的路由
|
|
6
|
+
*
|
|
7
|
+
* @param layout 你的layout组件
|
|
8
|
+
* @returns {array}
|
|
9
|
+
*/
|
|
10
|
+
export function injectDefaultRoute(layout) {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
path: Const.redirectPath,
|
|
14
|
+
component: layout,
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
path: '*',
|
|
18
|
+
component: Redirect
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一些可能后续会修改的公用逻辑
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Const from './const'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 获取每个路由对应的唯一key
|
|
9
|
+
* @param route {Route}
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
export function getRouterKey(route) {
|
|
13
|
+
const { name, path, fullPath, meta: { usePathKey, useFullPathKey } = {} } = route
|
|
14
|
+
return usePathKey
|
|
15
|
+
? path
|
|
16
|
+
: useFullPathKey
|
|
17
|
+
? fullPath
|
|
18
|
+
: name || fullPath
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 获取路由标题
|
|
23
|
+
* @param route {Route}
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function getRouterTitle(route) {
|
|
27
|
+
const { title, dynamicTitle } = route.meta || {}
|
|
28
|
+
|
|
29
|
+
return typeof dynamicTitle === 'function'
|
|
30
|
+
? dynamicTitle(route) || title
|
|
31
|
+
: title
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 获取路由对应的激活菜单index
|
|
36
|
+
* @param route {Route}
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
export function getRouterActiveMenu(route) {
|
|
40
|
+
const { path, meta: { activeMenu } = {} } = route
|
|
41
|
+
|
|
42
|
+
return activeMenu || path
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 判断路由是否为redirect路由,是则返回true
|
|
47
|
+
* @param route {Route}
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
export function isRedirectRouter(route) {
|
|
51
|
+
const [first] = route.matched
|
|
52
|
+
return first && first.path === Const.redirectPath
|
|
53
|
+
}
|
package/src/helper.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 为避免循环依赖拆分出来的工具类
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Const } from './config'
|
|
6
|
+
import { tagsViewMutations } from './store'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 根据宽度判断是否为移动端,是则返回true
|
|
10
|
+
*
|
|
11
|
+
* @return {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export function isMobile() {
|
|
14
|
+
return window.innerWidth <= Const.maxMobileWidth
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 路由刷新
|
|
19
|
+
*
|
|
20
|
+
* @param router vue-router实例
|
|
21
|
+
* @param route 需要刷新的路由对象,默认为当前路由
|
|
22
|
+
* @param replace {boolean} 是否使用replace进行跳转
|
|
23
|
+
* @return {Promise} 返回vue-router跳转的结果
|
|
24
|
+
*/
|
|
25
|
+
export function refreshPage(router, route = router.currentRoute, replace = true) {
|
|
26
|
+
tagsViewMutations.delCacheOnly(route)
|
|
27
|
+
const to = `${Const.redirectPath}${route.fullPath}`
|
|
28
|
+
return router[replace ? 'replace' : 'push'](to)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 关闭当前页,如果传入next则跳转到next页面
|
|
33
|
+
*
|
|
34
|
+
* @param router vue-router实例
|
|
35
|
+
* @param next {string|route} 跳转的目标页面,作为第一个参数传入vue-router.replace
|
|
36
|
+
* @return {undefined|Promise} 仅在next有值时,返回vue-router.replace的结果
|
|
37
|
+
*/
|
|
38
|
+
export function closeCurrentPage(router, next) {
|
|
39
|
+
tagsViewMutations.delTagAndCache(router.currentRoute)
|
|
40
|
+
if (next) {
|
|
41
|
+
return router.replace(next)
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Layout from './component/Layout'
|
|
2
|
+
import Breadcrumb from './component/Breadcrumb'
|
|
3
|
+
import ContextMenu from './component/ContextMenu'
|
|
4
|
+
import Hamburger from './component/Hamburger'
|
|
5
|
+
import HorizontalResizableMenu from './component/HorizontalResizableMenu'
|
|
6
|
+
import Logo from './component/Logo'
|
|
7
|
+
import NavMenu from './component/NavMenu'
|
|
8
|
+
|
|
9
|
+
export default Layout
|
|
10
|
+
export { Breadcrumb, ContextMenu, Hamburger, HorizontalResizableMenu, Logo, NavMenu }
|
|
11
|
+
|
|
12
|
+
export * from './config'
|
|
13
|
+
export * from './store'
|
|
14
|
+
|
|
15
|
+
export { refreshPage, closeCurrentPage } from './helper'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 顶部菜单和侧边栏菜单的公共混入
|
|
3
|
+
*/
|
|
4
|
+
import { refreshPage } from '../helper'
|
|
5
|
+
import { getRouterKey } from '../config/logic'
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
data() {
|
|
9
|
+
return {
|
|
10
|
+
// 当前激活的菜单的fullPath
|
|
11
|
+
// 之所以手动维护是因为el-menu在点击后就会设置activeIndex
|
|
12
|
+
activeMenu: '',
|
|
13
|
+
|
|
14
|
+
// 传递给nav-menu,只会在activeMenu第一次变化时变化
|
|
15
|
+
defaultActive: ''
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
methods: {
|
|
20
|
+
// 点击菜单后的动作
|
|
21
|
+
actionOnSelectMenu(menuIndex, refreshWhenSame = true) {
|
|
22
|
+
// 外部链接时打开新窗口
|
|
23
|
+
if (menuIndex.startsWith('http')) {
|
|
24
|
+
window.open(menuIndex)
|
|
25
|
+
return this.resetActiveMenu()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { route } = this.$router.resolve(menuIndex)
|
|
29
|
+
|
|
30
|
+
if (route.matched.length === 0) {
|
|
31
|
+
console.warn(`点击菜单时出错,'${menuIndex}'没有对应的路由`)
|
|
32
|
+
return this.resetActiveMenu()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 触发的菜单会跳转到当前路由时,根据参数判断是否进行刷新
|
|
36
|
+
getRouterKey(this.$route) === getRouterKey(route)
|
|
37
|
+
? refreshWhenSame && refreshPage(this.$router)
|
|
38
|
+
: this.$router.push(route)
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// el-menu的高亮结果可能有误,所以手动更新
|
|
42
|
+
resetActiveMenu() {
|
|
43
|
+
const elMenu = this.$_getElMenuInstance()
|
|
44
|
+
|
|
45
|
+
// 仅当存在index为this.activeMenu的el-menu-item时才更新
|
|
46
|
+
if (elMenu && elMenu.items[this.activeMenu]) {
|
|
47
|
+
elMenu.updateActiveIndex(this.activeMenu)
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// 将defaultActive更新为activeMenu的值,如果两者相同会调用resetActiveMenu()
|
|
52
|
+
// 尽量少调用,defaultActive的变化将导致调用方以及nav-menu的重新渲染
|
|
53
|
+
setDefaultActiveMenu() {
|
|
54
|
+
const newVal = this.activeMenu
|
|
55
|
+
const oldVal = this.defaultActive
|
|
56
|
+
|
|
57
|
+
// 该值变化时,nav-menu会重新渲染来更新高亮菜单
|
|
58
|
+
this.defaultActive = newVal
|
|
59
|
+
|
|
60
|
+
// 未变化时需要手动更新
|
|
61
|
+
if (oldVal === newVal) {
|
|
62
|
+
this.resetActiveMenu()
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// 获取el-menu实例
|
|
67
|
+
$_getElMenuInstance() {
|
|
68
|
+
const navMenu = this.$refs['nav-menu']
|
|
69
|
+
return navMenu && navMenu.$refs['el-menu']
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|