xto-fronted 0.1.1 → 0.1.2

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 (88) hide show
  1. package/dist/{403-MQkNUulz.js → 403-DM5wfQkM.js} +6 -6
  2. package/dist/{404-BOFYLq4X.js → 404-BurAu5LC.js} +7 -7
  3. package/dist/api/auth.d.ts +9 -8
  4. package/dist/api/menu.d.ts +3 -0
  5. package/dist/api/user.d.ts +2 -12
  6. package/dist/composables/index.d.ts +8 -0
  7. package/dist/composables/useApp.d.ts +64 -0
  8. package/dist/composables/useAuth.d.ts +19 -4
  9. package/dist/composables/useMenu.d.ts +34 -0
  10. package/dist/config/index.d.ts +11 -0
  11. package/dist/index-BNiEld34.js +15 -0
  12. package/dist/index-Be9RiEfo.js +98 -0
  13. package/dist/index-BqRv1bdN.js +1185 -0
  14. package/dist/index-CQLVXvNJ.js +15 -0
  15. package/dist/index-CyiE8n2V.js +15 -0
  16. package/dist/index-xauR1bOL.js +15 -0
  17. package/dist/index.d.ts +7 -4
  18. package/dist/index.es.js +50 -66
  19. package/dist/index.umd.js +1 -1
  20. package/dist/stores/auth.d.ts +60 -23
  21. package/dist/stores/menu.d.ts +40 -29
  22. package/dist/stores/user.d.ts +63 -84
  23. package/dist/style.css +1 -1
  24. package/dist/utils/auth.d.ts +15 -7
  25. package/dist/utils/permission.d.ts +1 -6
  26. package/dist/views/system/menu/index.vue.d.ts +1 -3
  27. package/dist/views/system/role/index.vue.d.ts +1 -3
  28. package/dist/views/system/user/index.vue.d.ts +1 -3
  29. package/package.json +21 -26
  30. package/.env.development +0 -4
  31. package/.env.production +0 -4
  32. package/dist/index-BJxYdNPy.js +0 -475
  33. package/dist/index-BvnIIBR1.js +0 -142
  34. package/dist/index-CEvAq6KE.js +0 -372
  35. package/dist/index-DPkqej__.js +0 -345
  36. package/dist/index-pq9Z5K62.js +0 -184
  37. package/dist/index-vVfjShJR.js +0 -1183
  38. package/index.html +0 -13
  39. package/public/vite.svg +0 -10
  40. package/src/App.vue +0 -20
  41. package/src/api/auth.ts +0 -26
  42. package/src/api/system.ts +0 -65
  43. package/src/api/user.ts +0 -46
  44. package/src/assets/styles/_dark.scss +0 -407
  45. package/src/assets/styles/_reset.scss +0 -126
  46. package/src/assets/styles/_root.scss +0 -140
  47. package/src/assets/styles/_transition.scss +0 -119
  48. package/src/assets/styles/_variables.scss +0 -45
  49. package/src/assets/styles/index.scss +0 -187
  50. package/src/components/Layout/Footer.vue +0 -17
  51. package/src/components/Layout/Header.vue +0 -390
  52. package/src/components/Layout/Sidebar.vue +0 -297
  53. package/src/components/Layout/Tabs.vue +0 -134
  54. package/src/components/Layout/index.vue +0 -62
  55. package/src/composables/useAuth.ts +0 -45
  56. package/src/composables/useForm.ts +0 -79
  57. package/src/composables/useTable.ts +0 -97
  58. package/src/directives/permission.ts +0 -38
  59. package/src/enums/index.ts +0 -63
  60. package/src/env.d.ts +0 -17
  61. package/src/index.ts +0 -39
  62. package/src/main.ts +0 -34
  63. package/src/router/dynamicRoutes.ts +0 -163
  64. package/src/router/index.ts +0 -81
  65. package/src/router/staticRoutes.ts +0 -43
  66. package/src/stores/app.ts +0 -145
  67. package/src/stores/auth.ts +0 -32
  68. package/src/stores/index.ts +0 -15
  69. package/src/stores/menu.ts +0 -80
  70. package/src/stores/user.ts +0 -73
  71. package/src/types/api.d.ts +0 -84
  72. package/src/types/global.d.ts +0 -45
  73. package/src/types/router.d.ts +0 -48
  74. package/src/types/xto.d.ts +0 -149
  75. package/src/utils/auth.ts +0 -62
  76. package/src/utils/permission.ts +0 -42
  77. package/src/utils/request.ts +0 -126
  78. package/src/utils/storage.ts +0 -63
  79. package/src/views/dashboard/index.vue +0 -284
  80. package/src/views/error/403.vue +0 -57
  81. package/src/views/error/404.vue +0 -57
  82. package/src/views/login/index.vue +0 -248
  83. package/src/views/system/menu/index.vue +0 -381
  84. package/src/views/system/role/index.vue +0 -304
  85. package/src/views/system/user/index.vue +0 -327
  86. package/tsconfig.json +0 -26
  87. package/tsconfig.node.json +0 -11
  88. package/vite.config.ts +0 -139
