xto-fronted 0.3.2 → 0.3.4
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/Header.vue.d.ts +1 -0
- package/dist/components/Layout/Sidebar.vue.d.ts +8 -1
- package/dist/index-Bn4ThpX9.js +142 -0
- package/dist/index-CmQfZC8r.js +372 -0
- package/dist/index-Dga14ZN7.js +1774 -0
- package/dist/index-fyarVCog.js +475 -0
- package/dist/index-orZCyV6I.js +345 -0
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +19 -12
- package/src/components/Layout/Header.vue +466 -21
- package/src/components/Layout/Sidebar.vue +55 -11
- package/src/components/Layout/index.vue +181 -7
|
@@ -10,6 +10,17 @@ import { Button } from '@xto/base'
|
|
|
10
10
|
import { Input } from '@xto/form'
|
|
11
11
|
import type { MenuItem as MenuItemType } from '@/types/api'
|
|
12
12
|
|
|
13
|
+
const props = withDefaults(
|
|
14
|
+
defineProps<{
|
|
15
|
+
mode?: 'vertical' | 'horizontal'
|
|
16
|
+
showLogo?: boolean
|
|
17
|
+
}>(),
|
|
18
|
+
{
|
|
19
|
+
mode: 'vertical',
|
|
20
|
+
showLogo: true
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
13
24
|
const route = useRoute()
|
|
14
25
|
const router = useRouter()
|
|
15
26
|
const menuStore = useMenuStore()
|
|
@@ -103,18 +114,25 @@ const getMenuIcon = (icon?: string) => {
|
|
|
103
114
|
}
|
|
104
115
|
return iconMap[icon || ''] || '📄'
|
|
105
116
|
}
|
|
117
|
+
|
|
118
|
+
const isVertical = computed(() => props.mode === 'vertical')
|
|
119
|
+
const isHorizontal = computed(() => props.mode === 'horizontal')
|
|
106
120
|
</script>
|
|
107
121
|
|
|
108
122
|
<template>
|
|
109
|
-
<div class="sidebar"
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
<div class="sidebar" :class="[
|
|
124
|
+
`sidebar--${mode}`,
|
|
125
|
+
{ 'sidebar--collapsed': isCollapsed && isVertical },
|
|
126
|
+
{ 'sidebar--horizontal': isHorizontal }
|
|
127
|
+
]">
|
|
128
|
+
<!-- Logo(仅垂直模式显示) -->
|
|
129
|
+
<div v-if="showLogo && isVertical" class="sidebar__logo">
|
|
112
130
|
<img src="/vite.svg" alt="Logo" class="sidebar__logo-img" />
|
|
113
|
-
<span v-show="!isCollapsed" class="sidebar__logo-text">
|
|
131
|
+
<span v-show="!isCollapsed" class="sidebar__logo-text">{{ appStore.appName }}</span>
|
|
114
132
|
</div>
|
|
115
133
|
|
|
116
|
-
<!--
|
|
117
|
-
<div v-if="!isCollapsed" class="sidebar__search">
|
|
134
|
+
<!-- 搜索框(仅垂直模式显示) -->
|
|
135
|
+
<div v-if="isVertical && !isCollapsed" class="sidebar__search">
|
|
118
136
|
<Input
|
|
119
137
|
v-model="searchKeyword"
|
|
120
138
|
placeholder="搜索菜单..."
|
|
@@ -141,7 +159,8 @@ const getMenuIcon = (icon?: string) => {
|
|
|
141
159
|
<!-- 菜单 -->
|
|
142
160
|
<Menu
|
|
143
161
|
:default-active="activeMenu"
|
|
144
|
-
:
|
|
162
|
+
:mode="mode"
|
|
163
|
+
:collapse="isCollapsed && isVertical"
|
|
145
164
|
:collapse-transition="false"
|
|
146
165
|
:background-color="menuBgColor"
|
|
147
166
|
:text-color="menuTextColor"
|
|
@@ -173,11 +192,11 @@ const getMenuIcon = (icon?: string) => {
|
|
|
173
192
|
</template>
|
|
174
193
|
</Menu>
|
|
175
194
|
|
|
176
|
-
<!--
|
|
177
|
-
<div
|
|
195
|
+
<!-- 用户信息(仅垂直模式显示) -->
|
|
196
|
+
<div v-if="isVertical && !isCollapsed" class="sidebar__user">
|
|
178
197
|
<div class="sidebar__user-info">
|
|
179
198
|
<span class="sidebar__user-name">{{ userStore.nickname }}</span>
|
|
180
|
-
<span class="sidebar__user-role">{{ userStore.roles
|
|
199
|
+
<span class="sidebar__user-role">{{ userStore.roles?.join(', ') }}</span>
|
|
181
200
|
</div>
|
|
182
201
|
<Button type="text" size="small" @click="handleLogout">退出</Button>
|
|
183
202
|
</div>
|
|
@@ -191,11 +210,36 @@ const getMenuIcon = (icon?: string) => {
|
|
|
191
210
|
flex-direction: column;
|
|
192
211
|
background-color: var(--bg-color);
|
|
193
212
|
|
|
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 {
|
|
232
|
+
.sidebar__logo {
|
|
233
|
+
justify-content: center;
|
|
234
|
+
padding: 0;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
194
238
|
&__logo {
|
|
195
239
|
height: 50px;
|
|
196
240
|
display: flex;
|
|
197
241
|
align-items: center;
|
|
198
|
-
|
|
242
|
+
padding: 0 20px;
|
|
199
243
|
gap: 10px;
|
|
200
244
|
border-bottom: 1px solid var(--color-border-lighter);
|
|
201
245
|
}
|
|
@@ -9,19 +9,73 @@ 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')
|
|
12
18
|
</script>
|
|
13
19
|
|
|
14
20
|
<template>
|
|
15
|
-
<div class="layout">
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
<div class="layout" :class="`layout--${layoutMode}`">
|
|
22
|
+
<!-- 顶部模式:顶部导航 + 内容 -->
|
|
23
|
+
<template v-if="isTopMode">
|
|
24
|
+
<header class="layout__header-top">
|
|
25
|
+
<div class="layout__header-logo">
|
|
26
|
+
<img src="/vite.svg" alt="Logo" class="layout__logo-img" />
|
|
27
|
+
<span class="layout__logo-text">{{ appStore.appName }}</span>
|
|
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>
|
|
33
|
+
</header>
|
|
21
34
|
<main class="layout__content">
|
|
22
35
|
<router-view />
|
|
23
36
|
</main>
|
|
24
|
-
</
|
|
37
|
+
</template>
|
|
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>
|
|
25
79
|
</div>
|
|
26
80
|
</template>
|
|
27
81
|
|
|
@@ -31,6 +85,126 @@ const sidebarWidth = computed(() =>
|
|
|
31
85
|
width: 100%;
|
|
32
86
|
height: 100%;
|
|
33
87
|
|
|
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
|
+
// 通用样式
|
|
34
208
|
&__aside {
|
|
35
209
|
transition: width 0.3s;
|
|
36
210
|
overflow: hidden;
|