@lvetechs/create-app 1.0.3 → 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.
- package/package.json +1 -1
- package/src/index.js +18 -4
- package/templates/react/.env +3 -0
- package/templates/react/.env.development +3 -0
- package/templates/react/.env.production +3 -0
- package/templates/react/index.html +1 -1
- package/templates/react/package.json +45 -45
- package/templates/react/pnpm-lock.yaml +166 -5
- package/templates/react/src/hooks/useForm.ts +77 -0
- package/templates/react/src/layouts/DefaultLayout.tsx +6 -1
- package/templates/react/src/layouts/menuConfig.ts +3 -33
- package/templates/react/src/main.tsx +3 -0
- package/templates/react/src/router/index.tsx +0 -39
- package/templates/react/src/stores/app.ts +141 -3
- package/templates/react/src/stores/notification.ts +128 -0
- package/templates/react/src/stores/permission.ts +183 -0
- package/templates/react/src/stores/user.ts +151 -4
- package/templates/react/src/views/home/index.tsx +205 -43
- package/templates/react/src/views/system/user/index.tsx +171 -5
- package/templates/react/tsconfig.json +1 -1
- package/templates/react/vite.config.ts +7 -3
- package/templates/vue/.env +2 -2
- package/templates/vue/.env.development +2 -2
- package/templates/vue/.env.production +2 -2
- package/templates/vue/index.html +1 -1
- package/templates/vue/package.json +0 -1
- package/templates/vue/pnpm-lock.yaml +3307 -3307
- package/templates/vue/src/auto-imports.d.ts +5 -0
- package/templates/vue/src/layouts/DefaultLayout.vue +2 -3
- package/templates/vue/src/layouts/menuConfig.ts +3 -33
- package/templates/vue/src/router/index.ts +4 -89
- package/templates/vue/src/stores/app.ts +133 -2
- package/templates/vue/src/stores/notification.ts +172 -0
- package/templates/vue/src/stores/permission.ts +184 -0
- package/templates/vue/src/stores/user.ts +109 -2
- package/templates/vue/src/views/home/index.vue +8 -8
- package/templates/react/_gitignore +0 -25
- package/templates/react/src/views/about/index.tsx +0 -40
- package/templates/react/src/views/login/index.tsx +0 -138
- package/templates/react/src/views/register/index.tsx +0 -143
- package/templates/react/src/views/result/fail.tsx +0 -39
- package/templates/react/src/views/result/success.tsx +0 -35
- package/templates/react/src/views/screen/index.tsx +0 -120
- package/templates/react/src/views/system/log/login.tsx +0 -51
- package/templates/react/src/views/system/log/operation.tsx +0 -47
- package/templates/react/src/views/system/menu/index.tsx +0 -62
- package/templates/react/src/views/system/role/index.tsx +0 -63
- package/templates/react/tsconfig.node.json +0 -11
- package/templates/vue/_gitignore +0 -25
- package/templates/vue/src/views/about/index.vue +0 -67
- package/templates/vue/src/views/login/index.vue +0 -153
- package/templates/vue/src/views/register/index.vue +0 -169
- package/templates/vue/src/views/result/fail.vue +0 -92
- package/templates/vue/src/views/result/success.vue +0 -92
- package/templates/vue/src/views/screen/index.vue +0 -150
- package/templates/vue/src/views/system/log/login.vue +0 -51
- package/templates/vue/src/views/system/log/operation.vue +0 -47
- package/templates/vue/src/views/system/menu/index.vue +0 -58
- package/templates/vue/src/views/system/role/index.vue +0 -59
|
@@ -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
|
-
|
|
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
|
)
|
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref } from 'vue'
|
|
3
|
-
|
|
3
|
+
const title = computed(() => import.meta.env.VITE_APP_TITLE)
|
|
4
4
|
const count = ref(0)
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<template>
|
|
8
8
|
<div class="home-page">
|
|
9
9
|
<div class="welcome-card">
|
|
10
|
-
<h1
|
|
10
|
+
<h1>欢迎使用 {{title}}</h1>
|
|
11
11
|
<p>
|
|
12
12
|
该模板集成了 Vue 3 + Vite + TypeScript + Vue Router + Pinia + Axios + TailwindCSS
|
|
13
13
|
等常用工具和最佳实践。
|
|
14
14
|
</p>
|
|
15
15
|
<div class="features">
|
|
16
16
|
<div class="feature-item">
|
|
17
|
-
<h3
|
|
17
|
+
<h3>Vite</h3>
|
|
18
18
|
<p>下一代前端构建工具,极速开发体验</p>
|
|
19
19
|
</div>
|
|
20
20
|
<div class="feature-item">
|
|
21
|
-
<h3
|
|
21
|
+
<h3>TypeScript</h3>
|
|
22
22
|
<p>强类型语言支持,提高代码质量</p>
|
|
23
23
|
</div>
|
|
24
24
|
<div class="feature-item">
|
|
25
|
-
<h3
|
|
25
|
+
<h3>Pinia</h3>
|
|
26
26
|
<p>轻量灵活的状态管理方案</p>
|
|
27
27
|
</div>
|
|
28
28
|
<div class="feature-item">
|
|
29
|
-
<h3
|
|
29
|
+
<h3>Vue Router</h3>
|
|
30
30
|
<p>官方路由管理,支持懒加载</p>
|
|
31
31
|
</div>
|
|
32
32
|
<div class="feature-item">
|
|
33
|
-
<h3
|
|
33
|
+
<h3>Auto Import</h3>
|
|
34
34
|
<p>API 和组件自动按需导入</p>
|
|
35
35
|
</div>
|
|
36
36
|
<div class="feature-item">
|
|
37
|
-
<h3
|
|
37
|
+
<h3>TailwindCSS</h3>
|
|
38
38
|
<p>即时按需原子化 CSS 引擎</p>
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Logs
|
|
2
|
-
logs
|
|
3
|
-
*.log
|
|
4
|
-
npm-debug.log*
|
|
5
|
-
yarn-debug.log*
|
|
6
|
-
yarn-error.log*
|
|
7
|
-
pnpm-debug.log*
|
|
8
|
-
lerna-debug.log*
|
|
9
|
-
|
|
10
|
-
node_modules
|
|
11
|
-
dist
|
|
12
|
-
dist-ssr
|
|
13
|
-
*.local
|
|
14
|
-
|
|
15
|
-
# Editor directories and files
|
|
16
|
-
.vscode/*
|
|
17
|
-
!.vscode/extensions.json
|
|
18
|
-
.idea
|
|
19
|
-
.DS_Store
|
|
20
|
-
*.suo
|
|
21
|
-
*.ntvs*
|
|
22
|
-
*.njsproj
|
|
23
|
-
*.sln
|
|
24
|
-
*.sw?
|
|
25
|
-
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import '@/styles/page-common.scss'
|
|
2
|
-
|
|
3
|
-
export default function About() {
|
|
4
|
-
const features = [
|
|
5
|
-
'React 18 + Hooks',
|
|
6
|
-
'Vite 5 构建工具',
|
|
7
|
-
'TypeScript 5 类型支持',
|
|
8
|
-
'React Router 6 路由管理',
|
|
9
|
-
'Zustand 状态管理 (支持持久化)',
|
|
10
|
-
'Axios 请求封装 (拦截器 + 类型)',
|
|
11
|
-
'TailwindCSS 实用优先 CSS',
|
|
12
|
-
'ESLint + Prettier 代码规范'
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<div style={{ maxWidth: 700, margin: '0 auto' }}>
|
|
17
|
-
<div className="page-card">
|
|
18
|
-
<h1 style={{ fontSize: 24, marginBottom: 12 }}>关于</h1>
|
|
19
|
-
<p style={{ color: 'var(--text-color-secondary)', marginBottom: 16 }}>
|
|
20
|
-
这是一个基于 React 18 的现代化前端模板项目。
|
|
21
|
-
</p>
|
|
22
|
-
<ul style={{ listStyle: 'none', padding: 0 }}>
|
|
23
|
-
{features.map((item) => (
|
|
24
|
-
<li
|
|
25
|
-
key={item}
|
|
26
|
-
style={{
|
|
27
|
-
padding: '8px 0',
|
|
28
|
-
borderBottom: '1px solid var(--border-color-light)',
|
|
29
|
-
color: 'var(--text-color-regular)'
|
|
30
|
-
}}
|
|
31
|
-
>
|
|
32
|
-
✅ {item}
|
|
33
|
-
</li>
|
|
34
|
-
))}
|
|
35
|
-
</ul>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { useNavigate } from 'react-router-dom'
|
|
3
|
-
import { useUserStore } from '@/stores/user'
|
|
4
|
-
|
|
5
|
-
export default function Login() {
|
|
6
|
-
const navigate = useNavigate()
|
|
7
|
-
const { setToken, setUserInfo } = useUserStore()
|
|
8
|
-
|
|
9
|
-
const [form, setForm] = useState({ username: '', password: '' })
|
|
10
|
-
const [loading, setLoading] = useState(false)
|
|
11
|
-
|
|
12
|
-
async function handleLogin(e: React.FormEvent) {
|
|
13
|
-
e.preventDefault()
|
|
14
|
-
if (!form.username || !form.password) {
|
|
15
|
-
alert('请输入用户名和密码')
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
setLoading(true)
|
|
20
|
-
try {
|
|
21
|
-
// TODO: 调用登录 API
|
|
22
|
-
// const res = await loginApi(form)
|
|
23
|
-
// setToken(res.data.token)
|
|
24
|
-
|
|
25
|
-
// 模拟登录
|
|
26
|
-
setToken('mock-token')
|
|
27
|
-
setUserInfo({
|
|
28
|
-
id: 1,
|
|
29
|
-
username: form.username,
|
|
30
|
-
nickname: '管理员',
|
|
31
|
-
avatar: '',
|
|
32
|
-
roles: ['admin']
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
navigate('/')
|
|
36
|
-
} catch (error) {
|
|
37
|
-
console.error('登录失败:', error)
|
|
38
|
-
} finally {
|
|
39
|
-
setLoading(false)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<div className="login-page">
|
|
45
|
-
<div className="login-card">
|
|
46
|
-
<h2>用户登录</h2>
|
|
47
|
-
<form onSubmit={handleLogin}>
|
|
48
|
-
<div className="form-item">
|
|
49
|
-
<label>用户名</label>
|
|
50
|
-
<input
|
|
51
|
-
type="text"
|
|
52
|
-
placeholder="请输入用户名"
|
|
53
|
-
autoComplete="username"
|
|
54
|
-
value={form.username}
|
|
55
|
-
onChange={(e) => setForm({ ...form, username: e.target.value })}
|
|
56
|
-
/>
|
|
57
|
-
</div>
|
|
58
|
-
<div className="form-item">
|
|
59
|
-
<label>密码</label>
|
|
60
|
-
<input
|
|
61
|
-
type="password"
|
|
62
|
-
placeholder="请输入密码"
|
|
63
|
-
autoComplete="current-password"
|
|
64
|
-
value={form.password}
|
|
65
|
-
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
|
66
|
-
/>
|
|
67
|
-
</div>
|
|
68
|
-
<button type="submit" className="login-btn" disabled={loading}>
|
|
69
|
-
{loading ? '登录中...' : '登 录'}
|
|
70
|
-
</button>
|
|
71
|
-
</form>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<style>{`
|
|
75
|
-
.login-page {
|
|
76
|
-
height: 100vh;
|
|
77
|
-
display: flex;
|
|
78
|
-
align-items: center;
|
|
79
|
-
justify-content: center;
|
|
80
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
81
|
-
}
|
|
82
|
-
.login-card {
|
|
83
|
-
width: 380px;
|
|
84
|
-
padding: 40px;
|
|
85
|
-
background: #fff;
|
|
86
|
-
border-radius: 12px;
|
|
87
|
-
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
|
|
88
|
-
}
|
|
89
|
-
.login-card h2 {
|
|
90
|
-
text-align: center;
|
|
91
|
-
margin-bottom: 32px;
|
|
92
|
-
color: #333;
|
|
93
|
-
font-size: 24px;
|
|
94
|
-
}
|
|
95
|
-
.form-item {
|
|
96
|
-
margin-bottom: 20px;
|
|
97
|
-
}
|
|
98
|
-
.form-item label {
|
|
99
|
-
display: block;
|
|
100
|
-
margin-bottom: 6px;
|
|
101
|
-
font-size: 14px;
|
|
102
|
-
color: #606266;
|
|
103
|
-
}
|
|
104
|
-
.form-item input {
|
|
105
|
-
width: 100%;
|
|
106
|
-
padding: 10px 12px;
|
|
107
|
-
border: 1px solid #dcdfe6;
|
|
108
|
-
border-radius: 6px;
|
|
109
|
-
font-size: 14px;
|
|
110
|
-
outline: none;
|
|
111
|
-
transition: border-color 0.2s;
|
|
112
|
-
box-sizing: border-box;
|
|
113
|
-
}
|
|
114
|
-
.form-item input:focus {
|
|
115
|
-
border-color: #409eff;
|
|
116
|
-
}
|
|
117
|
-
.form-item input::placeholder {
|
|
118
|
-
color: #c0c4cc;
|
|
119
|
-
}
|
|
120
|
-
.login-btn {
|
|
121
|
-
width: 100%;
|
|
122
|
-
padding: 12px;
|
|
123
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
124
|
-
color: #fff;
|
|
125
|
-
border: none;
|
|
126
|
-
border-radius: 6px;
|
|
127
|
-
font-size: 16px;
|
|
128
|
-
cursor: pointer;
|
|
129
|
-
transition: opacity 0.2s;
|
|
130
|
-
margin-top: 8px;
|
|
131
|
-
}
|
|
132
|
-
.login-btn:hover { opacity: 0.9; }
|
|
133
|
-
.login-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
134
|
-
`}</style>
|
|
135
|
-
</div>
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
|