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.
@@ -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
- `sidebar--${mode}`,
125
- { 'sidebar--collapsed': isCollapsed && isVertical },
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="isVertical && !isCollapsed" class="sidebar__search">
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
- <span class="menu-icon">{{ getMenuIcon(item.icon) }}</span>
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
- :mode="mode"
163
- :collapse="isCollapsed && isVertical"
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
- <span class="menu-icon">{{ getMenuIcon(menu.icon) }}</span>
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
- <span class="menu-icon">{{ getMenuIcon(child.icon) }}</span>
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
- <span class="menu-icon">{{ getMenuIcon(menu.icon) }}</span>
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="isVertical && !isCollapsed" class="sidebar__user">
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" :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>
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
- </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>
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;