@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
@@ -7,22 +7,10 @@ const DefaultLayout = lazy(() => import('@/layouts/DefaultLayout'))
7
7
 
8
8
  // ==================== Layout 内页面 ====================
9
9
  const Home = lazy(() => import('@/views/home'))
10
- const About = lazy(() => import('@/views/about'))
11
10
 
12
11
  // 系统管理 (多级路由)
13
12
  const SystemUser = lazy(() => import('@/views/system/user'))
14
13
  const SystemUserDetail = lazy(() => import('@/views/system/user/detail'))
15
- const SystemRole = lazy(() => import('@/views/system/role'))
16
- const SystemMenu = lazy(() => import('@/views/system/menu'))
17
- const OperationLog = lazy(() => import('@/views/system/log/operation'))
18
- const LoginLog = lazy(() => import('@/views/system/log/login'))
19
-
20
- // ==================== 独立页面 (无 Layout) ====================
21
- const Login = lazy(() => import('@/views/login'))
22
- const Register = lazy(() => import('@/views/register'))
23
- const ResultSuccess = lazy(() => import('@/views/result/success'))
24
- const ResultFail = lazy(() => import('@/views/result/fail'))
25
- const DataScreen = lazy(() => import('@/views/screen'))
26
14
 
27
15
  // ==================== 错误页 ====================
28
16
  const Forbidden = lazy(() => import('@/views/error/403'))
@@ -63,39 +51,12 @@ export const routes: RouteObject[] = [
63
51
  children: [
64
52
  { index: true, element: <Navigate to="/system/user" replace /> },
65
53
  { path: 'user', element: lazyLoad(SystemUser) },
66
- { path: 'role', element: lazyLoad(SystemRole) },
67
- { path: 'menu', element: lazyLoad(SystemMenu) },
68
- // 三级路由: 系统管理 > 日志管理
69
- {
70
- path: 'log',
71
- children: [
72
- { index: true, element: <Navigate to="/system/log/operation" replace /> },
73
- { path: 'operation', element: lazyLoad(OperationLog) },
74
- { path: 'login', element: lazyLoad(LoginLog) }
75
- ]
76
- }
77
54
  ]
78
55
  },
79
- // 关于
80
- { path: 'about', element: lazyLoad(About) },
81
56
  // 详情页 (Layout 内但菜单隐藏)
82
57
  { path: 'user/detail/:id', element: lazyLoad(SystemUserDetail) }
83
58
  ]
84
59
  },
85
-
86
- // ==================== 独立页面 (无 Layout) ====================
87
- { path: '/login', element: lazyLoad(Login) },
88
- { path: '/register', element: lazyLoad(Register) },
89
- {
90
- path: '/result',
91
- children: [
92
- { index: true, element: <Navigate to="/result/success" replace /> },
93
- { path: 'success', element: lazyLoad(ResultSuccess) },
94
- { path: 'fail', element: lazyLoad(ResultFail) }
95
- ]
96
- },
97
- { path: '/screen', element: lazyLoad(DataScreen) },
98
-
99
60
  // ==================== 错误页 ====================
100
61
  { path: '/403', element: lazyLoad(Forbidden) },
101
62
  { path: '*', element: lazyLoad(NotFound) }
@@ -1,30 +1,168 @@
1
1
  import { create } from 'zustand'
2
2
  import { persist } from 'zustand/middleware'
3
3
 
