@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,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,189 @@
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
+ }
134
+ }
135
+
136
+ // 从 API 获取消息
137
+ async function fetchNotifications() {
138
+ try {
139
+ // 动态导入避免循环依赖
140
+ const { getNotificationsApi } = await import('@/api/notification')
141
+ const response = await getNotificationsApi({ unreadOnly: false, pageSize: 20 })
142
+ if (response.data) {
143
+ // 合并新消息(实际项目中需要去重逻辑)
144
+ const existingIds = new Set(notifications.value.map((n) => n.id))
145
+ response.data.list.forEach((item) => {
146
+ if (!existingIds.has(item.id)) {
147
+ addNotification({
148
+ title: item.title,
149
+ content: item.content,
150
+ type: item.type,
151
+ category: item.category,
152
+ actionUrl: item.actionUrl
153
+ })
154
+ }
155
+ })
156
+ }
157
+ } catch (error) {
158
+ console.error('获取消息失败:', error)
159
+ // 如果 API 失败,使用示例数据
160
+ initSampleData()
161
+ }
162
+ }
163
+
164
+ return {
165
+ notifications,
166
+ unreadCount,
167
+ notificationsByType,
168
+ notificationsByCategory,
169
+ addNotification,
170
+ markAsRead,
171
+ markAllAsRead,
172
+ markCategoryAsRead,
173
+ removeNotification,
174
+ removeReadNotifications,
175
+ clearAll,
176
+ getUnreadNotifications,
177
+ getNotificationsByType,
178
+ getNotificationsByCategory,
179
+ initSampleData,
180
+ fetchNotifications
181
+ }
182
+ },
183
+ {
184
+ persist: {
185
+ key: 'notification-store',
186
+ paths: ['notifications']
187
+ }
188
+ }
189
+ )
@@ -0,0 +1,184 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref, computed } from 'vue'
3
+ import { useUserStore } from './user'
4
+
5
+ export type Permission = string
6
+ export type Role = string
7
+
8
+ export interface PermissionConfig {
9
+ // 权限标识
10
+ permission: Permission
11
+ // 权限名称
12
+ name: string
13
+ // 权限描述
14
+ description?: string
15
+ // 所属模块
16
+ module?: string
17
+ }
18
+
19
+ export interface RoleConfig {
20
+ // 角色标识
21
+ role: Role
22
+ // 角色名称
23
+ name: string
24
+ // 角色描述
25
+ description?: string
26
+ // 角色拥有的权限列表
27
+ permissions: Permission[]
28
+ }
29
+
30
+ export const usePermissionStore = defineStore(
31
+ 'permission',
32
+ () => {
33
+ // 所有权限配置
34
+ const allPermissions = ref<PermissionConfig[]>([
35
+ { permission: 'user:view', name: '查看用户', module: '用户管理' },
36
+ { permission: 'user:create', name: '创建用户', module: '用户管理' },
37
+ { permission: 'user:edit', name: '编辑用户', module: '用户管理' },
38
+ { permission: 'user:delete', name: '删除用户', module: '用户管理' },
39
+ { permission: 'role:view', name: '查看角色', module: '角色管理' },
40
+ { permission: 'role:create', name: '创建角色', module: '角色管理' },
41
+ { permission: 'role:edit', name: '编辑角色', module: '角色管理' },
42
+ { permission: 'role:delete', name: '删除角色', module: '角色管理' },
43
+ { permission: 'system:config', name: '系统配置', module: '系统管理' },
44
+ { permission: 'system:log', name: '查看日志', module: '系统管理' }
45
+ ])
46
+
47
+ // 所有角色配置
48
+ const allRoles = ref<RoleConfig[]>([
49
+ {
50
+ role: 'admin',
51
+ name: '超级管理员',
52
+ description: '拥有所有权限',
53
+ permissions: ['user:view', 'user:create', 'user:edit', 'user:delete', 'role:view', 'role:create', 'role:edit', 'role:delete', 'system:config', 'system:log']
54
+ },
55
+ {
56
+ role: 'editor',
57
+ name: '编辑',
58
+ description: '可以查看和编辑内容',
59
+ permissions: ['user:view', 'user:create', 'user:edit', 'role:view']
60
+ },
61
+ {
62
+ role: 'viewer',
63
+ name: '只读',
64
+ description: '只能查看内容',
65
+ permissions: ['user:view', 'role:view']
66
+ }
67
+ ])
68
+
69
+ // 当前用户拥有的权限(从 user store 获取)
70
+ const userPermissions = computed(() => {
71
+ const userStore = useUserStore()
72
+ if (!userStore.userInfo) return []
73
+
74
+ const userRoles = userStore.userInfo.roles || []
75
+ const permissions: Permission[] = []
76
+
77
+ // 根据用户角色获取所有权限
78
+ userRoles.forEach((role) => {
79
+ const roleConfig = allRoles.value.find((r) => r.role === role)
80
+ if (roleConfig) {
81
+ permissions.push(...roleConfig.permissions)
82
+ }
83
+ })
84
+
85
+ // 去重
86
+ return Array.from(new Set(permissions))
87
+ })
88
+
89
+ // 检查是否有指定权限
90
+ function hasPermission(permission: Permission): boolean {
91
+ return userPermissions.value.includes(permission)
92
+ }
93
+
94
+ // 检查是否有任意一个权限
95
+ function hasAnyPermission(permissions: Permission[]): boolean {
96
+ return permissions.some((p) => hasPermission(p))
97
+ }
98
+
99
+ // 检查是否有所有权限
100
+ function hasAllPermissions(permissions: Permission[]): boolean {
101
+ return permissions.every((p) => hasPermission(p))
102
+ }
103
+
104
+ // 检查是否有指定角色
105
+ function hasRole(role: Role): boolean {
106
+ const userStore = useUserStore()
107
+ if (!userStore.userInfo) return false
108
+ return userStore.userInfo.roles?.includes(role) || false
109
+ }
110
+
111
+ // 检查是否有任意一个角色
112
+ function hasAnyRole(roles: Role[]): boolean {
113
+ return roles.some((r) => hasRole(r))
114
+ }
115
+
116
+ // 获取角色配置
117
+ function getRoleConfig(role: Role): RoleConfig | undefined {
118
+ return allRoles.value.find((r) => r.role === role)
119
+ }
120
+
121
+ // 获取权限配置
122
+ function getPermissionConfig(permission: Permission): PermissionConfig | undefined {
123
+ return allPermissions.value.find((p) => p.permission === permission)
124
+ }
125
+
126
+ // 按模块分组权限
127
+ const permissionsByModule = computed(() => {
128
+ const grouped: Record<string, PermissionConfig[]> = {}
129
+ allPermissions.value.forEach((permission) => {
130
+ const module = permission.module || '其他'
131
+ if (!grouped[module]) {
132
+ grouped[module] = []
133
+ }
134
+ grouped[module].push(permission)
135
+ })
136
+ return grouped
137
+ })
138
+
139
+ // 添加权限配置
140
+ function addPermission(permission: PermissionConfig) {
141
+ if (!allPermissions.value.find((p) => p.permission === permission.permission)) {
142
+ allPermissions.value.push(permission)
143
+ }
144
+ }
145
+
146
+ // 添加角色配置
147
+ function addRole(role: RoleConfig) {
148
+ if (!allRoles.value.find((r) => r.role === role.role)) {
149
+ allRoles.value.push(role)
150
+ }
151
+ }
152
+
153
+ // 更新角色权限
154
+ function updateRolePermissions(role: Role, permissions: Permission[]) {
155
+ const roleConfig = allRoles.value.find((r) => r.role === role)
156
+ if (roleConfig) {
157
+ roleConfig.permissions = permissions
158
+ }
159
+ }
160
+
161
+ return {
162
+ allPermissions,
163
+ allRoles,
164
+ userPermissions,
165
+ permissionsByModule,
166
+ hasPermission,
167
+ hasAnyPermission,
168
+ hasAllPermissions,
169
+ hasRole,
170
+ hasAnyRole,
171
+ getRoleConfig,
172
+ getPermissionConfig,
173
+ addPermission,
174
+ addRole,
175
+ updateRolePermissions
176
+ }
177
+ },
178
+ {
179
+ persist: {
180
+ key: 'permission-store',
181
+ paths: ['allPermissions', 'allRoles']
182
+ }
183
+ }
184
+ )
@@ -1,5 +1,6 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { ref, computed } from 'vue'
3
+ import { usePermissionStore } from './permission'
3
4
 
