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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 toesbieya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# el-admin-layout
|
|
2
|
+
|
|
3
|
+
这是一个基于`element-ui`的后台管理layout组件,需要配合`vue2`、`vue-router3`使用。
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## 功能特性
|
|
7
|
+
|
|
8
|
+
- flex布局、有限的移动端适配
|
|
9
|
+
- 仿`ant-design-pro`的三种导航
|
|
10
|
+
- 多页签
|
|
11
|
+
- 侧边栏和顶栏的深色\亮色主题
|
|
12
|
+
- 侧边栏可搜索
|
|
13
|
+
- 路由和菜单分离
|
|
14
|
+
- 支持各种页面缓存(不支持嵌套路由)
|
|
15
|
+
- 支持iframe、外链
|
|
16
|
+
- 数据外部可控,满足大部分定制化需求
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
npm i el-admin-layout -S
|
|
22
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qdt-admin-layout",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "基于element-ui的后台管理layout",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"jsdelivr": "dist/index.umd.min.js",
|
|
8
|
+
"types": "types/index.d.ts",
|
|
9
|
+
"author": "toesbieya",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"homepage": "",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": ""
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": ""
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"serve": "vue-cli-service serve",
|
|
21
|
+
"build:example": "vue-cli-service build",
|
|
22
|
+
"build:lib-umd": "vue-cli-service build --target lib --no-clean --formats umd-min --name index build/umd.js",
|
|
23
|
+
"build:lib-esm": "node build/esm.js",
|
|
24
|
+
"build:css": "node build/css.js",
|
|
25
|
+
"dist": "npm run build:lib-umd && npm run build:lib-esm && npm run build:css"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"element-ui": "^2.15.0",
|
|
29
|
+
"vue": "^2.6.12"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@vue/cli-plugin-babel": "^5.0.0-rc.1",
|
|
33
|
+
"@vue/cli-service": "^5.0.0-rc.1",
|
|
34
|
+
"element-ui": "^2.15.7",
|
|
35
|
+
"sass": "^1.43.4",
|
|
36
|
+
"sass-loader": "^12.3.0",
|
|
37
|
+
"vue": "^2.6.14",
|
|
38
|
+
"vue-router": "^3.5.3",
|
|
39
|
+
"vue-template-compiler": "^2.6.14",
|
|
40
|
+
"vuex": "^3.6.2"
|
|
41
|
+
},
|
|
42
|
+
"browserslist": [
|
|
43
|
+
"Chrome > 80"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* 默认的侧边栏实现,支持以抽屉形式渲染
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import menuMixin from '../../../mixin/menu'
|
|
7
|
+
import { appGetters, asideGetters, asideMutations } from '../../../store'
|
|
8
|
+
import Logo from '../../../component/Logo'
|
|
9
|
+
import NavMenu from '../../../component/NavMenu'
|
|
10
|
+
import Hamburger from '../../../component/Hamburger'
|
|
11
|
+
import LoadingSpinner from '../../../component/LoadingSpinner'
|
|
12
|
+
import { getRouterActiveMenu, isRedirectRouter } from '../../../config/logic'
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
name: 'DefaultSidebar',
|
|
16
|
+
|
|
17
|
+
mixins: [menuMixin],
|
|
18
|
+
|
|
19
|
+
data() {
|
|
20
|
+
return {
|
|
21
|
+
// 开启了自动隐藏时,判断鼠标是否在侧边栏外
|
|
22
|
+
mouseOutside: true,
|
|
23
|
+
// 开启了自动隐藏时,用于判断鼠标是否在弹出菜单内
|
|
24
|
+
openedMenuNum: 0
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
computed: {
|
|
29
|
+
// 侧边栏菜单
|
|
30
|
+
sidebarMenus() {
|
|
31
|
+
const menus = appGetters.menus
|
|
32
|
+
let finalData
|
|
33
|
+
|
|
34
|
+
// 移动端时,侧边栏只会按侧边栏导航模式渲染
|
|
35
|
+
if (appGetters.isMobile) finalData = menus
|
|
36
|
+
else {
|
|
37
|
+
// 只有导航模式为aside或mix时才会渲染侧边栏
|
|
38
|
+
switch (appGetters.navMode) {
|
|
39
|
+
case 'aside':
|
|
40
|
+
finalData = menus
|
|
41
|
+
break
|
|
42
|
+
case 'mix':
|
|
43
|
+
const root = menus.find(i => i.fullPath === appGetters.activeRootMenu)
|
|
44
|
+
finalData = root ? root.children || [] : []
|
|
45
|
+
break
|
|
46
|
+
default:
|
|
47
|
+
finalData = []
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const f = asideGetters.postMenus
|
|
52
|
+
return f ? f(this.copyMenus(finalData)) : finalData
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// 当是移动端或设置了侧边栏自动隐藏时将侧边栏用抽屉包裹
|
|
56
|
+
renderInDrawer() {
|
|
57
|
+
return appGetters.isMobile || asideGetters.autoHide
|
|
58
|
+
},
|
|
59
|
+
// el-drawer的自定义类名
|
|
60
|
+
drawerClass() {
|
|
61
|
+
const behindHeader = !appGetters.isMobile && appGetters.struct === 'top-bottom'
|
|
62
|
+
return `sidebar-drawer${behindHeader ? ' behind-header' : ''}`
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// 侧边栏的显隐状态,true显示、false隐藏
|
|
66
|
+
show() {
|
|
67
|
+
// store中要显示那就显示
|
|
68
|
+
if (asideGetters.show) return true
|
|
69
|
+
|
|
70
|
+
// 移动端,false
|
|
71
|
+
if (appGetters.isMobile) return false
|
|
72
|
+
|
|
73
|
+
// 未设置侧边栏自动隐藏,false
|
|
74
|
+
if (!asideGetters.autoHide) return false
|
|
75
|
+
|
|
76
|
+
// 鼠标在侧边栏内,true
|
|
77
|
+
if (!this.mouseOutside) return true
|
|
78
|
+
|
|
79
|
+
// 侧边栏处于折叠状态 且 存在弹出的子菜单,true
|
|
80
|
+
return this.collapse && this.openedMenuNum > 0
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// 侧边栏的折叠状态,true折叠、false展开,仅在pc端可折叠
|
|
84
|
+
collapse() {
|
|
85
|
+
return asideGetters.collapse && !appGetters.isMobile
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// 是否需要显示logo
|
|
89
|
+
showLogo() {
|
|
90
|
+
return appGetters.showLogo && (appGetters.isMobile || appGetters.struct === 'left-right')
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
sidebarClass() {
|
|
94
|
+
return { 'sidebar': true, 'collapse': this.collapse }
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// 只有设置了自动隐藏时才需要绑定鼠标的移入移出事件
|
|
98
|
+
sidebarEvent() {
|
|
99
|
+
return !appGetters.isMobile && asideGetters.autoHide
|
|
100
|
+
? { mouseleave: this.onMouseLeave, mouseenter: this.onMouseEnter }
|
|
101
|
+
: undefined
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
watch: {
|
|
106
|
+
// 记录高亮菜单以及一些其他操作
|
|
107
|
+
$route: {
|
|
108
|
+
immediate: true,
|
|
109
|
+
handler(to) {
|
|
110
|
+
if (isRedirectRouter(to)) return
|
|
111
|
+
|
|
112
|
+
this.activeMenu = getRouterActiveMenu(this.$route)
|
|
113
|
+
|
|
114
|
+
const elMenu = this.$_getElMenuInstance()
|
|
115
|
+
if (!elMenu) return
|
|
116
|
+
|
|
117
|
+
this.resetActiveMenu()
|
|
118
|
+
|
|
119
|
+
const item = elMenu.items[this.activeMenu]
|
|
120
|
+
if (!item) return
|
|
121
|
+
|
|
122
|
+
// 由于elMenu的initOpenedMenu()不会触发select事件,所以选择手动触发
|
|
123
|
+
this.onSelect(item.index, item.indexPath, item, false)
|
|
124
|
+
|
|
125
|
+
// 滚动至激活的菜单
|
|
126
|
+
this.$nextTick(this.moveToActiveMenuVertically)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
methods: {
|
|
132
|
+
// 返回传入的菜单数据的拷贝副本
|
|
133
|
+
copyMenus(menus) {
|
|
134
|
+
return menus.map(menu => {
|
|
135
|
+
const result = { ...menu }
|
|
136
|
+
if (result.children) {
|
|
137
|
+
result.children = this.copyMenus(result.children)
|
|
138
|
+
}
|
|
139
|
+
return result
|
|
140
|
+
})
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// 模拟选中菜单
|
|
144
|
+
onSelect(index, indexPath, item, jump = true) {
|
|
145
|
+
// 非折叠且开启手风琴模式时,收起其它展开项
|
|
146
|
+
if (!asideGetters.collapse && asideGetters.uniqueOpen) {
|
|
147
|
+
const elMenu = this.$_getElMenuInstance()
|
|
148
|
+
elMenu.openedMenus = indexPath.slice(0, -1)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
jump && this.actionOnSelectMenu(index)
|
|
152
|
+
|
|
153
|
+
// 抽屉模式下需要关闭抽屉
|
|
154
|
+
if (this.renderInDrawer && this.show) {
|
|
155
|
+
asideMutations.close()
|
|
156
|
+
this.mouseOutside = true
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// 将当前激活的菜单移动到视窗中
|
|
161
|
+
moveToActiveMenuVertically() {
|
|
162
|
+
const elMenu = this.$_getElMenuInstance()
|
|
163
|
+
if (!elMenu) return
|
|
164
|
+
|
|
165
|
+
const cur = elMenu.activeIndex
|
|
166
|
+
if (!cur) return
|
|
167
|
+
|
|
168
|
+
const curInstance = elMenu.items[cur]
|
|
169
|
+
if (!curInstance) return
|
|
170
|
+
|
|
171
|
+
let el = curInstance.$el
|
|
172
|
+
|
|
173
|
+
// 当侧边栏折叠时,需要滚动至可视区域的元素是激活菜单的最顶层父节点
|
|
174
|
+
if (elMenu.collapse) {
|
|
175
|
+
let rootParent = curInstance
|
|
176
|
+
while (rootParent.$parent.$options.componentName !== 'ElMenu') {
|
|
177
|
+
rootParent = rootParent.$parent
|
|
178
|
+
}
|
|
179
|
+
el = rootParent.$el
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/*
|
|
183
|
+
* 这里考虑了菜单展开时的200ms动画时间
|
|
184
|
+
* 为什么不分情况讨论?比如当subMenu已经是展开状态时,无需延时滚动
|
|
185
|
+
* 但这种情况无法判断,因为这时menu.openedMenus已经包含了subMenu,无论subMenu之前是否展开
|
|
186
|
+
* 所以统一延时300ms
|
|
187
|
+
* */
|
|
188
|
+
window.setTimeout(() => el.scrollIntoView({ behavior: 'smooth', block: 'nearest' }), 300)
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// 开启侧边栏自动隐藏后的鼠标移动事件
|
|
192
|
+
onMouseMove(e) {
|
|
193
|
+
// 鼠标移动至屏幕左侧边缘时,标识鼠标在侧边栏内部
|
|
194
|
+
if (e.clientX <= 1) this.mouseOutside = false
|
|
195
|
+
},
|
|
196
|
+
onMouseLeave() {
|
|
197
|
+
this.mouseOutside = true
|
|
198
|
+
},
|
|
199
|
+
onMouseEnter() {
|
|
200
|
+
this.mouseOutside = false
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// 渲染el-menu时监听其展开菜单
|
|
204
|
+
watchOpenedMenus() {
|
|
205
|
+
// 尝试取消之前设置的监听
|
|
206
|
+
if (this.watchOpenedMenusCallback) {
|
|
207
|
+
this.watchOpenedMenusCallback()
|
|
208
|
+
this.watchOpenedMenusCallback = null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const elMenu = this.$_getElMenuInstance()
|
|
212
|
+
if (!elMenu) return
|
|
213
|
+
|
|
214
|
+
this.watchOpenedMenusCallback = elMenu.$watch('openedMenus', v => {
|
|
215
|
+
this.openedMenuNum = v.length
|
|
216
|
+
})
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// 抽屉打开的过渡动画结束时,滚动至高亮菜单
|
|
220
|
+
onDrawerOpened() {
|
|
221
|
+
this.$nextTick(this.moveToActiveMenuVertically)
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
renderHeader(h) {
|
|
225
|
+
const defaultContent = this.showLogo && <Logo show-title={!this.collapse}/>
|
|
226
|
+
const { headerSlot } = asideGetters
|
|
227
|
+
|
|
228
|
+
return headerSlot ? headerSlot(h, defaultContent) : defaultContent
|
|
229
|
+
},
|
|
230
|
+
renderFooter(h) {
|
|
231
|
+
const defaultContent = asideGetters.showHamburger && !this.renderInDrawer && <Hamburger/>
|
|
232
|
+
const { footerSlot } = asideGetters
|
|
233
|
+
|
|
234
|
+
let children
|
|
235
|
+
|
|
236
|
+
if (footerSlot) {
|
|
237
|
+
let renderResult = footerSlot(h, defaultContent)
|
|
238
|
+
if (Array.isArray(renderResult)) {
|
|
239
|
+
renderResult = renderResult.filter(Boolean)
|
|
240
|
+
if (renderResult.length > 0) {
|
|
241
|
+
children = renderResult
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else children = renderResult
|
|
245
|
+
}
|
|
246
|
+
else children = defaultContent
|
|
247
|
+
|
|
248
|
+
return children && (
|
|
249
|
+
<div class="sidebar-footer">
|
|
250
|
+
{children}
|
|
251
|
+
</div>
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
created() {
|
|
257
|
+
// 侧边栏菜单变化时设置当前的高亮菜单
|
|
258
|
+
// 在此前,activeMenu会在watch:$route中发生第一次变化(不会真有人把menu.meta.activeMenu设成''吧?)
|
|
259
|
+
this.$watch('sidebarMenus', this.setDefaultActiveMenu, { immediate: true })
|
|
260
|
+
|
|
261
|
+
// 添加或移除鼠标移动事件
|
|
262
|
+
this.$watch(
|
|
263
|
+
() => {
|
|
264
|
+
// 如果是移动端,false
|
|
265
|
+
if (appGetters.isMobile) return false
|
|
266
|
+
|
|
267
|
+
// 如果未启用侧边栏自动隐藏,false
|
|
268
|
+
if (!asideGetters.autoHide) return false
|
|
269
|
+
|
|
270
|
+
// 侧边栏为打开状态,false
|
|
271
|
+
if (this.show) return false
|
|
272
|
+
|
|
273
|
+
// 鼠标在侧边栏外部,true
|
|
274
|
+
return this.mouseOutside
|
|
275
|
+
},
|
|
276
|
+
v => {
|
|
277
|
+
// 尝试移除之前可能添加的事件
|
|
278
|
+
window.removeEventListener('mousemove', this.onMouseMove)
|
|
279
|
+
|
|
280
|
+
v && window.addEventListener('mousemove', this.onMouseMove)
|
|
281
|
+
},
|
|
282
|
+
{ immediate: true }
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
// 切换至移动端 或 切换至桌面端且设置了自动隐藏时,收起侧边栏
|
|
286
|
+
this.$watch(
|
|
287
|
+
() => appGetters.isMobile,
|
|
288
|
+
v => (v || asideGetters.autoHide) && asideMutations.close()
|
|
289
|
+
)
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
beforeDestroy() {
|
|
293
|
+
window.removeEventListener('mousemove', this.onMouseMove)
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
render(h) {
|
|
297
|
+
// 没有菜单时,仅当设置了alwaysRender才退出后续渲染
|
|
298
|
+
if (this.sidebarMenus.length === 0 && !asideGetters.alwaysRender) {
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const sidebar = (
|
|
303
|
+
<div {...{ class: this.sidebarClass, on: this.sidebarEvent }}>
|
|
304
|
+
{this.renderHeader(h)}
|
|
305
|
+
|
|
306
|
+
{appGetters.loadingMenu
|
|
307
|
+
? (
|
|
308
|
+
<div style="position: relative;flex: 1">
|
|
309
|
+
<LoadingSpinner/>
|
|
310
|
+
</div>
|
|
311
|
+
)
|
|
312
|
+
: <NavMenu
|
|
313
|
+
ref="nav-menu"
|
|
314
|
+
menus={this.sidebarMenus}
|
|
315
|
+
collapse={this.collapse}
|
|
316
|
+
default-active={this.defaultActive}
|
|
317
|
+
default-openeds={asideGetters.defaultOpeneds}
|
|
318
|
+
theme={asideGetters.theme}
|
|
319
|
+
unique-opened={asideGetters.uniqueOpen}
|
|
320
|
+
show-parent-on-collapse={asideGetters.showParentOnCollapse}
|
|
321
|
+
inline-indent={asideGetters.inlineIndent}
|
|
322
|
+
switch-transition-name={asideGetters.switchTransitionName}
|
|
323
|
+
menu-icon-slot={asideGetters.menuIconSlot}
|
|
324
|
+
menu-content-slot={asideGetters.menuContentSlot}
|
|
325
|
+
{...{
|
|
326
|
+
// 只能在nav-menu的mounted里,自身mounted时nav-menu可能还未渲染
|
|
327
|
+
on: { select: this.onSelect, 'hook:mounted': this.watchOpenedMenus }
|
|
328
|
+
}}
|
|
329
|
+
/>
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
{this.renderFooter(h)}
|
|
333
|
+
</div>
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if (!this.renderInDrawer) return sidebar
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<el-drawer
|
|
340
|
+
visible={this.show}
|
|
341
|
+
with-header={false}
|
|
342
|
+
custom-class={this.drawerClass}
|
|
343
|
+
modal={appGetters.isMobile} // 设置自动隐藏时不使用遮罩
|
|
344
|
+
direction="ltr"
|
|
345
|
+
size="auto"
|
|
346
|
+
on-close={asideMutations.close}
|
|
347
|
+
on-opened={this.onDrawerOpened}
|
|
348
|
+
>
|
|
349
|
+
{sidebar}
|
|
350
|
+
</el-drawer>
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
</script>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.sidebar {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
height: 100%;
|
|
5
|
+
width: $aside-width;
|
|
6
|
+
transition: width 0.2s;
|
|
7
|
+
|
|
8
|
+
> .el-menu {
|
|
9
|
+
flex: 1;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&.collapse {
|
|
13
|
+
width: 90px;
|
|
14
|
+
|
|
15
|
+
.logo-container {
|
|
16
|
+
padding-left: ($aside-collapse-width - $logo-size) / 2;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//侧边栏抽屉模式
|
|
22
|
+
.sidebar-drawer {
|
|
23
|
+
// 页面为上下结构时,侧边栏的抽屉模式需要扣除顶栏的高度
|
|
24
|
+
&.behind-header {
|
|
25
|
+
top: $header-height;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 为了el-menu能够滚动
|
|
29
|
+
> .el-drawer__body {
|
|
30
|
+
height: 100%;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* 侧边栏
|
|
4
|
+
* 拆成Aside和DefaultSidebar是为了能让用户自定义侧边栏内容
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { appGetters, asideGetters } from '../../store'
|
|
8
|
+
import DefaultSidebar from './DefaultSidebar'
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
name: 'Aside',
|
|
12
|
+
|
|
13
|
+
computed: {
|
|
14
|
+
// 移动端下不能设置z-index,否则会被el-drawer的遮罩遮住
|
|
15
|
+
style() {
|
|
16
|
+
return appGetters.isMobile ? 'z-index: auto !important' : undefined
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
render(h) {
|
|
21
|
+
const { defaultSlot } = asideGetters
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<aside class={`aside ${asideGetters.theme}`} style={this.style}>
|
|
25
|
+
{defaultSlot ? defaultSlot(h) : <DefaultSidebar ref="default-sidebar"/>}
|
|
26
|
+
</aside>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@import './DefaultSidebar/style';
|
|
2
|
+
|
|
3
|
+
.aside {
|
|
4
|
+
z-index: $header-z-index - 1;
|
|
5
|
+
|
|
6
|
+
// 侧边栏底部区域
|
|
7
|
+
.sidebar-footer {
|
|
8
|
+
border-top-width: 1px;
|
|
9
|
+
border-top-style: solid;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 底部汉堡包
|
|
13
|
+
.hamburger {
|
|
14
|
+
padding-left: $menu-padding;
|
|
15
|
+
height: 48px;
|
|
16
|
+
line-height: 48px;
|
|
17
|
+
font-size: $menu-icon-size;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@import './theme-light';
|
|
23
|
+
@import './theme-dark';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.aside.dark {
|
|
2
|
+
&,
|
|
3
|
+
// 侧边栏可能为抽屉
|
|
4
|
+
.el-drawer {
|
|
5
|
+
background-color: $menu-background-dark;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.logo-container {
|
|
9
|
+
color: $--color-white;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sidebar-footer {
|
|
13
|
+
border-top-color: $--border-color-dark;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.hamburger {
|
|
17
|
+
color: $menu-text-color-dark;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.aside.light {
|
|
2
|
+
.logo-container {
|
|
3
|
+
color: $--color-primary;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.root-sidebar {
|
|
7
|
+
background-color: $--color-white;
|
|
8
|
+
border-right-color: $--border-color-light;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.sidebar-footer {
|
|
12
|
+
border-top-color: $--border-color-light;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.hamburger {
|
|
16
|
+
color: $menu-text-color-light;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="breadcrumb">
|
|
3
|
+
<transition-group name="breadcrumb">
|
|
4
|
+
<div
|
|
5
|
+
v-for="({fullPath, meta: {title}}, index) in items"
|
|
6
|
+
v-if="index !== items.length - 1"
|
|
7
|
+
:key="fullPath"
|
|
8
|
+
class="breadcrumb-item"
|
|
9
|
+
>
|
|
10
|
+
<span class="breadcrumb-inner is-link" @click="() => onClick(fullPath)">
|
|
11
|
+
{{ title }}
|
|
12
|
+
</span>
|
|
13
|
+
<span class="breadcrumb-separator">/</span>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div v-if="lastItem" :key="lastItem.fullPath" class="breadcrumb-item">
|
|
17
|
+
<span class="breadcrumb-inner">{{ lastItem.meta.title }}</span>
|
|
18
|
+
</div>
|
|
19
|
+
</transition-group>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script>
|
|
24
|
+
/**
|
|
25
|
+
* 面包屑,用于页面(Page)的页头
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { getMenuByFullPath } from '../../store'
|
|
29
|
+
import { getRouterKey, getRouterTitle, isRedirectRouter } from '../../config/logic'
|
|
30
|
+
|
|
31
|
+
export default {
|
|
32
|
+
name: 'Breadcrumb',
|
|
33
|
+
|
|
34
|
+
data() {
|
|
35
|
+
return {
|
|
36
|
+
items: []
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
computed: {
|
|
41
|
+
lastItem() {
|
|
42
|
+
return this.items.length <= 1
|
|
43
|
+
? undefined
|
|
44
|
+
: this.items[this.items.length - 1]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
watch: {
|
|
49
|
+
$route: {
|
|
50
|
+
handler(to) {
|
|
51
|
+
const result = this.generateBreadcrumb(to)
|
|
52
|
+
if (Array.isArray(result) && result.length !== 0) {
|
|
53
|
+
this.items = result
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
immediate: true
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
methods: {
|
|
61
|
+
onClick(fullPath) {
|
|
62
|
+
let menu = getMenuByFullPath(fullPath)
|
|
63
|
+
if (!menu) return
|
|
64
|
+
|
|
65
|
+
// 找到其叶子菜单
|
|
66
|
+
while (menu.children && menu.children.length >= 1) {
|
|
67
|
+
menu = menu.children[0]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { route } = this.$router.resolve(menu.fullPath)
|
|
71
|
+
|
|
72
|
+
// 不刷新
|
|
73
|
+
if (getRouterKey(route) !== getRouterKey(this.$route)) {
|
|
74
|
+
this.$router.push(route)
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
generateBreadcrumb(route) {
|
|
79
|
+
const { path, fullPath, meta: { activeMenu } } = route
|
|
80
|
+
|
|
81
|
+
// 刷新时返回undefined
|
|
82
|
+
if (isRedirectRouter(route)) return
|
|
83
|
+
|
|
84
|
+
// 使用route.path而非fullPath进行匹配
|
|
85
|
+
const menuFullPath = activeMenu || path
|
|
86
|
+
const menu = getMenuByFullPath(menuFullPath)
|
|
87
|
+
|
|
88
|
+
// 没有匹配的菜单
|
|
89
|
+
if (!menu) return []
|
|
90
|
+
|
|
91
|
+
// 将菜单的所有父级放入数组
|
|
92
|
+
const items = [menu]
|
|
93
|
+
let parent = menu.parent
|
|
94
|
+
while (parent) {
|
|
95
|
+
items.unshift(parent)
|
|
96
|
+
parent = parent.parent
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 使用了activeMenu的还需要拼接上自己的标题
|
|
100
|
+
if (activeMenu) {
|
|
101
|
+
items.push({
|
|
102
|
+
fullPath,
|
|
103
|
+
meta: { title: getRouterTitle(route) }
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return items
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
</script>
|