diff --git a/README.md b/README.md index 5a6782a..1727020 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ open http://localhost:3018 | 方法 | 路径 | 说明 | 权限 | |---|---|---|---| -| POST | /api/auth/register | 注册(默认 user) | 公开 | | POST | /api/auth/login | 登录 | 公开 | | GET | /api/auth/me | 当前用户 | 需登录 | | POST | /api/auth/logout | 登出 | 需登录 | diff --git a/backend/internal/handlers/auth.go b/backend/internal/handlers/auth.go index 9fdd4ae..c3dea86 100644 --- a/backend/internal/handlers/auth.go +++ b/backend/internal/handlers/auth.go @@ -19,80 +19,6 @@ type AuthHandler struct { CFG *config.Config } -func (h *AuthHandler) Register(c *gin.Context) { - var req struct { - Username string `json:"username" binding:"required"` - Email *string `json:"email"` - Password string `json:"password" binding:"required"` - } - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "请求参数错误"}) - return - } - - if err := validateUsername(req.Username); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": err.Error()}) - return - } - if len(req.Password) < 6 { - c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "密码长度至少 6 位"}) - return - } - - username := strings.ToLower(strings.TrimSpace(req.Username)) - - var role models.Role - if err := h.DB.Where("name = ?", models.RoleUser).First(&role).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "internal server error"}) - return - } - - hash, err := hashPassword(req.Password) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "internal server error"}) - return - } - - var email *string - if req.Email != nil && *req.Email != "" { - e := strings.ToLower(strings.TrimSpace(*req.Email)) - email = &e - } - - user := models.User{ - Username: username, - Email: email, - PasswordHash: hash, - RoleID: role.ID, - Status: "active", - } - - if err := h.DB.Create(&user).Error; err != nil { - if strings.Contains(err.Error(), "duplicate key") { - c.JSON(http.StatusConflict, gin.H{"success": false, "error": "用户名或邮箱已存在"}) - return - } - c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "internal server error"}) - return - } - - if err := h.DB.Preload("Role").First(&user, "id = ?", user.ID).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "internal server error"}) - return - } - - token, err := middleware.GenerateToken(user.ID, models.RoleUser, h.CFG) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "internal server error"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "token": token, - "user": user.ToPublicInfo(), - }) -} - func (h *AuthHandler) Login(c *gin.Context) { var req struct { Username string `json:"username" binding:"required"` diff --git a/backend/internal/middleware/auth.go b/backend/internal/middleware/auth.go index c728854..3b3585e 100644 --- a/backend/internal/middleware/auth.go +++ b/backend/internal/middleware/auth.go @@ -20,8 +20,7 @@ type CurrentUser struct { } var publicPaths = map[string]struct{}{ - "/api/auth/register": {}, - "/api/auth/login": {}, + "/api/auth/login": {}, } func AuthMiddleware(cfg *config.Config, db *gorm.DB) gin.HandlerFunc { diff --git a/backend/internal/routes/routes.go b/backend/internal/routes/routes.go index e12cfe9..ad2358a 100644 --- a/backend/internal/routes/routes.go +++ b/backend/internal/routes/routes.go @@ -30,8 +30,7 @@ func Setup(cfg *config.Config, db *gorm.DB) *gin.Engine { r.Use(middleware.AuthMiddleware(cfg, db)) - // 公开接口(仅登录/注册) - r.POST("/api/auth/register", authHandler.Register) + // 公开接口(仅登录) r.POST("/api/auth/login", authHandler.Login) // 受保护接口 diff --git a/frontend/src/components/CreateUserDialog.tsx b/frontend/src/components/CreateUserDialog.tsx new file mode 100644 index 0000000..a99389f --- /dev/null +++ b/frontend/src/components/CreateUserDialog.tsx @@ -0,0 +1,178 @@ +import { useState } from 'react' +import { X, UserPlus, AlertCircle, Loader2 } from 'lucide-react' +import { api } from '@/lib/api' +import { RoleName, roleLabel } from '@/lib/auth' +import { cn } from '@/lib/cn' + +interface CreateUserDialogProps { + open: boolean + onClose: () => void + onSuccess: () => void + allowedRoles: RoleName[] +} + +export function CreateUserDialog({ open, onClose, onSuccess, allowedRoles }: CreateUserDialogProps) { + const [form, setForm] = useState({ + username: '', + email: '', + password: '', + confirmPassword: '', + role: (allowedRoles[0] || 'user') as RoleName, + }) + const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + + if (!open) return null + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setError('') + + if (!form.username.trim()) { + setError('请输入用户名') + return + } + if (form.password.length < 6) { + setError('密码长度至少 6 位') + return + } + if (form.password !== form.confirmPassword) { + setError('两次输入的密码不一致') + return + } + if (!allowedRoles.includes(form.role as RoleName)) { + setError('没有权限分配该角色') + return + } + + setLoading(true) + try { + await api.createUser({ + username: form.username.trim(), + email: form.email.trim() || undefined, + password: form.password, + role: form.role, + }) + setForm({ username: '', email: '', password: '', confirmPassword: '', role: (allowedRoles[0] || 'user') as RoleName }) + onSuccess() + } catch (err: any) { + setError(err?.message || '创建失败') + } finally { + setLoading(false) + } + } + + return ( +
+
+
+
+
+
+ +
+

