xto-fronted 0.1.1 → 0.1.3

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 (67) hide show
  1. package/.env.development +3 -4
  2. package/.env.production +3 -4
  3. package/bin/cli.js +104 -0
  4. package/dist/{403-MQkNUulz.js → 403-DM5wfQkM.js} +6 -6
  5. package/dist/{404-BOFYLq4X.js → 404-BurAu5LC.js} +7 -7
  6. package/dist/api/auth.d.ts +9 -8
  7. package/dist/api/menu.d.ts +3 -0
  8. package/dist/api/user.d.ts +2 -12
  9. package/dist/composables/index.d.ts +8 -0
  10. package/dist/composables/useApp.d.ts +64 -0
  11. package/dist/composables/useAuth.d.ts +19 -4
  12. package/dist/composables/useMenu.d.ts +34 -0
  13. package/dist/config/index.d.ts +11 -0
  14. package/dist/index-BNiEld34.js +15 -0
  15. package/dist/index-Be9RiEfo.js +98 -0
  16. package/dist/index-BqRv1bdN.js +1185 -0
  17. package/dist/index-CQLVXvNJ.js +15 -0
  18. package/dist/index-CyiE8n2V.js +15 -0
  19. package/dist/index-xauR1bOL.js +15 -0
  20. package/dist/index.d.ts +7 -4
  21. package/dist/index.es.js +50 -66
  22. package/dist/index.umd.js +1 -1
  23. package/dist/stores/auth.d.ts +60 -23
  24. package/dist/stores/menu.d.ts +40 -29
  25. package/dist/stores/user.d.ts +63 -84
  26. package/dist/style.css +1 -1
  27. package/dist/utils/auth.d.ts +15 -7
  28. package/dist/utils/permission.d.ts +1 -6
  29. package/dist/views/system/menu/index.vue.d.ts +1 -3
  30. package/dist/views/system/role/index.vue.d.ts +1 -3
  31. package/dist/views/system/user/index.vue.d.ts +1 -3
  32. package/package.json +27 -19
  33. package/src/api/auth.ts +34 -25
  34. package/src/api/menu.ts +13 -0
  35. package/src/api/user.ts +11 -45
  36. package/src/components/Layout/Header.vue +334 -389
  37. package/src/components/Layout/Sidebar.vue +212 -296
  38. package/src/components/Layout/Tabs.vue +19 -133
  39. package/src/composables/index.ts +9 -0
  40. package/src/composables/useApp.ts +170 -0
  41. package/src/composables/useAuth.ts +69 -44
  42. package/src/composables/useMenu.ts +141 -0
  43. package/src/config/index.ts +19 -0
  44. package/src/directives/permission.ts +40 -37
  45. package/src/index.ts +9 -4
  46. package/src/router/index.ts +70 -80
  47. package/src/stores/auth.ts +44 -31
  48. package/src/stores/menu.ts +157 -79
  49. package/src/stores/user.ts +40 -72
  50. package/src/types/api.d.ts +102 -83
  51. package/src/types/xto.d.ts +148 -148
  52. package/src/utils/auth.ts +85 -61
  53. package/src/utils/permission.ts +29 -41
  54. package/src/utils/request.ts +125 -125
  55. package/src/utils/storage.ts +10 -1
  56. package/src/views/dashboard/index.vue +31 -283
  57. package/src/views/login/index.vue +140 -247
  58. package/src/views/system/menu/index.vue +31 -380
  59. package/src/views/system/role/index.vue +31 -303
  60. package/src/views/system/user/index.vue +31 -326
  61. package/vite.config.ts +3 -3
  62. package/dist/index-BJxYdNPy.js +0 -475
  63. package/dist/index-BvnIIBR1.js +0 -142
  64. package/dist/index-CEvAq6KE.js +0 -372
  65. package/dist/index-DPkqej__.js +0 -345
  66. package/dist/index-pq9Z5K62.js +0 -184
  67. package/dist/index-vVfjShJR.js +0 -1183