@@ -1,390 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, computed, onMounted, onUnmounted } from 'vue'
3
- import { useRoute, useRouter } from 'vue-router'
4
- import { useAppStore } from '@/stores/app'
5
- import { useUserStore } from '@/stores/user'
6
- import { useAuthStore } from '@/stores/auth'
7
- import { useMenuStore } from '@/stores/menu'
8
-
9
- const route = useRoute()
10
- const router = useRouter()
11
- const appStore = useAppStore()
12
- const userStore = useUserStore()
13
- const authStore = useAuthStore()
14
- const menuStore = useMenuStore()
15
-
16
- const isCollapsed = computed(() => appStore.isCollapsed)
17
- const dropdownVisible = ref(false)
18
- const dropdownRef = ref<HTMLElement | null>(null)
19
- const isFullscreen = ref(false)
20
-
21
- // 面包屑
22
- const breadcrumbs = computed(() => {
23
- const matched = route.matched.filter(item => item.meta && item.meta.title)
24
- return matched.map(item => ({
25
- title: item.meta.title as string,
26
- path: item.path
27
- }))
28
- })
29
-
30
- // 切换折叠
31
- const toggleCollapse = () => {
32
- appStore.toggleCollapse()
33
- }
34
-
35
- // 切换主题
36
- const toggleTheme = () => {
37
- appStore.toggleTheme()
38
- }
39
-
40
- // 切换全屏
41
- const toggleFullscreen = () => {
42
- if (!document.fullscreenElement) {
43
- document.documentElement.requestFullscreen()
44
- } else {
45
- document.exitFullscreen()
46
- }
47
- }
48
-
49
- // 监听全屏变化
50
- const handleFullscreenChange = () => {
51
- isFullscreen.value = !!document.fullscreenElement
52
- }
53
-
54
- // 切换下拉菜单
55
- const toggleDropdown = () => {
56
- dropdownVisible.value = !dropdownVisible.value
57
- }
58
-
59
- // 关闭下拉菜单
60
- const closeDropdown = () => {
61
- dropdownVisible.value = false
62
- }
63
-
64
- // 个人信息
65
- const handleProfile = () => {
66
- closeDropdown()
67
- // TODO: 跳转到个人信息页面
68
- alert('个人信息功能开发中...')
69
- }
70
-
71
- // 修改密码
72
- const handleChangePassword = () => {
73
- closeDropdown()
74
- // TODO: 打开修改密码弹窗
75
- alert('修改密码功能开发中...')
76
- }
77
-
78
- // 退出登录
79
- const handleLogout = () => {
80
- closeDropdown()
81
- authStore.logout()
82
- userStore.clearUserInfo()
83
- menuStore.clearMenu()
84
- router.push('/login')
85
- }
86
-
87
- // 点击外部关闭下拉菜单
88
- const handleClickOutside = (event: MouseEvent) => {
89
- if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
90
- closeDropdown()
91
- }
92
- }
93
-
94
- onMounted(() => {
95
- document.addEventListener('click', handleClickOutside)
96
- document.addEventListener('fullscreenchange', handleFullscreenChange)
97
- })
98
-
99
- onUnmounted(() => {
100
- document.removeEventListener('click', handleClickOutside)
101
- document.removeEventListener('fullscreenchange', handleFullscreenChange)
102
- })
103
- </script>
104
-
105
- <template>
106
- <div class="header">
107
- <!-- 左侧 -->
108
- <div class="header__left">
109
- <div class="header__collapse" @click="toggleCollapse">
110
- <span v-if="isCollapsed">☰</span>
111
- <span v-else>☰</span>
112
- </div>
113
- <div class="header__breadcrumb">
114
- <span v-for="(item, index) in breadcrumbs" :key="item.path">
115
- <span v-if="index > 0"> / </span>
116
- <span :class="{ 'is-current': index === breadcrumbs.length - 1 }">
117
- {{ item.title }}
118
- </span>
119
- </span>
120
- </div>
121
- </div>
122
-
123
- <!-- 右侧 -->
124
- <div class="header__right">
125
- <!-- 全屏切换 -->
126
- <div class="header__action" @click="toggleFullscreen" :title="isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'">
127
- <svg v-if="isFullscreen" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
128
- <path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
129
- </svg>
130
- <svg v-else viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
131
- <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
132
- </svg>
133
- </div>
134
-
135
- <!-- 主题切换 -->
136
- <div class="header__action" @click="toggleTheme" title="切换主题">
137
- <span v-if="appStore.isDark">🌙</span>
138
- <span v-else>☀️</span>
139
- </div>
140
-
141
- <!-- 用户头像 -->
142
- <div class="header__user" ref="dropdownRef">
143
- <div class="header__user-trigger" @click.stop="toggleDropdown">
144
- <div class="header__avatar">
145
- <span>{{ userStore.nickname?.charAt(0) || 'U' }}</span>
146
- </div>
147
- <span class="header__user-name">{{ userStore.nickname }}</span>
148
- <span class="header__user-arrow" :class="{ 'is-active': dropdownVisible }">▼</span>
149
- </div>
150
-
151
- <!-- 下拉菜单 -->
152
- <Transition name="dropdown">
153
- <div v-if="dropdownVisible" class="header__dropdown">
154
- <div class="header__dropdown-header">
155
- <div class="header__dropdown-avatar">
156
- <span>{{ userStore.nickname?.charAt(0) || 'U' }}</span>
157
- </div>
158
- <div class="header__dropdown-info">
159
- <div class="header__dropdown-name">{{ userStore.nickname }}</div>
160
- <div class="header__dropdown-role">{{ userStore.roles.join(', ') }}</div>
161
- </div>
162
- </div>
163
- <div class="header__dropdown-divider"></div>
164
- <div class="header__dropdown-menu">
165
- <div class="header__dropdown-item" @click="handleProfile">
166
- <span class="header__dropdown-icon">👤</span>
167
- <span>个人信息</span>
168
- </div>
169
- <div class="header__dropdown-item" @click="handleChangePassword">
170
- <span class="header__dropdown-icon">🔐</span>
171
- <span>修改密码</span>
172
- </div>
173
- <div class="header__dropdown-divider"></div>
174
- <div class="header__dropdown-item header__dropdown-item--danger" @click="handleLogout">
175
- <span class="header__dropdown-icon">🚪</span>
176
- <span>退出登录</span>
177
- </div>
178
- </div>
179
- </div>
180
- </Transition>
181
- </div>
182
- </div>
183
- </div>
184
- </template>
185
-
186
- <style lang="scss" scoped>
187
- .header {
188
- display: flex;
189
- align-items: center;
190
- justify-content: space-between;
191
- padding: 0 20px;
192
- height: 100%;
193
-
194
- &__left {
195
- display: flex;
196
- align-items: center;
197
- gap: 15px;
198
- }
199
-
200
- &__collapse {
201
- width: 24px;
202
- height: 24px;
203
- display: flex;
204
- align-items: center;
205
- justify-content: center;
206
- cursor: pointer;
207
- font-size: 18px;
208
- color: var(--color-text-regular);
209
-
210
- &:hover {
211
- color: var(--color-primary);
212
- }
213
- }
214
-
215
- &__breadcrumb {
216
- font-size: 14px;
217
- color: var(--color-text-secondary);
218
-
219
- .is-current {
220
- color: var(--color-text-primary);
221
- font-weight: 500;
222
- }
223
- }
224
-
225
- &__right {
226
- display: flex;
227
- align-items: center;
228
- gap: 15px;
229
- }
230
-
231
- &__action {
232
- width: 32px;
233
- height: 32px;
234
- display: flex;
235
- align-items: center;
236
- justify-content: center;
237
- cursor: pointer;
238
- border-radius: var(--border-radius-base);
239
- font-size: 16px;
240
-
241
- &:hover {
242
- background-color: var(--color-fill);
243
- }
244
- }
245
-
246
- &__user {
247
- position: relative;
248
-
249
- &-trigger {
250
- display: flex;
251
- align-items: center;
252
- gap: 8px;
253
- cursor: pointer;
254
- padding: 4px 8px;
255
- border-radius: var(--border-radius-base);
256
- transition: background-color 0.2s;
257
-
258
- &:hover {
259
- background-color: var(--color-fill);
260
- }
261
- }
262
-
263
- &-name {
264
- font-size: 14px;
265
- color: var(--color-text-primary);
266
- }
267
-
268
- &-arrow {
269
- font-size: 10px;
270
- color: var(--color-text-secondary);
271
- transition: transform 0.2s;
272
-
273
- &.is-active {
274
- transform: rotate(180deg);
275
- }
276
- }
277
- }
278
-
279
- &__avatar {
280
- width: 32px;
281
- height: 32px;
282
- border-radius: 50%;
283
- background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light-3));
284
- display: flex;
285
- align-items: center;
286
- justify-content: center;
287
- color: #fff;
288
- font-size: 14px;
289
- font-weight: 500;
290
- }
291
-
292
- &__dropdown {
293
- position: absolute;
294
- top: calc(100% + 8px);
295
- right: 0;
296
- min-width: 200px;
297
- background-color: var(--bg-color);
298
- border-radius: var(--border-radius-base);
299
- box-shadow: var(--box-shadow);
300
- overflow: hidden;
301
- z-index: 100;
302
-
303
- &-header {
304
- display: flex;
305
- align-items: center;
306
- gap: 12px;
307
- padding: 16px;
308
- }
309
-
310
- &-avatar {
311
- width: 40px;
312
- height: 40px;
313
- border-radius: 50%;
314
- background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light-3));
315
- display: flex;
316
- align-items: center;
317
- justify-content: center;
318
- color: #fff;
319
- font-size: 16px;
320
- font-weight: 500;
321
- }
322
-
323
- &-info {
324
- flex: 1;
325
- }
326
-
327
- &-name {
328
- font-size: 14px;
329
- font-weight: 500;
330
- color: var(--color-text-primary);
331
- }
332
-
333
- &-role {
334
- font-size: 12px;
335
- color: var(--color-text-secondary);
336
- margin-top: 2px;
337
- }
338
-
339
- &-divider {
340
- height: 1px;
341
- background-color: var(--color-border-lighter);
342
- }
343
-
344
- &-menu {
345
- padding: 8px 0;
346
- }
347
-
348
- &-item {
349
- display: flex;
350
- align-items: center;
351
- gap: 10px;
352
- padding: 10px 16px;
353
- cursor: pointer;
354
- font-size: 14px;
355
- color: var(--color-text-regular);
356
- transition: all 0.2s;
357
-
358
- &:hover {
359
- background-color: var(--color-fill);
360
- color: var(--color-text-primary);
361
- }
362
-
363
- &--danger {
364
- color: var(--color-danger);
365
-
366
- &:hover {
367
- background-color: var(--color-danger-light);
368
- color: var(--color-danger);
369
- }
370
- }
371
- }
372
-
373
- &-icon {
374
- font-size: 16px;
375
- }
376
- }
377
- }
378
-
379
- // 下拉动画
380
- .dropdown-enter-active,
381
- .dropdown-leave-active {
382
- transition: all 0.2s ease;
383
- }
384
-
385
- .dropdown-enter-from,
386
- .dropdown-leave-to {
387
- opacity: 0;
388
- transform: translateY(-10px);
389
- }
390
- </style>
@@ -1,297 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, computed } from 'vue'
3
- import { useRoute, useRouter } from 'vue-router'
4
- import { useMenuStore } from '@/stores/menu'
5
- import { useUserStore } from '@/stores/user'
6
- import { useAuthStore } from '@/stores/auth'
7
- import { useAppStore } from '@/stores/app'
8
- import { Menu, MenuItem, SubMenu } from '@xto/navigation'
9
- import { Button } from '@xto/base'
10
- import { Input } from '@xto/form'
11
- import type { MenuItem as MenuItemType } from '@/types/api'
12
-
13
- const route = useRoute()
14
- const router = useRouter()
15
- const menuStore = useMenuStore()
16
- const userStore = useUserStore()
17
- const authStore = useAuthStore()
18
- const appStore = useAppStore()
19
-
20
- const searchKeyword = ref('')
21
- const isCollapsed = computed(() => appStore.isCollapsed)
22
- const activeMenu = computed(() => route.path)
23
-
24
- // 菜单主题相关
25
- const menuBgColor = computed(() => appStore.isDark ? '#1d1e1f' : '#fff')
26
- const menuTextColor = computed(() => appStore.isDark ? '#cfd3dc' : '#303133')
27
- const menuActiveTextColor = computed(() => '#409eff')
28
-
29
- // 扁平化菜单用于搜索
30
- const flattenMenus = (menus: MenuItemType[], parentTitle = ''): (MenuItemType & { parentTitle: string })[] => {
31
- const result: (MenuItemType & { parentTitle: string })[] = []
32
- menus.forEach(menu => {
33
- if (menu.children && menu.children.length > 0) {
34
- result.push(...flattenMenus(menu.children, menu.title))
35
- } else {
36
- result.push({ ...menu, parentTitle })
37
- }
38
- })
39
- return result
40
- }
41
-
42
- // 搜索结果
43
- const searchResults = computed(() => {
44
- if (!searchKeyword.value.trim()) return []
45
- const flatMenus = flattenMenus(menuStore.menuList)
46
- return flatMenus.filter(menu =>
47
- menu.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
48
- )
49
- })
50
-
51
- // 过滤后的菜单列表
52
- const filteredMenuList = computed(() => {
53
- if (!searchKeyword.value.trim()) return menuStore.menuList
54
-
55
- return menuStore.menuList.map(menu => {
56
- if (menu.children && menu.children.length > 0) {
57
- const filteredChildren = menu.children.filter(child =>
58
- child.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
59
- )
60
- if (filteredChildren.length > 0) {
61
- return { ...menu, children: filteredChildren }
62
- }
63
- return null
64
- }
65
- if (menu.title.toLowerCase().includes(searchKeyword.value.toLowerCase())) {
66
- return menu
67
- }
68
- return null
69
- }).filter(Boolean) as MenuItemType[]
70
- })
71
-
72
- // 菜单选择
73
- const handleMenuSelect = (index: string) => {
74
- if (index && index !== route.path) {
75
- router.push(index)
76
- searchKeyword.value = ''
77
- }
78
- }
79
-
80
- // 搜索结果点击
81
- const handleSearchItemClick = (path: string) => {
82
- router.push(path)
83
- searchKeyword.value = ''
84
- }
85
-
86
- // 退出登录
87
- const handleLogout = () => {
88
- authStore.logout()
89
- userStore.clearUserInfo()
90
- menuStore.clearMenu()
91
- router.push('/login')
92
- }
93
-
94
- // 菜单图标
95
- const getMenuIcon = (icon?: string) => {
96
- const iconMap: Record<string, string> = {
97
- dashboard: '📊',
98
- system: '⚙️',
99
- user: '👤',
100
- role: '👥',
101
- menu: '📋',
102
- setting: '🔧'
103
- }
104
- return iconMap[icon || ''] || '📄'
105
- }
106
- </script>
107
-
108
- <template>
109
- <div class="sidebar">
110
- <!-- Logo -->
111
- <div class="sidebar__logo">
112
- <img src="/vite.svg" alt="Logo" class="sidebar__logo-img" />
113
- <span v-show="!isCollapsed" class="sidebar__logo-text">Xto Demo</span>
114
- </div>
115
-
116
- <!-- 搜索框 -->
117
- <div v-if="!isCollapsed" class="sidebar__search">
118
- <Input
119
- v-model="searchKeyword"
120
- placeholder="搜索菜单..."
121
- size="small"
122
- clearable
123
- />
124
- <!-- 搜索结果 -->
125
- <div v-if="searchResults.length > 0" class="sidebar__search-results">
126
- <div
127
- v-for="item in searchResults"
128
- :key="item.path"
129
- class="sidebar__search-item"
130
- @click="handleSearchItemClick(item.path)"
131
- >
132
- <span class="menu-icon">{{ getMenuIcon(item.icon) }}</span>
133
- <div class="sidebar__search-item-info">
134
- <span class="sidebar__search-item-title">{{ item.title }}</span>
135
- <span v-if="item.parentTitle" class="sidebar__search-item-parent">{{ item.parentTitle }}</span>
136
- </div>
137
- </div>
138
- </div>
139
- </div>
140
-
141
- <!-- 菜单 -->
142
- <Menu
143
- :default-active="activeMenu"
144
- :collapse="isCollapsed"
145
- :collapse-transition="false"
146
- :background-color="menuBgColor"
147
- :text-color="menuTextColor"
148
- :active-text-color="menuActiveTextColor"
149
- class="sidebar__menu"
150
- @select="handleMenuSelect"
151
- >
152
- <template v-for="menu in filteredMenuList" :key="menu.path">
153
- <!-- 有子菜单 -->
154
- <SubMenu v-if="menu.children && menu.children.length > 0" :index="menu.path">
155
- <template #title>
156
- <span class="menu-icon">{{ getMenuIcon(menu.icon) }}</span>
157
- <span>{{ menu.title }}</span>
158
- </template>
159
- <MenuItem
160
- v-for="child in menu.children"
161
- :key="child.path"
162
- :index="child.path"
163
- >
164
- <span class="menu-icon">{{ getMenuIcon(child.icon) }}</span>
165
- <span>{{ child.title }}</span>
166
- </MenuItem>
167
- </SubMenu>
168
- <!-- 无子菜单 -->
169
- <MenuItem v-else :index="menu.path">
170
- <span class="menu-icon">{{ getMenuIcon(menu.icon) }}</span>
171
- <span>{{ menu.title }}</span>
172
- </MenuItem>
173
- </template>
174
- </Menu>
175
-
176
- <!-- 用户信息 -->
177
- <div class="sidebar__user" v-if="!isCollapsed">
178
- <div class="sidebar__user-info">
179
- <span class="sidebar__user-name">{{ userStore.nickname }}</span>
180
- <span class="sidebar__user-role">{{ userStore.roles.join(', ') }}</span>
181
- </div>
182
- <Button type="text" size="small" @click="handleLogout">退出</Button>
183
- </div>
184
- </div>
185
- </template>
186
-
187
- <style lang="scss" scoped>
188
- .sidebar {
189
- height: 100%;
190
- display: flex;
191
- flex-direction: column;
192
- background-color: var(--bg-color);
193
-
194
- &__logo {
195
- height: 50px;
196
- display: flex;
197
- align-items: center;
198
- justify-content: center;
199
- gap: 10px;
200
- border-bottom: 1px solid var(--color-border-lighter);
201
- }
202
-
203
- &__logo-img {
204
- width: 32px;
205
- height: 32px;
206
- }
207
-
208
- &__logo-text {
209
- font-size: 16px;
210
- font-weight: 600;
211
- color: var(--color-primary);
212
- }
213
-
214
- &__search {
215
- padding: 10px;
216
- border-bottom: 1px solid var(--color-border-lighter);
217
- position: relative;
218
- }
219
-
220
- &__search-results {
221
- position: absolute;
222
- top: 100%;
223
- left: 0;
224
- right: 0;
225
- background-color: var(--bg-color);
226
- border: 1px solid var(--color-border-lighter);
227
- border-radius: var(--border-radius-base);
228
- box-shadow: var(--box-shadow);
229
- max-height: 300px;
230
- overflow-y: auto;
231
- z-index: 100;
232
- }
233
-
234
- &__search-item {
235
- display: flex;
236
- align-items: center;
237
- gap: 10px;
238
- padding: 10px 12px;
239
- cursor: pointer;
240
- transition: background-color 0.2s;
241
-
242
- &:hover {
243
- background-color: var(--color-fill);
244
- }
245
-
246
- &-info {
247
- display: flex;
248
- flex-direction: column;
249
- gap: 2px;
250
- }
251
-
252
- &-title {
253
- font-size: 14px;
254
- color: var(--color-text-primary);
255
- }
256
-
257
- &-parent {
258
- font-size: 12px;
259
- color: var(--color-text-secondary);
260
- }
261
- }
262
-
263
- &__menu {
264
- flex: 1;
265
- border-right: none;
266
- overflow-y: auto;
267
- }
268
-
269
- &__user {
270
- padding: 10px;
271
- border-top: 1px solid var(--color-border-lighter);
272
- display: flex;
273
- align-items: center;
274
- justify-content: space-between;
275
- }
276
-
277
- &__user-info {
278
- display: flex;
279
- flex-direction: column;
280
- gap: 2px;
281
- }
282
-
283
- &__user-name {
284
- font-size: 14px;
285
- font-weight: 500;
286
- }
287
-
288
- &__user-role {
289
- font-size: 12px;
290
- color: var(--color-text-secondary);
291
- }
292
- }
293
-
294
- .menu-icon {
295
- margin-right: 8px;
296
- }
297
- </style>