@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvetechs/create-app",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "快速创建 Vue 3 / React 18 项目的脚手架工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,3 +1,3 @@
1
- # 基础环境变量
2
- VITE_APP_TITLE=My React App
3
- VITE_APP_BASE_API=/api
1
+ # 基础环境变量
2
+ VITE_APP_TITLE=My React App
3
+ VITE_APP_BASE_API=/api
@@ -1,3 +1,3 @@
1
- # 开发环境
2
- VITE_APP_TITLE=My React App (Dev)
3
- VITE_APP_BASE_API=/api
1
+ # 开发环境
2
+ VITE_APP_TITLE=My React App (Dev)
3
+ VITE_APP_BASE_API=/api
@@ -1,3 +1,3 @@
1
- # 生产环境
2
- VITE_APP_TITLE=My React App
3
- VITE_APP_BASE_API=https://api.example.com
1
+ # 生产环境
2
+ VITE_APP_TITLE=My React App
3
+ VITE_APP_BASE_API=https://api.example.com
@@ -20,7 +20,7 @@
20
20
  "zustand": "^4.5.2"
21
21
  },
22
22
  "devDependencies": {
23
- "@lvetechs/ui-lib": "^1.1.6",
23
+ "@lvetechs/ui-lib": "^1.1.7",
24
24
  "@types/node": "^25.2.3",
25
25
  "@types/nprogress": "^0.2.3",
26
26
  "@types/react": "^18.3.1",
@@ -34,8 +34,8 @@ importers:
34
34
  version: 4.5.7(@types/react@18.3.28)(react@18.3.1)
35
35
  devDependencies:
36
36
  '@lvetechs/ui-lib':
37
- specifier: ^1.1.6
38
- version: 1.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vue@3.5.28(typescript@5.9.3))
37
+ specifier: ^1.1.7
38
+ version: 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vue@3.5.28(typescript@5.9.3))
39
39
  '@types/node':
40
40
  specifier: ^25.2.3
41
41
  version: 25.2.3
@@ -371,8 +371,8 @@ packages:
371
371
  '@jridgewell/trace-mapping@0.3.31':
372
372
  resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
373
373
 
374
- '@lvetechs/ui-lib@1.1.6':
375
- resolution: {integrity: sha512-bFRfKcmhVN3UuA3skrEiwZs+u6ij0PhvT/QfPU1edfSjP55TjXaH6USysHLoRZnMibvAcrWCveTzmEppLCh6ew==}
374
+ '@lvetechs/ui-lib@1.1.7':
375
+ resolution: {integrity: sha512-BH3QmLcLxK9A7C5mAs/pLovO9ZJ2MBlxjJhdMCElxMn935OQ0TXhrVmHI39asST5xAmTNeSyDMXLVuS3eNuu6w==}
376
376
  peerDependencies:
377
377
  react: ^18.0.0
378
378
  react-dom: ^18.0.0
@@ -2005,7 +2005,7 @@ snapshots:
2005
2005
  '@jridgewell/resolve-uri': 3.1.2
2006
2006
  '@jridgewell/sourcemap-codec': 1.5.5
2007
2007
 
2008
- '@lvetechs/ui-lib@1.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vue@3.5.28(typescript@5.9.3))':
2008
+ '@lvetechs/ui-lib@1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vue@3.5.28(typescript@5.9.3))':
2009
2009
  dependencies:
2010
2010
  lucide-react: 0.563.0(react@18.3.1)
2011
2011
  react: 18.3.1
