xto-fronted 0.3.4 → 0.3.5
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/dist/components/Layout/Sidebar.vue.d.ts +1 -8
- package/dist/index-B6DTsC6l.js +1715 -0
- package/dist/index-BGmUwemj.js +372 -0
- package/dist/index-Cb-SxHJp.js +345 -0
- package/dist/index-DnJ481u1.js +475 -0
- package/dist/index-YDlNLFVk.js +142 -0
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Layout/Header.vue +327 -188
- package/src/components/Layout/Sidebar.vue +25 -63
- package/src/components/Layout/index.vue +8 -180
|
@@ -8,19 +8,9 @@ import { useAppStore } from '@/stores/app'
|
|
|
8
8
|
import { Menu, MenuItem, SubMenu } from '@xto/navigation'
|
|
9
9
|
import { Button } from '@xto/base'
|
|
10
10
|
import { Input } from '@xto/form'
|
|
11
|
+
import { Icon } from '@xto/base'
|
|
11
12
|
import type { MenuItem as MenuItemType } from '@/types/api'
|
|
12
13
|
|
|
13
|
-
const props = withDefaults(
|
|
14
|
-
defineProps<{
|
|
15
|
-
mode?: 'vertical' | 'horizontal'
|
|
16
|
-
showLogo?: boolean
|
|
17
|
-
}>(),
|
|
18
|
-
{
|
|
19
|
-
mode: 'vertical',
|
|
20
|
-
showLogo: true
|
|
21
|
-
}
|
|
22
|
-
)
|
|
23
|
-
|
|
24
14
|
const route = useRoute()
|
|
25
15
|
const router = useRouter()
|
|
26
16
|
const menuStore = useMenuStore()
|
|
@@ -102,37 +92,30 @@ const handleLogout = () => {
|
|
|
102
92
|
router.push('/login')
|
|
103
93
|
}
|
|
104
94
|
|
|
105
|
-
//
|
|
106
|
-
const getMenuIcon = (icon?: string) => {
|
|
95
|
+
// 获取菜单图标名称
|
|
96
|
+
const getMenuIcon = (icon?: string): string => {
|
|
107
97
|
const iconMap: Record<string, string> = {
|
|
108
|
-
dashboard: '
|
|
109
|
-
system: '
|
|
110
|
-
user: '
|
|
111
|
-
role: '
|
|
112
|
-
menu: '
|
|
113
|
-
setting: '
|
|
98
|
+
dashboard: 'dashboard',
|
|
99
|
+
system: 'system',
|
|
100
|
+
user: 'user',
|
|
101
|
+
role: 'role',
|
|
102
|
+
menu: 'list',
|
|
103
|
+
setting: 'setting'
|
|
114
104
|
}
|
|
115
|
-
return iconMap[icon || ''] || '
|
|
105
|
+
return iconMap[icon || ''] || 'file'
|
|
116
106
|
}
|
|
117
|
-
|
|
118
|
-
const isVertical = computed(() => props.mode === 'vertical')
|
|
119
|
-
const isHorizontal = computed(() => props.mode === 'horizontal')
|
|
120
107
|
</script>
|
|
121
108
|
|
|
122
109
|
<template>
|
|
123
|
-
<div class="sidebar" :class="
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{ 'sidebar--horizontal': isHorizontal }
|
|
127
|
-
]">
|
|
128
|
-
<!-- Logo(仅垂直模式显示) -->
|
|
129
|
-
<div v-if="showLogo && isVertical" class="sidebar__logo">
|
|
110
|
+
<div class="sidebar" :class="{ 'sidebar--collapsed': isCollapsed }">
|
|
111
|
+
<!-- Logo -->
|
|
112
|
+
<div class="sidebar__logo">
|
|
130
113
|
<img src="/vite.svg" alt="Logo" class="sidebar__logo-img" />
|
|
131
114
|
<span v-show="!isCollapsed" class="sidebar__logo-text">{{ appStore.appName }}</span>
|
|
132
115
|
</div>
|
|
133
116
|
|
|
134
|
-
<!--
|
|
135
|
-
<div v-if="
|
|
117
|
+
<!-- 搜索框 -->
|
|
118
|
+
<div v-if="!isCollapsed" class="sidebar__search">
|
|
136
119
|
<Input
|
|
137
120
|
v-model="searchKeyword"
|
|
138
121
|
placeholder="搜索菜单..."
|
|
@@ -147,7 +130,7 @@ const isHorizontal = computed(() => props.mode === 'horizontal')
|
|
|
147
130
|
class="sidebar__search-item"
|
|
148
131
|
@click="handleSearchItemClick(item.path)"
|
|
149
132
|
>
|
|
150
|
-
<
|
|
133
|
+
<Icon :name="getMenuIcon(item.icon)" :size="16" />
|
|
151
134
|
<div class="sidebar__search-item-info">
|
|
152
135
|
<span class="sidebar__search-item-title">{{ item.title }}</span>
|
|
153
136
|
<span v-if="item.parentTitle" class="sidebar__search-item-parent">{{ item.parentTitle }}</span>
|
|
@@ -159,8 +142,8 @@ const isHorizontal = computed(() => props.mode === 'horizontal')
|
|
|
159
142
|
<!-- 菜单 -->
|
|
160
143
|
<Menu
|
|
161
144
|
:default-active="activeMenu"
|
|
162
|
-
|
|
163
|
-
:collapse="isCollapsed
|
|
145
|
+
mode="vertical"
|
|
146
|
+
:collapse="isCollapsed"
|
|
164
147
|
:collapse-transition="false"
|
|
165
148
|
:background-color="menuBgColor"
|
|
166
149
|
:text-color="menuTextColor"
|
|
@@ -172,7 +155,7 @@ const isHorizontal = computed(() => props.mode === 'horizontal')
|
|
|
172
155
|
<!-- 有子菜单 -->
|
|
173
156
|
<SubMenu v-if="menu.children && menu.children.length > 0" :index="menu.path">
|
|
174
157
|
<template #title>
|
|
175
|
-
<
|
|
158
|
+
<Icon :name="getMenuIcon(menu.icon)" :size="16" />
|
|
176
159
|
<span>{{ menu.title }}</span>
|
|
177
160
|
</template>
|
|
178
161
|
<MenuItem
|
|
@@ -180,20 +163,20 @@ const isHorizontal = computed(() => props.mode === 'horizontal')
|
|
|
180
163
|
:key="child.path"
|
|
181
164
|
:index="child.path"
|
|
182
165
|
>
|
|
183
|
-
<
|
|
166
|
+
<Icon :name="getMenuIcon(child.icon)" :size="16" />
|
|
184
167
|
<span>{{ child.title }}</span>
|
|
185
168
|
</MenuItem>
|
|
186
169
|
</SubMenu>
|
|
187
170
|
<!-- 无子菜单 -->
|
|
188
171
|
<MenuItem v-else :index="menu.path">
|
|
189
|
-
<
|
|
172
|
+
<Icon :name="getMenuIcon(menu.icon)" :size="16" />
|
|
190
173
|
<span>{{ menu.title }}</span>
|
|
191
174
|
</MenuItem>
|
|
192
175
|
</template>
|
|
193
176
|
</Menu>
|
|
194
177
|
|
|
195
|
-
<!--
|
|
196
|
-
<div v-if="
|
|
178
|
+
<!-- 用户信息 -->
|
|
179
|
+
<div v-if="!isCollapsed" class="sidebar__user">
|
|
197
180
|
<div class="sidebar__user-info">
|
|
198
181
|
<span class="sidebar__user-name">{{ userStore.nickname }}</span>
|
|
199
182
|
<span class="sidebar__user-role">{{ userStore.roles?.join(', ') }}</span>
|
|
@@ -209,26 +192,9 @@ const isHorizontal = computed(() => props.mode === 'horizontal')
|
|
|
209
192
|
display: flex;
|
|
210
193
|
flex-direction: column;
|
|
211
194
|
background-color: var(--bg-color);
|
|
195
|
+
border-right: 1px solid var(--color-border-lighter);
|
|
212
196
|
|
|
213
|
-
|
|
214
|
-
&--vertical {
|
|
215
|
-
border-right: 1px solid var(--color-border-lighter);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// 水平模式
|
|
219
|
-
&--horizontal {
|
|
220
|
-
flex-direction: row;
|
|
221
|
-
border: none;
|
|
222
|
-
background-color: transparent;
|
|
223
|
-
|
|
224
|
-
.sidebar__menu {
|
|
225
|
-
border: none;
|
|
226
|
-
background-color: transparent;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// 折叠状态
|
|
231
|
-
&--collapsed.sidebar--vertical {
|
|
197
|
+
&--collapsed {
|
|
232
198
|
.sidebar__logo {
|
|
233
199
|
justify-content: center;
|
|
234
200
|
padding: 0;
|
|
@@ -334,8 +300,4 @@ const isHorizontal = computed(() => props.mode === 'horizontal')
|
|
|
334
300
|
color: var(--color-text-secondary);
|
|
335
301
|
}
|
|
336
302
|
}
|
|
337
|
-
|
|
338
|
-
.menu-icon {
|
|
339
|
-
margin-right: 8px;
|
|
340
|
-
}
|
|
341
303
|
</style>
|
|
@@ -9,73 +9,21 @@ const appStore = useAppStore()
|
|
|
9
9
|
const sidebarWidth = computed(() =>
|
|
10
10
|
appStore.isCollapsed ? '64px' : '210px'
|
|
11
11
|
)
|
|
12
|
-
|
|
13
|
-
const layoutMode = computed(() => appStore.layout)
|
|
14
|
-
|
|
15
|
-
const isSidebarMode = computed(() => layoutMode.value === 'sidebar')
|
|
16
|
-
const isTopMode = computed(() => layoutMode.value === 'top')
|
|
17
|
-
const isMixMode = computed(() => layoutMode.value === 'mix')
|
|
18
12
|
</script>
|
|
19
13
|
|
|
20
14
|
<template>
|
|
21
|
-
<div class="layout"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
</div>
|
|
29
|
-
<Sidebar class="layout__top-menu" mode="horizontal" />
|
|
30
|
-
<div class="layout__header-right">
|
|
31
|
-
<Header :show-breadcrumb="false" :show-collapse="false" />
|
|
32
|
-
</div>
|
|
15
|
+
<div class="layout">
|
|
16
|
+
<aside class="layout__aside" :style="{ width: sidebarWidth }">
|
|
17
|
+
<Sidebar />
|
|
18
|
+
</aside>
|
|
19
|
+
<div class="layout__main">
|
|
20
|
+
<header class="layout__header">
|
|
21
|
+
<Header />
|
|
33
22
|
</header>
|
|
34
23
|
<main class="layout__content">
|
|
35
24
|
<router-view />
|
|
36
25
|
</main>
|
|
37
|
-
</
|
|
38
|
-
|
|
39
|
-
<!-- 混合模式:顶部导航 + 左侧菜单 -->
|
|
40
|
-
<template v-else-if="isMixMode">
|
|
41
|
-
<header class="layout__header-mix">
|
|
42
|
-
<div class="layout__header-left">
|
|
43
|
-
<div class="layout__collapse" @click="appStore.toggleCollapse">
|
|
44
|
-
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
|
|
45
|
-
<path v-if="appStore.isCollapsed" d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
|
|
46
|
-
<path v-else d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
|
|
47
|
-
</svg>
|
|
48
|
-
</div>
|
|
49
|
-
<Sidebar class="layout__top-menu" mode="horizontal" />
|
|
50
|
-
</div>
|
|
51
|
-
<div class="layout__header-right">
|
|
52
|
-
<Header :show-breadcrumb="false" :show-collapse="false" />
|
|
53
|
-
</div>
|
|
54
|
-
</header>
|
|
55
|
-
<div class="layout__body">
|
|
56
|
-
<aside class="layout__aside" :style="{ width: sidebarWidth }">
|
|
57
|
-
<Sidebar mode="vertical" :show-logo="false" />
|
|
58
|
-
</aside>
|
|
59
|
-
<main class="layout__content">
|
|
60
|
-
<router-view />
|
|
61
|
-
</main>
|
|
62
|
-
</div>
|
|
63
|
-
</template>
|
|
64
|
-
|
|
65
|
-
<!-- 侧边栏模式:左侧菜单 + 顶部 -->
|
|
66
|
-
<template v-else-if="isSidebarMode">
|
|
67
|
-
<aside class="layout__aside" :style="{ width: sidebarWidth }">
|
|
68
|
-
<Sidebar />
|
|
69
|
-
</aside>
|
|
70
|
-
<div class="layout__main">
|
|
71
|
-
<header class="layout__header">
|
|
72
|
-
<Header />
|
|
73
|
-
</header>
|
|
74
|
-
<main class="layout__content">
|
|
75
|
-
<router-view />
|
|
76
|
-
</main>
|
|
77
|
-
</div>
|
|
78
|
-
</template>
|
|
26
|
+
</div>
|
|
79
27
|
</div>
|
|
80
28
|
</template>
|
|
81
29
|
|
|
@@ -85,126 +33,6 @@ const isMixMode = computed(() => layoutMode.value === 'mix')
|
|
|
85
33
|
width: 100%;
|
|
86
34
|
height: 100%;
|
|
87
35
|
|
|
88
|
-
// 侧边栏模式
|
|
89
|
-
&--sidebar {
|
|
90
|
-
flex-direction: row;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 顶部模式
|
|
94
|
-
&--top {
|
|
95
|
-
flex-direction: column;
|
|
96
|
-
|
|
97
|
-
.layout__header-top {
|
|
98
|
-
height: 50px;
|
|
99
|
-
background-color: var(--bg-color);
|
|
100
|
-
border-bottom: 1px solid var(--color-border-lighter);
|
|
101
|
-
display: flex;
|
|
102
|
-
align-items: center;
|
|
103
|
-
padding: 0 20px;
|
|
104
|
-
gap: 20px;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.layout__header-logo {
|
|
108
|
-
display: flex;
|
|
109
|
-
align-items: center;
|
|
110
|
-
gap: 10px;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.layout__logo-img {
|
|
114
|
-
width: 32px;
|
|
115
|
-
height: 32px;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.layout__logo-text {
|
|
119
|
-
font-size: 16px;
|
|
120
|
-
font-weight: 600;
|
|
121
|
-
color: var(--color-primary);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.layout__top-menu {
|
|
125
|
-
flex: 1;
|
|
126
|
-
border: none;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.layout__header-right {
|
|
130
|
-
display: flex;
|
|
131
|
-
align-items: center;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.layout__content {
|
|
135
|
-
flex: 1;
|
|
136
|
-
overflow: auto;
|
|
137
|
-
background-color: var(--bg-color-page);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 混合模式
|
|
142
|
-
&--mix {
|
|
143
|
-
flex-direction: column;
|
|
144
|
-
|
|
145
|
-
.layout__header-mix {
|
|
146
|
-
height: 50px;
|
|
147
|
-
background-color: var(--bg-color);
|
|
148
|
-
border-bottom: 1px solid var(--color-border-lighter);
|
|
149
|
-
display: flex;
|
|
150
|
-
align-items: center;
|
|
151
|
-
justify-content: space-between;
|
|
152
|
-
padding: 0 20px;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.layout__header-left {
|
|
156
|
-
display: flex;
|
|
157
|
-
align-items: center;
|
|
158
|
-
gap: 15px;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.layout__collapse {
|
|
162
|
-
width: 24px;
|
|
163
|
-
height: 24px;
|
|
164
|
-
display: flex;
|
|
165
|
-
align-items: center;
|
|
166
|
-
justify-content: center;
|
|
167
|
-
cursor: pointer;
|
|
168
|
-
color: var(--color-text-regular);
|
|
169
|
-
transition: color 0.2s;
|
|
170
|
-
|
|
171
|
-
&:hover {
|
|
172
|
-
color: var(--color-primary);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.layout__top-menu {
|
|
177
|
-
border: none;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.layout__header-right {
|
|
181
|
-
display: flex;
|
|
182
|
-
align-items: center;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.layout__body {
|
|
186
|
-
flex: 1;
|
|
187
|
-
display: flex;
|
|
188
|
-
overflow: hidden;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.layout__aside {
|
|
192
|
-
transition: width 0.3s;
|
|
193
|
-
overflow: hidden;
|
|
194
|
-
flex-shrink: 0;
|
|
195
|
-
height: 100%;
|
|
196
|
-
background-color: var(--bg-color);
|
|
197
|
-
border-right: 1px solid var(--color-border-lighter);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.layout__content {
|
|
201
|
-
flex: 1;
|
|
202
|
-
overflow: auto;
|
|
203
|
-
background-color: var(--bg-color-page);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// 通用样式
|
|
208
36
|
&__aside {
|
|
209
37
|
transition: width 0.3s;
|
|
210
38
|
overflow: hidden;
|