@lvetechs/create-app 1.0.4 → 1.0.5

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 (47) 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/hooks/useForm.ts +77 -0
  8. package/templates/react/src/layouts/menuConfig.ts +3 -33
  9. package/templates/react/src/router/index.tsx +0 -39
  10. package/templates/react/src/stores/app.ts +141 -3
  11. package/templates/react/src/stores/notification.ts +128 -0
  12. package/templates/react/src/stores/permission.ts +183 -0
  13. package/templates/react/src/stores/user.ts +151 -4
  14. package/templates/react/src/views/home/index.tsx +167 -6
  15. package/templates/react/src/views/system/user/index.tsx +171 -5
  16. package/templates/vue/.env +2 -2
  17. package/templates/vue/.env.development +2 -2
  18. package/templates/vue/.env.production +2 -2
  19. package/templates/vue/pnpm-lock.yaml +3307 -3307
  20. package/templates/vue/src/auto-imports.d.ts +5 -0
  21. package/templates/vue/src/layouts/menuConfig.ts +3 -33
  22. package/templates/vue/src/router/index.ts +3 -88
  23. package/templates/vue/src/stores/app.ts +133 -2
  24. package/templates/vue/src/stores/notification.ts +172 -0
  25. package/templates/vue/src/stores/permission.ts +184 -0
  26. package/templates/vue/src/stores/user.ts +109 -2
  27. package/templates/vue/src/views/home/index.vue +7 -7
  28. package/templates/react/src/views/about/index.tsx +0 -40
  29. package/templates/react/src/views/login/index.tsx +0 -138
  30. package/templates/react/src/views/register/index.tsx +0 -143
  31. package/templates/react/src/views/result/fail.tsx +0 -39
  32. package/templates/react/src/views/result/success.tsx +0 -35
  33. package/templates/react/src/views/screen/index.tsx +0 -120
  34. package/templates/react/src/views/system/log/login.tsx +0 -51
  35. package/templates/react/src/views/system/log/operation.tsx +0 -47
  36. package/templates/react/src/views/system/menu/index.tsx +0 -62
  37. package/templates/react/src/views/system/role/index.tsx +0 -63
  38. package/templates/vue/src/views/about/index.vue +0 -67
  39. package/templates/vue/src/views/login/index.vue +0 -153
  40. package/templates/vue/src/views/register/index.vue +0 -169
  41. package/templates/vue/src/views/result/fail.vue +0 -92
  42. package/templates/vue/src/views/result/success.vue +0 -92
  43. package/templates/vue/src/views/screen/index.vue +0 -150
  44. package/templates/vue/src/views/system/log/login.vue +0 -51
  45. package/templates/vue/src/views/system/log/operation.vue +0 -47
  46. package/templates/vue/src/views/system/menu/index.vue +0 -58
  47. package/templates/vue/src/views/system/role/index.vue +0 -59
@@ -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']>
@@ -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
 
@@ -1,33 +1,164 @@
1
1
  import { defineStore } from 'pinia'
2
- import { ref } from 'vue'
2
+ import { ref, computed } from 'vue'
3
+
4
+ export interface AppError {
5
+ id: string
6
+ message: string
7
+ code?: string
8
+ timestamp: number
9
+ type?: 'error' | 'warning' | 'info'
10
+ }
3
11
 
