xto-fronted 0.4.7 → 0.4.8

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 (75) 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-B-sX4Ru0.js +1 -0
  10. package/dist/assets/index-BMcziU5a.css +1 -0
  11. package/dist/assets/index-BRR97dc6.js +1 -0
  12. package/dist/assets/index-BZA0ksjx.css +1 -0
  13. package/dist/assets/index-BpV_8nl0.js +1 -0
  14. package/dist/assets/index-BvzhR4zp.js +1 -0
  15. package/dist/assets/index-CUh_s55Z.css +1 -0
  16. package/dist/assets/index-CVjdnIgR.css +1 -0
  17. package/dist/assets/index-CYq57-zj.js +1 -0
  18. package/dist/assets/index-CkL3sVAQ.js +2 -0
  19. package/dist/assets/index-CtrKVYJb.css +1 -0
  20. package/dist/assets/index-Cz2P_bsS.js +1 -0
  21. package/dist/assets/index-D9wlAuR_.js +1 -0
  22. package/dist/assets/index-DawJb02s.css +1 -0
  23. package/dist/assets/index-DfFR6NLf.js +1 -0
  24. package/dist/assets/index-DwVgMO8e.js +1 -0
  25. package/dist/assets/index-GDP-IkXE.css +1 -0
  26. package/dist/assets/index-Iaz1ZzPC.js +2 -0
  27. package/dist/assets/index-PfV8pzQz.css +1 -0
  28. package/dist/assets/index-Swfu6yvD.css +1 -0
  29. package/dist/assets/index-Te8_PRgJ.js +1 -0
  30. package/dist/assets/index-WyZ91RLx.css +1 -0
  31. package/dist/assets/index-tFYRoFdE.js +1 -0
  32. package/dist/assets/vendor-42ANG6Sg.js +6 -0
  33. package/dist/assets/vite-Dw-pgLOX.js +1 -0
  34. package/dist/assets/vue-vendor-Br-l7wbK.js +29 -0
  35. package/dist/assets/xto-base-C-IBqjVs.js +1 -0
  36. package/dist/assets/xto-base-C6eqMPdO.css +1 -0
  37. package/dist/assets/xto-business--V1F5Gwb.css +1 -0
  38. package/dist/assets/xto-core-DZK7Cyg0.js +1 -0
  39. package/dist/assets/xto-data-BFpiDgJi.js +1 -0
  40. package/dist/assets/xto-data-CnAQAQH2.css +1 -0
  41. package/dist/assets/xto-feedback-B7ipsTfz.js +1 -0
  42. package/dist/assets/xto-feedback-DBwJzoTj.css +1 -0
  43. package/dist/assets/xto-form-CrsyAjyr.css +1 -0
  44. package/dist/assets/xto-form-NRjKKNcY.js +1 -0
  45. package/dist/assets/xto-layout-BqU8RuWL.css +1 -0
  46. package/dist/assets/xto-navigation-BiSaXPfr.js +1 -0
  47. package/dist/assets/xto-navigation-C1cnSL2E.css +1 -0
  48. package/dist/assets/xto-navigation-CBPg4dCc.css +1 -0
  49. package/dist/assets/xto-navigation-CKabFu9d.js +1 -0
  50. package/dist/index.html +28 -0
  51. package/package.json +85 -85
  52. package/src/api/auth.ts +25 -25
  53. package/src/api/system.ts +66 -66
  54. package/src/assets/styles/_dark.scss +406 -406
  55. package/src/components/Layout/Header.vue +973 -973
  56. package/src/components/Layout/Sidebar.vue +273 -273
  57. package/src/components/Layout/index.vue +443 -63
  58. package/src/composables/useApp.ts +61 -61
  59. package/src/composables/useAuth.ts +16 -16
  60. package/src/directives/permission.ts +27 -27
  61. package/src/env.d.ts +18 -18
  62. package/src/index.ts +47 -47
  63. package/src/router/dynamicRoutes.ts +162 -162
  64. package/src/router/guards.ts +128 -128
  65. package/src/router/index.ts +79 -79
  66. package/src/stores/auth.ts +65 -65
  67. package/src/stores/menu.ts +48 -48
  68. package/src/stores/user.ts +50 -50
  69. package/src/types/api.d.ts +80 -80
  70. package/src/utils/auth.ts +99 -99
  71. package/src/utils/config.ts +80 -80
  72. package/src/utils/permission.ts +32 -32
  73. package/src/utils/request.ts +124 -124
  74. package/src/views/login/index.vue +194 -194
  75. package/vite.config.ts +135 -135
