更新
This commit is contained in:
@@ -104,7 +104,11 @@ func (h *AdminHandler) Delete(c *gin.Context) {
|
||||
|
||||
// AuditLogs GET /api/admin/classes/:id/logs
|
||||
func (h *AdminHandler) AuditLogs(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "无效的班级ID")
|
||||
return
|
||||
}
|
||||
logs, err := h.svc.AuditLogs(uint(id))
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
@@ -154,6 +158,18 @@ func (h *AdminHandler) UnbanUser(c *gin.Context) {
|
||||
response.OK(c, gin.H{"message": "已解封"})
|
||||
}
|
||||
|
||||
// ListUsers GET /api/admin/users 用户列表(支持状态与搜索)
|
||||
func (h *AdminHandler) ListUsers(c *gin.Context) {
|
||||
status := c.Query("status")
|
||||
keyword := c.Query("keyword")
|
||||
list, err := h.svc.ListUsers(status, keyword)
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// BannedUsers GET /api/admin/users/banned 封禁用户列表
|
||||
func (h *AdminHandler) BannedUsers(c *gin.Context) {
|
||||
list, err := h.svc.ListBannedUsers()
|
||||
|
||||
@@ -35,7 +35,11 @@ func (h *ClassHandler) Create(c *gin.Context) {
|
||||
|
||||
// Detail GET /api/classes/:id
|
||||
func (h *ClassHandler) Detail(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "无效的班级ID")
|
||||
return
|
||||
}
|
||||
userID := middleware.CurrentUserID(c)
|
||||
isSuperAdmin := middleware.CurrentRole(c) == "admin"
|
||||
detail, err := h.svc.GetDetail(uint(id), userID, isSuperAdmin)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -29,14 +30,14 @@ func (h *MenuHandler) GetUserMenus(c *gin.Context) {
|
||||
response.OK(c, tree)
|
||||
}
|
||||
|
||||
// GetAll GET /api/admin/menus 获取所有菜单(管理用)
|
||||
// GetAll GET /api/admin/menus 获取所有菜单树结构(管理用)
|
||||
func (h *MenuHandler) GetAll(c *gin.Context) {
|
||||
list, err := h.menu.GetAll()
|
||||
tree, err := h.menu.GetAllTree()
|
||||
if err != nil {
|
||||
response.Internal(c, "获取菜单失败")
|
||||
response.Internal(c, "获取菜单失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
response.OK(c, tree)
|
||||
}
|
||||
|
||||
// Create POST /api/admin/menus 创建菜单
|
||||
@@ -46,34 +47,59 @@ func (h *MenuHandler) Create(c *gin.Context) {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(m.Name) == "" {
|
||||
response.BadRequest(c, "路由名称(name)不能为空")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(m.Title) == "" {
|
||||
response.BadRequest(c, "菜单标题(title)不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.menu.Create(&m); err != nil {
|
||||
response.Internal(c, "创建菜单失败")
|
||||
response.Internal(c, "创建菜单失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, m)
|
||||
}
|
||||
|
||||
// Update PUT /api/admin/menus/:id 更新菜单
|
||||
// Update PUT /api/admin/menus/:id 更新菜单(支持全量或部分字段更新)
|
||||
func (h *MenuHandler) Update(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var m model.Menu
|
||||
if err := c.ShouldBindJSON(&m); err != nil {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "无效的菜单ID")
|
||||
return
|
||||
}
|
||||
var body map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
m.ID = uint(id)
|
||||
if err := h.menu.Update(&m); err != nil {
|
||||
response.Internal(c, "更新菜单失败")
|
||||
// 兼容 sort / sort_order
|
||||
if v, ok := body["sort_order"]; ok {
|
||||
body["sort"] = v
|
||||
delete(body, "sort_order")
|
||||
}
|
||||
delete(body, "id")
|
||||
delete(body, "children") // 过滤可能携带的子项数组,避免 GORM 尝试保存未知列
|
||||
delete(body, "created_at")
|
||||
delete(body, "updated_at")
|
||||
|
||||
if err := h.menu.UpdateFields(uint(id), body); err != nil {
|
||||
response.Internal(c, "更新菜单失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, m)
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
}
|
||||
|
||||
// Delete DELETE /api/admin/menus/:id 删除菜单
|
||||
func (h *MenuHandler) Delete(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if id == 0 {
|
||||
response.BadRequest(c, "无效的菜单ID")
|
||||
return
|
||||
}
|
||||
if err := h.menu.Delete(uint(id)); err != nil {
|
||||
response.Internal(c, "删除菜单失败")
|
||||
response.Internal(c, "删除菜单失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "删除成功"})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/model"
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
"strconv"
|
||||
@@ -16,10 +17,11 @@ func NewRegionHandler(svc *service.RegionService) *RegionHandler {
|
||||
return &RegionHandler{svc: svc}
|
||||
}
|
||||
|
||||
// List GET /api/regions?parent_id=0
|
||||
// List GET /api/regions?parent_id=0&level=city
|
||||
func (h *RegionHandler) List(c *gin.Context) {
|
||||
parentID, _ := strconv.ParseUint(c.DefaultQuery("parent_id", "0"), 10, 64)
|
||||
list, err := h.svc.ListByParent(uint(parentID))
|
||||
level := c.Query("level")
|
||||
list, err := h.svc.ListByParent(uint(parentID), model.RegionLevel(level))
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
@@ -39,7 +41,11 @@ func (h *RegionHandler) Countries(c *gin.Context) {
|
||||
|
||||
// Path GET /api/regions/:id/path
|
||||
func (h *RegionHandler) Path(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "无效的地区ID")
|
||||
return
|
||||
}
|
||||
path, err := h.svc.GetPath(uint(id))
|
||||
if err != nil {
|
||||
response.NotFound(c, "地区不存在")
|
||||
@@ -55,10 +61,98 @@ func (h *RegionHandler) Search(c *gin.Context) {
|
||||
response.BadRequest(c, "请输入搜索关键词")
|
||||
return
|
||||
}
|
||||
list, err := h.svc.Search(keyword, 20)
|
||||
list, err := h.svc.Search(keyword, 50)
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// Tree GET /api/admin/regions/tree
|
||||
func (h *RegionHandler) Tree(c *gin.Context) {
|
||||
parentID, _ := strconv.ParseUint(c.DefaultQuery("parent_id", "0"), 10, 64)
|
||||
tree, err := h.svc.GetTree(uint(parentID))
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, tree)
|
||||
}
|
||||
|
||||
// Create POST /api/admin/regions
|
||||
func (h *RegionHandler) Create(c *gin.Context) {
|
||||
var r model.Region
|
||||
if err := c.ShouldBindJSON(&r); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.svc.Create(&r); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, r)
|
||||
}
|
||||
|
||||
// Update PUT /api/admin/regions/:id
|
||||
func (h *RegionHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "无效的地区ID")
|
||||
return
|
||||
}
|
||||
var body map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
updates := map[string]interface{}{}
|
||||
if v, ok := body["name"]; ok {
|
||||
updates["name"] = v
|
||||
}
|
||||
if v, ok := body["name_en"]; ok {
|
||||
updates["name_en"] = v
|
||||
}
|
||||
if v, ok := body["level"]; ok {
|
||||
updates["level"] = v
|
||||
}
|
||||
if v, ok := body["code"]; ok {
|
||||
updates["code"] = v
|
||||
}
|
||||
if v, ok := body["sort_order"]; ok {
|
||||
updates["sort_order"] = v
|
||||
}
|
||||
if err := h.svc.Update(uint(id), updates); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
}
|
||||
|
||||
// Delete DELETE /api/admin/regions/:id
|
||||
func (h *RegionHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "无效的地区ID")
|
||||
return
|
||||
}
|
||||
if err := h.svc.Delete(uint(id)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "删除成功"})
|
||||
}
|
||||
|
||||
// InitChina POST /api/admin/regions/init-china
|
||||
func (h *RegionHandler) InitChina(c *gin.Context) {
|
||||
created, total, err := h.svc.InitChinaRegions()
|
||||
if err != nil {
|
||||
response.Internal(c, "初始化失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{
|
||||
"message": "初始化中国行政区划成功",
|
||||
"created_count": created,
|
||||
"total_count": total,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/middleware"
|
||||
"photowall/internal/model"
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -17,7 +19,7 @@ func NewSchoolHandler(svc *service.SchoolService) *SchoolHandler {
|
||||
return &SchoolHandler{svc: svc}
|
||||
}
|
||||
|
||||
// List GET /api/schools
|
||||
// List GET /api/schools or /api/admin/schools
|
||||
func (h *SchoolHandler) List(c *gin.Context) {
|
||||
var q service.SchoolQuery
|
||||
if err := c.ShouldBindQuery(&q); err != nil {
|
||||
@@ -32,13 +34,51 @@ func (h *SchoolHandler) List(c *gin.Context) {
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
// Create POST /api/schools
|
||||
// Stats GET /api/admin/schools/stats
|
||||
func (h *SchoolHandler) Stats(c *gin.Context) {
|
||||
stats, err := h.svc.Stats()
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, stats)
|
||||
}
|
||||
|
||||
// Create POST /api/schools or /api/admin/schools
|
||||
func (h *SchoolHandler) Create(c *gin.Context) {
|
||||
var school model.School
|
||||
if err := c.ShouldBindJSON(&school); err != nil {
|
||||
var body struct {
|
||||
RegionID uint `json:"region_id"`
|
||||
Name string `json:"name"`
|
||||
NameEn string `json:"name_en"`
|
||||
SchoolType model.SchoolType `json:"school_type"` // 兼容旧的单选提交
|
||||
SchoolTypes []string `json:"school_types"` // 新的多选提交
|
||||
Address string `json:"address"`
|
||||
Status model.SchoolStatus `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
types := model.NormalizeSchoolTypes(body.SchoolTypes)
|
||||
if len(types) == 0 && body.SchoolType != "" {
|
||||
types = model.NormalizeSchoolTypes([]string{string(body.SchoolType)})
|
||||
}
|
||||
if len(types) == 0 {
|
||||
response.BadRequest(c, "请选择学校类型")
|
||||
return
|
||||
}
|
||||
school := model.School{
|
||||
RegionID: body.RegionID,
|
||||
Name: strings.TrimSpace(body.Name),
|
||||
NameEn: strings.TrimSpace(body.NameEn),
|
||||
SchoolType: types[0],
|
||||
SchoolTypes: model.JoinSchoolTypes(types),
|
||||
Address: strings.TrimSpace(body.Address),
|
||||
Status: body.Status,
|
||||
}
|
||||
if uid := middleware.CurrentUserID(c); uid > 0 {
|
||||
school.CreatedBy = uid
|
||||
}
|
||||
if err := h.svc.Create(&school); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
@@ -67,7 +107,6 @@ func (h *SchoolHandler) Update(c *gin.Context) {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
// 只允许更新部分字段
|
||||
updates := map[string]interface{}{}
|
||||
if v, ok := body["name"]; ok {
|
||||
updates["name"] = v
|
||||
@@ -75,12 +114,38 @@ func (h *SchoolHandler) Update(c *gin.Context) {
|
||||
if v, ok := body["name_en"]; ok {
|
||||
updates["name_en"] = v
|
||||
}
|
||||
if v, ok := body["school_type"]; ok {
|
||||
updates["school_type"] = v
|
||||
// 多学段:优先取数组形式的 school_types
|
||||
if v, ok := body["school_types"]; ok {
|
||||
switch raw := v.(type) {
|
||||
case []interface{}:
|
||||
items := make([]string, 0, len(raw))
|
||||
for _, item := range raw {
|
||||
if s, ok := item.(string); ok {
|
||||
items = append(items, s)
|
||||
}
|
||||
}
|
||||
updates["school_types"] = model.JoinSchoolTypes(model.NormalizeSchoolTypes(items))
|
||||
case string:
|
||||
updates["school_types"] = raw
|
||||
}
|
||||
} else if v, ok := body["school_type"]; ok {
|
||||
// 兼容旧的单选提交:同步写入两个字段
|
||||
if s, ok := v.(string); ok {
|
||||
updates["school_types"] = s
|
||||
}
|
||||
}
|
||||
if v, ok := body["address"]; ok {
|
||||
updates["address"] = v
|
||||
}
|
||||
if v, ok := body["region_id"]; ok {
|
||||
updates["region_id"] = v
|
||||
}
|
||||
if v, ok := body["status"]; ok {
|
||||
updates["status"] = v
|
||||
}
|
||||
if v, ok := body["reject_reason"]; ok {
|
||||
updates["reject_reason"] = v
|
||||
}
|
||||
if err := h.svc.Update(uint(id), updates); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
@@ -88,6 +153,41 @@ func (h *SchoolHandler) Update(c *gin.Context) {
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
}
|
||||
|
||||
// Approve POST /api/admin/schools/:id/approve
|
||||
func (h *SchoolHandler) Approve(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "无效的学校ID")
|
||||
return
|
||||
}
|
||||
if err := h.svc.Approve(uint(id)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "审核通过"})
|
||||
}
|
||||
|
||||
// Reject POST /api/admin/schools/:id/reject
|
||||
func (h *SchoolHandler) Reject(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "无效的学校ID")
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil || strings.TrimSpace(body.Reason) == "" {
|
||||
response.BadRequest(c, "请输入打回原因")
|
||||
return
|
||||
}
|
||||
if err := h.svc.Reject(uint(id), strings.TrimSpace(body.Reason)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "打回成功"})
|
||||
}
|
||||
|
||||
// Delete DELETE /api/schools/:id
|
||||
func (h *SchoolHandler) Delete(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
|
||||
@@ -38,10 +38,15 @@ func (h *UserHandler) UpdateProfile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.svc.UpdateProfile(userID, &req); err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
u, err := h.svc.GetUserByID(userID)
|
||||
if err != nil {
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
return
|
||||
}
|
||||
response.OK(c, u)
|
||||
}
|
||||
|
||||
// AddContact POST /api/user/contacts
|
||||
@@ -149,11 +154,21 @@ func (h *UserHandler) HasEducation(c *gin.Context) {
|
||||
// PublicProfile GET /api/user/:id 公开信息(班级成员查看)
|
||||
func (h *UserHandler) PublicProfile(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
currentUserID := middleware.CurrentUserID(c)
|
||||
user, err := h.svc.GetUserByID(uint(id))
|
||||
if err != nil {
|
||||
response.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
// 隐私脱敏保护:非本人且非管理员,对身份证号码脱敏
|
||||
isAdmin := middleware.CurrentRole(c) == "admin"
|
||||
if currentUserID != user.ID && !isAdmin {
|
||||
if len(user.IdCard) == 18 {
|
||||
user.IdCard = user.IdCard[:6] + "********" + user.IdCard[14:]
|
||||
} else if user.IdCard != "" {
|
||||
user.IdCard = "****************"
|
||||
}
|
||||
}
|
||||
contacts := h.svc.GetUserContacts(uint(id))
|
||||
response.OK(c, gin.H{"user": user, "contacts": contacts})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user