@lvetechs/create-app 1.0.4 → 1.0.6

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 (58) hide show
  1. package/package.json +1 -1
  2. package/templates/react/.env +3 -3
  3. package/templates/react/.env.development +3 -3
  4. package/templates/react/.env.production +3 -3
  5. package/templates/react/package.json +1 -1
  6. package/templates/react/pnpm-lock.yaml +5 -5
  7. package/templates/react/src/App.tsx +7 -1
  8. package/templates/react/src/api/notification.ts +43 -0
  9. package/templates/react/src/components/NotificationButton/index.tsx +219 -0
  10. package/templates/react/src/components/Toast/index.tsx +150 -0
  11. package/templates/react/src/hooks/useForm.ts +77 -0
  12. package/templates/react/src/layouts/DefaultLayout.tsx +2 -0
  13. package/templates/react/src/layouts/menuConfig.ts +3 -33
  14. package/templates/react/src/router/index.tsx +0 -39
  15. package/templates/react/src/stores/app.ts +141 -3
  16. package/templates/react/src/stores/notification.ts +146 -0
  17. package/templates/react/src/stores/permission.ts +173 -0
  18. package/templates/react/src/stores/user.ts +151 -4
  19. package/templates/react/src/views/home/index.tsx +167 -6
  20. package/templates/react/src/views/system/user/index.tsx +171 -5
  21. package/templates/vue/.env +2 -2
  22. package/templates/vue/.env.development +2 -2
  23. package/templates/vue/.env.production +2 -2
  24. package/templates/vue/pnpm-lock.yaml +3307 -3307
  25. package/templates/vue/src/App.vue +2 -0
  26. package/templates/vue/src/api/notification.ts +43 -0
  27. package/templates/vue/src/auto-imports.d.ts +5 -0
  28. package/templates/vue/src/components/NotificationButton/index.vue +242 -0
  29. package/templates/vue/src/components/Toast/index.vue +126 -0
  30. package/templates/vue/src/components.d.ts +2 -0
  31. package/templates/vue/src/layouts/DefaultLayout.vue +2 -0
  32. package/templates/vue/src/layouts/menuConfig.ts +3 -33
  33. package/templates/vue/src/router/index.ts +3 -88
  34. package/templates/vue/src/stores/app.ts +133 -2
  35. package/templates/vue/src/stores/notification.ts +189 -0
  36. package/templates/vue/src/stores/permission.ts +184 -0
  37. package/templates/vue/src/stores/user.ts +109 -2
  38. package/templates/vue/src/views/home/index.vue +7 -7
  39. package/templates/react/src/views/about/index.tsx +0 -40
  40. package/templates/react/src/views/login/index.tsx +0 -138
  41. package/templates/react/src/views/register/index.tsx +0 -143
  42. package/templates/react/src/views/result/fail.tsx +0 -39
  43. package/templates/react/src/views/result/success.tsx +0 -35
  44. package/templates/react/src/views/screen/index.tsx +0 -120
  45. package/templates/react/src/views/system/log/login.tsx +0 -51
  46. package/templates/react/src/views/system/log/operation.tsx +0 -47
  47. package/templates/react/src/views/system/menu/index.tsx +0 -62
  48. package/templates/react/src/views/system/role/index.tsx +0 -63
  49. package/templates/vue/src/views/about/index.vue +0 -67
  50. package/templates/vue/src/views/login/index.vue +0 -153
  51. package/templates/vue/src/views/register/index.vue +0 -169
  52. package/templates/vue/src/views/result/fail.vue +0 -92
  53. package/templates/vue/src/views/result/success.vue +0 -92
  54. package/templates/vue/src/views/screen/index.vue +0 -150
  55. package/templates/vue/src/views/system/log/login.vue +0 -51
  56. package/templates/vue/src/views/system/log/operation.vue +0 -47
  57. package/templates/vue/src/views/system/menu/index.vue +0 -58
  58. package/templates/vue/src/views/system/role/index.vue +0 -59
@@ -1,8 +1,10 @@
1
1
  <script setup lang="ts">
