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 && (
+
+ )}
+
+
+
+
+ )
+}
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() {
用户管理