4
12
  export const useAppStore = defineStore(
5
13
  'app',
6
14
  () => {
15
+ // 基础配置
7
16
  const sidebarCollapsed = ref(false)
8
17
  const theme = ref<'light' | 'dark'>('light')
9
18
  const language = ref('zh-CN')
10
19
 
20
+ // 加载状态
21
+ const loading = ref(false)
22
+ const loadingText = ref('加载中...')
23
+ const loadingStack = ref<string[]>([]) // 支持多个并发加载任务
24
+
25
+ // 错误处理
26
+ const errors = ref<AppError[]>([])
27
+ const maxErrorCount = 10 // 最多保留的错误数量
28
+
29
+ // 计算属性:是否有加载任务
30
+ const isLoading = computed(() => loadingStack.value.length > 0)
31
+
32
+ // 计算属性:未处理的错误数量
33
+ const unhandledErrorCount = computed(() => {
34
+ return errors.value.filter((e) => e.type === 'error').length
35
+ })
36
+
37
+ // 侧边栏控制
11
38
  function toggleSidebar() {
12
39
  sidebarCollapsed.value = !sidebarCollapsed.value
13
40
  }
14
41
 
42
+ function setSidebarCollapsed(collapsed: boolean) {
43
+ sidebarCollapsed.value = collapsed
44
+ }
45
+
46
+ // 主题控制
15
47
  function setTheme(val: 'light' | 'dark') {
16
48
  theme.value = val
17
49
  document.documentElement.setAttribute('data-theme', val)
18
50
  }
19
51
 
52
+ function toggleTheme() {
53
+ setTheme(theme.value === 'light' ? 'dark' : 'light')
54
+ }
55
+
56
+ // 语言控制
20
57
  function setLanguage(val: string) {
21
58
  language.value = val
22
59
  }
23
60
 
61
+ // 加载状态管理
62
+ function startLoading(taskId?: string, text?: string) {
63
+ const id = taskId || `loading-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
64
+ loadingStack.value.push(id)
65
+ if (text) {
66
+ loadingText.value = text
67
+ }
68
+ loading.value = true
69
+ return id
70
+ }
71
+
72
+ function stopLoading(taskId?: string) {
73
+ if (taskId) {
74
+ const index = loadingStack.value.indexOf(taskId)
75
+ if (index > -1) {
76
+ loadingStack.value.splice(index, 1)
77
+ }
78
+ } else {
79
+ loadingStack.value.pop()
80
+ }
81
+
82
+ if (loadingStack.value.length === 0) {
83
+ loading.value = false
84
+ loadingText.value = '加载中...'
85
+ }
86
+ }
87
+
88
+ // 错误处理
89
+ function addError(
90
+ message: string,
91
+ code?: string,
92
+ type: AppError['type'] = 'error'
93
+ ) {
94
+ const error: AppError = {
95
+ id: `error-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
96
+ message,
97
+ code,
98
+ timestamp: Date.now(),
99
+ type
100
+ }
101
+ errors.value.unshift(error)
102
+
103
+ // 限制错误数量
104
+ if (errors.value.length > maxErrorCount) {
105
+ errors.value = errors.value.slice(0, maxErrorCount)
106
+ }
107
+
108
+ return error.id
109
+ }
110
+
111
+ function removeError(id: string) {
112
+ const index = errors.value.findIndex((e) => e.id === id)
113
+ if (index > -1) {
114
+ errors.value.splice(index, 1)
115
+ }
116
+ }
117
+
118
+ function clearErrors() {
119
+ errors.value = []
120
+ }
121
+
122
+ function clearErrorsByType(type: AppError['type']) {
123
+ errors.value = errors.value.filter((e) => e.type !== type)
124
+ }
125
+
126
+ // 获取指定类型的错误
127
+ function getErrorsByType(type: AppError['type']): AppError[] {
128
+ return errors.value.filter((e) => e.type === type)
129
+ }
130
+
131
+ // 全局提示(简化版,实际项目中可以使用更完善的 toast 组件)
132
+ function showMessage(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') {
133
+ addError(message, undefined, type)
134
+ }
135
+
24
136
  return {
137
+ // 状态
25
138
  sidebarCollapsed,
26
139
  theme,
27
140
  language,
141
+ loading,
142
+ loadingText,
143
+ loadingStack,
144
+ errors,
145
+ // 计算属性
146
+ isLoading,
147
+ unhandledErrorCount,
148
+ // 方法
28
149
  toggleSidebar,
150
+ setSidebarCollapsed,
29
151
  setTheme,
30
- setLanguage
152
+ toggleTheme,
153
+ setLanguage,
154
+ startLoading,
155
+ stopLoading,
156
+ addError,
157
+ removeError,
158
+ clearErrors,
159
+ clearErrorsByType,
160
+ getErrorsByType,
161
+ showMessage
31
162
  }
32
163
  },