2
+ import Toast from '@/components/Toast/index.vue'
2
3
  </script>
3
4
 
4
5
  <template>
5
6
  <router-view />
7
+ <Toast />
6
8
  </template>
7
9
 
8
10
  <style scoped>
@@ -0,0 +1,43 @@
1
+ import { http } from '@/utils/request'
2
+ import type { Notification } from '@/stores/notification'
3
+
4
+ /** 获取通知列表参数 */
5
+ export interface GetNotificationsParams {
6
+ page?: number
7
+ pageSize?: number
8
+ type?: Notification['type']
9
+ category?: string
10
+ unreadOnly?: boolean
11
+ }
12
+
13
+ /** 获取通知列表响应 */
14
+ export interface GetNotificationsResult {
15
+ list: Notification[]
16
+ total: number
17
+ unreadCount: number
18
+ }
19
+
20
+ /** 获取通知列表 */
21
+ export function getNotificationsApi(params?: GetNotificationsParams) {
22
+ return http.get<GetNotificationsResult>('/notification/list', { params })
23
+ }
24
+
25
+ /** 标记通知为已读 */
26
+ export function markNotificationReadApi(id: string) {
27
+ return http.post(`/notification/${id}/read`)
28
+ }
29
+
30
+ /** 批量标记为已读 */
31
+ export function markAllReadApi() {
32
+ return http.post('/notification/read-all')
33
+ }
34
+
35
+ /** 删除通知 */
36
+ export function deleteNotificationApi(id: string) {
37
+ return http.delete(`/notification/${id}`)
38
+ }
39
+
40
+ /** 获取未读数量 */
41
+ export function getUnreadCountApi() {
42
+ return http.get<{ count: number }>('/notification/unread-count')
43
+ }
@@ -144,6 +144,7 @@ declare global {
144
144
  const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
145
145
  const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
146
146
  const useCached: typeof import('@vueuse/core')['useCached']
147
+ const useCartStore: typeof import('./stores/cart')['useCartStore']
147
148
  const useClipboard: typeof import('@vueuse/core')['useClipboard']
148
149
  const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
149
150
  const useCloned: typeof import('@vueuse/core')['useCloned']
@@ -213,6 +214,7 @@ declare global {
213
214
  const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
214
215
  const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
215
216
  const useNetwork: typeof import('@vueuse/core')['useNetwork']
217
+ const useNotificationStore: typeof import('./stores/notification')['useNotificationStore']
216
218
  const useNow: typeof import('@vueuse/core')['useNow']
217
219
  const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
218
220
  const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
@@ -223,6 +225,7 @@ declare global {
223
225
  const useParentElement: typeof import('@vueuse/core')['useParentElement']
224
226
  const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
225
227
  const usePermission: typeof import('@vueuse/core')['usePermission']
228
+ const usePermissionStore: typeof import('./stores/permission')['usePermissionStore']
226
229
  const usePointer: typeof import('@vueuse/core')['usePointer']
227
230
  const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
228
231
  const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
@@ -523,6 +526,7 @@ declare module 'vue' {
523
526
  readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
524
527
  readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
525
528
  readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
529
+ readonly useNotificationStore: UnwrapRef<typeof import('./stores/notification')['useNotificationStore']>
526
530
  readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
527
531
  readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
528
532
  readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
@@ -533,6 +537,7 @@ declare module 'vue' {
533
537
  readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
534
538
  readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
535
539
  readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
540
+ readonly usePermissionStore: UnwrapRef<typeof import('./stores/permission')['usePermissionStore']>
536
541
  readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
537
542
  readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
538
543
  readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
@@ -0,0 +1,242 @@
1
+ <template>
2
+ <div class="relative" ref="dropdownRef">
3
+ <button
4
+ class="relative flex items-center justify-center w-10 h-10 rounded-lg border border-slate-200 hover:border-indigo-500 hover:text-indigo-500 transition-colors"
5
+ @click="toggleDropdown"
6
+ title="消息通知"
7
+ >
8
+ <span class="text-lg">🔔</span>
9
+ <span
10
+ v-if="notificationStore.unreadCount > 0"
11
+ class="absolute -top-1 -right-1 flex items-center justify-center min-w-[18px] h-[18px] px-1 text-xs font-bold text-white bg-red-500 rounded-full"
12
+ >
13
+ {{ notificationStore.unreadCount > 99 ? '99+' : notificationStore.unreadCount }}
14
+ </span>
15
+ </button>
16
+
17
+ <Transition name="fade">
18
+ <div
19
+ v-if="showDropdown"
20
+ class="absolute right-0 top-12 w-80 bg-white rounded-lg shadow-lg border border-slate-200 z-50 max-h-[500px] flex flex-col"
21
+ >
22
+ <!-- 头部 -->
23
+ <div class="flex items-center justify-between p-4 border-b border-slate-200">
24
+ <h3 class="font-semibold text-slate-700">消息通知</h3>
25
+ <div class="flex gap-2">
26
+ <button
27
+ class="text-xs text-indigo-600 hover:text-indigo-700"
28
+ @click="fetchNotifications"
29
+ >
30
+ 刷新
31
+ </button>
32
+ <!-- <button
33
+ v-if="notificationStore.unreadCount > 0"
34
+ class="text-xs text-indigo-600 hover:text-indigo-700"
35
+ @click="handleMarkAllRead"
36
+ >
37
+ 全部已读
38
+ </button> -->
39
+ </div>
40
+ </div>
41
+
42
+ <!-- 消息列表 -->
43
+ <div class="flex-1 overflow-y-auto">
44
+ <div v-if="notificationStore.notifications.length === 0" class="flex flex-col items-center justify-center py-8 text-slate-400">
45
+ <span class="text-4xl mb-2"></span>
46
+ <p class="text-sm">暂无消息</p>
47
+ </div>
48
+ <div v-else class="divide-y divide-slate-100">
49
+ <div
50
+ v-for="item in notificationStore.notifications"
51
+ :key="item.id"
52
+ :class="[
53
+ 'p-3 hover:bg-slate-50 cursor-pointer transition-colors',
54
+ !item.read ? 'bg-blue-50' : ''
55
+ ]"
56
+ @click="!item.read && handleMarkAsRead(item.id)"
57
+ >
58
+ <div class="flex items-start gap-2">
59
+ <span :class="['text-lg', typeColors[item.type]]">
60
+ {{ typeIcons[item.type] }}
61
+ </span>
62
+ <div class="flex-1 min-w-0">
63
+ <div class="flex items-center justify-between mb-1">
64
+ <h4 class="font-medium text-sm text-slate-700 truncate">
65
+ {{ item.title }}
66
+ </h4>
67
+ <span
68
+ v-if="!item.read"
69
+ class="flex-shrink-0 w-2 h-2 bg-blue-500 rounded-full ml-2"
70
+ ></span>
71
+ </div>
72
+ <p class="text-xs text-slate-500 line-clamp-2 mb-1">
73
+ {{ item.content }}
74
+ </p>
75
+ <div class="flex items-center justify-between">
76
+ <span class="text-xs text-slate-400">{{ formatTime(item.timestamp) }}</span>
77
+ <span
78
+ v-if="item.category"
79
+ class="text-xs text-slate-400 bg-slate-100 px-2 py-0.5 rounded"
80
+ >
81
+ {{ item.category }}
82
+ </span>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- 底部 -->
91
+ <!-- <div v-if="notificationStore.notifications.length > 0" class="p-2 border-t border-slate-200 text-center">
92
+ <button
93
+ class="text-xs text-slate-500 hover:text-indigo-600"
94
+ @click="() => console.log('查看全部消息')"
95
+ >
96
+ 查看全部消息
97
+ </button>
98
+ </div> -->
99
+ </div>
100
+ </Transition>
101
+ </div>
102
+ </template>
103
+
104
+ <script setup lang="ts">
105
+ import { ref, onMounted, onUnmounted } from 'vue'
106
+ import { useNotificationStore } from '@/stores/notification'
107
+ import { getNotificationsApi, markNotificationReadApi, markAllReadApi } from '@/api/notification'
108
+ import type { Notification } from '@/stores/notification'
109
+
110
+ const notificationStore = useNotificationStore()
111
+ const showDropdown = ref(false)
112
+ const dropdownRef = ref<HTMLElement>()
113
+
114
+ const typeIcons = {
115
+ info: 'ℹ️',
116
+ success: '✓',
117
+ warning: '⚠️',
118
+ error: '✕'
119
+ }
120
+
121
+ const typeColors = {
122
+ info: 'text-blue-500',
123
+ success: 'text-green-500',
124
+ warning: 'text-yellow-500',
125
+ error: 'text-red-500'
126
+ }
127
+
128
+ function toggleDropdown() {
129
+ showDropdown.value = !showDropdown.value
130
+ // if (showDropdown.value) {
131
+ // fetchNotifications()
132
+ // }
133
+ }
134
+
135
+ // 获取最新消息
136
+ async function fetchNotifications() {
137
+ try {
138
+ const response = await getNotificationsApi({ unreadOnly: false, pageSize: 20 })
139
+ if (response.data) {
140
+ // 将新消息添加到 store(实际项目中可能需要去重逻辑)
141
+ const existingIds = new Set(notificationStore.notifications.map((n) => n.id))
142
+ response.data.list.forEach((item) => {
143
+ if (!existingIds.has(item.id)) {
144
+ notificationStore.addNotification({
145
+ title: item.title,
146
+ content: item.content,
147
+ type: item.type,
148
+ category: item.category,
149
+ actionUrl: item.actionUrl
150
+ })
151
+ }
152
+ })
153
+ if ((window as any).$toast) {
154
+ ;(window as any).$toast.success(`获取到 ${response.data.list.length} 条消息`)
155
+ }
156
+ }
157
+ } catch (error) {
158
+ console.error('获取消息失败:', error)
159
+ if ((window as any).$toast) {
160
+ ;(window as any).$toast.error('获取消息失败')
161
+ }
162
+ // 如果 API 失败,使用示例数据
163
+ notificationStore.initSampleData()
164
+ }
165
+ }
166
+
167
+ // 标记为已读
168
+ async function handleMarkAsRead(id: string) {
169
+ try {
170
+ await markNotificationReadApi(id)
171
+ notificationStore.markAsRead(id)
172
+ if ((window as any).$toast) {
173
+ ;(window as any).$toast.success('已标记为已读')
174
+ }
175
+ } catch (error) {
176
+ console.error('标记已读失败:', error)
177
+ if ((window as any).$toast) {
178
+ ;(window as any).$toast.error('标记已读失败')
179
+ }
180
+ }
181
+ }
182
+
183
+ // 全部标记为已读
184
+ async function handleMarkAllRead() {
185
+ try {
186
+ await markAllReadApi()
187
+ notificationStore.markAllAsRead()
188
+ if ((window as any).$toast) {
189
+ ;(window as any).$toast.success('已全部标记为已读')
190
+ }
191
+ } catch (error) {
192
+ console.error('全部标记已读失败:', error)
193
+ if ((window as any).$toast) {
194
+ ;(window as any).$toast.error('全部标记已读失败')
195
+ }
196
+ }
197
+ }
198
+
199
+ // 格式化时间
200
+ function formatTime(timestamp: number) {
201
+ const now = Date.now()
202
+ const diff = now - timestamp
203
+ const minutes = Math.floor(diff / 60000)
204
+ const hours = Math.floor(diff / 3600000)
205
+ const days = Math.floor(diff / 86400000)
206
+
207
+ if (minutes < 1) return '刚刚'
208
+ if (minutes < 60) return `${minutes}分钟前`
209
+ if (hours < 24) return `${hours}小时前`
210
+ if (days < 7) return `${days}天前`
211
+ return new Date(timestamp).toLocaleDateString()
212
+ }
213
+
214
+ // 点击外部关闭下拉菜单
215
+ function handleClickOutside(event: MouseEvent) {
216
+ if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
217
+ showDropdown.value = false
218
+ }
219
+ }
220
+
221
+ onMounted(() => {
222
+ document.addEventListener('mousedown', handleClickOutside)
223
+ // 初始化时获取一次消息
224
+ // fetchNotifications()
225
+ })
226
+
227
+ onUnmounted(() => {
228
+ document.removeEventListener('mousedown', handleClickOutside)
229
+ })
230
+ </script>
231
+
232
+ <style scoped lang="scss">
233
+ .fade-enter-active,
234
+ .fade-leave-active {
235
+ transition: opacity 0.2s ease;
236
+ }
237
+
238
+ .fade-enter-from,
239
+ .fade-leave-to {
240
+ opacity: 0;
241
+ }
242
+ </style>
@@ -0,0 +1,126 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <TransitionGroup
4
+ name="toast"
5
+ tag="div"
6
+ class="fixed top-4 right-4 z-[9999] flex flex-col gap-2"
7
+ >
8
+ <div
9
+ v-for="toast in toasts"
10
+ :key="toast.id"
11
+ :class="[
12
+ 'flex items-center gap-3 rounded-lg px-4 py-3 text-white shadow-lg transition-all cursor-pointer',
13
+ toastColors[toast.type]
14
+ ]"
15
+ @click="closeToast(toast.id)"
16
+ >
17
+ <span class="text-lg">{{ toastIcons[toast.type] }}</span>
18
+ <span class="flex-1 text-sm">{{ toast.message }}</span>
19
+ <button
20
+ class="ml-2 text-white opacity-70 hover:opacity-100"
21
+ @click.stop="closeToast(toast.id)"
22
+ >
23
+
24
+ </button>
25
+ </div>
26
+ </TransitionGroup>
27
+ </Teleport>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { ref, onMounted, onUnmounted } from 'vue'
32
+
33
+ export type ToastType = 'success' | 'error' | 'warning' | 'info'
34
+
35
+ export interface ToastItem {
36
+ id: string
37
+ message: string
38
+ type: ToastType
39
+ duration?: number
40
+ }
41
+
42
+ const toasts = ref<ToastItem[]>([])
43
+
44
+ const toastIcons = {
45
+ success: '✓',
46
+ error: '✕',
47
+ warning: '⚠',
48
+ info: 'ℹ'
49
+ }
50
+
51
+ const toastColors = {
52
+ success: 'bg-green-500',
53
+ error: 'bg-red-500',
54
+ warning: 'bg-yellow-500',
55
+ info: 'bg-blue-500'
56
+ }
57
+
58
+ const timers = new Map<string, NodeJS.Timeout>()
59
+
60
+ function closeToast(id: string) {
61
+ const index = toasts.value.findIndex((t) => t.id === id)
62
+ if (index > -1) {
63
+ toasts.value.splice(index, 1)
64
+ const timer = timers.get(id)
65
+ if (timer) {
66
+ clearTimeout(timer)
67
+ timers.delete(id)
68
+ }
69
+ }
70
+ }
71
+
72
+ function showToast(message: string, type: ToastType = 'info', duration = 3000) {
73
+ const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
74
+ const toast: ToastItem = { id, message, type, duration }
75
+ toasts.value.push(toast)
76
+
77
+ if (duration && duration > 0) {
78
+ const timer = setTimeout(() => {
79
+ closeToast(id)
80
+ }, duration)
81
+ timers.set(id, timer)
82
+ }
83
+
84
+ return id
85
+ }
86
+
87
+ // 暴露方法给全局使用
88
+ const toast = {
89
+ success: (message: string, duration?: number) => showToast(message, 'success', duration),
90
+ error: (message: string, duration?: number) => showToast(message, 'error', duration),
91
+ warning: (message: string, duration?: number) => showToast(message, 'warning', duration),
92
+ info: (message: string, duration?: number) => showToast(message, 'info', duration)
93
+ }
94
+
95
+ // 挂载到全局
96
+ onMounted(() => {
97
+ ;(window as any).$toast = toast
98
+ })
99
+
100
+ onUnmounted(() => {
101
+ timers.forEach((timer) => clearTimeout(timer))
102
+ timers.clear()
103
+ })
104
+
105
+ defineExpose({ toast })
106
+ </script>
107
+
108
+ <style scoped lang="scss">
109
+ .toast-enter-active {
110
+ transition: all 0.3s ease-out;
111
+ }
112
+
113
+ .toast-leave-active {
114
+ transition: all 0.2s ease-in;
115
+ }
116
+
117
+ .toast-enter-from {
118
+ opacity: 0;
119
+ transform: translateX(100%);
120
+ }
121
+
122
+ .toast-leave-to {
123
+ opacity: 0;
124
+ transform: translateX(100%);
125
+ }
126
+ </style>
@@ -7,8 +7,10 @@ export {}
7
7
 
8
8
  declare module 'vue' {
9
9
  export interface GlobalComponents {
10
+ NotificationButton: typeof import('./components/NotificationButton/index.vue')['default']
10
11
  RouterLink: typeof import('vue-router')['RouterLink']
11
12
  RouterView: typeof import('vue-router')['RouterView']
12
13
  SvgIcon: typeof import('./components/SvgIcon.vue')['default']
14
+ Toast: typeof import('./components/Toast/index.vue')['default']
13
15
  }
14
16
  }
@@ -2,6 +2,7 @@
2
2
  import { useAppStore } from '@/stores/app'
3
3
  import SidebarMenu from './components/SidebarMenu.vue'
4
4
  import { menuRoutes } from './menuConfig'
5
+ import NotificationButton from '@/components/NotificationButton/index.vue'
5
6
  const title = computed(() => import.meta.env.VITE_APP_TITLE)
6
7
  const appStore = useAppStore()
7
8
  </script>
@@ -19,6 +20,7 @@
19
20
  </button>
20
21
  </div>
21
22
  <div class="header-right">
23
+ <NotificationButton />
22
24
  <button
23
25
  class="theme-btn"
24
26
  @click="appStore.setTheme(appStore.theme === 'light' ? 'dark' : 'light')"
@@ -25,49 +25,19 @@ export const menuRoutes: MenuItem[] = [
25
25
  {
26
26
  path: '/home',
27
27
  title: '首页',
28
- icon: '🏠'
28
+ icon: ''
29
29
  },
30
30
  {
31
31
  path: '/system',
32
32
  title: '系统管理',
33
- icon: '⚙️',
33
+ icon: '',
34
34
  children: [
35
35
  {
36
36
  path: '/system/user',
37
37
  title: '用户管理',
38
- icon: '👤'
39
- },
40
- {
41
- path: '/system/role',
42
- title: '角色管理',
43
- icon: '🔑'
44
- },
45
- {
46
- path: '/system/menu',
47
- title: '菜单管理',
48
- icon: '📋'
49
- },
50
- {
51
- path: '/system/log',
52
- title: '日志管理',
53
- icon: '📝',
54
- children: [
55
- {
56
- path: '/system/log/operation',
57
- title: '操作日志'
58
- },
59
- {
60
- path: '/system/log/login',
61
- title: '登录日志'
62
- }
63
- ]
38
+ icon: ''
64
39
  }
65
40
  ]
66
- },
67
- {
68
- path: '/about',
69
- title: '关于',
70
- icon: 'ℹ️'
71
41
  }
72
42
  ]
73
43
 
@@ -32,7 +32,7 @@ const layoutRoutes: RouteRecordRaw = {
32
32
  path: 'home',
33
33
  name: 'Home',
34
34
  component: () => import('@/views/home/index.vue'),
35
- meta: { title: '首页', icon: '🏠', requireAuth: true }
35
+ meta: { title: '首页', icon: '', requireAuth: true }
36
36
  },
37
37
 
38
38
  // ---------- 系统管理 (多级路由示例) ----------
@@ -40,58 +40,17 @@ const layoutRoutes: RouteRecordRaw = {
40
40
  path: 'system',
41
41
  name: 'System',
42
42
  redirect: '/system/user',
43
- meta: { title: '系统管理', icon: '⚙️' },
43
+ meta: { title: '系统管理', icon: '' },
44
44
  children: [
45
45
  {
46
46
  path: 'user',
47
47
  name: 'SystemUser',
48
48
  component: () => import('@/views/system/user/index.vue'),
49
- meta: { title: '用户管理', icon: '👤', requireAuth: true }
50
- },
51
- {
52
- path: 'role',
53
- name: 'SystemRole',
54
- component: () => import('@/views/system/role/index.vue'),
55
- meta: { title: '角色管理', icon: '🔑', requireAuth: true }
56
- },
57
- {
58
- path: 'menu',
59
- name: 'SystemMenu',
60
- component: () => import('@/views/system/menu/index.vue'),
61
- meta: { title: '菜单管理', icon: '📋', requireAuth: true }
62
- },
63
- // 三级路由示例: 系统管理 > 日志管理 > 操作日志 / 登录日志
64
- {
65
- path: 'log',
66
- name: 'SystemLog',
67
- redirect: '/system/log/operation',
68
- meta: { title: '日志管理', icon: '📝' },
69
- children: [
70
- {
71
- path: 'operation',
72
- name: 'OperationLog',
73
- component: () => import('@/views/system/log/operation.vue'),
74
- meta: { title: '操作日志', requireAuth: true }
75
- },
76
- {
77
- path: 'login',
78
- name: 'LoginLog',
79
- component: () => import('@/views/system/log/login.vue'),
80
- meta: { title: '登录日志', requireAuth: true }
81
- }
82
- ]
49
+ meta: { title: '用户管理', icon: '', requireAuth: true }
83
50
  }
84
51
  ]
85
52
  },
86
53
 
87
- // ---------- 关于页 ----------
88
- {
89
- path: 'about',
90
- name: 'About',
91
- component: () => import('@/views/about/index.vue'),
92
- meta: { title: '关于', icon: 'ℹ️' }
93
- },
94
-
95
54
  // ---------- 详情页 (Layout 内但菜单隐藏) ----------
96
55
  {
97
56
  path: 'user/detail/:id',
@@ -102,49 +61,6 @@ const layoutRoutes: RouteRecordRaw = {
102
61
  ]
103
62
  }
104
63
 
105
- // ==================== 独立页面 (无 Layout) ====================
106
- const standaloneRoutes: RouteRecordRaw[] = [
107
- {
108
- path: '/login',
109
- name: 'Login',
110
- component: () => import('@/views/login/index.vue'),
111
- meta: { title: '登录' }
112
- },
113
- {
114
- path: '/register',
115
- name: 'Register',
116
- component: () => import('@/views/register/index.vue'),
117
- meta: { title: '注册' }
118
- },
119
- // 独立结果页 (不带侧边栏,全屏展示)
120
- {
121
- path: '/result',
122
- name: 'Result',
123
- redirect: '/result/success',
124
- children: [
125
- {
126
- path: 'success',
127
- name: 'ResultSuccess',
128
- component: () => import('@/views/result/success.vue'),
129
- meta: { title: '操作成功' }
130
- },
131
- {
132
- path: 'fail',
133
- name: 'ResultFail',
134
- component: () => import('@/views/result/fail.vue'),
135
- meta: { title: '操作失败' }
136
- }
137
- ]
138
- },
139
- // 独立大屏页面示例
140
- {
141
- path: '/screen',
142
- name: 'DataScreen',
143
- component: () => import('@/views/screen/index.vue'),
144
- meta: { title: '数据大屏' }
145
- }
146
- ]
147
-
148
64
  // ==================== 错误页 ====================
149
65
  const errorRoutes: RouteRecordRaw[] = [
150
66
  {
@@ -164,7 +80,6 @@ const errorRoutes: RouteRecordRaw[] = [
164
80
  // ==================== 合并所有路由 ====================
165
81
  const routes: RouteRecordRaw[] = [
166
82
  layoutRoutes,
167
- ...standaloneRoutes,
168
83
  ...errorRoutes
169
84
  ]
170
85