xto-fronted 0.4.7 → 0.4.9

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 (82) hide show
  1. package/.env.development +7 -7
  2. package/.env.production +7 -7
  3. package/dist/assets/403-AFBQifUI.js +1 -0
  4. package/dist/assets/403-BHEXXbt2.css +1 -0
  5. package/dist/assets/404-Ct_A1n7S.css +1 -0
  6. package/dist/assets/404-WFvpcD2_.js +1 -0
  7. package/dist/assets/_plugin-vue_export-helper-DlAUqK2U.js +1 -0
  8. package/dist/assets/index-1juADvYN.js +2 -0
  9. package/dist/assets/index-4-QoJAgA.css +1 -0
  10. package/dist/assets/index-B-sX4Ru0.js +1 -0
  11. package/dist/assets/index-BHwEwbkp.js +1 -0
  12. package/dist/assets/index-BMcziU5a.css +1 -0
  13. package/dist/assets/index-BRR97dc6.js +1 -0
  14. package/dist/assets/index-BTsRosKu.js +1 -0
  15. package/dist/assets/index-BZA0ksjx.css +1 -0
  16. package/dist/assets/index-BpV_8nl0.js +1 -0
  17. package/dist/assets/index-BvzhR4zp.js +1 -0
  18. package/dist/assets/index-CUh_s55Z.css +1 -0
  19. package/dist/assets/index-CVjdnIgR.css +1 -0
  20. package/dist/assets/index-CYq57-zj.js +1 -0
  21. package/dist/assets/index-CZAlkDIC.css +1 -0
  22. package/dist/assets/index-CkL3sVAQ.js +2 -0
  23. package/dist/assets/index-CtrKVYJb.css +1 -0
  24. package/dist/assets/index-Cz2P_bsS.js +1 -0
  25. package/dist/assets/index-D9wlAuR_.js +1 -0
  26. package/dist/assets/index-DawJb02s.css +1 -0
  27. package/dist/assets/index-DfFR6NLf.js +1 -0
  28. package/dist/assets/index-Do3gMkWw.js +2 -0
  29. package/dist/assets/index-DwVgMO8e.js +1 -0
  30. package/dist/assets/index-GDP-IkXE.css +1 -0
  31. package/dist/assets/index-Iaz1ZzPC.js +2 -0
  32. package/dist/assets/index-PfV8pzQz.css +1 -0
  33. package/dist/assets/index-Swfu6yvD.css +1 -0
  34. package/dist/assets/index-Te8_PRgJ.js +1 -0
  35. package/dist/assets/index-WyZ91RLx.css +1 -0
  36. package/dist/assets/index-tFYRoFdE.js +1 -0
  37. package/dist/assets/vendor-42ANG6Sg.js +6 -0
  38. package/dist/assets/vite-Dw-pgLOX.js +1 -0
  39. package/dist/assets/vue-vendor-Br-l7wbK.js +29 -0
  40. package/dist/assets/xto-base-C-IBqjVs.js +1 -0
  41. package/dist/assets/xto-base-C6eqMPdO.css +1 -0
  42. package/dist/assets/xto-business--V1F5Gwb.css +1 -0
  43. package/dist/assets/xto-core-DZK7Cyg0.js +1 -0
  44. package/dist/assets/xto-data-BFpiDgJi.js +1 -0
  45. package/dist/assets/xto-data-CnAQAQH2.css +1 -0
  46. package/dist/assets/xto-feedback-B7ipsTfz.js +1 -0
  47. package/dist/assets/xto-feedback-DBwJzoTj.css +1 -0
  48. package/dist/assets/xto-form-CrsyAjyr.css +1 -0
  49. package/dist/assets/xto-form-NRjKKNcY.js +1 -0
  50. package/dist/assets/xto-layout-BqU8RuWL.css +1 -0
  51. package/dist/assets/xto-navigation-BiSaXPfr.js +1 -0
  52. package/dist/assets/xto-navigation-C1cnSL2E.css +1 -0
  53. package/dist/assets/xto-navigation-CBPg4dCc.css +1 -0
  54. package/dist/assets/xto-navigation-CKabFu9d.js +1 -0
  55. package/dist/index.html +28 -0
  56. package/package.json +85 -85
  57. package/src/api/auth.ts +25 -25
  58. package/src/api/system.ts +66 -66
  59. package/src/assets/styles/_dark.scss +524 -406
  60. package/src/assets/styles/index.scss +8 -0
  61. package/src/components/Layout/Header.vue +968 -973
  62. package/src/components/Layout/Sidebar.vue +283 -273
  63. package/src/components/Layout/TopMenu.vue +186 -0
  64. package/src/components/Layout/index.vue +60 -3
  65. package/src/composables/useApp.ts +61 -61
  66. package/src/composables/useAuth.ts +16 -16
  67. package/src/directives/permission.ts +27 -27
  68. package/src/env.d.ts +18 -18
  69. package/src/index.ts +47 -47
  70. package/src/router/dynamicRoutes.ts +162 -162
  71. package/src/router/guards.ts +128 -128
  72. package/src/router/index.ts +79 -79
  73. package/src/stores/auth.ts +65 -65
  74. package/src/stores/menu.ts +48 -48
  75. package/src/stores/user.ts +50 -50
  76. package/src/types/api.d.ts +80 -80
  77. package/src/utils/auth.ts +99 -99
  78. package/src/utils/config.ts +80 -80
  79. package/src/utils/permission.ts +32 -32
  80. package/src/utils/request.ts +124 -124
  81. package/src/views/login/index.vue +194 -194
  82. package/vite.config.ts +135 -135