4
5
  export interface UserInfo {
5
6
  id: number
@@ -7,6 +8,10 @@ export interface UserInfo {
7
8
  nickname: string
8
9
  avatar: string
9
10
  roles: string[]
11
+ email?: string
12
+ phone?: string
13
+ department?: string
14
+ lastLoginTime?: number
10
15
  }
11
16
 
12
17
  export const useUserStore = defineStore(
@@ -14,35 +19,137 @@ export const useUserStore = defineStore(
14
19
  () => {
15
20
  const token = ref<string>('')
16
21
  const userInfo = ref<UserInfo | null>(null)
22
+ const loginHistory = ref<Array<{ time: number; ip: string; location?: string }>>([])
17
23
 
18
24
  const isLoggedIn = computed(() => !!token.value)
19
25
 
26
+ // 计算属性:用户显示名称
27
+ const displayName = computed(() => {
28
+ return userInfo.value?.nickname || userInfo.value?.username || '未登录'
29
+ })
30
+
31
+ // 计算属性:用户头像
32
+ const avatar = computed(() => {
33
+ return userInfo.value?.avatar || '/default-avatar.png'
34
+ })
35
+
36
+ // 计算属性:是否是管理员
37
+ const isAdmin = computed(() => {
38
+ return userInfo.value?.roles?.includes('admin') || false
39
+ })
40
+
20
41
  function setToken(val: string) {
21
42
  token.value = val
22
43
  }
23
44
 
24
45
  function setUserInfo(info: UserInfo) {
25
46
  userInfo.value = info
47
+ // 更新最后登录时间
48
+ if (userInfo.value) {
49
+ userInfo.value.lastLoginTime = Date.now()
50
+ }
51
+ }
52
+
53
+ // 更新用户信息(部分更新)
54
+ function updateUserInfo(updates: Partial<UserInfo>) {
55
+ if (userInfo.value) {
56
+ userInfo.value = { ...userInfo.value, ...updates }
57
+ }
58
+ }
59
+
60
+ // 添加登录历史记录
61
+ function addLoginHistory(ip: string, location?: string) {
62
+ loginHistory.value.unshift({
63
+ time: Date.now(),
64
+ ip,
65
+ location
66
+ })
67
+ // 只保留最近 10 条记录
68
+ if (loginHistory.value.length > 10) {
69
+ loginHistory.value = loginHistory.value.slice(0, 10)
70
+ }
71
+ }
72
+
73
+ // 检查用户是否有指定权限(通过 permission store)
74
+ function hasPermission(permission: string): boolean {
75
+ const permissionStore = usePermissionStore()
76
+ return permissionStore.hasPermission(permission)
77
+ }
78
+
79
+ // 检查用户是否有指定角色
80
+ function hasRole(role: string): boolean {
81
+ return userInfo.value?.roles?.includes(role) || false
82
+ }
83
+
84
+ // 添加角色
85
+ function addRole(role: string) {
86
+ if (userInfo.value && !userInfo.value.roles.includes(role)) {
87
+ userInfo.value.roles.push(role)
88
+ }
89
+ }
90
+
91
+ // 移除角色
92
+ function removeRole(role: string) {
93
+ if (userInfo.value) {
94
+ userInfo.value.roles = userInfo.value.roles.filter((r) => r !== role)
95
+ }
26
96
  }
27
97
 
28
98
  function logout() {
29
99
  token.value = ''
30
100
  userInfo.value = null
101
+ // 清空登录历史(可选,根据需求决定)
102
+ // loginHistory.value = []
103
+ }
104
+
105
+ // 模拟登录
106
+ async function login(username: string, _password: string) {
107
+ // 这里应该是实际的 API 调用
108
+ // 模拟异步登录
109
+ await new Promise((resolve) => setTimeout(resolve, 1000))
110
+
111
+ // 模拟登录成功
112
+ const mockUserInfo: UserInfo = {
113
+ id: 1,
114
+ username,
115
+ nickname: username === 'admin' ? '管理员' : '普通用户',
116
+ avatar: '/avatar.png',
117
+ roles: username === 'admin' ? ['admin'] : ['viewer'],
118
+ email: `${username}@example.com`,
119
+ lastLoginTime: Date.now()
120
+ }
121
+
122
+ setToken('mock-token-' + Date.now())
123
+ setUserInfo(mockUserInfo)
124
+ addLoginHistory('192.168.1.100', '本地')
125
+
126
+ return mockUserInfo
31
127
  }
32
128
 
33
129
  return {
34
130
  token,
35
131
  userInfo,
132
+ loginHistory,
36
133
  isLoggedIn,
134
+ displayName,
135
+ avatar,
136
+ isAdmin,
37
137
  setToken,
38
138
  setUserInfo,
39
- logout
139
+ updateUserInfo,
140
+ addLoginHistory,
141
+ hasPermission,
142
+ hasRole,
143
+ addRole,
144
+ removeRole,
145
+ logout,
146
+ login
40
147
  }
41
148
  },
42
149
  {
43
150
  persist: {
44
151
  key: 'user-store',
45
- paths: ['token']
152
+ paths: ['token', 'userInfo', 'loginHistory']
46
153
  }
47
154
  }
48
155
  )