@@ -1,64 +1,444 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import { useAppStore } from '@/stores/app'
4
- import Sidebar from './Sidebar.vue'
5
- import Header from './Header.vue'
6
-
7
- const appStore = useAppStore()
8
-
9
- const sidebarWidth = computed(() =>
10
- appStore.isCollapsed ? '64px' : '210px'
11
- )
12
- </script>
13
-
14
- <template>
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 />
22
- </header>
23
- <main class="layout__content">
24
- <router-view />
25
- </main>
26
- </div>
27
- </div>
28
- </template>
29
-
30
- <style lang="scss" scoped>
31
- .layout {
32
- display: flex;
33
- width: 100%;
34
- height: 100%;
35
-
36
- &__aside {
37
- transition: width 0.3s;
38
- overflow: hidden;
39
- flex-shrink: 0;
40
- height: 100%;
41
- }
42
-
43
- &__main {
44
- flex: 1;
45
- display: flex;
46
- flex-direction: column;
47
- overflow: hidden;
48
- height: 100%;
49
- }
50
-
51
- &__header {
52
- height: 50px;
53
- background-color: var(--bg-color);
54
- border-bottom: 1px solid var(--color-border-lighter);
55
- flex-shrink: 0;
56
- }
57
-
58
- &__content {
59
- flex: 1;
60
- overflow: auto;
61
- background-color: var(--bg-color-page);
62
- }
63
- }
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { useRoute, useRouter } from 'vue-router'
4
+ import { useAppStore } from '@/stores/app'
5
+ import { useMenuStore } from '@/stores/menu'
6
+ import Sidebar from './Sidebar.vue'
7
+ import Header from './Header.vue'
8
+ import { Menu, MenuItem, SubMenu } from '@xto/navigation'
9
+ import { Icon } from '@xto/base'
10
+
11
+ const route = useRoute()
12
+ const router = useRouter()
13
+ const appStore = useAppStore()
14
+ const menuStore = useMenuStore()
15
+
16
+ // 菜单选择处理
17
+ const handleMenuSelect = (index: string) => {
18
+ if (index && index !== route.path) {
19
+ router.push(index)
20
+ }
21
+ }
22
+
23
+ // 混合布局顶部菜单选择处理
24
+ const handleTopMenuSelect = (index: string) => {
25
+ const selectedMenu = menuStore.menuList.find(menu => menu.menuCode === index)
26
+ if (selectedMenu) {
27
+ if (selectedMenu.children && selectedMenu.children.length > 0) {
28
+ const firstChild = selectedMenu.children[0]
29
+ if (firstChild.menuUrl && firstChild.menuUrl !== route.path) {
30
+ router.push(firstChild.menuUrl)
31
+ }
32
+ } else if (selectedMenu.menuUrl && selectedMenu.menuUrl !== route.path) {
33
+ router.push(selectedMenu.menuUrl)
34
+ }
35
+ }
36
+ }
37
+
38
+ const sidebarWidth = computed(() =>
39
+ appStore.isCollapsed ? '64px' : '210px'
40
+ )
41
+
42
+ const currentLayout = computed(() => appStore.layout)
43
+ const currentPath = computed(() => route.path)
44
+
45
+ // 获取一级菜单
46
+ const topLevelMenus = computed(() => {
47
+ return menuStore.menuList.map(menu => ({
48
+ menuCode: menu.menuCode,
49
+ menuName: menu.menuName,
50
+ menuUrl: menu.menuUrl,
51
+ icon: menu.icon,
52
+ hasChildren: menu.children && menu.children.length > 0
53
+ }))
54
+ })
55
+
56
+ // 活动的顶部菜单
57
+ const activeTopMenu = computed(() => {
58
+ for (const menu of menuStore.menuList) {
59
+ if (menu.menuUrl === currentPath.value) return menu.menuCode
60
+ if (menu.children) {
61
+ for (const child of menu.children) {
62
+ if (child.menuUrl === currentPath.value) return menu.menuCode
63
+ }
64
+ }
65
+ }
66
+ return topLevelMenus.value[0]?.menuCode || ''
67
+ })
68
+
69
+ // 混合布局子菜单
70
+ const mixSubMenus = computed(() => {
71
+ if (currentLayout.value !== 'mix') return []
72
+ const selectedTopMenu = menuStore.menuList.find(
73
+ menu => menu.menuCode === activeTopMenu.value
74
+ )
75
+ return selectedTopMenu?.children || []
76
+ })
77
+
78
+ // 菜单主题
79
+ const menuBgColor = computed(() => appStore.isDark ? '#1d1e1f' : '#fff')
80
+ const menuTextColor = computed(() => appStore.isDark ? '#cfd3dc' : '#303133')
81
+ const menuActiveTextColor = computed(() => '#409eff')
82
+
83
+ // 图标列表
84
+ const knownIcons = new Set([
85
+ 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right',
86
+ 'caret-down', 'caret-right', 'plus', 'minus', 'close', 'check',
87
+ 'edit', 'delete', 'copy', 'download', 'upload', 'refresh', 'search',
88
+ 'filter', 'more', 'setting', 'share', 'loading', 'info', 'success',
89
+ 'warning', 'error', 'question', 'user', 'user-add', 'user-group',
90
+ 'logout', 'login', 'file', 'folder', 'folder-open', 'document',
91
+ 'image', 'video', 'music', 'camera', 'mail', 'phone', 'chat',
92
+ 'bell', 'message', 'eye', 'eye-off', 'calendar', 'clock', 'history',
93
+ 'timer', 'location', 'map', 'globe', 'star', 'heart', 'thumb-up',
94
+ 'link', 'external-link', 'lock', 'unlock', 'key', 'home', 'menu',
95
+ 'menu-fold', 'menu-unfold', 'sidebar-fold', 'sidebar-expand',
96
+ 'sidebar-left', 'dashboard', 'chart', 'chart-pie', 'chart-line',
97
+ 'report', 'analytics', 'system', 'permission', 'role', 'user-manage',
98
+ 'log', 'notification', 'app', 'list', 'grid', 'fullscreen',
99
+ 'fullscreen-exit', 'zoom-in', 'zoom-out', 'print', 'bookmark',
100
+ 'tag', 'code', 'terminal', 'database', 'server', 'cloud', 'gift',
101
+ 'moon', 'sun', 'theme', 'skin'
102
+ ])
103
+
104
+ const getMenuIcon = (icon?: string): string => {
105
+ if (!icon || icon === '') return ''
106
+ if (icon.startsWith('tineco-icon-')) {
107
+ return icon.replace('tineco-icon-', '')
108
+ }
109
+ return icon
110
+ }
111
+
112
+ const iconExists = (iconName: string): boolean => {
113
+ return knownIcons.has(iconName)
114
+ }
115
+
116
+ const getFirstChar = (name?: string): string => {
117
+ if (!name) return ''
118
+ return name.charAt(0)
119
+ }
120
+ </script>
121
+
122
+ <template>
123
+ <!-- 左侧菜单布局 -->
124
+ <div v-if="currentLayout === 'sidebar'" class="layout layout--sidebar">
125
+ <aside class="layout__aside" :style="{ width: sidebarWidth }">
126
+ <Sidebar />
127
+ </aside>
128
+ <div class="layout__main">
129
+ <header class="layout__header">
130
+ <Header />
131
+ </header>
132
+ <main class="layout__content">
133
+ <router-view />
134
+ </main>
135
+ </div>
136
+ </div>
137
+
138
+ <!-- 顶部菜单布局 -->
139
+ <div v-else-if="currentLayout === 'top'" class="layout layout--top">
140
+ <header class="layout__header-top">
141
+ <div class="layout__logo">
142
+ <img src="/vite.svg" alt="Logo" class="layout__logo-img" />
143
+ <span class="layout__logo-text">{{ appStore.appName }}</span>
144
+ </div>
145
+ <Menu
146
+ mode="horizontal"
147
+ :default-active="activeTopMenu"
148
+ class="layout__top-menu"
149
+ background-color="transparent"
150
+ :text-color="menuTextColor"
151
+ :active-text-color="menuActiveTextColor"
152
+ @select="handleMenuSelect"
153
+ >
154
+ <template v-for="menu in topLevelMenus" :key="menu.menuUrl">
155
+ <SubMenu v-if="menu.hasChildren" :index="menu.menuCode">
156
+ <template #title>
157
+ <span class="layout__menu-icon">
158
+ <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
159
+ <span v-else class="layout__menu-char">{{ getFirstChar(menu.menuName) }}</span>
160
+ </span>
161
+ <span>{{ menu.menuName }}</span>
162
+ </template>
163
+ <MenuItem
164
+ v-for="child in menuStore.menuList.find(m => m.menuCode === menu.menuCode)?.children"
165
+ :key="child.menuUrl"
166
+ :index="child.menuUrl"
167
+ >
168
+ <span>{{ child.menuName }}</span>
169
+ </MenuItem>
170
+ </SubMenu>
171
+ <MenuItem v-else :index="menu.menuUrl">
172
+ <span class="layout__menu-icon">
173
+ <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
174
+ <span v-else class="layout__menu-char">{{ getFirstChar(menu.menuName) }}</span>
175
+ </span>
176
+ <span>{{ menu.menuName }}</span>
177
+ </MenuItem>
178
+ </template>
179
+ </Menu>
180
+ <div class="layout__header-right">
181
+ <Header />
182
+ </div>
183
+ </header>
184
+ <main class="layout__content-top">
185
+ <router-view />
186
+ </main>
187
+ </div>
188
+
189
+ <!-- 混合布局 -->
190
+ <div v-else-if="currentLayout === 'mix'" class="layout layout--mix">
191
+ <header class="layout__header-mix">
192
+ <div class="layout__logo">
193
+ <img src="/vite.svg" alt="Logo" class="layout__logo-img" />
194
+ <span class="layout__logo-text">{{ appStore.appName }}</span>
195
+ </div>
196
+ <Menu
197
+ mode="horizontal"
198
+ :default-active="activeTopMenu"
199
+ class="layout__top-menu"
200
+ background-color="transparent"
201
+ :text-color="menuTextColor"
202
+ :active-text-color="menuActiveTextColor"
203
+ @select="handleTopMenuSelect"
204
+ >
205
+ <MenuItem
206
+ v-for="menu in topLevelMenus"
207
+ :key="menu.menuCode"
208
+ :index="menu.menuCode"
209
+ >
210
+ <span class="layout__menu-icon">
211
+ <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
212
+ <span v-else class="layout__menu-char">{{ getFirstChar(menu.menuName) }}</span>
213
+ </span>
214
+ <span>{{ menu.menuName }}</span>
215
+ </MenuItem>
216
+ </Menu>
217
+ <div class="layout__header-right">
218
+ <Header />
219
+ </div>
220
+ </header>
221
+ <div class="layout__mix-body">
222
+ <aside v-if="mixSubMenus.length > 0" class="layout__mix-aside" :style="{ width: sidebarWidth }">
223
+ <div class="layout__mix-sidebar">
224
+ <Menu
225
+ mode="vertical"
226
+ :collapse="appStore.isCollapsed"
227
+ :collapse-transition="false"
228
+ :default-active="currentPath"
229
+ :background-color="menuBgColor"
230
+ :text-color="menuTextColor"
231
+ :active-text-color="menuActiveTextColor"
232
+ class="layout__mix-menu"
233
+ @select="handleMenuSelect"
234
+ >
235
+ <MenuItem
236
+ v-for="child in mixSubMenus"
237
+ :key="child.menuUrl"
238
+ :index="child.menuUrl"
239
+ >
240
+ <span class="layout__menu-icon">
241
+ <Icon v-if="iconExists(getMenuIcon(child.icon))" :name="getMenuIcon(child.icon)" :size="16" />
242
+ <span v-else class="layout__menu-char">{{ getFirstChar(child.menuName) }}</span>
243
+ </span>
244
+ <span>{{ child.menuName }}</span>
245
+ </MenuItem>
246
+ </Menu>
247
+ </div>
248
+ </aside>
249
+ <main class="layout__content-mix">
250
+ <router-view />
251
+ </main>
252
+ </div>
253
+ </div>
254
+ </template>
255
+
256
+ <style lang="scss" scoped>
257
+ .layout {
258
+ display: flex;
259
+ width: 100%;
260
+ height: 100%;
261
+
262
+ // 左侧菜单布局
263
+ &--sidebar {
264
+ flex-direction: row;
265
+
266
+ .layout__aside {
267
+ transition: width 0.3s;
268
+ overflow: hidden;
269
+ flex-shrink: 0;
270
+ height: 100%;
271
+ }
272
+
273
+ .layout__main {
274
+ flex: 1;
275
+ display: flex;
276
+ flex-direction: column;
277
+ overflow: hidden;
278
+ height: 100%;
279
+ }
280
+
281
+ .layout__header {
282
+ height: 50px;
283
+ background-color: var(--bg-color);
284
+ border-bottom: 1px solid var(--color-border-lighter);
285
+ flex-shrink: 0;
286
+ }
287
+
288
+ .layout__content {
289
+ flex: 1;
290
+ overflow: auto;
291
+ background-color: var(--bg-color-page);
292
+ }
293
+ }
294
+
295
+ // 顶部菜单布局
296
+ &--top {
297
+ flex-direction: column;
298
+
299
+ .layout__header-top {
300
+ height: 60px;
301
+ display: flex;
302
+ align-items: center;
303
+ padding: 0 20px;
304
+ background-color: var(--bg-color);
305
+ border-bottom: 1px solid var(--color-border-lighter);
306
+ flex-shrink: 0;
307
+
308
+ .layout__logo {
309
+ display: flex;
310
+ align-items: center;
311
+ gap: 10px;
312
+ margin-right: 30px;
313
+ }
314
+
315
+ .layout__logo-img {
316
+ width: 32px;
317
+ height: 32px;
318
+ }
319
+
320
+ .layout__logo-text {
321
+ font-size: 16px;
322
+ font-weight: 600;
323
+ color: var(--color-primary);
324
+ }
325
+
326
+ .layout__top-menu {
327
+ flex: 1;
328
+ border-bottom: none;
329
+ background-color: transparent;
330
+ }
331
+
332
+ .layout__header-right {
333
+ display: flex;
334
+ align-items: center;
335
+ }
336
+ }
337
+
338
+ .layout__content-top {
339
+ flex: 1;
340
+ overflow: auto;
341
+ background-color: var(--bg-color-page);
342
+ }
343
+ }
344
+
345
+ // 混合布局
346
+ &--mix {
347
+ flex-direction: column;
348
+
349
+ .layout__header-mix {
350
+ height: 60px;
351
+ display: flex;
352
+ align-items: center;
353
+ padding: 0 20px;
354
+ background-color: var(--bg-color);
355
+ border-bottom: 1px solid var(--color-border-lighter);
356
+ flex-shrink: 0;
357
+
358
+ .layout__logo {
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 10px;
362
+ margin-right: 30px;
363
+ }
364
+
365
+ .layout__logo-img {
366
+ width: 32px;
367
+ height: 32px;
368
+ }
369
+
370
+ .layout__logo-text {
371
+ font-size: 16px;
372
+ font-weight: 600;
373
+ color: var(--color-primary);
374
+ }
375
+
376
+ .layout__top-menu {
377
+ flex: 1;
378
+ border-bottom: none;
379
+ background-color: transparent;
380
+ }
381
+
382
+ .layout__header-right {
383
+ display: flex;
384
+ align-items: center;
385
+ }
386
+ }
387
+
388
+ .layout__mix-body {
389
+ flex: 1;
390
+ display: flex;
391
+ overflow: hidden;
392
+
393
+ .layout__mix-aside {
394
+ transition: width 0.3s;
395
+ overflow: hidden;
396
+ flex-shrink: 0;
397
+ background-color: var(--bg-color);
398
+ border-right: 1px solid var(--color-border-lighter);
399
+ }
400
+
401
+ .layout__mix-sidebar {
402
+ height: 100%;
403
+ display: flex;
404
+ flex-direction: column;
405
+ }
406
+
407
+ .layout__mix-menu {
408
+ flex: 1;
409
+ border-right: none;
410
+ overflow-y: auto;
411
+ }
412
+
413
+ .layout__content-mix {
414
+ flex: 1;
415
+ overflow: auto;
416
+ background-color: var(--bg-color-page);
417
+ }
418
+ }
419
+ }
420
+
421
+ // 公共样式
422
+ &__menu-icon {
423
+ display: inline-flex;
424
+ align-items: center;
425
+ justify-content: center;
426
+ width: 16px;
427
+ height: 16px;
428
+ margin-right: 8px;
429
+ }
430
+
431
+ &__menu-char {
432
+ display: inline-flex;
433
+ align-items: center;
434
+ justify-content: center;
435
+ width: 16px;
436
+ height: 16px;
437
+ font-size: 12px;
438
+ font-weight: 600;
439
+ color: var(--color-primary);
440
+ background-color: var(--color-primary-light-8);
441
+ border-radius: 4px;
442
+ }
443
+ }
64
444
  </style>
