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,131 @@
1
+ <script>
2
+ /**
3
+ * 顶栏
4
+ */
5
+
6
+ //TODO header会在head-menu渲染后再次渲染,初步排查是head-menu中的activeMenu改变所致
7
+ import { appGetters, headerGetters } from "../../store";
8
+ import Logo from "../../component/Logo";
9
+ import Hamburger from "../../component/Hamburger";
10
+ import HorizontalResizableMenu from "../../component/HorizontalResizableMenu";
11
+ import { refreshPage } from "../../helper";
12
+ import { isEmpty } from "../../util";
13
+
14
+ export default {
15
+ name: "Header",
16
+
17
+ computed: {
18
+ // 左侧logo
19
+ defaultLogo() {
20
+ // 渲染顶栏logo的条件
21
+ // ①桌面端
22
+ // ②设置了显示logo
23
+ // ③导航模式为顶部导航或页面为上下结构
24
+ const renderLogo =
25
+ !appGetters.isMobile &&
26
+ appGetters.showLogo &&
27
+ (appGetters.navMode === "head" || appGetters.struct === "top-bottom");
28
+ return renderLogo && <Logo show-title />;
29
+ },
30
+ // 左侧汉堡包
31
+ defaultHamburger() {
32
+ // 移动端时必须渲染,不然侧边栏怎么出来
33
+ return (
34
+ appGetters.isMobile && <Hamburger class="header-item header-icon" />
35
+ );
36
+ },
37
+ // 中间的导航菜单
38
+ defaultHeadMenu() {
39
+ // 渲染顶部导航菜单的条件
40
+ // ①桌面端
41
+ // ②导航模式为顶部导航或混合导航
42
+ const renderHeadMenu =
43
+ !appGetters.isMobile && ["head", "aside"].includes(appGetters.navMode);
44
+
45
+ return renderHeadMenu && <HorizontalResizableMenu ref="head-menu" />;
46
+ },
47
+ // 右侧刷新按钮
48
+ defaultRefreshBtn() {
49
+ // 这里为了可读性,不再将click事件放到外面
50
+ return (
51
+ <div
52
+ title="刷新"
53
+ class="header-item"
54
+ on-click={() => refreshPage(this.$router)}
55
+ >
56
+ <i class="el-icon-refresh-right header-icon" />
57
+ </div>
58
+ );
59
+ },
60
+ // 右侧下拉菜单
61
+ defaultUserDropdown() {
62
+ const {
63
+ username,
64
+ logout = {},
65
+ message = {},
66
+ userinfo = {},
67
+ } = headerGetters;
68
+
69
+ return (
70
+ <div class="user-dropdown-reference">
71
+ <el-avatar
72
+ size={28}
73
+ src={headerGetters.avatar}
74
+ icon="el-icon-user-solid"
75
+ />
76
+ {!isEmpty(username) && (
77
+ <span class="username hide-on-mobile" on-click={userinfo}>
78
+ {username}
79
+ </span>
80
+ )}
81
+
82
+ <span class="username shu">|</span>
83
+ <i
84
+ style="color:#fff"
85
+ class="el-icon-bell header-icon"
86
+ on-click={message}
87
+ />
88
+ <span class="username shu">|</span>
89
+ <span class="username exit" on-click={logout}>
90
+ 退出
91
+ </span>
92
+ </div>
93
+ );
94
+ },
95
+ },
96
+
97
+ methods: {
98
+ /*顶栏的左、中、右三部分内容*/
99
+ renderLeftContent(h) {
100
+ const defaultContent = [this.defaultLogo, this.defaultHamburger];
101
+ const { leftSlot } = headerGetters;
102
+
103
+ return leftSlot ? leftSlot(h, defaultContent) : defaultContent;
104
+ },
105
+ renderCenterContent(h) {
106
+ const defaultContent = [this.defaultHeadMenu];
107
+ const { centerSlot } = headerGetters;
108
+
109
+ return centerSlot ? centerSlot(h, defaultContent) : defaultContent;
110
+ },
111
+ renderRightContent(h) {
112
+ const defaultContent = [this.defaultRefreshBtn, this.defaultUserDropdown];
113
+ const { rightSlot } = headerGetters;
114
+
115
+ return rightSlot ? rightSlot(h, defaultContent) : defaultContent;
116
+ },
117
+ },
118
+
119
+ render(h) {
120
+ return (
121
+ <header class={`header ${headerGetters.theme}`}>
122
+ <div class="header-left">{this.renderLeftContent(h)}</div>
123
+
124
+ <div class="header-center">{this.renderCenterContent(h)}</div>
125
+
126
+ <div class="header-right">{this.renderRightContent(h)}</div>
127
+ </header>
128
+ );
129
+ },
130
+ };
131
+ </script>
@@ -0,0 +1,74 @@
1
+ .header {
2
+ display: flex;
3
+ justify-content: space-between;
4
+ align-items: center;
5
+ flex-wrap: nowrap;
6
+ height: $header-height;
7
+ padding-left: $header-padding-left;
8
+ z-index: $header-z-index;
9
+ box-shadow: 0 4px 4px -4px rgba(0, 0, 0, 0.5);
10
+
11
+ &-left,
12
+ &-center,
13
+ &-right {
14
+ height: 100%;
15
+ }
16
+
17
+ &-left,
18
+ &-right {
19
+ display: flex;
20
+ }
21
+
22
+ &-center {
23
+ flex: 1;
24
+ }
25
+
26
+ .header-icon {
27
+ font-size: 18px;
28
+ padding: 0 7px;
29
+ }
30
+
31
+ .header-item {
32
+ display: flex;
33
+ align-items: center;
34
+ padding: 0 8px;
35
+ cursor: pointer;
36
+ }
37
+
38
+ // 左侧logo
39
+ .logo-container {
40
+ width: auto;
41
+ min-width: $aside-width - $header-padding-left;
42
+ padding-left: $menu-padding - $header-padding-left;
43
+ }
44
+
45
+ // 右侧用户头像和用户名
46
+ .user-dropdown-reference {
47
+ -moz-user-select: none; /*火狐*/
48
+ -webkit-user-select: none; /*webkit浏览器*/
49
+ -ms-user-select: none; /*IE10*/
50
+ -khtml-user-select: none; /*早期浏览器*/
51
+ user-select: none;
52
+ cursor: pointer;
53
+ display: flex;
54
+ align-items: center;
55
+ height: 100%;
56
+
57
+ .username {
58
+ font-size: 14px;
59
+ color: #fff;
60
+ padding: 0 7px;
61
+ }
62
+ .shu {
63
+ padding: 0 13px;
64
+ font-size: 14px;
65
+ }
66
+ .exit {
67
+ padding-left: 7px;
68
+ padding-right: 20px;
69
+ }
70
+ }
71
+ }
72
+
73
+ @import "theme-light";
74
+ @import "theme-dark";
@@ -0,0 +1,31 @@
1
+ .header.dark {
2
+ background-color: $header-background-dark;
3
+
4
+ .logo-container {
5
+ color: $--color-white;
6
+ }
7
+
8
+ .header-item {
9
+ color: $header-item-color-dark;
10
+
11
+ &:hover {
12
+ background-color: $header-item-hover-color-dark;
13
+ }
14
+ }
15
+ }
16
+
17
+ //暗色主题的下拉菜单
18
+ .header-dropdown.dark {
19
+ background-color: $header-background-dark;
20
+ border: none;
21
+
22
+ .el-dropdown-menu__item {
23
+ color: $menu-text-color-dark;
24
+
25
+ &:hover,
26
+ &:focus {
27
+ background-color: transparent;
28
+ color: $menu-text-hover-color-dark;
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,15 @@
1
+ .header.light {
2
+ background: linear-gradient(90deg, #f58852, #f56c45);
3
+
4
+ .logo-container {
5
+ color: #fff;
6
+ }
7
+
8
+ .header-item {
9
+ color: #fff;
10
+
11
+ &:hover {
12
+ background-color: $header-item-hover-color-light;
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,101 @@
1
+ <script>
2
+ /**
3
+ * 用于计算顶栏菜单的宽度
4
+ * 由nav-menu简化而来,仅保留第一级菜单以及必要属性
5
+ */
6
+
7
+ import SubMenu from '../../component/ElMenu/sub'
8
+ import NavMenu from '../../component/NavMenu'
9
+ import { headerGetters, mapGetters } from '../../store'
10
+
11
+ export default {
12
+ name: 'GhostMenu',
13
+
14
+ data() {
15
+ return {
16
+ // 外部菜单数据
17
+ menus: [],
18
+ // 各个第一级菜单节点的宽度
19
+ menuItemSizes: []
20
+ }
21
+ },
22
+
23
+ computed: {
24
+ ...mapGetters(headerGetters, [
25
+ 'menuIconSlot',
26
+ 'menuContentSlot'
27
+ ]),
28
+ menuClass() {
29
+ const { theme, showCollapseIcon } = headerGetters
30
+ return [
31
+ 'el-menu--horizontal',
32
+ `el-menu--${theme}`,
33
+ !showCollapseIcon && 'hide-collapse-icon'
34
+ ]
35
+ }
36
+ },
37
+
38
+ methods: {
39
+ renderMenuIcon: NavMenu.methods.renderMenuIcon,
40
+ renderMenuContent: NavMenu.methods.renderMenuContent,
41
+ renderSingleMenu: NavMenu.methods.renderSingleMenu,
42
+
43
+ // 渲染有子级的菜单
44
+ renderSubMenu(h, menu, depth) {
45
+ const { fullPath } = menu
46
+
47
+ return (
48
+ <SubMenu key={fullPath} index={fullPath}>
49
+ <template slot="title">
50
+ {this.renderMenuIcon(h, menu, depth)}
51
+ {this.renderMenuContent(h, menu, depth)}
52
+ </template>
53
+ </SubMenu>
54
+ )
55
+ },
56
+ // 渲染菜单项
57
+ renderMenus(h, menus, depth = 1) {
58
+ return menus.map(menu => {
59
+ return Array.isArray(menu.children)
60
+ ? this.renderSubMenu(h, menu, depth)
61
+ : this.renderSingleMenu(h, menu, depth)
62
+ })
63
+ },
64
+
65
+ resize() {
66
+ const ul = this.$el
67
+ if (!ul) return
68
+
69
+ const menuItemNodes = ul.children
70
+ if (!menuItemNodes || menuItemNodes.length === 0) return
71
+
72
+ this.menuItemSizes = Array.from(menuItemNodes).map(i => i.getBoundingClientRect().width)
73
+ }
74
+ },
75
+
76
+ mounted() {
77
+ const dom = this.$el
78
+ dom.style.position = 'fixed'
79
+ dom.style.bottom = '-1000px'
80
+ dom.style.right = '-1000px'
81
+ dom.style.width = 'auto'
82
+ document.body.appendChild(dom)
83
+
84
+ this.resizeObserver = new window.ResizeObserver(this.resize)
85
+ this.resizeObserver.observe(dom)
86
+
87
+ this.$once('hook:beforeDestroy', () => {
88
+ this.$el && document.body.removeChild(this.$el)
89
+ if (Reflect.has(this, '$_resizeObserver')) {
90
+ this.$_resizeObserver.disconnect()
91
+ delete this.$_resizeObserver
92
+ }
93
+ })
94
+ },
95
+
96
+ render(h) {
97
+ const children = this.renderMenus(h, this.menus)
98
+ return h('el-menu', { class: this.menuClass, props: { mode: 'horizontal' } }, children)
99
+ }
100
+ }
101
+ </script>
@@ -0,0 +1,280 @@
1
+ <script>
2
+ /**
3
+ * 顶部菜单,参考了ant design的响应式设计
4
+ */
5
+
6
+ import Vue from 'vue'
7
+ import menuMixin from '../../mixin/menu'
8
+ import { appGetters, appMutations, headerGetters } from '../../store'
9
+ import NavMenu from '../../component/NavMenu'
10
+ import LoadingSpinner from '../../component/LoadingSpinner'
11
+ import GhostMenu from './GhostMenu'
12
+ import { getRouterActiveMenu } from '../../config/logic'
13
+ import { getMenuByFullPath } from '../../store'
14
+ import { findFirstLeaf } from '../../util'
15
+
16
+ export default {
17
+ name: 'HorizontalResizableMenu',
18
+
19
+ mixins: [menuMixin],
20
+
21
+ data() {
22
+ return {
23
+ // 最后一个不被隐藏的顶部菜单的数组下标
24
+ // 为undefined时,说明不需要隐藏菜单,为-1时,说明需要隐藏全部菜单
25
+ lastVisibleIndex: undefined,
26
+ // 各个第一级菜单节点的宽度,取ghost-menu中的值
27
+ $menuItemSizes: [],
28
+ // ...指示器的宽度
29
+ $overflowedIndicatorWidth: 0,
30
+
31
+ // 用于生成el-submenu的key
32
+ seed: 0
33
+ }
34
+ },
35
+
36
+ computed: {
37
+ // 原始的菜单数组
38
+ menus() {
39
+ const { menus, navMode } = appGetters
40
+
41
+ switch (navMode) {
42
+ case 'head' :
43
+ return menus
44
+ case 'mix':
45
+ return menus.map(menu => ({ ...menu, children: undefined }))
46
+ default:
47
+ return []
48
+ }
49
+ },
50
+ // 实际用于渲染的菜单数组(仿antd的自适应宽度)
51
+ realMenus() {
52
+ const { lastVisibleIndex, menus, seed } = this
53
+
54
+ // 不需要隐藏菜单
55
+ if (lastVisibleIndex === undefined) {
56
+ return menus
57
+ }
58
+
59
+ const fullPath = `_${seed}`
60
+
61
+ // 隐藏全部菜单
62
+ if (lastVisibleIndex === -1) {
63
+ return [{ fullPath, meta: { icon: 'el-icon-menu' }, children: menus }]
64
+ }
65
+
66
+ const visible = menus.slice(0, lastVisibleIndex + 1)
67
+ const hidden = menus.slice(lastVisibleIndex + 1)
68
+
69
+ visible.push({ fullPath, meta: { title: '...' }, children: hidden })
70
+
71
+ return visible
72
+ },
73
+ // 是否有dom,加载中、无菜单时没有
74
+ hasDom() {
75
+ return !appGetters.loadingMenu && this.menus.length > 0
76
+ }
77
+ },
78
+
79
+ watch: {
80
+ // 路由变化时设置高亮菜单
81
+ $route: {
82
+ immediate: true,
83
+ handler(to) {
84
+ if (this.setActiveRootMenu(to)) {
85
+ this.setActiveMenu(appGetters.navMode, to)
86
+ }
87
+ }
88
+ },
89
+ // 顶部菜单变化时设置高亮菜单
90
+ realMenus: {
91
+ immediate: true,
92
+ handler(v) {
93
+ this.setActiveMenu()
94
+ this.setDefaultActiveMenu(v)
95
+ }
96
+ },
97
+ // 有dom时才进行自适应处理
98
+ hasDom: {
99
+ immediate: true,
100
+ async handler(value) {
101
+ // 等待mounted
102
+ await this.$nextTick()
103
+
104
+ value ? this.startObserver() : this.stopObserver()
105
+ }
106
+ },
107
+ // 变动时修改种子,避免组件不更新
108
+ lastVisibleIndex() {
109
+ this.seed++
110
+ }
111
+ },
112
+
113
+ methods: {
114
+ // 路由变化时设置高亮根节点菜单,有匹配菜单时返回true
115
+ setActiveRootMenu({ path, meta: { activeMenu } }) {
116
+ // 优先使用activeMenu找到路由匹配的菜单
117
+ const menu = getMenuByFullPath(activeMenu || path)
118
+ if (!menu) return false
119
+
120
+ // 向上找出菜单所属的根节点
121
+ let rootMenu = menu
122
+ while (rootMenu.parent) {
123
+ rootMenu = rootMenu.parent
124
+ }
125
+
126
+ appMutations.activeRootMenu(rootMenu.fullPath)
127
+
128
+ return true
129
+ },
130
+ setActiveMenu(navMode = appGetters.navMode, route = this.$route) {
131
+ const oldVal = this.activeMenu
132
+
133
+ // 只有在混合导航模式下才将当前激活的顶部菜单认为是当前菜单
134
+ if (navMode === 'mix') {
135
+ this.activeMenu = appGetters.activeRootMenu
136
+ }
137
+ // 否则按照路由配置项设置
138
+ else this.activeMenu = getRouterActiveMenu(route)
139
+
140
+ this.activeMenu !== oldVal && this.resetActiveMenu()
141
+ },
142
+
143
+ // 点击菜单时触发
144
+ onSelect(index) {
145
+ // 混合导航模式下点击的必是根节点
146
+ if (appGetters.navMode === 'mix') {
147
+ return this.onSelectRootMenu(index)
148
+ }
149
+
150
+ this.actionOnSelectMenu(index)
151
+ },
152
+ onSelectRootMenu(index) {
153
+ const root = getMenuByFullPath(index)
154
+
155
+ // vue-router中对应index的路由可能有子级且未设置redirect,此时访问index会404
156
+ const { leaf, hasOtherLeaf } = findFirstLeaf(root)
157
+
158
+ // 如果该根节点已激活且有多个叶子节点,退出
159
+ if (!leaf || appGetters.activeRootMenu === index && hasOtherLeaf) {
160
+ return
161
+ }
162
+
163
+ this.actionOnSelectMenu(leaf.fullPath)
164
+ },
165
+
166
+ // 获取el-menu的dom
167
+ getMenuEl() {
168
+ return this.$el
169
+ },
170
+ // 创建一个用于获取宽度的菜单,若已创建,则更新其菜单
171
+ genGhostMenu() {
172
+ if (Reflect.has(this, '$_ghostMenu')) {
173
+ this.$_ghostMenu.menus = this.menus
174
+ return
175
+ }
176
+
177
+ const ctor = Vue.extend(GhostMenu)
178
+ const instance = new ctor({ data: { menus: this.menus } })
179
+ instance.$watch('menuItemSizes', value => {
180
+ this.$data.$menuItemSizes = value
181
+ this.resize()
182
+ })
183
+ instance.$mount()
184
+ this.$_ghostMenu = instance
185
+ },
186
+ // 移除辅助菜单
187
+ destroyGhostMenu() {
188
+ Reflect.has(this, '$_ghostMenu') && this.$_ghostMenu.$destroy()
189
+ },
190
+ // 设置'...'菜单的宽度,只在mounted时调用一次
191
+ setOverflowedIndicatorWidth() {
192
+ const ul = document.createElement('ul')
193
+
194
+ ul.className = 'el-menu--horizontal el-menu el-menu--horizontal'
195
+ ul.style.position = 'fixed'
196
+ ul.style.top = '-100px'
197
+ ul.style.right = '-1000px'
198
+ ul.innerHTML = `<li class="el-submenu"><div class="el-submenu__title"><span>...</span></div></li>`
199
+
200
+ document.body.appendChild(ul)
201
+
202
+ this.$data.$overflowedIndicatorWidth = ul.children[0].offsetWidth + 1
203
+
204
+ document.body.removeChild(ul)
205
+ },
206
+
207
+ resize() {
208
+ const width = this.getMenuEl().getBoundingClientRect().width
209
+ const { $menuItemSizes, $overflowedIndicatorWidth } = this.$data
210
+
211
+ let lastVisibleIndex = -1
212
+
213
+ for (let i = $menuItemSizes.length - 1, sum = 0; i >= 0; i--) {
214
+ sum += $menuItemSizes[i]
215
+
216
+ if (sum + $overflowedIndicatorWidth > width) {
217
+ break
218
+ }
219
+
220
+ lastVisibleIndex += 1
221
+
222
+ if (lastVisibleIndex === $menuItemSizes.length - 1) {
223
+ lastVisibleIndex = undefined
224
+ break
225
+ }
226
+ }
227
+
228
+ this.lastVisibleIndex = lastVisibleIndex
229
+ },
230
+ startObserver() {
231
+ this.resizeObserver = new window.ResizeObserver(this.resize)
232
+ this.resizeObserver.observe(this.getMenuEl())
233
+ },
234
+ stopObserver() {
235
+ if (this.resizeObserver) {
236
+ this.resizeObserver.disconnect()
237
+ this.resizeObserver = null
238
+ }
239
+ this.destroyGhostMenu()
240
+ }
241
+ },
242
+
243
+ async mounted() {
244
+ await this.$nextTick()
245
+ this.setOverflowedIndicatorWidth()
246
+ },
247
+
248
+ beforeDestroy() {
249
+ this.stopObserver()
250
+ },
251
+
252
+ render() {
253
+ if (appGetters.loadingMenu) {
254
+ return (
255
+ <div style="position: relative;width: 100%;height: 100%">
256
+ <LoadingSpinner/>
257
+ </div>
258
+ )
259
+ }
260
+
261
+ if (this.menus.length === 0) return
262
+
263
+ this.genGhostMenu()
264
+
265
+ return (
266
+ <NavMenu
267
+ ref="nav-menu"
268
+ menus={this.realMenus}
269
+ theme={headerGetters.theme}
270
+ mode="horizontal"
271
+ default-active={this.defaultActive}
272
+ show-collapse-icon={headerGetters.showCollapseIcon}
273
+ menu-icon-slot={headerGetters.menuIconSlot}
274
+ menu-content-slot={headerGetters.menuContentSlot}
275
+ on-select={this.onSelect}
276
+ />
277
+ )
278
+ }
279
+ }
280
+ </script>