4
+ export interface AppError {
5
+ id: string
6
+ message: string
7
+ code?: string
8
+ timestamp: number
9
+ type?: 'error' | 'warning' | 'info' | 'success'
10
+ }
11
+
4
12
  interface AppState {
13
+ /** 侧边栏是否收起 */
5
14
  sidebarCollapsed: boolean
15
+ /** 主题 */
6
16
  theme: 'light' | 'dark'
17
+ /** 语言 */
7
18
  language: string
19
+
20
+ /** 全局加载状态(是否存在任何加载任务) */
21
+ loading: boolean
22
+ /** 加载文案 */
23
+ loadingText: string
24
+ /** 加载任务栈,支持多个并发任务 */
25
+ loadingStack: string[]
26
+
27
+ /** 全局错误 / 提示列表 */
28
+ errors: AppError[]
29
+
30
+ // ========== 计算相关(通过方法计算,而不是状态字段) ==========
31
+ /** 是否有加载任务 */
32
+ getIsLoading: () => boolean
33
+ /** 未处理错误数量(error 类型) */
34
+ getUnhandledErrorCount: () => number
35
+
36
+ // ========== 基础设置 ==========
8
37
  toggleSidebar: () => void
38
+ setSidebarCollapsed: (collapsed: boolean) => void
9
39
  setTheme: (val: 'light' | 'dark') => void
40
+ toggleTheme: () => void
10
41
  setLanguage: (val: string) => void
42
+
43
+ // ========== 加载状态管理 ==========
44
+ startLoading: (taskId?: string, text?: string) => string
45
+ stopLoading: (taskId?: string) => void
46
+
47
+ // ========== 错误管理 ==========
48
+ addError: (message: string, code?: string, type?: AppError['type']) => string
49
+ removeError: (id: string) => void
50
+ clearErrors: () => void
51
+ clearErrorsByType: (type: AppError['type']) => void
52
+ getErrorsByType: (type: AppError['type']) => AppError[]
53
+
54
+ /** 简单的全局消息(内部用 addError 存一份,可配合全局提示组件展示) */
55
+ showMessage: (message: string, type?: 'success' | 'error' | 'warning' | 'info') => void
11
56
  }
12
57
 