@@ -1,390 +1,335 @@
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
- }
1
+ <script setup lang="ts">
2
+ /**
3
+ * 头部组件
4
+ * 使用统一的 useApp hooks
5
+ */
6
+ import { ref, computed, onMounted, onUnmounted } from 'vue'
7
+ import { useApp } from '@/composables'
8
+ import { useAppStore } from '@/stores/app'
9
+
10
+ const appStore = useAppStore()
11
+ const { logout, userName, userInfo } = useApp()
12
+
13
+ const dropdownVisible = ref(false)
14
+ const dropdownRef = ref<HTMLElement | null>(null)
15
+ const isFullscreen = ref(false)
16
+
17
+ // 用户显示名称
18
+ const userDisplayName = computed(() => userName.value || '用户')
19
+
20
+ // 切换折叠
21
+ const toggleCollapse = () => {
22
+ appStore.toggleCollapse()
23
+ }
24
+
25
+ // 切换主题
26
+ const toggleTheme = () => {
27
+ appStore.toggleTheme()
28
+ }
29
+
30
+ // 切换全屏
31
+ const toggleFullscreen = () => {
32
+ if (!document.fullscreenElement) {
33
+ document.documentElement.requestFullscreen()
34
+ } else {
35
+ document.exitFullscreen()
36
+ }
37
+ }
38
+
39
+ // 监听全屏变化
40
+ const handleFullscreenChange = () => {
41
+ isFullscreen.value = !!document.fullscreenElement
42
+ }
43
+
44
+ // 切换下拉菜单
45
+ const toggleDropdown = () => {
46
+ dropdownVisible.value = !dropdownVisible.value
47
+ }
48
+
49
+ // 关闭下拉菜单
50
+ const closeDropdown = () => {
51
+ dropdownVisible.value = false
52
+ }
53
+
54
+ // 退出登录
55
+ const handleLogout = async () => {
56
+ closeDropdown()
57
+ await logout()
58
+ }
59
+
60
+ // 点击外部关闭下拉菜单
61
+ const handleClickOutside = (event: MouseEvent) => {
62
+ if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
63
+ closeDropdown()
64
+ }
65
+ }
66
+
67
+ onMounted(() => {
68
+ document.addEventListener('click', handleClickOutside)
69
+ document.addEventListener('fullscreenchange', handleFullscreenChange)
70
+ })
71
+
72
+ onUnmounted(() => {
73
+ document.removeEventListener('click', handleClickOutside)
74
+ document.removeEventListener('fullscreenchange', handleFullscreenChange)
75
+ })
76
+ </script>
77
+
78
+ <template>
79
+ <div class="header">
80
+ <!-- 左侧 -->
81
+ <div class="header__left">
82
+ <div class="header__collapse" @click="toggleCollapse">
83
+ <span>☰</span>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- 右侧 -->
88
+ <div class="header__right">
89
+ <!-- 全屏切换 -->
90
+ <div class="header__action" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏'">
91
+ <svg v-if="isFullscreen" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
92
+ <path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
93
+ </svg>
94
+ <svg v-else viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
95
+ <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
96
+ </svg>
97
+ </div>
98
+
99
+ <!-- 主题切换 -->
100
+ <div class="header__action" @click="toggleTheme" title="切换主题">
101
+ <span v-if="appStore.isDark">🌙</span>
102
+ <span v-else>☀️</span>
103
+ </div>
104
+
105
+ <!-- 用户头像 -->
106
+ <div class="header__user" ref="dropdownRef">
107
+ <div class="header__user-trigger" @click.stop="toggleDropdown">
108
+ <div class="header__avatar">
109
+ <span>{{ userDisplayName.charAt(0) }}</span>
110
+ </div>
111
+ <span class="header__user-name">{{ userDisplayName }}</span>
112
+ <span class="header__user-arrow" :class="{ 'is-active': dropdownVisible }">▼</span>
113
+ </div>
114
+
115
+ <!-- 下拉菜单 -->
116
+ <Transition name="dropdown">
117
+ <div v-if="dropdownVisible" class="header__dropdown">
118
+ <div class="header__dropdown-header">
119
+ <div class="header__dropdown-avatar">
120
+ <span>{{ userDisplayName.charAt(0) }}</span>
121
+ </div>
122
+ <div class="header__dropdown-info">
123
+ <div class="header__dropdown-name">{{ userDisplayName }}</div>
124
+ <div class="header__dropdown-role">{{ userInfo?.departmentName || '' }}</div>
125
+ </div>
126
+ </div>
127
+ <div class="header__dropdown-divider"></div>
128
+ <div class="header__dropdown-menu">
129
+ <div class="header__dropdown-item header__dropdown-item--danger" @click="handleLogout">
130
+ <span class="header__dropdown-icon">🚪</span>
131
+ <span>退出登录</span>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </Transition>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </template>
140
+
141
+ <style lang="scss" scoped>
142
+ .header {
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: space-between;
146
+ padding: 0 20px;
147
+ height: 100%;
148
+
149
+ &__left {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 15px;
153
+ }
154
+
155
+ &__collapse {
156
+ width: 24px;
157
+ height: 24px;
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ cursor: pointer;
162
+ font-size: 18px;
163
+ color: var(--color-text-regular);
164
+
165
+ &:hover {
166
+ color: var(--color-primary);
167
+ }
168
+ }
169
+
170
+ &__right {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 15px;
174
+ }
175
+
176
+ &__action {
177
+ width: 32px;
178
+ height: 32px;
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ cursor: pointer;
183
+ border-radius: var(--border-radius-base);
184
+ font-size: 16px;
185
+
186
+ &:hover {
187
+ background-color: var(--color-fill);
188
+ }
189
+ }
190
+
191
+ &__user {
192
+ position: relative;
193
+
194
+ &-trigger {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 8px;
198
+ cursor: pointer;
199
+ padding: 4px 8px;
200
+ border-radius: var(--border-radius-base);
201
+ transition: background-color 0.2s;
202
+
203
+ &:hover {
204
+ background-color: var(--color-fill);
205
+ }
206
+ }
207
+
208
+ &-name {
209
+ font-size: 14px;
210
+ color: var(--color-text-primary);
211
+ }
212
+
213
+ &-arrow {
214
+ font-size: 10px;
215
+ color: var(--color-text-secondary);
216
+ transition: transform 0.2s;
217
+
218
+ &.is-active {
219
+ transform: rotate(180deg);
220
+ }
221
+ }
222
+ }
223
+
224
+ &__avatar {
225
+ width: 32px;
226
+ height: 32px;
227
+ border-radius: 50%;
228
+ background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light-3));
229
+ display: flex;
230
+ align-items: center;
231
+ justify-content: center;
232
+ color: #fff;
233
+ font-size: 14px;
234
+ font-weight: 500;
235
+ }
236
+
237
+ &__dropdown {
238
+ position: absolute;
239
+ top: calc(100% + 8px);
240
+ right: 0;
241
+ min-width: 200px;
242
+ background-color: var(--bg-color);
243
+ border-radius: var(--border-radius-base);
244
+ box-shadow: var(--box-shadow);
245
+ overflow: hidden;
246
+ z-index: 100;
247
+
248
+ &-header {
249
+ display: flex;
250
+ align-items: center;
251
+ gap: 12px;
252
+ padding: 16px;
253
+ }
254
+
255
+ &-avatar {
256
+ width: 40px;
257
+ height: 40px;
258
+ border-radius: 50%;
259
+ background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light-3));
260
+ display: flex;
261
+ align-items: center;
262
+ justify-content: center;
263
+ color: #fff;
264
+ font-size: 16px;
265
+ font-weight: 500;
266
+ }
267
+
268
+ &-info {
269
+ flex: 1;
270
+ }
271
+
272
+ &-name {
273
+ font-size: 14px;
274
+ font-weight: 500;
275
+ color: var(--color-text-primary);
276
+ }
277
+
278
+ &-role {
279
+ font-size: 12px;
280
+ color: var(--color-text-secondary);
281
+ margin-top: 2px;
282
+ }
283
+
284
+ &-divider {
285
+ height: 1px;
286
+ background-color: var(--color-border-lighter);
287
+ }
288
+
289
+ &-menu {
290
+ padding: 8px 0;
291
+ }
292
+
293
+ &-item {
294
+ display: flex;
295
+ align-items: center;
296
+ gap: 10px;
297
+ padding: 10px 16px;
298
+ cursor: pointer;
299
+ font-size: 14px;
300
+ color: var(--color-text-regular);
301
+ transition: all 0.2s;
302
+
303
+ &:hover {
304
+ background-color: var(--color-fill);
305
+ color: var(--color-text-primary);
306
+ }
307
+
308
+ &--danger {
309
+ color: var(--color-danger);
310
+
311
+ &:hover {
312
+ background-color: var(--color-danger-light);
313
+ color: var(--color-danger);
314
+ }
315
+ }
316
+ }
317
+
318
+ &-icon {
319
+ font-size: 16px;
320
+ }
321
+ }
322
+ }
323
+
324
+ // 下拉动画
325
+ .dropdown-enter-active,
326
+ .dropdown-leave-active {
327
+ transition: all 0.2s ease;
328
+ }
329
+
330
+ .dropdown-enter-from,
331
+ .dropdown-leave-to {
332
+ opacity: 0;
333
+ transform: translateY(-10px);
334
+ }
390
335
  </style>