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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -0
  3. package/package.json +45 -0
  4. package/src/component/Aside/DefaultSidebar/index.vue +354 -0
  5. package/src/component/Aside/DefaultSidebar/style.scss +32 -0
  6. package/src/component/Aside/index.vue +30 -0
  7. package/src/component/Aside/style.scss +23 -0
  8. package/src/component/Aside/theme-dark.scss +19 -0
  9. package/src/component/Aside/theme-light.scss +18 -0
  10. package/src/component/Breadcrumb/index.vue +111 -0
  11. package/src/component/Breadcrumb/style.scss +27 -0
  12. package/src/component/CachedRouterView/index.vue +162 -0
  13. package/src/component/ContextMenu/functionalUse.js +26 -0
  14. package/src/component/ContextMenu/index.vue +140 -0
  15. package/src/component/ContextMenu/style.scss +30 -0
  16. package/src/component/ElMenu/item.vue +66 -0
  17. package/src/component/ElMenu/mixin.js +36 -0
  18. package/src/component/ElMenu/sub.vue +118 -0
  19. package/src/component/Hamburger/index.vue +27 -0
  20. package/src/component/Hamburger/style.scss +3 -0
  21. package/src/component/Header/index.vue +131 -0
  22. package/src/component/Header/style.scss +74 -0
  23. package/src/component/Header/theme-dark.scss +31 -0
  24. package/src/component/Header/theme-light.scss +15 -0
  25. package/src/component/HorizontalResizableMenu/GhostMenu.vue +101 -0
  26. package/src/component/HorizontalResizableMenu/index.vue +280 -0
  27. package/src/component/HorizontalScroller/index.vue +91 -0
  28. package/src/component/HorizontalScroller/style.scss +12 -0
  29. package/src/component/Layout/index.vue +153 -0
  30. package/src/component/Layout/style.scss +42 -0
  31. package/src/component/LoadingSpinner/index.vue +17 -0
  32. package/src/component/Logo/index.vue +41 -0
  33. package/src/component/Logo/style.scss +26 -0
  34. package/src/component/NavMenu/index.vue +206 -0
  35. package/src/component/NavMenu/style.scss +159 -0
  36. package/src/component/NavMenu/theme-dark.scss +59 -0
  37. package/src/component/NavMenu/theme-light.scss +81 -0
  38. package/src/component/Page/content.vue +58 -0
  39. package/src/component/Page/iframe.vue +63 -0
  40. package/src/component/Page/index.vue +22 -0
  41. package/src/component/Page/style.scss +48 -0
  42. package/src/component/Redirect/index.vue +19 -0
  43. package/src/component/TagsView/index.vue +255 -0
  44. package/src/component/TagsView/style.scss +51 -0
  45. package/src/component/TagsView/util.js +67 -0
  46. package/src/config/const.js +24 -0
  47. package/src/config/defaultRoute.js +23 -0
  48. package/src/config/index.js +4 -0
  49. package/src/config/logic.js +53 -0
  50. package/src/helper.js +43 -0
  51. package/src/index.js +15 -0
  52. package/src/mixin/menu.js +72 -0
  53. package/src/store/app.js +132 -0
  54. package/src/store/aside.js +92 -0
  55. package/src/store/header.js +37 -0
  56. package/src/store/index.js +20 -0
  57. package/src/store/page.js +67 -0
  58. package/src/store/tagsView.js +186 -0
  59. package/src/store/util.js +35 -0
  60. package/src/style/index.scss +23 -0
  61. package/src/style/maxViewHeight.scss +65 -0
  62. package/src/style/transition.scss +71 -0
  63. package/src/style/var.scss +81 -0
  64. package/src/util.js +69 -0
  65. package/types/config.d.ts +12 -0
  66. package/types/helper.d.ts +10 -0
  67. package/types/index.d.ts +5 -0
  68. package/types/menu.d.ts +17 -0
  69. package/types/route.d.ts +15 -0
  70. package/types/store.d.ts +156 -0
  71. 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>
@@ -0,0 +1,3 @@
1
+ .hamburger.collapsed > i {
2
+ transform: rotate(180deg);
3
+ }