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,27 @@
|
|
|
1
|
+
.breadcrumb {
|
|
2
|
+
white-space: nowrap;
|
|
3
|
+
font-size: $--font-size-base;
|
|
4
|
+
|
|
5
|
+
&-separator {
|
|
6
|
+
margin: 0 9px;
|
|
7
|
+
color: $--color-text-placeholder;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
&-item {
|
|
11
|
+
display: inline-block;
|
|
12
|
+
|
|
13
|
+
&:not(:last-child) .breadcrumb-inner {
|
|
14
|
+
color: $--color-text-regular;
|
|
15
|
+
transition: $--color-transition-base;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
|
|
18
|
+
&:hover {
|
|
19
|
+
color: $--color-primary;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&:last-child .breadcrumb-inner {
|
|
24
|
+
color: $--color-text-secondary;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* 自行实现缓存控制的router-view,不支持嵌套路由
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { pageGetters, tagsViewGetters } from '../../store'
|
|
7
|
+
import { getRouterKey } from '../../config/logic'
|
|
8
|
+
import { isEmpty } from '../../util'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 根据指定的key清除keep-alive组件内的缓存
|
|
12
|
+
*
|
|
13
|
+
* @param key {string}
|
|
14
|
+
* @param instance {Vue} keep-alive组件实例
|
|
15
|
+
*/
|
|
16
|
+
function pruneCacheEntry(key, instance) {
|
|
17
|
+
if (!instance) return
|
|
18
|
+
|
|
19
|
+
const { cache, _vnode: current } = instance
|
|
20
|
+
const entry = cache[key]
|
|
21
|
+
|
|
22
|
+
if (entry && (!current || entry.tag !== current.tag)) {
|
|
23
|
+
entry.componentInstance.$destroy()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
delete cache[key]
|
|
27
|
+
|
|
28
|
+
// keep-alive中的keys仅是用于max,所以直接清空
|
|
29
|
+
instance.keys = []
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 获取组件构造器的id
|
|
34
|
+
*
|
|
35
|
+
* @param instance {Vue}
|
|
36
|
+
* @returns {number}
|
|
37
|
+
*/
|
|
38
|
+
function getCtorId(instance) {
|
|
39
|
+
const opt = instance.$vnode.componentOptions
|
|
40
|
+
return opt && opt.Ctor.cid
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default {
|
|
44
|
+
name: 'CachedRouterView',
|
|
45
|
+
|
|
46
|
+
inheritAttrs: true,
|
|
47
|
+
|
|
48
|
+
data() {
|
|
49
|
+
return {
|
|
50
|
+
renderView: true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
computed: {
|
|
55
|
+
// 当前路由的唯一标识
|
|
56
|
+
routerViewKey() {
|
|
57
|
+
return getRouterKey(this.$route)
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// 是否使用keep-alive
|
|
61
|
+
useKeepAlive() {
|
|
62
|
+
return tagsViewGetters.enabled && tagsViewGetters.enableCache
|
|
63
|
+
},
|
|
64
|
+
// 传递给keep-alive组件的属性
|
|
65
|
+
keepAliveData() {
|
|
66
|
+
const timestamp = Date.now()
|
|
67
|
+
// 确保缓存控制数组变动时,keep-alive也会更新
|
|
68
|
+
// 因为会有关掉的页签非当前页签的情况,此时cachedViews虽然变化,但keep-alive不会更新
|
|
69
|
+
const exclude = tagsViewGetters.cachedViews.map(key => key + timestamp)
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
props: { exclude },
|
|
73
|
+
on: { 'hook:updated': this.onKeepAliveUpdated }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
// 加速读取的缓存key哈希表
|
|
77
|
+
cachedKeyMap() {
|
|
78
|
+
return tagsViewGetters.cachedViews.reduce((map, key) => {
|
|
79
|
+
map[key] = 1
|
|
80
|
+
return map
|
|
81
|
+
}, {})
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// 是否使用过渡动画
|
|
85
|
+
useTransition() {
|
|
86
|
+
return pageGetters.enableTransition
|
|
87
|
+
},
|
|
88
|
+
// 过渡动画名称
|
|
89
|
+
transitionName() {
|
|
90
|
+
return pageGetters.transition.curr
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
methods: {
|
|
95
|
+
// 获取keep-alive实例
|
|
96
|
+
getKeepAliveInstance() {
|
|
97
|
+
if (!this._vnode) return
|
|
98
|
+
|
|
99
|
+
const vnode = this._vnode.children[0]
|
|
100
|
+
|
|
101
|
+
// 未被transition包裹时
|
|
102
|
+
if (!pageGetters.enableTransition) {
|
|
103
|
+
return vnode.componentInstance
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return vnode.child._vnode.componentInstance
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// 在keep-alive更新后对其缓存做处理
|
|
110
|
+
onKeepAliveUpdated() {
|
|
111
|
+
const instance = this.getKeepAliveInstance()
|
|
112
|
+
const { cache } = instance
|
|
113
|
+
const hasTransition = pageGetters.enableTransition
|
|
114
|
+
|
|
115
|
+
// 由于keep-alive会缓存所有,所以需要清除不需要缓存的部分
|
|
116
|
+
Object.keys(cache).forEach(key => {
|
|
117
|
+
const routerKey = hasTransition
|
|
118
|
+
// 被transition包裹时,key会加上一个前缀
|
|
119
|
+
? key.replace(/__transition-(\d+)-/, '')
|
|
120
|
+
: key
|
|
121
|
+
!this.cachedKeyMap[routerKey] && pruneCacheEntry(key, instance)
|
|
122
|
+
|
|
123
|
+
// 解决HMR无效
|
|
124
|
+
if (process.env.NODE_ENV === 'development') {
|
|
125
|
+
if (cache[key]) {
|
|
126
|
+
const comp = cache[key].componentInstance
|
|
127
|
+
const lastCtorId = cache[key]._ctorId
|
|
128
|
+
const ctorId = (cache[key]._ctorId = getCtorId(comp))
|
|
129
|
+
|
|
130
|
+
if (!isEmpty(lastCtorId) && lastCtorId !== ctorId) {
|
|
131
|
+
pruneCacheEntry(key, instance)
|
|
132
|
+
this.rerenderRouterView()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// 强制router-view重新渲染
|
|
140
|
+
rerenderRouterView() {
|
|
141
|
+
if (!this.renderView) return
|
|
142
|
+
|
|
143
|
+
this.renderView = false
|
|
144
|
+
this.$nextTick(() => this.renderView = true)
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
render(h) {
|
|
149
|
+
let view = this.renderView && <router-view key={this.routerViewKey}/>
|
|
150
|
+
|
|
151
|
+
if (this.useKeepAlive) {
|
|
152
|
+
view = <keep-alive {...this.keepAliveData}>{view}</keep-alive>
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (this.useTransition) {
|
|
156
|
+
view = <transition name={this.transitionName} mode="out-in">{view}</transition>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return <div>{view}</div>
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Vue from 'vue'
|
|
2
|
+
import ContextMenu from './index'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 函数形式创建右键菜单
|
|
6
|
+
* 注意!每次调用时都会创建一个新的实例,需要自行销毁上一次产生的实例
|
|
7
|
+
*
|
|
8
|
+
* @param items {{content:string,click:function}[]} 菜单数组
|
|
9
|
+
* @param options {{left:number,top:number,minDistance?:number}} 配置项
|
|
10
|
+
* @return {function} 关闭右键菜单的方法
|
|
11
|
+
*/
|
|
12
|
+
export default function(items, options) {
|
|
13
|
+
const ctor = Vue.extend(ContextMenu)
|
|
14
|
+
const instance = new ctor({ propsData: { value: true, items, ...options } })
|
|
15
|
+
|
|
16
|
+
instance.$mount()
|
|
17
|
+
instance.$once('input', value => {
|
|
18
|
+
if (!value) {
|
|
19
|
+
instance.$destroy()
|
|
20
|
+
document.body.removeChild(instance.$el)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
document.body.appendChild(instance.$el)
|
|
24
|
+
|
|
25
|
+
return () => instance.close()
|
|
26
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ul
|
|
3
|
+
v-show="value"
|
|
4
|
+
class="context-menu"
|
|
5
|
+
:style="style"
|
|
6
|
+
@contextmenu.prevent
|
|
7
|
+
@click="onClick"
|
|
8
|
+
>
|
|
9
|
+
<li
|
|
10
|
+
v-for="(i, index) in menuItems"
|
|
11
|
+
:key="i.content"
|
|
12
|
+
:data-index="index"
|
|
13
|
+
class="context-menu-item"
|
|
14
|
+
>
|
|
15
|
+
{{ i.content }}
|
|
16
|
+
</li>
|
|
17
|
+
</ul>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
/**
|
|
22
|
+
* 右键菜单,用于页签栏,右键点击页签时弹出
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
name: 'ContextMenu',
|
|
27
|
+
|
|
28
|
+
props: {
|
|
29
|
+
// 是否显示,支持v-modal
|
|
30
|
+
value: Boolean,
|
|
31
|
+
// 菜单定义数组,{content: string 菜单文字, click: function 点击菜单时触发的函数}
|
|
32
|
+
items: Array,
|
|
33
|
+
// 菜单距离屏幕左侧的距离,单位px
|
|
34
|
+
left: Number,
|
|
35
|
+
// 菜单距离屏幕顶部的距离,单位px
|
|
36
|
+
top: Number,
|
|
37
|
+
// 菜单距离屏幕边缘的最小距离,单位px
|
|
38
|
+
minDistance: { type: Number, default: 10 }
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
data() {
|
|
42
|
+
this.willAutoAdaptLeft = false
|
|
43
|
+
this.willAutoAdaptTop = false
|
|
44
|
+
return {
|
|
45
|
+
realLeft: '0px',
|
|
46
|
+
realTop: '0px'
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
computed: {
|
|
51
|
+
style() {
|
|
52
|
+
return { left: this.realLeft, top: this.realTop }
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
menuItems() {
|
|
56
|
+
return this.items.filter(Boolean)
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
watch: {
|
|
61
|
+
value: {
|
|
62
|
+
immediate: true,
|
|
63
|
+
handler(v) {
|
|
64
|
+
document.body[v ? 'addEventListener' : 'removeEventListener']('click', this.close)
|
|
65
|
+
if (v) {
|
|
66
|
+
this.willAutoAdaptLeft = true
|
|
67
|
+
this.willAutoAdaptTop = true
|
|
68
|
+
this.$nextTick(this.autoAdapt)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
left: {
|
|
73
|
+
immediate: true,
|
|
74
|
+
handler: 'autoAdaptLeft'
|
|
75
|
+
},
|
|
76
|
+
top: {
|
|
77
|
+
immediate: true,
|
|
78
|
+
handler: 'autoAdaptTop'
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
methods: {
|
|
83
|
+
close() {
|
|
84
|
+
this.$emit('input', false)
|
|
85
|
+
},
|
|
86
|
+
autoAdapt() {
|
|
87
|
+
this.autoAdaptTop(this.top)
|
|
88
|
+
this.autoAdaptLeft(this.left)
|
|
89
|
+
},
|
|
90
|
+
autoAdaptTop(v) {
|
|
91
|
+
if (this.willAutoAdaptTop) {
|
|
92
|
+
this.willAutoAdaptTop = false
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
if (!this.value || v == null) return
|
|
96
|
+
|
|
97
|
+
const elHeight = this.$el.offsetHeight
|
|
98
|
+
const remainHeight = document.body.clientHeight - v - this.minDistance
|
|
99
|
+
const over = elHeight - remainHeight
|
|
100
|
+
const finalTop = over > 0 ? v - over : v
|
|
101
|
+
|
|
102
|
+
this.realTop = `${finalTop}px`
|
|
103
|
+
},
|
|
104
|
+
autoAdaptLeft(v) {
|
|
105
|
+
if (this.willAutoAdaptLeft) {
|
|
106
|
+
this.willAutoAdaptLeft = false
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
if (!this.value || v == null) return
|
|
110
|
+
|
|
111
|
+
const elWidth = this.$el.offsetWidth
|
|
112
|
+
const remainWidth = document.body.clientWidth - v - this.minDistance
|
|
113
|
+
const over = elWidth - remainWidth
|
|
114
|
+
const finalLeft = over > 0 ? v - over : v
|
|
115
|
+
|
|
116
|
+
this.realLeft = `${finalLeft}px`
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 事件代理
|
|
121
|
+
*
|
|
122
|
+
* @param event {Event}
|
|
123
|
+
*/
|
|
124
|
+
onClick(event) {
|
|
125
|
+
if (!event.target.classList.contains('context-menu-item')) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const index = Number(event.target.dataset.index)
|
|
130
|
+
const menuItem = this.menuItems[index]
|
|
131
|
+
|
|
132
|
+
menuItem && menuItem.click()
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
beforeDestroy() {
|
|
137
|
+
document.body.removeEventListener('click', this.close)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.context-menu {
|
|
2
|
+
position: fixed;
|
|
3
|
+
z-index: $--index-popper;
|
|
4
|
+
padding: 5px 0;
|
|
5
|
+
margin: 0;
|
|
6
|
+
border-radius: 4px;
|
|
7
|
+
font-size: 12px;
|
|
8
|
+
max-height: 50vh;
|
|
9
|
+
color: $--color-white;
|
|
10
|
+
background-color: $menu-background-dark;
|
|
11
|
+
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
|
|
12
|
+
overflow-y: auto;
|
|
13
|
+
overflow-x: hidden;
|
|
14
|
+
|
|
15
|
+
&::-webkit-scrollbar {
|
|
16
|
+
display: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&-item {
|
|
20
|
+
margin: 0;
|
|
21
|
+
padding: 7px 16px;
|
|
22
|
+
white-space: nowrap;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
list-style-type: none;
|
|
25
|
+
|
|
26
|
+
&:hover {
|
|
27
|
+
color: $--color-primary;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<li :class="className" :style="paddingStyle" @click="handleClick">
|
|
3
|
+
<el-tooltip v-if="showTooltip" effect="dark" placement="right">
|
|
4
|
+
<div :style="iconContainerStyle">
|
|
5
|
+
<slot />
|
|
6
|
+
</div>
|
|
7
|
+
<template v-slot:content>
|
|
8
|
+
<slot name="title" />
|
|
9
|
+
</template>
|
|
10
|
+
</el-tooltip>
|
|
11
|
+
|
|
12
|
+
<template v-else>
|
|
13
|
+
<slot />
|
|
14
|
+
<slot name="title" />
|
|
15
|
+
</template>
|
|
16
|
+
</li>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
/**
|
|
21
|
+
* 改写el-menu-item
|
|
22
|
+
*
|
|
23
|
+
* 移除了所有背景色、hover颜色,由样式控制
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import MenuMixin from "./mixin";
|
|
27
|
+
import { MenuItem } from "element-ui";
|
|
28
|
+
|
|
29
|
+
export default {
|
|
30
|
+
name: MenuItem.name,
|
|
31
|
+
|
|
32
|
+
componentName: MenuItem.componentName,
|
|
33
|
+
|
|
34
|
+
mixins: [MenuMixin, ...MenuItem.mixins.slice(1)],
|
|
35
|
+
|
|
36
|
+
props: MenuItem.props,
|
|
37
|
+
|
|
38
|
+
computed: {
|
|
39
|
+
active: MenuItem.computed.active,
|
|
40
|
+
className() {
|
|
41
|
+
return {
|
|
42
|
+
"el-menu-item": true,
|
|
43
|
+
"is-active": this.active,
|
|
44
|
+
"is-disabled": this.disabled,
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
showTooltip() {
|
|
48
|
+
return (
|
|
49
|
+
this.parentMenu.$options.componentName === "ElMenu" &&
|
|
50
|
+
this.rootMenu.collapse
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
iconContainerStyle() {
|
|
54
|
+
return `position: absolute;left: 0;top: 0;height: 100%;width: 100%;display: inline-block;box-sizing: border-box;padding: 0 ${this.inlineIndent}px;`;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
methods: {
|
|
59
|
+
handleClick: MenuItem.methods.handleClick,
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
mounted: MenuItem.mounted,
|
|
63
|
+
|
|
64
|
+
beforeDestroy: MenuItem.beforeDestroy,
|
|
65
|
+
};
|
|
66
|
+
</script>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 在原基础上增加了 inlineIndent 属性,用于确定每级节点的单位缩进距离
|
|
3
|
+
*/
|
|
4
|
+
import { MenuItem } from 'element-ui'
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
...MenuItem.mixins[0],
|
|
8
|
+
|
|
9
|
+
props: {
|
|
10
|
+
inlineIndent: {
|
|
11
|
+
type: Number, default: 20
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
computed: {
|
|
16
|
+
...MenuItem.mixins[0].computed,
|
|
17
|
+
|
|
18
|
+
paddingStyle() {
|
|
19
|
+
if (this.rootMenu.mode !== 'vertical') return undefined
|
|
20
|
+
|
|
21
|
+
let padding = this.inlineIndent
|
|
22
|
+
|
|
23
|
+
if (!this.rootMenu.collapse) {
|
|
24
|
+
let parent = this.$parent
|
|
25
|
+
while (parent && parent.$options.componentName !== 'ElMenu') {
|
|
26
|
+
if (parent.$options.componentName === 'ElSubmenu') {
|
|
27
|
+
padding += this.inlineIndent
|
|
28
|
+
}
|
|
29
|
+
parent = parent.$parent
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { 'padding-left': `${padding}px` }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* 改写el-submenu
|
|
4
|
+
*
|
|
5
|
+
* 移除了所有背景色、hover颜色,由样式控制
|
|
6
|
+
* props中popperAppendToBody默认为true
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import MenuMixin from "./mixin";
|
|
10
|
+
import { Submenu } from "element-ui";
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
name: Submenu.name,
|
|
14
|
+
|
|
15
|
+
componentName: Submenu.componentName,
|
|
16
|
+
|
|
17
|
+
mixins: [MenuMixin, ...Submenu.mixins.slice(1)],
|
|
18
|
+
|
|
19
|
+
props: {
|
|
20
|
+
...Submenu.props,
|
|
21
|
+
popperAppendToBody: { type: Boolean, default: true },
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
data: Submenu.data,
|
|
25
|
+
|
|
26
|
+
computed: {
|
|
27
|
+
appendToBody: Submenu.computed.appendToBody,
|
|
28
|
+
menuTransitionName: Submenu.computed.menuTransitionName,
|
|
29
|
+
opened: Submenu.computed.opened,
|
|
30
|
+
active: Submenu.computed.active,
|
|
31
|
+
mode: Submenu.computed.mode,
|
|
32
|
+
isMenuPopup: Submenu.computed.isMenuPopup,
|
|
33
|
+
isFirstLevel: Submenu.computed.isFirstLevel,
|
|
34
|
+
className() {
|
|
35
|
+
return {
|
|
36
|
+
"el-submenu": true,
|
|
37
|
+
"is-active": this.active,
|
|
38
|
+
"is-opened": this.opened,
|
|
39
|
+
"is-disabled": this.disabled,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
submenuArrowClass() {
|
|
43
|
+
const icon =
|
|
44
|
+
(this.rootMenu.mode === "horizontal" && this.isFirstLevel) ||
|
|
45
|
+
(this.rootMenu.mode === "vertical" && !this.rootMenu.collapse)
|
|
46
|
+
? "el-icon-arrow-down"
|
|
47
|
+
: "el-icon-arrow-right";
|
|
48
|
+
return "el-submenu__icon-arrow " + icon;
|
|
49
|
+
},
|
|
50
|
+
popupMenuClass() {
|
|
51
|
+
return `el-menu el-menu--popup el-menu--popup-${this.currentPlacement}`;
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
watch: Submenu.watch,
|
|
56
|
+
|
|
57
|
+
methods: Submenu.methods,
|
|
58
|
+
|
|
59
|
+
created: Submenu.created,
|
|
60
|
+
|
|
61
|
+
mounted: Submenu.mounted,
|
|
62
|
+
|
|
63
|
+
beforeDestroy: Submenu.beforeDestroy,
|
|
64
|
+
|
|
65
|
+
render() {
|
|
66
|
+
return (
|
|
67
|
+
<li
|
|
68
|
+
class={this.className}
|
|
69
|
+
on-mouseenter={this.handleMouseenter}
|
|
70
|
+
on-mouseleave={() => this.handleMouseleave(false)}
|
|
71
|
+
on-focus={this.handleMouseenter}
|
|
72
|
+
>
|
|
73
|
+
<div
|
|
74
|
+
ref="submenu-title"
|
|
75
|
+
class="el-submenu__title"
|
|
76
|
+
on-click={this.handleClick}
|
|
77
|
+
>
|
|
78
|
+
{this.$slots.title}
|
|
79
|
+
<i class={this.submenuArrowClass} />
|
|
80
|
+
</div>
|
|
81
|
+
{this.isMenuPopup ? (
|
|
82
|
+
<transition name={this.menuTransitionName}>
|
|
83
|
+
<div
|
|
84
|
+
ref="menu"
|
|
85
|
+
v-show={this.opened}
|
|
86
|
+
class={[`el-menu--${this.mode}`, this.popperClass]}
|
|
87
|
+
on-mouseenter={(e) => this.handleMouseenter(e, 100)}
|
|
88
|
+
on-mouseleave={() => this.handleMouseleave(true)}
|
|
89
|
+
on-focus={(e) => this.handleMouseenter(e, 100)}
|
|
90
|
+
>
|
|
91
|
+
{this.$slots.default.map((item) => {
|
|
92
|
+
return (
|
|
93
|
+
<ul
|
|
94
|
+
style="display:inline-block;width:200px"
|
|
95
|
+
class={this.popupMenuClass}
|
|
96
|
+
>
|
|
97
|
+
{item}
|
|
98
|
+
</ul>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
</div>
|
|
102
|
+
</transition>
|
|
103
|
+
) : (
|
|
104
|
+
<el-collapse-transition>
|
|
105
|
+
<ul
|
|
106
|
+
style="display:inline-block"
|
|
107
|
+
v-show={this.opened}
|
|
108
|
+
class="el-menu el-menu--inline"
|
|
109
|
+
>
|
|
110
|
+
{this.$slots.default}
|
|
111
|
+
</ul>
|
|
112
|
+
</el-collapse-transition>
|
|
113
|
+
)}
|
|
114
|
+
</li>
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* 汉堡包,控制侧边栏的展开折叠状态(如果侧边栏是抽屉形式,则是显示和隐藏),用于侧边栏底部和顶栏左侧
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { appGetters, asideGetters, asideMutations } from '../../store'
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'Hamburger',
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
// 考虑侧边栏可能是抽屉的情况
|
|
13
|
+
const collapsed = appGetters.isMobile ? asideGetters.show : !asideGetters.collapse
|
|
14
|
+
const className = `hamburger${collapsed ? ' collapsed' : ''}`
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
class={className}
|
|
19
|
+
title={`${collapsed ? '折叠' : '展开'}侧边栏`}
|
|
20
|
+
on-click={asideMutations.switch}
|
|
21
|
+
>
|
|
22
|
+
<i class="el-icon-s-unfold"/>
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
</script>
|