@@ -1,62 +1,62 @@
1
- /**
2
- * 应用组合函数
3
- * 提供应用级别的状态和方法
4
- */
5
-
6
- import { computed } from 'vue'
7
- import { useAppStore } from '@/stores/app'
8
- import { useUserStore } from '@/stores/user'
9
- import { useAuthStore } from '@/stores/auth'
10
-
11
- /**
12
- * 应用级别组合函数
13
- * @returns 应用状态和方法
14
- */
15
- export function useApp() {
16
- const appStore = useAppStore()
17
- const userStore = useUserStore()
18
- const authStore = useAuthStore()
19
-
20
- // 用户名
21
- const userName = computed(() => userStore.userName || '')
22
-
23
- // 用户信息
24
- const userInfo = computed(() => userStore.userInfo)
25
-
26
- // 应用名称
27
- const appName = computed(() => appStore.appName)
28
-
29
- // 是否已登录
30
- const isLoggedIn = computed(() => authStore.isLoggedIn)
31
-
32
- // 主题相关
33
- const isDark = computed(() => appStore.isDark)
34
- const theme = computed(() => appStore.theme)
35
-
36
- // 布局相关
37
- const isCollapsed = computed(() => appStore.isCollapsed)
38
- const layout = computed(() => appStore.layout)
39
-
40
- // 切换主题
41
- const toggleTheme = () => {
42
- appStore.toggleTheme()
43
- }
44
-
45
- // 切换菜单折叠
46
- const toggleCollapse = () => {
47
- appStore.toggleCollapse()
48
- }
49
-
50
- return {
51
- userName,
52
- userInfo,
53
- appName,
54
- isLoggedIn,
55
- isDark,
56
- theme,
57
- isCollapsed,
58
- layout,
59
- toggleTheme,
60
- toggleCollapse
61
- }
1
+ /**
2
+ * 应用组合函数
3
+ * 提供应用级别的状态和方法
4
+ */
5
+
6
+ import { computed } from 'vue'
7
+ import { useAppStore } from '@/stores/app'
8
+ import { useUserStore } from '@/stores/user'
9
+ import { useAuthStore } from '@/stores/auth'
10
+
11
+ /**
12
+ * 应用级别组合函数
13
+ * @returns 应用状态和方法
14
+ */
15
+ export function useApp() {
16
+ const appStore = useAppStore()
17
+ const userStore = useUserStore()
18
+ const authStore = useAuthStore()
19
+
20
+ // 用户名
21
+ const userName = computed(() => userStore.userName || '')
22
+
23
+ // 用户信息
24
+ const userInfo = computed(() => userStore.userInfo)
25
+
26
+ // 应用名称
27
+ const appName = computed(() => appStore.appName)
28
+
29
+ // 是否已登录
30
+ const isLoggedIn = computed(() => authStore.isLoggedIn)
31
+
32
+ // 主题相关
33
+ const isDark = computed(() => appStore.isDark)
34
+ const theme = computed(() => appStore.theme)
35
+
36
+ // 布局相关
37
+ const isCollapsed = computed(() => appStore.isCollapsed)
38
+ const layout = computed(() => appStore.layout)
39
+
40
+ // 切换主题
41
+ const toggleTheme = () => {
42
+ appStore.toggleTheme()
43
+ }
44
+
45
+ // 切换菜单折叠
46
+ const toggleCollapse = () => {
47
+ appStore.toggleCollapse()
48
+ }
49
+
50
+ return {
51
+ userName,
52
+ userInfo,
53
+ appName,
54
+ isLoggedIn,
55
+ isDark,
56
+ theme,
57
+ isCollapsed,
58
+ layout,
59
+ toggleTheme,
60
+ toggleCollapse
61
+ }
62
62
  }
@@ -1,17 +1,17 @@
1
- /**
2
- * 权限组合函数
3
- */
4
-
5
- import { computed } from 'vue'
6
- import { useUserStore } from '@/stores/user'
7
-
8
- export function useAuth() {
9
- const userStore = useUserStore()
10
-
11
- // 是否已登录
12
- const isLoggedIn = computed(() => userStore.isLoggedIn)
13
-
14
- return {
15
- isLoggedIn
16
- }
1
+ /**
2
+ * 权限组合函数
3
+ */
4
+
5
+ import { computed } from 'vue'
6
+ import { useUserStore } from '@/stores/user'
7
+
8
+ export function useAuth() {
9
+ const userStore = useUserStore()
10
+
11
+ // 是否已登录
12
+ const isLoggedIn = computed(() => userStore.isLoggedIn)
13
+
14
+ return {
15
+ isLoggedIn
16
+ }
17
17
  }