13
58
  export const useAppStore = create<AppState>()(
14
59
  persist(
15
- (set) => ({
60
+ (set, get) => ({
16
61
  sidebarCollapsed: false,
17
62
  theme: 'light',
18
63
  language: 'zh-CN',
19
64
 
65
+ loading: false,
66
+ loadingText: '加载中...',
67
+ loadingStack: [],
68
+
69
+ errors: [],
70
+
71
+ // 计算相关
72
+ getIsLoading: () => get().loadingStack.length > 0,
73
+ getUnhandledErrorCount: () =>
74
+ get().errors.filter((e) => !e.type || e.type === 'error').length,
75
+
76
+ // 基础设置
20
77
  toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
21
78
 
79
+ setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
80
+
22
81
  setTheme: (val) => {
23
- document.documentElement.setAttribute('data-theme', val)
82
+ if (typeof document !== 'undefined') {
83
+ document.documentElement.setAttribute('data-theme', val)
84
+ }
24
85
  set({ theme: val })
25
86
  },
26
87
 
27
- setLanguage: (val) => set({ language: val })
88
+ toggleTheme: () =>
89
+ set((state) => {
90
+ const next = state.theme === 'light' ? 'dark' : 'light'
91
+ if (typeof document !== 'undefined') {
92
+ document.documentElement.setAttribute('data-theme', next)
93
+ }
94
+ return { theme: next }
95
+ }),
96
+
97
+ setLanguage: (val) => set({ language: val }),
98
+
99
+ // 加载状态管理
100
+ startLoading: (taskId, text) => {
101
+ const id =
102
+ taskId || `loading-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
103
+ set((state) => ({
104
+ loading: true,
105
+ loadingText: text || state.loadingText,
106
+ loadingStack: [...state.loadingStack, id]
107
+ }))
108
+ return id
109
+ },
110
+
111
+ stopLoading: (taskId) =>
112
+ set((state) => {
113
+ const stack = [...state.loadingStack]
114
+ if (taskId) {
115
+ const index = stack.indexOf(taskId)
116
+ if (index > -1) stack.splice(index, 1)
117
+ } else {
118
+ stack.pop()
119
+ }
120
+ return {
121
+ loading: stack.length > 0,
122
+ loadingText: stack.length > 0 ? state.loadingText : '加载中...',
123
+ loadingStack: stack
124
+ }
125
+ }),
126
+
127
+ // 错误管理
128
+ addError: (message, code, type = 'error') => {
129
+ const error: AppError = {
130
+ id: `error-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
131
+ message,
132
+ code,
133
+ timestamp: Date.now(),
134
+ type
135
+ }
136
+
137
+ set((state) => {
138
+ const next = [error, ...state.errors]
139
+ // 最多保留 10 条
140
+ if (next.length > 10) {
141
+ next.length = 10
142
+ }
143
+ return { errors: next }
144
+ })
145
+
146
+ return error.id
147
+ },
148
+
149
+ removeError: (id) =>
150
+ set((state) => ({
151
+ errors: state.errors.filter((e) => e.id !== id)
152
+ })),
153
+
154
+ clearErrors: () => set({ errors: [] }),
155
+
156
+ clearErrorsByType: (type) =>
157
+ set((state) => ({
158
+ errors: state.errors.filter((e) => e.type !== type)
159
+ })),
160
+
161
+ getErrorsByType: (type) => get().errors.filter((e) => e.type === type),
162
+
163
+ showMessage: (message, type = 'info') => {
164
+ get().addError(message, undefined, type)
165
+ }
28
166
  }),
29
167
  {
30
168
  name: 'app-store',
@@ -0,0 +1,146 @@
1
+ import { create } from 'zustand'
2
+ import { persist } from 'zustand/middleware'
3
+
4
+ export type NotificationType = 'info' | 'success' | 'warning' | 'error'
5
+
6
+ export interface NotificationItem {
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
+ interface NotificationState {
18
+ notifications: NotificationItem[]
19
+
20
+ // 计算相关(通过方法计算)
21
+ getUnreadCount: () => number
22
+ getNotificationsByType: (type: NotificationType) => NotificationItem[]
23
+ getNotificationsByCategory: (category: string) => NotificationItem[]
24
+
25
+ // 基本操作
26
+ addNotification: (
27
+ notification: Omit<NotificationItem, 'id' | 'read' | 'timestamp'>
28
+ ) => string
29
+ markAsRead: (id: string) => void
30
+ markAllAsRead: () => void
31
+ markCategoryAsRead: (category: string) => void
32
+ removeNotification: (id: string) => void
33
+ removeReadNotifications: () => void
34
+ clearAll: () => void
35
+
36
+ // 示例数据
37
+ initSampleData: () => void
38
+
39
+ // API 相关
40
+ fetchNotifications: () => Promise<void>
41
+ }
42
+
43
+ export const useNotificationStore = create<NotificationState>()(
44
+ persist(
45
+ (set, get) => ({
46
+ notifications: [],
47
+
48
+ getUnreadCount: () => get().notifications.filter((n) => !n.read).length,
49
+
50
+ getNotificationsByType: (type) =>
51
+ get().notifications.filter((n) => n.type === type),
52
+
53
+ getNotificationsByCategory: (category) =>
54
+ get().notifications.filter((n) => n.category === category),
55
+
56
+ addNotification: (notification) => {
57
+ const newNotification: NotificationItem = {
58
+ ...notification,
59
+ id: `notif-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
60
+ read: false,
61
+ timestamp: Date.now()
62
+ }
63
+ set((state) => ({
64
+ notifications: [newNotification, ...state.notifications]
65
+ }))
66
+ return newNotification.id
67
+ },
68
+
69
+ markAsRead: (id) =>
70
+ set((state) => ({
71
+ notifications: state.notifications.map((n) =>
72
+ n.id === id ? { ...n, read: true } : n
73
+ )
74
+ })),
75
+
76
+ markAllAsRead: () =>
77
+ set((state) => ({
78
+ notifications: state.notifications.map((n) => ({ ...n, read: true }))
79
+ })),
80
+
81
+ markCategoryAsRead: (category) =>
82
+ set((state) => ({
83
+ notifications: state.notifications.map((n) =>
84
+ n.category === category ? { ...n, read: true } : n
85
+ )
86
+ })),
87
+
88
+ removeNotification: (id) =>
89
+ set((state) => ({
90
+ notifications: state.notifications.filter((n) => n.id !== id)
91
+ })),
92
+
93
+ removeReadNotifications: () =>
94
+ set((state) => ({
95
+ notifications: state.notifications.filter((n) => !n.read)
96
+ })),
97
+
98
+ clearAll: () => set({ notifications: [] }),
99
+
100
+ initSampleData: () => {
101
+ if (get().notifications.length > 0) return
102
+ const add = get().addNotification
103
+ add({
104
+ title: '系统通知',
105
+ content: '欢迎使用本系统!',
106
+ type: 'info',
107
+ category: '系统'
108
+ })
109
+ },
110
+
111
+ fetchNotifications: async () => {
112
+ try {
113
+ // 动态导入避免循环依赖
114
+ const { getNotificationsApi } = await import('@/api/notification')
115
+ const response = await getNotificationsApi({ unreadOnly: false, pageSize: 20 })
116
+ if (response.data) {
117
+ // 合并新消息(实际项目中需要去重逻辑)
118
+ const existingIds = new Set(get().notifications.map((n) => n.id))
119
+ response.data.list.forEach((item) => {
120
+ if (!existingIds.has(item.id)) {
121
+ get().addNotification({
122
+ title: item.title,
123
+ content: item.content,
124
+ type: item.type,
125
+ category: item.category,
126
+ actionUrl: item.actionUrl
127
+ })
128
+ }
129
+ })
130
+ }
131
+ } catch (error) {
132
+ console.error('获取消息失败:', error)
133
+ // 如果 API 失败,使用示例数据
134
+ get().initSampleData()
135
+ }
136
+ }
137
+ }),
138
+ {
139
+ name: 'notification-store',
140
+ partialize: (state) => ({
141
+ notifications: state.notifications
142
+ })
143
+ }
144
+ )
145
+ )
146
+
@@ -0,0 +1,173 @@
1
+ import { create } from 'zustand'
2
+ import { persist } from 'zustand/middleware'
3
+ import { useUserStore } from './user'
4
+
5
+ export type Permission = string
6
+ export type Role = string
7
+
8
+ export interface PermissionConfig {
9
+ permission: Permission
10
+ name: string
11
+ description?: string
12
+ module?: string
13
+ }
14
+
15
+ export interface RoleConfig {
16
+ role: Role
17
+ name: string
18
+ description?: string
19
+ permissions: Permission[]
20
+ }
21
+
22
+ interface PermissionState {
23
+ allPermissions: PermissionConfig[]
24
+ allRoles: RoleConfig[]
25
+
26
+ // 计算相关
27
+ getUserPermissions: () => Permission[]
28
+ getPermissionsByModule: () => Record<string, PermissionConfig[]>
29
+
30
+ // 检查权限 / 角色
31
+ hasPermission: (permission: Permission) => boolean
32
+ hasAnyPermission: (permissions: Permission[]) => boolean
33
+ hasAllPermissions: (permissions: Permission[]) => boolean
34
+ hasRole: (role: Role) => boolean
35
+ hasAnyRole: (roles: Role[]) => boolean
36
+
37
+ // 查询配置
38
+ getRoleConfig: (role: Role) => RoleConfig | undefined
39
+ getPermissionConfig: (permission: Permission) => PermissionConfig | undefined
40
+
41
+ // 修改配置
42
+ addPermission: (permission: PermissionConfig) => void
43
+ addRole: (role: RoleConfig) => void
44
+ updateRolePermissions: (role: Role, permissions: Permission[]) => void
45
+ }
46
+
47
+ export const usePermissionStore = create<PermissionState>()(
48
+ persist(
49
+ (set, get) => ({
50
+ allPermissions: [
51
+ { permission: 'user:view', name: '查看用户', module: '用户管理' },
52
+ { permission: 'user:create', name: '创建用户', module: '用户管理' },
53
+ { permission: 'user:edit', name: '编辑用户', module: '用户管理' },
54
+ { permission: 'user:delete', name: '删除用户', module: '用户管理' },
55
+ { permission: 'role:view', name: '查看角色', module: '角色管理' },
56
+ { permission: 'role:create', name: '创建角色', module: '角色管理' },
57
+ { permission: 'role:edit', name: '编辑角色', module: '角色管理' },
58
+ { permission: 'role:delete', name: '删除角色', module: '角色管理' },
59
+ { permission: 'system:config', name: '系统配置', module: '系统管理' },
60
+ { permission: 'system:log', name: '查看日志', module: '系统管理' }
61
+ ],
62
+
63
+ allRoles: [
64
+ {
65
+ role: 'admin',
66
+ name: '超级管理员',
67
+ description: '拥有所有权限',
68
+ permissions: [
69
+ 'user:view',
70
+ 'user:create',
71
+ 'user:edit',
72
+ 'user:delete',
73
+ 'role:view',
74
+ 'role:create',
75
+ 'role:edit',
76
+ 'role:delete',
77
+ 'system:config',
78
+ 'system:log'
79
+ ]
80
+ },
81
+ {
82
+ role: 'editor',
83
+ name: '编辑',
84
+ description: '可以查看和编辑内容',
85
+ permissions: ['user:view', 'user:create', 'user:edit', 'role:view']
86
+ },
87
+ {
88
+ role: 'viewer',
89
+ name: '只读',
90
+ description: '只能查看内容',
91
+ permissions: ['user:view', 'role:view']
92
+ }
93
+ ],
94
+
95
+ getUserPermissions: () => {
96
+ const user = useUserStore.getState().userInfo
97
+ if (!user) return []
98
+
99
+ const roles = user.roles || []
100
+ const allRoles = get().allRoles
101
+
102
+ const permissions: Permission[] = []
103
+ roles.forEach((role) => {
104
+ const roleConfig = allRoles.find((r) => r.role === role)
105
+ if (roleConfig) {
106
+ permissions.push(...roleConfig.permissions)
107
+ }
108
+ })
109
+
110
+ return Array.from(new Set(permissions))
111
+ },
112
+
113
+ getPermissionsByModule: () => {
114
+ const grouped: Record<string, PermissionConfig[]> = {}
115
+ get().allPermissions.forEach((permission) => {
116
+ const module = permission.module || '其他'
117
+ if (!grouped[module]) grouped[module] = []
118
+ grouped[module].push(permission)
119
+ })
120
+ return grouped
121
+ },
122
+
123
+ hasPermission: (permission) => get().getUserPermissions().includes(permission),
124
+
125
+ hasAnyPermission: (permissions) => permissions.some((p) => get().hasPermission(p)),
126
+
127
+ hasAllPermissions: (permissions) => permissions.every((p) => get().hasPermission(p)),
128
+
129
+ hasRole: (role) => {
130
+ const user = useUserStore.getState().userInfo
131
+ return user?.roles?.includes(role) ?? false
132
+ },
133
+
134
+ hasAnyRole: (roles) => roles.some((r) => get().hasRole(r)),
135
+
136
+ getRoleConfig: (role) => get().allRoles.find((r) => r.role === role),
137
+
138
+ getPermissionConfig: (permission) =>
139
+ get().allPermissions.find((p) => p.permission === permission),
140
+
141
+ addPermission: (permission) =>
142
+ set((state) => {
143
+ if (state.allPermissions.some((p) => p.permission === permission.permission)) {
144
+ return state
145
+ }
146
+ return { allPermissions: [...state.allPermissions, permission] }
147
+ }),
148
+
149
+ addRole: (role) =>
150
+ set((state) => {
151
+ if (state.allRoles.some((r) => r.role === role.role)) {
152
+ return state
153
+ }
154
+ return { allRoles: [...state.allRoles, role] }
155
+ }),
156
+
157
+ updateRolePermissions: (role, permissions) =>
158
+ set((state) => ({
159
+ allRoles: state.allRoles.map((r) =>
160
+ r.role === role ? { ...r, permissions } : r
161
+ )
162
+ }))
163
+ }),
164
+ {
165
+ name: 'permission-store',
166
+ partialize: (state) => ({
167
+ allPermissions: state.allPermissions,
168
+ allRoles: state.allRoles
169
+ })
170
+ }
171
+ )
172
+ )
173
+