@@ -0,0 +1,77 @@
1
+ import { useState, useCallback, ChangeEvent, FormEvent } from 'react'
2
+
3
+ export type FormErrors<T> = Partial<Record<keyof T, string>>
4
+
5
+ export interface UseFormOptions<T> {
6
+ /** 初始表单值 */
7
+ initialValues: T
8
+ /** 校验函数(可选) */
9
+ validate?: (values: T) => FormErrors<T>
10
+ /** 提交成功回调(可选) */
11
+ onSubmit?: (values: T) => void | Promise<void>
12
+ }
13
+
14
+ /**
15
+ * 通用表单 Hook 示例
16
+ *
17
+ * - 管理表单值 values
18
+ * - 简单错误提示 errors
19
+ * - 提供 handleChange / handleSubmit / reset
20
+ */
21
+ export function useForm<T extends Record<string, any>>({
22
+ initialValues,
23
+ validate,
24
+ onSubmit
25
+ }: UseFormOptions<T>) {
26
+ const [values, setValues] = useState<T>(initialValues)
27
+ const [errors, setErrors] = useState<FormErrors<T>>({})
28
+ const [submitting, setSubmitting] = useState(false)
29
+
30
+ const handleChange = useCallback(
31
+ (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
32
+ const { name, value } = e.target
33
+ setValues((prev) => ({
34
+ ...prev,
35
+ [name]: value
36
+ }))
37
+ },
38
+ []
39
+ )
40
+
41
+ const reset = useCallback(() => {
42
+ setValues(initialValues)
43
+ setErrors({})
44
+ }, [initialValues])
45
+
46
+ const handleSubmit = useCallback(
47
+ async () => {
48
+ if (validate) {
49
+ const nextErrors = validate(values)
50
+ setErrors(nextErrors)
51
+ const hasError = Object.values(nextErrors).some(Boolean)
52
+ if (hasError) return false;
53
+ }
54
+
55
+ if (!onSubmit) return
56
+ try {
57
+ setSubmitting(true)
58
+ await onSubmit(values)
59
+ } finally {
60
+ setSubmitting(false)
61
+ }
62
+ },
63
+ [onSubmit, validate, values]
64
+ )
65
+
66
+ return {
67
+ values,
68
+ errors,
69
+ submitting,
70
+ handleChange,
71
+ handleSubmit,
72
+ reset,
73
+ setValues,
74
+ setErrors
75
+ }
76
+ }
77
+
@@ -24,49 +24,19 @@ export const menuRoutes: MenuItem[] = [
24
24
  {
25
25
  path: '/home',
26
26
  title: '首页',
27
- icon: '🏠'
27
+ icon: ''
28
28
  },
29
29
  {
30
30
  path: '/system',
31
31
  title: '系统管理',
32
- icon: '⚙️',
32
+ icon: '',
33
33
  children: [
34
34
  {
35
35
  path: '/system/user',
36
36
  title: '用户管理',
37
- icon: '👤'
37
+ icon: ''
38
38
  },
39
- {
40
- path: '/system/role',
41
- title: '角色管理',
42
- icon: '🔑'
43
- },
44
- {
45
- path: '/system/menu',
46
- title: '菜单管理',
47
- icon: '📋'
48
- },
49
- {
50
- path: '/system/log',
51
- title: '日志管理',
52
- icon: '📝',
53
- children: [
54
- {
55
- path: '/system/log/operation',
56
- title: '操作日志'
57
- },
58
- {
59
- path: '/system/log/login',
60
- title: '登录日志'
61
- }
62
- ]
63
- }
64
39
  ]
65
- },
66
- {
67
- path: '/about',
68
- title: '关于',
69
- icon: 'ℹ️'
70
40
  }
71
41
  ]
72
42
 
@@ -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,128 @@
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
+
40
+ export const useNotificationStore = create<NotificationState>()(
41
+ persist(
42
+ (set, get) => ({
43
+ notifications: [],
44
+
45
+ getUnreadCount: () => get().notifications.filter((n) => !n.read).length,
46
+
47
+ getNotificationsByType: (type) =>
48
+ get().notifications.filter((n) => n.type === type),
49
+
50
+ getNotificationsByCategory: (category) =>
51
+ get().notifications.filter((n) => n.category === category),
52
+
53
+ addNotification: (notification) => {
54
+ const newNotification: NotificationItem = {
55
+ ...notification,
56
+ id: `notif-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
57
+ read: false,
58
+ timestamp: Date.now()
59
+ }
60
+ set((state) => ({
61
+ notifications: [newNotification, ...state.notifications]
62
+ }))
63
+ return newNotification.id
64
+ },
65
+
66
+ markAsRead: (id) =>
67
+ set((state) => ({
68
+ notifications: state.notifications.map((n) =>
69
+ n.id === id ? { ...n, read: true } : n
70
+ )
71
+ })),
72
+
73
+ markAllAsRead: () =>
74
+ set((state) => ({
75
+ notifications: state.notifications.map((n) => ({ ...n, read: true }))
76
+ })),
77
+
78
+ markCategoryAsRead: (category) =>
79
+ set((state) => ({
80
+ notifications: state.notifications.map((n) =>
81
+ n.category === category ? { ...n, read: true } : n
82
+ )
83
+ })),
84
+
85
+ removeNotification: (id) =>
86
+ set((state) => ({
87
+ notifications: state.notifications.filter((n) => n.id !== id)
88
+ })),
89
+
90
+ removeReadNotifications: () =>
91
+ set((state) => ({
92
+ notifications: state.notifications.filter((n) => !n.read)
93
+ })),
94
+
95
+ clearAll: () => set({ notifications: [] }),
96
+
97
+ initSampleData: () => {
98
+ if (get().notifications.length > 0) return
99
+ const add = get().addNotification
100
+ add({
101
+ title: '系统通知',
102
+ content: '欢迎使用本系统!',
103
+ type: 'info',
104
+ category: '系统'
105
+ })
106
+ add({
107
+ title: '订单提醒',
108
+ content: '您有新的订单待处理',
109
+ type: 'warning',
110
+ category: '订单'
111
+ })
112
+ add({
113
+ title: '操作成功',
114
+ content: '数据保存成功',
115
+ type: 'success',
116
+ category: '操作'
117
+ })
118
+ }
119
+ }),
120
+ {
121
+ name: 'notification-store',
122
+ partialize: (state) => ({
123
+ notifications: state.notifications
124
+ })
125
+ }
126
+ )
127
+ )
128
+