创建用户

+
+ +
+ + {error && ( +
+ + {error} +
+ )} + +
+
+ + setForm({ ...form, username: e.target.value })} + className="w-full rounded-input bg-base border border-border px-3 py-2 text-sm focus:outline-none focus:border-accent" + /> +
+ +
+ + setForm({ ...form, email: e.target.value })} + className="w-full rounded-input bg-base border border-border px-3 py-2 text-sm focus:outline-none focus:border-accent" + /> +
+ +
+
+ + setForm({ ...form, password: e.target.value })} + className="w-full rounded-input bg-base border border-border px-3 py-2 text-sm focus:outline-none focus:border-accent" + /> +
+
+ + setForm({ ...form, confirmPassword: e.target.value })} + className="w-full rounded-input bg-base border border-border px-3 py-2 text-sm focus:outline-none focus:border-accent" + /> +
+
+ +
+ + +
+ +
+ + +
+
+
+
+ ) +} diff --git a/frontend/src/components/LoginForm.tsx b/frontend/src/components/LoginForm.tsx index 0259ff4..45a9043 100644 --- a/frontend/src/components/LoginForm.tsx +++ b/frontend/src/components/LoginForm.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { Shield, Lock, AlertCircle, Loader2, UserPlus, LogIn } from 'lucide-react' +import { Shield, Lock, AlertCircle, Loader2, LogIn } from 'lucide-react' import { api, setToken } from '@/lib/api' import { setStoredUser } from '@/lib/auth' @@ -8,9 +8,7 @@ interface LoginFormProps { } export function LoginForm({ onSuccess }: LoginFormProps) { - const [mode, setMode] = useState<'login' | 'register'>('login') const [username, setUsername] = useState('') - const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState('') const [loading, setLoading] = useState(false) @@ -22,25 +20,14 @@ export function LoginForm({ onSuccess }: LoginFormProps) { setError('请输入用户名和密码') return } - if (mode === 'register' && password.length < 6) { - setError('密码长度至少 6 位') - return - } setLoading(true) try { - const res = - mode === 'login' - ? await api.login({ username: username.trim(), password }) - : await api.register({ - username: username.trim(), - email: email.trim() || undefined, - password, - }) + const res = await api.login({ username: username.trim(), password }) setToken(res.token) setStoredUser(res.user) onSuccess() } catch (err: any) { - setError(err?.message || '操作失败,请稍后重试') + setError(err?.message || '登录失败,请稍后重试') } finally { setLoading(false) } @@ -61,13 +48,9 @@ export function LoginForm({ onSuccess }: LoginFormProps) { >
-

- {mode === 'login' ? '登录账号' : '注册账号'} -

+

登录账号

- {mode === 'login' - ? '请输入用户名和密码进入系统。' - : '注册后默认获得用户权限。'} + 请输入用户名和密码进入系统。

@@ -86,29 +69,13 @@ export function LoginForm({ onSuccess }: LoginFormProps) { /> - {mode === 'register' && ( -
-
- -
- setEmail(e.target.value)} - className="w-full pl-9 pr-3 py-2.5 rounded-input bg-base border border-border text-sm focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/30 transition-all" - /> -
- )} -
setPassword(e.target.value)} @@ -130,26 +97,15 @@ export function LoginForm({ onSuccess }: LoginFormProps) { > {loading ? ( - ) : mode === 'login' ? ( - ) : ( - + )} - {loading ? '处理中…' : mode === 'login' ? '登录' : '注册'} + {loading ? '处理中…' : '登录'}
- + 没有账号?请联系管理员创建
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 0b6f696..cf026da 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -79,12 +79,6 @@ async function request( } export const api = { - register: (body: { username: string; email?: string; password: string }) => - request('/api/auth/register', { - method: 'POST', - body: JSON.stringify(body), - }), - login: (body: { username: string; password: string }) => request('/api/auth/login', { method: 'POST', diff --git a/frontend/src/pages/Users.tsx b/frontend/src/pages/Users.tsx index b638571..b82b6ce 100644 --- a/frontend/src/pages/Users.tsx +++ b/frontend/src/pages/Users.tsx @@ -1,23 +1,30 @@ import { useEffect, useState } from 'react' -import { Loader2, Plus, Trash2, Users as UsersIcon, AlertCircle } from 'lucide-react' -import { api, Role, UserInfo } from '@/lib/api' -import { canDeleteUsers, roleLabel } from '@/lib/auth' +import { Plus, Trash2, Users as UsersIcon, AlertCircle, Loader2 } from 'lucide-react' +import { api, UserInfo } from '@/lib/api' +import { canDeleteUsers, getStoredUser, roleLabel, RoleName } from '@/lib/auth' +import { CreateUserDialog } from '@/components/CreateUserDialog' import { cn } from '@/lib/cn' +const creatableRoles: Record = { + system_admin: ['admin', 'user'], + admin: ['user'], + user: [], +} + export function Users() { const [users, setUsers] = useState([]) - const [roles, setRoles] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') - const [formOpen, setFormOpen] = useState(false) - const [form, setForm] = useState({ username: '', email: '', password: '', role: 'user' }) + const [dialogOpen, setDialogOpen] = useState(false) + + const currentUser = getStoredUser() + const allowedRoles = currentUser ? creatableRoles[currentUser.role] : [] const fetchData = async () => { setLoading(true) try { - const [u, r] = await Promise.all([api.listUsers(), api.listRoles()]) + const u = await api.listUsers() setUsers(u) - setRoles(r) setError('') } catch (err: any) { setError(err?.message || '加载失败') @@ -30,23 +37,6 @@ export function Users() { fetchData() }, []) - const handleCreate = async (e: React.FormEvent) => { - e.preventDefault() - try { - await api.createUser({ - username: form.username, - email: form.email || undefined, - password: form.password, - role: form.role, - }) - setForm({ username: '', email: '', password: '', role: 'user' }) - setFormOpen(false) - fetchData() - } catch (err: any) { - setError(err?.message || '创建失败') - } - } - const handleDelete = async (id: string) => { if (!confirm('确定删除该用户?')) return try { @@ -66,7 +56,7 @@ export function Users() { } } - const currentUser = users.find((u) => u.username === JSON.parse(localStorage.getItem('user') || '{}')?.username) + const current = users.find((u) => u.username === currentUser?.username) return (
@@ -78,7 +68,7 @@ export function Users() {

用户管理

- - )} -
@@ -173,7 +118,7 @@ export function Users() {
- {canDeleteUsers(currentUser?.role || 'user') && u.id !== currentUser?.id && ( + {canDeleteUsers(current?.role || 'user') && u.id !== current?.id && (
+ + setDialogOpen(false)} + onSuccess={() => { + setDialogOpen(false) + fetchData() + }} + allowedRoles={allowedRoles} + /> ) }