33
164
  {
@@ -0,0 +1,172 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref, computed } from 'vue'
3
+
4
+ export type NotificationType = 'info' | 'success' | 'warning' | 'error'
5
+
6
+ export interface Notification {
7
+ id: string
8
+ title: string
9
+ content: string
10
+ type: NotificationType
11
+ read: boolean
12
+ timestamp: number
13
+ category?: string
14
+ actionUrl?: string
15
+ }
16
+
17
+ export const useNotificationStore = defineStore(
18
+ 'notification',
19
+ () => {
20
+ // 通知列表
21
+ const notifications = ref<Notification[]>([])
22
+
23
+ // 计算属性:未读数量
24
+ const unreadCount = computed(() => {
25
+ return notifications.value.filter((n) => !n.read).length
26
+ })
27
+
28
+ // 计算属性:按类型分组的通知
29
+ const notificationsByType = computed(() => {
30
+ const grouped: Record<NotificationType, Notification[]> = {
31
+ info: [],
32
+ success: [],
33
+ warning: [],
34
+ error: []
35
+ }
36
+ notifications.value.forEach((notification) => {
37
+ grouped[notification.type].push(notification)
38
+ })
39
+ return grouped
40
+ })
41
+
42
+ // 计算属性:按分类分组的通知
43
+ const notificationsByCategory = computed(() => {
44
+ const grouped: Record<string, Notification[]> = {}
45
+ notifications.value.forEach((notification) => {
46
+ const category = notification.category || '其他'
47
+ if (!grouped[category]) {
48
+ grouped[category] = []
49
+ }
50
+ grouped[category].push(notification)
51
+ })
52
+ return grouped
53
+ })
54
+
55
+ // 添加通知
56
+ function addNotification(notification: Omit<Notification, 'id' | 'read' | 'timestamp'>) {
57
+ const newNotification: Notification = {
58
+ ...notification,
59
+ id: `notif-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
60
+ read: false,
61
+ timestamp: Date.now()
62
+ }
63
+ notifications.value.unshift(newNotification) // 新通知添加到顶部
64
+ return newNotification.id
65
+ }
66
+
67
+ // 标记为已读
68
+ function markAsRead(id: string) {
69
+ const notification = notifications.value.find((n) => n.id === id)
70
+ if (notification) {
71
+ notification.read = true
72
+ }
73
+ }
74
+
75
+ // 批量标记为已读
76
+ function markAllAsRead() {
77
+ notifications.value.forEach((notification) => {
78
+ notification.read = true
79
+ })
80
+ }
81
+
82
+ // 标记分类为已读
83
+ function markCategoryAsRead(category: string) {
84
+ notifications.value.forEach((notification) => {
85
+ if (notification.category === category) {
86
+ notification.read = true
87
+ }
88
+ })
89
+ }
90
+
91
+ // 删除通知
92
+ function removeNotification(id: string) {
93
+ const index = notifications.value.findIndex((n) => n.id === id)
94
+ if (index > -1) {
95
+ notifications.value.splice(index, 1)
96
+ }
97
+ }
98
+
99
+ // 批量删除已读通知
100
+ function removeReadNotifications() {
101
+ notifications.value = notifications.value.filter((n) => !n.read)
102
+ }
103
+
104
+ // 清空所有通知
105
+ function clearAll() {
106
+ notifications.value = []
107
+ }
108
+
109
+ // 获取未读通知
110
+ function getUnreadNotifications(): Notification[] {
111
+ return notifications.value.filter((n) => !n.read)
112
+ }
113
+
114
+ // 获取指定类型的通知
115
+ function getNotificationsByType(type: NotificationType): Notification[] {
116
+ return notifications.value.filter((n) => n.type === type)
117
+ }
118
+
119
+ // 获取指定分类的通知
120
+ function getNotificationsByCategory(category: string): Notification[] {
121
+ return notifications.value.filter((n) => n.category === category)
122
+ }
123
+
124
+ // 初始化示例数据
125
+ function initSampleData() {
126
+ if (notifications.value.length === 0) {
127
+ addNotification({
128
+ title: '系统通知',
129
+ content: '欢迎使用本系统!',
130
+ type: 'info',
131
+ category: '系统'
132
+ })
133
+ addNotification({
134
+ title: '订单提醒',
135
+ content: '您有新的订单待处理',
136
+ type: 'warning',
137
+ category: '订单'
138
+ })
139
+ addNotification({
140
+ title: '操作成功',
141
+ content: '数据保存成功',
142
+ type: 'success',
143
+ category: '操作'
144
+ })
145
+ }
146
+ }
147
+
148
+ return {
149
+ notifications,
150
+ unreadCount,
151
+ notificationsByType,
152
+ notificationsByCategory,
153
+ addNotification,
154
+ markAsRead,
155
+ markAllAsRead,
156
+ markCategoryAsRead,
157
+ removeNotification,
158
+ removeReadNotifications,
159
+ clearAll,
160
+ getUnreadNotifications,
161
+ getNotificationsByType,
162
+ getNotificationsByCategory,
163
+ initSampleData
164
+ }
165
+ },
166
+ {
167
+ persist: {
168
+ key: 'notification-store',
169
+ paths: ['notifications']
170
+ }
171
+ }
172
+ )