@@ -1,274 +1,284 @@
1
- <script setup lang="ts">
2
- import { 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, Icon } from '@xto/base'
10
-
11
- const route = useRoute()
12
- const router = useRouter()
13
- const menuStore = useMenuStore()
14
- const userStore = useUserStore()
15
- const authStore = useAuthStore()
16
- const appStore = useAppStore()
17
-
18
- const isCollapsed = computed(() => appStore.isCollapsed)
19
- const activeMenu = computed(() => route.path)
20
-
21
- // 菜单主题相关
22
- const menuBgColor = computed(() => appStore.isDark ? '#1d1e1f' : '#fff')
23
- const menuTextColor = computed(() => appStore.isDark ? '#cfd3dc' : '#303133')
24
- const menuActiveTextColor = computed(() => '#409eff')
25
-
26
- // 菜单选择
27
- const handleMenuSelect = (index: string) => {
28
- if (index && index !== route.path) {
29
- router.push(index)
30
- }
31
- }
32
-
33
- // 退出登录
34
- const handleLogout = () => {
35
- authStore.logout()
36
- userStore.clearUserInfo()
37
- menuStore.clearMenu()
38
- router.push('/login')
39
- }
40
-
41
- // 已知的图标名称列表(来自 @xto/base/icons.ts)
42
- const knownIcons = new Set([
43
- 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right',
44
- 'caret-down', 'caret-right', 'plus', 'minus', 'close', 'check',
45
- 'edit', 'delete', 'copy', 'download', 'upload', 'refresh', 'search',
46
- 'filter', 'more', 'setting', 'share', 'loading', 'info', 'success',
47
- 'warning', 'error', 'question', 'user', 'user-add', 'user-group',
48
- 'logout', 'login', 'file', 'folder', 'folder-open', 'document',
49
- 'image', 'video', 'music', 'camera', 'mail', 'phone', 'chat',
50
- 'bell', 'message', 'eye', 'eye-off', 'calendar', 'clock', 'history',
51
- 'timer', 'location', 'map', 'globe', 'star', 'heart', 'thumb-up',
52
- 'link', 'external-link', 'lock', 'unlock', 'key', 'home', 'menu',
53
- 'menu-fold', 'menu-unfold', 'sidebar-fold', 'sidebar-expand',
54
- 'sidebar-left', 'dashboard', 'chart', 'chart-pie', 'chart-line',
55
- 'report', 'analytics', 'system', 'permission', 'role', 'user-manage',
56
- 'log', 'notification', 'app', 'list', 'grid', 'fullscreen',
57
- 'fullscreen-exit', 'zoom-in', 'zoom-out', 'print', 'bookmark',
58
- 'tag', 'code', 'terminal', 'database', 'server', 'cloud', 'gift',
59
- 'moon', 'sun', 'theme', 'skin'
60
- ])
61
-
62
- // 获取菜单图标名称
63
- const getMenuIcon = (icon?: string): string => {
64
- // 无图标时返回空
65
- if (!icon || icon === '') return ''
66
-
67
- // 处理 tineco-ui 的图标类名(如 tineco-icon-home)
68
- if (icon.startsWith('tineco-icon-')) {
69
- const iconName = icon.replace('tineco-icon-', '')
70
- // 常见的 tineco icon 映射
71
- const tinecoIconMap: Record<string, string> = {
72
- home: 'home',
73
- dashboard: 'dashboard',
74
- system: 'system',
75
- user: 'user',
76
- role: 'role',
77
- menu: 'list',
78
- setting: 'setting',
79
- file: 'file',
80
- folder: 'folder',
81
- chart: 'chart',
82
- report: 'report',
83
- analytics: 'analytics'
84
- }
85
- return tinecoIconMap[iconName] || iconName
86
- }
87
-
88
- // 常见业务图标映射
89
- const iconMap: Record<string, string> = {
90
- dashboard: 'dashboard',
91
- system: 'system',
92
- user: 'user',
93
- role: 'role',
94
- menu: 'list',
95
- setting: 'setting',
96
- home: 'home',
97
- chart: 'chart',
98
- report: 'report',
99
- analytics: 'analytics',
100
- permission: 'permission',
101
- log: 'log',
102
- notification: 'notification',
103
- app: 'app',
104
- list: 'list',
105
- grid: 'grid'
106
- }
107
-
108
- return iconMap[icon] || icon
109
- }
110
-
111
- // 获取菜单名称第一个字
112
- const getFirstChar = (name?: string): string => {
113
- if (!name) return ''
114
- return name.charAt(0)
115
- }
116
-
117
- // 判断图标是否存在
118
- const iconExists = (iconName: string): boolean => {
119
- return knownIcons.has(iconName)
120
- }
121
- </script>
122
-
123
- <template>
124
- <div class="sidebar" :class="{ 'sidebar--collapsed': isCollapsed }">
125
- <!-- Logo -->
126
- <div class="sidebar__logo">
127
- <img src="/vite.svg" alt="Logo" class="sidebar__logo-img" />
128
- <span v-show="!isCollapsed" class="sidebar__logo-text">{{ appStore.appName }}</span>
129
- </div>
130
-
131
- <!-- 菜单 -->
132
- <Menu
133
- :default-active="activeMenu"
134
- mode="vertical"
135
- :collapse="isCollapsed"
136
- :collapse-transition="false"
137
- :background-color="menuBgColor"
138
- :text-color="menuTextColor"
139
- :active-text-color="menuActiveTextColor"
140
- class="sidebar__menu"
141
- @select="handleMenuSelect"
142
- >
143
- <template v-for="menu in menuStore.menuList" :key="menu.menuUrl">
144
- <!-- 有子菜单 -->
145
- <SubMenu v-if="menu.children && menu.children.length > 0" :index="menu.menuUrl">
146
- <template #title>
147
- <span class="sidebar__menu-icon">
148
- <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
149
- <span v-else class="sidebar__menu-char">{{ getFirstChar(menu.menuName) }}</span>
150
- </span>
151
- <span>{{ menu.menuName }}</span>
152
- </template>
153
- <MenuItem
154
- v-for="child in menu.children"
155
- :key="child.menuUrl"
156
- :index="child.menuUrl"
157
- >
158
- <span class="sidebar__menu-icon">
159
- <Icon v-if="iconExists(getMenuIcon(child.icon))" :name="getMenuIcon(child.icon)" :size="16" />
160
- <span v-else class="sidebar__menu-char">{{ getFirstChar(child.menuName) }}</span>
161
- </span>
162
- <span>{{ child.menuName }}</span>
163
- </MenuItem>
164
- </SubMenu>
165
- <!-- 无子菜单 -->
166
- <MenuItem v-else :index="menu.menuUrl">
167
- <span class="sidebar__menu-icon">
168
- <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
169
- <span v-else class="sidebar__menu-char">{{ getFirstChar(menu.menuName) }}</span>
170
- </span>
171
- <span>{{ menu.menuName }}</span>
172
- </MenuItem>
173
- </template>
174
- </Menu>
175
-
176
- <!-- 用户信息 -->
177
- <div v-if="!isCollapsed" class="sidebar__user">
178
- <div class="sidebar__user-info">
179
- <span class="sidebar__user-name">{{ userStore.userName }}</span>
180
- <span class="sidebar__user-role">{{ userStore.departmentName }}</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
- border-right: 1px solid var(--color-border-lighter);
194
-
195
- &--collapsed {
196
- .sidebar__logo {
197
- justify-content: center;
198
- padding: 0;
199
- }
200
- }
201
-
202
- &__logo {
203
- height: 50px;
204
- display: flex;
205
- align-items: center;
206
- padding: 0 20px;
207
- gap: 10px;
208
- border-bottom: 1px solid var(--color-border-lighter);
209
- }
210
-
211
- &__logo-img {
212
- width: 32px;
213
- height: 32px;
214
- }
215
-
216
- &__logo-text {
217
- font-size: 16px;
218
- font-weight: 600;
219
- color: var(--color-primary);
220
- }
221
-
222
- &__menu {
223
- flex: 1;
224
- border-right: none;
225
- overflow-y: auto;
226
- }
227
-
228
- &__menu-icon {
229
- display: inline-flex;
230
- align-items: center;
231
- justify-content: center;
232
- width: 16px;
233
- height: 16px;
234
- margin-right: 8px;
235
- }
236
-
237
- &__menu-char {
238
- display: inline-flex;
239
- align-items: center;
240
- justify-content: center;
241
- width: 16px;
242
- height: 16px;
243
- font-size: 12px;
244
- font-weight: 600;
245
- color: var(--color-primary);
246
- background-color: var(--color-primary-light-8);
247
- border-radius: 4px;
248
- }
249
-
250
- &__user {
251
- padding: 10px;
252
- border-top: 1px solid var(--color-border-lighter);
253
- display: flex;
254
- align-items: center;
255
- justify-content: space-between;
256
- }
257
-
258
- &__user-info {
259
- display: flex;
260
- flex-direction: column;
261
- gap: 2px;
262
- }
263
-
264
- &__user-name {
265
- font-size: 14px;
266
- font-weight: 500;
267
- }
268
-
269
- &__user-role {
270
- font-size: 12px;
271
- color: var(--color-text-secondary);
272
- }
273
- }
1
+ <script setup lang="ts">
2
+ import { 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, Icon } from '@xto/base'
10
+
11
+ // Props
12
+ const props = withDefaults(defineProps<{
13
+ menuList?: any[]
14
+ }>(), {
15
+ menuList: () => []
16
+ })
17
+
18
+ const route = useRoute()
19
+ const router = useRouter()
20
+ const menuStore = useMenuStore()
21
+ const userStore = useUserStore()
22
+ const authStore = useAuthStore()
23
+ const appStore = useAppStore()
24
+
25
+ // 使用传入的菜单列表,如果没有传入则使用 store 中的
26
+ const displayMenuList = computed(() => props.menuList.length > 0 ? props.menuList : menuStore.menuList)
27
+
28
+ const isCollapsed = computed(() => appStore.isCollapsed)
29
+ const activeMenu = computed(() => route.path)
30
+
31
+ // 菜单主题相关
32
+ const menuBgColor = computed(() => appStore.isDark ? '#1d1e1f' : '#fff')
33
+ const menuTextColor = computed(() => appStore.isDark ? '#cfd3dc' : '#303133')
34
+ const menuActiveTextColor = computed(() => '#409eff')
35
+
36
+ // 菜单选择
37
+ const handleMenuSelect = (index: string) => {
38
+ if (index && index !== route.path) {
39
+ router.push(index)
40
+ }
41
+ }
42
+
43
+ // 退出登录
44
+ const handleLogout = () => {
45
+ authStore.logout()
46
+ userStore.clearUserInfo()
47
+ menuStore.clearMenu()
48
+ router.push('/login')
49
+ }
50
+
51
+ // 已知的图标名称列表(来自 @xto/base/icons.ts)
52
+ const knownIcons = new Set([
53
+ 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right',
54
+ 'caret-down', 'caret-right', 'plus', 'minus', 'close', 'check',
55
+ 'edit', 'delete', 'copy', 'download', 'upload', 'refresh', 'search',
56
+ 'filter', 'more', 'setting', 'share', 'loading', 'info', 'success',
57
+ 'warning', 'error', 'question', 'user', 'user-add', 'user-group',
58
+ 'logout', 'login', 'file', 'folder', 'folder-open', 'document',
59
+ 'image', 'video', 'music', 'camera', 'mail', 'phone', 'chat',
60
+ 'bell', 'message', 'eye', 'eye-off', 'calendar', 'clock', 'history',
61
+ 'timer', 'location', 'map', 'globe', 'star', 'heart', 'thumb-up',
62
+ 'link', 'external-link', 'lock', 'unlock', 'key', 'home', 'menu',
63
+ 'menu-fold', 'menu-unfold', 'sidebar-fold', 'sidebar-expand',
64
+ 'sidebar-left', 'dashboard', 'chart', 'chart-pie', 'chart-line',
65
+ 'report', 'analytics', 'system', 'permission', 'role', 'user-manage',
66
+ 'log', 'notification', 'app', 'list', 'grid', 'fullscreen',
67
+ 'fullscreen-exit', 'zoom-in', 'zoom-out', 'print', 'bookmark',
68
+ 'tag', 'code', 'terminal', 'database', 'server', 'cloud', 'gift',
69
+ 'moon', 'sun', 'theme', 'skin'
70
+ ])
71
+
72
+ // 获取菜单图标名称
73
+ const getMenuIcon = (icon?: string): string => {
74
+ // 无图标时返回空
75
+ if (!icon || icon === '') return ''
76
+
77
+ // 处理 tineco-ui 的图标类名(如 tineco-icon-home)
78
+ if (icon.startsWith('tineco-icon-')) {
79
+ const iconName = icon.replace('tineco-icon-', '')
80
+ // 常见的 tineco icon 映射
81
+ const tinecoIconMap: Record<string, string> = {
82
+ home: 'home',
83
+ dashboard: 'dashboard',
84
+ system: 'system',
85
+ user: 'user',
86
+ role: 'role',
87
+ menu: 'list',
88
+ setting: 'setting',
89
+ file: 'file',
90
+ folder: 'folder',
91
+ chart: 'chart',
92
+ report: 'report',
93
+ analytics: 'analytics'
94
+ }
95
+ return tinecoIconMap[iconName] || iconName
96
+ }
97
+
98
+ // 常见业务图标映射
99
+ const iconMap: Record<string, string> = {
100
+ dashboard: 'dashboard',
101
+ system: 'system',
102
+ user: 'user',
103
+ role: 'role',
104
+ menu: 'list',
105
+ setting: 'setting',
106
+ home: 'home',
107
+ chart: 'chart',
108
+ report: 'report',
109
+ analytics: 'analytics',
110
+ permission: 'permission',
111
+ log: 'log',
112
+ notification: 'notification',
113
+ app: 'app',
114
+ list: 'list',
115
+ grid: 'grid'
116
+ }
117
+
118
+ return iconMap[icon] || icon
119
+ }
120
+
121
+ // 获取菜单名称第一个字
122
+ const getFirstChar = (name?: string): string => {
123
+ if (!name) return ''
124
+ return name.charAt(0)
125
+ }
126
+
127
+ // 判断图标是否存在
128
+ const iconExists = (iconName: string): boolean => {
129
+ return knownIcons.has(iconName)
130
+ }
131
+ </script>
132
+
133
+ <template>
134
+ <div class="sidebar" :class="{ 'sidebar--collapsed': isCollapsed }">
135
+ <!-- Logo -->
136
+ <div class="sidebar__logo">
137
+ <img src="/vite.svg" alt="Logo" class="sidebar__logo-img" />
138
+ <span v-show="!isCollapsed" class="sidebar__logo-text">{{ appStore.appName }}</span>
139
+ </div>
140
+
141
+ <!-- 菜单 -->
142
+ <Menu
143
+ :default-active="activeMenu"
144
+ mode="vertical"
145
+ :collapse="isCollapsed"
146
+ :collapse-transition="false"
147
+ :background-color="menuBgColor"
148
+ :text-color="menuTextColor"
149
+ :active-text-color="menuActiveTextColor"
150
+ class="sidebar__menu"
151
+ @select="handleMenuSelect"
152
+ >
153
+ <template v-for="menu in displayMenuList" :key="menu.menuUrl">
154
+ <!-- 有子菜单 -->
155
+ <SubMenu v-if="menu.children && menu.children.length > 0" :index="menu.menuUrl">
156
+ <template #title>
157
+ <span class="sidebar__menu-icon">
158
+ <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
159
+ <span v-else class="sidebar__menu-char">{{ getFirstChar(menu.menuName) }}</span>
160
+ </span>
161
+ <span>{{ menu.menuName }}</span>
162
+ </template>
163
+ <MenuItem
164
+ v-for="child in menu.children"
165
+ :key="child.menuUrl"
166
+ :index="child.menuUrl"
167
+ >
168
+ <span class="sidebar__menu-icon">
169
+ <Icon v-if="iconExists(getMenuIcon(child.icon))" :name="getMenuIcon(child.icon)" :size="16" />
170
+ <span v-else class="sidebar__menu-char">{{ getFirstChar(child.menuName) }}</span>
171
+ </span>
172
+ <span>{{ child.menuName }}</span>
173
+ </MenuItem>
174
+ </SubMenu>
175
+ <!-- 无子菜单 -->
176
+ <MenuItem v-else :index="menu.menuUrl">
177
+ <span class="sidebar__menu-icon">
178
+ <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
179
+ <span v-else class="sidebar__menu-char">{{ getFirstChar(menu.menuName) }}</span>
180
+ </span>
181
+ <span>{{ menu.menuName }}</span>
182
+ </MenuItem>
183
+ </template>
184
+ </Menu>
185
+
186
+ <!-- 用户信息 -->
187
+ <div v-if="!isCollapsed" class="sidebar__user">
188
+ <div class="sidebar__user-info">
189
+ <span class="sidebar__user-name">{{ userStore.userName }}</span>
190
+ <span class="sidebar__user-role">{{ userStore.departmentName }}</span>
191
+ </div>
192
+ <Button type="text" size="small" @click="handleLogout">退出</Button>
193
+ </div>
194
+ </div>
195
+ </template>
196
+
197
+ <style lang="scss" scoped>
198
+ .sidebar {
199
+ height: 100%;
200
+ display: flex;
201
+ flex-direction: column;
202
+ background-color: var(--bg-color);
203
+ border-right: 1px solid var(--color-border-lighter);
204
+
205
+ &--collapsed {
206
+ .sidebar__logo {
207
+ justify-content: center;
208
+ padding: 0;
209
+ }
210
+ }
211
+
212
+ &__logo {
213
+ height: 50px;
214
+ display: flex;
215
+ align-items: center;
216
+ padding: 0 20px;
217
+ gap: 10px;
218
+ border-bottom: 1px solid var(--color-border-lighter);
219
+ }
220
+
221
+ &__logo-img {
222
+ width: 32px;
223
+ height: 32px;
224
+ }
225
+
226
+ &__logo-text {
227
+ font-size: 16px;
228
+ font-weight: 600;
229
+ color: var(--color-primary);
230
+ }
231
+
232
+ &__menu {
233
+ flex: 1;
234
+ border-right: none;
235
+ overflow-y: auto;
236
+ }
237
+
238
+ &__menu-icon {
239
+ display: inline-flex;
240
+ align-items: center;
241
+ justify-content: center;
242
+ width: 16px;
243
+ height: 16px;
244
+ margin-right: 8px;
245
+ }
246
+
247
+ &__menu-char {
248
+ display: inline-flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ width: 16px;
252
+ height: 16px;
253
+ font-size: 12px;
254
+ font-weight: 600;
255
+ color: var(--color-primary);
256
+ background-color: var(--color-primary-light-8);
257
+ border-radius: 4px;
258
+ }
259
+
260
+ &__user {
261
+ padding: 10px;
262
+ border-top: 1px solid var(--color-border-lighter);
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: space-between;
266
+ }
267
+
268
+ &__user-info {
269
+ display: flex;
270
+ flex-direction: column;
271
+ gap: 2px;
272
+ }
273
+
274
+ &__user-name {
275
+ font-size: 14px;
276
+ font-weight: 500;
277
+ }
278
+
279
+ &__user-role {
280
+ font-size: 12px;
281
+ color: var(--color-text-secondary);
282
+ }
283
+ }
274
284
  </style>