diff --git a/backend/internal/api/handlers/approval.go b/backend/internal/api/handlers/approval.go
index f020d01..147bd11 100644
--- a/backend/internal/api/handlers/approval.go
+++ b/backend/internal/api/handlers/approval.go
@@ -206,230 +206,4 @@ func (h *ApprovalHandler) DeleteTemplate(c *gin.Context) {
})
}
-// ListProcesses 获取审批流程列表
-// @Summary 获取审批流程列表
-// @Description 获取所有审批流程列表
-// @Tags 审批流程
-// @Produce json
-// @Security Bearer
-// @Param page query int false "页码"
-// @Param page_size query int false "每页数量"
-// @Param status query string false "状态过滤"
-// @Success 200 {object} models.Response{data=models.ListResponse}
-// @Router /approvals/processes [get]
-func (h *ApprovalHandler) ListProcesses(c *gin.Context) {
- var query models.PageQuery
- c.ShouldBindQuery(&query)
-
- db := h.db.Model(&models.ApprovalProcess{}).Preload("Template").Preload("ApplicantUser")
-
- // 状态过滤
- if status := c.Query("status"); status != "" {
- db = db.Where("status = ?", status)
- }
-
- var total int64
- db.Count(&total)
-
- var processes []models.ApprovalProcess
- db.Order("created_at DESC").
- Offset(query.GetOffset()).
- Limit(query.GetLimit()).
- Find(&processes)
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "success",
- Data: models.ListResponse{
- List: processes,
- Total: total,
- },
- })
-}
-
-// GetProcess 获取审批流程详情
-// @Summary 获取审批流程详情
-// @Description 获取指定审批流程的详细信息
-// @Tags 审批流程
-// @Produce json
-// @Security Bearer
-// @Param id path int true "流程ID"
-// @Success 200 {object} models.Response{data=models.ApprovalProcess}
-// @Router /approvals/processes/{id} [get]
-func (h *ApprovalHandler) GetProcess(c *gin.Context) {
- id, _ := strconv.Atoi(c.Param("id"))
-
- var process models.ApprovalProcess
- if err := h.db.Preload("Template").Preload("ApplicantUser").Preload("Steps.ApproverUser").First(&process, id).Error; err != nil {
- c.JSON(http.StatusNotFound, models.Response{
- Code: 404,
- Message: "流程不存在",
- })
- return
- }
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "success",
- Data: process,
- })
-}
-
-// CreateProcess 创建审批流程
-// @Summary 创建审批流程
-// @Description 创建新审批流程
-// @Tags 审批流程
-// @Accept json
-// @Produce json
-// @Security Bearer
-// @Param request body models.CreateApprovalProcessRequest true "创建请求"
-// @Success 200 {object} models.Response{data=models.ApprovalProcess}
-// @Router /approvals/processes [post]
-func (h *ApprovalHandler) CreateProcess(c *gin.Context) {
- var req models.CreateApprovalProcessRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, models.Response{
- Code: 400,
- Message: "请求参数错误: " + err.Error(),
- })
- return
- }
-
- // 获取模板
- var template models.ApprovalTemplate
- if err := h.db.First(&template, req.TemplateID).Error; err != nil {
- c.JSON(http.StatusNotFound, models.Response{
- Code: 404,
- Message: "模板不存在",
- })
- return
- }
-
- process := models.ApprovalProcess{
- TemplateID: req.TemplateID,
- Title: req.Title,
- FormData: req.FormData,
- Status: "pending",
- CurrentStep: 0,
- TotalSteps: 1, // 简化处理
- }
-
- if err := h.db.Create(&process).Error; err != nil {
- c.JSON(http.StatusInternalServerError, models.Response{
- Code: 500,
- Message: "创建失败",
- })
- return
- }
-
- h.db.Preload("Template").First(&process, process.ID)
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "创建成功",
- Data: process,
- })
-}
-
-// ApproveProcess 审批通过
-// @Summary 审批通过
-// @Description 审批通过指定流程
-// @Tags 审批流程
-// @Accept json
-// @Produce json
-// @Security Bearer
-// @Param id path int true "流程ID"
-// @Param request body models.ApproveRequest true "审批请求"
-// @Success 200 {object} models.Response{data=models.ApprovalProcess}
-// @Router /approvals/processes/{id}/approve [post]
-func (h *ApprovalHandler) ApproveProcess(c *gin.Context) {
- id, _ := strconv.Atoi(c.Param("id"))
-
- var req models.ApproveRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, models.Response{
- Code: 400,
- Message: "请求参数错误",
- })
- return
- }
-
- var process models.ApprovalProcess
- if err := h.db.First(&process, id).Error; err != nil {
- c.JSON(http.StatusNotFound, models.Response{
- Code: 404,
- Message: "流程不存在",
- })
- return
- }
-
- process.Status = "approved"
- // now := time.Now()
- // process.ApprovedAt = &now
-
- if err := h.db.Save(&process).Error; err != nil {
- c.JSON(http.StatusInternalServerError, models.Response{
- Code: 500,
- Message: "审批失败",
- })
- return
- }
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "审批通过",
- Data: process,
- })
-}
-
-// RejectProcess 审批拒绝
-// @Summary 审批拒绝
-// @Description 审批拒绝指定流程
-// @Tags 审批流程
-// @Accept json
-// @Produce json
-// @Security Bearer
-// @Param id path int true "流程ID"
-// @Param request body models.ApproveRequest true "审批请求"
-// @Success 200 {object} models.Response{data=models.ApprovalProcess}
-// @Router /approvals/processes/{id}/reject [post]
-func (h *ApprovalHandler) RejectProcess(c *gin.Context) {
- id, _ := strconv.Atoi(c.Param("id"))
-
- var req models.ApproveRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, models.Response{
- Code: 400,
- Message: "请求参数错误",
- })
- return
- }
-
- var process models.ApprovalProcess
- if err := h.db.First(&process, id).Error; err != nil {
- c.JSON(http.StatusNotFound, models.Response{
- Code: 404,
- Message: "流程不存在",
- })
- return
- }
-
- process.Status = "rejected"
- process.RejectReason = req.Comment
- // now := time.Now()
- // process.RejectedAt = &now
-
- if err := h.db.Save(&process).Error; err != nil {
- c.JSON(http.StatusInternalServerError, models.Response{
- Code: 500,
- Message: "拒绝失败",
- })
- return
- }
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "已拒绝",
- Data: process,
- })
-}
+// Process 相关 handler 见 approval_process.go
diff --git a/backend/internal/api/handlers/approval_process.go b/backend/internal/api/handlers/approval_process.go
new file mode 100644
index 0000000..d8df630
--- /dev/null
+++ b/backend/internal/api/handlers/approval_process.go
@@ -0,0 +1,187 @@
+package handlers
+
+import (
+ "net/http"
+ "strconv"
+
+ "ezops/internal/models"
+
+ "github.com/gin-gonic/gin"
+)
+
+// ListProcesses 获取审批流程列表 // Logan.Li
+func (h *ApprovalHandler) ListProcesses(c *gin.Context) {
+ var query models.PageQuery
+ c.ShouldBindQuery(&query)
+
+ db := h.db.Model(&models.ApprovalProcess{}).Preload("Template").Preload("ApplicantUser")
+
+ // 状态过滤
+ if status := c.Query("status"); status != "" {
+ db = db.Where("status = ?", status)
+ }
+
+ var total int64
+ db.Count(&total)
+
+ var processes []models.ApprovalProcess
+ db.Order("created_at DESC").
+ Offset(query.GetOffset()).
+ Limit(query.GetLimit()).
+ Find(&processes)
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "success",
+ Data: models.ListResponse{
+ List: processes,
+ Total: total,
+ },
+ })
+}
+
+// GetProcess 获取审批流程详情 // Logan.Li
+func (h *ApprovalHandler) GetProcess(c *gin.Context) {
+ id, _ := strconv.Atoi(c.Param("id"))
+
+ var process models.ApprovalProcess
+ if err := h.db.Preload("Template").Preload("ApplicantUser").Preload("Steps.ApproverUser").First(&process, id).Error; err != nil {
+ c.JSON(http.StatusNotFound, models.Response{
+ Code: 404,
+ Message: "流程不存在",
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "success",
+ Data: process,
+ })
+}
+
+// CreateProcess 创建审批流程 // Logan.Li
+func (h *ApprovalHandler) CreateProcess(c *gin.Context) {
+ var req models.CreateApprovalProcessRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, models.Response{
+ Code: 400,
+ Message: "请求参数错误: " + err.Error(),
+ })
+ return
+ }
+
+ // 获取模板
+ var template models.ApprovalTemplate
+ if err := h.db.First(&template, req.TemplateID).Error; err != nil {
+ c.JSON(http.StatusNotFound, models.Response{
+ Code: 404,
+ Message: "模板不存在",
+ })
+ return
+ }
+
+ process := models.ApprovalProcess{
+ TemplateID: req.TemplateID,
+ Title: req.Title,
+ FormData: req.FormData,
+ Status: "pending",
+ CurrentStep: 0,
+ TotalSteps: 1, // 简化处理
+ }
+
+ if err := h.db.Create(&process).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, models.Response{
+ Code: 500,
+ Message: "创建失败",
+ })
+ return
+ }
+
+ h.db.Preload("Template").First(&process, process.ID)
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "创建成功",
+ Data: process,
+ })
+}
+
+// ApproveProcess 审批通过 // Logan.Li
+func (h *ApprovalHandler) ApproveProcess(c *gin.Context) {
+ id, _ := strconv.Atoi(c.Param("id"))
+
+ var req models.ApproveRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, models.Response{
+ Code: 400,
+ Message: "请求参数错误",
+ })
+ return
+ }
+
+ var process models.ApprovalProcess
+ if err := h.db.First(&process, id).Error; err != nil {
+ c.JSON(http.StatusNotFound, models.Response{
+ Code: 404,
+ Message: "流程不存在",
+ })
+ return
+ }
+
+ process.Status = "approved"
+
+ if err := h.db.Save(&process).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, models.Response{
+ Code: 500,
+ Message: "审批失败",
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "审批通过",
+ Data: process,
+ })
+}
+
+// RejectProcess 审批拒绝 // Logan.Li
+func (h *ApprovalHandler) RejectProcess(c *gin.Context) {
+ id, _ := strconv.Atoi(c.Param("id"))
+
+ var req models.ApproveRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, models.Response{
+ Code: 400,
+ Message: "请求参数错误",
+ })
+ return
+ }
+
+ var process models.ApprovalProcess
+ if err := h.db.First(&process, id).Error; err != nil {
+ c.JSON(http.StatusNotFound, models.Response{
+ Code: 404,
+ Message: "流程不存在",
+ })
+ return
+ }
+
+ process.Status = "rejected"
+ process.RejectReason = req.Comment
+
+ if err := h.db.Save(&process).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, models.Response{
+ Code: 500,
+ Message: "拒绝失败",
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "已拒绝",
+ Data: process,
+ })
+}
diff --git a/backend/internal/api/handlers/audit.go b/backend/internal/api/handlers/audit.go
index f2cdf9b..8a32b88 100644
--- a/backend/internal/api/handlers/audit.go
+++ b/backend/internal/api/handlers/audit.go
@@ -144,7 +144,7 @@ func (h *AuditHandler) GetAuditModules(c *gin.Context) {
{"value": "domain", "label": "域名管理"},
}
- c.JSON(http.StatusOK, gin.H{"data": modules})
+ c.JSON(http.StatusOK, models.Response{Code: 200, Data: modules}) // Logan.Li
}
// GetAuditActions 获取审计操作类型列表
@@ -170,7 +170,7 @@ func (h *AuditHandler) GetAuditActions(c *gin.Context) {
{"value": "export", "label": "导出"},
}
- c.JSON(http.StatusOK, gin.H{"data": actions})
+ c.JSON(http.StatusOK, models.Response{Code: 200, Data: actions}) // Logan.Li
}
// ExportAuditLogs 导出审计日志
@@ -222,11 +222,10 @@ func (h *AuditHandler) ExportAuditLogs(c *gin.Context) {
// TODO: 实现CSV/Excel导出功能
// 这里返回JSON格式的数据,实际项目中应该生成CSV或Excel文件
- c.JSON(http.StatusOK, gin.H{
- "message": "导出成功",
- "count": len(logs),
- "data": logs,
- })
+ c.JSON(http.StatusOK, models.Response{Code: 200, Message: "导出成功", Data: gin.H{
+ "count": len(logs),
+ "logs": logs,
+ }}) // Logan.Li
}
// GetAuditStatistics 获取审计统计
@@ -291,13 +290,13 @@ func (h *AuditHandler) GetAuditStatistics(c *gin.Context) {
Where("DATE(created_at) = ?", today).
Count(&todayCount)
- c.JSON(http.StatusOK, gin.H{
- "total_count": totalCount,
- "today_count": todayCount,
- "module_stats": moduleStats,
- "action_stats": actionStats,
- "daily_stats": dailyStats,
- })
+ c.JSON(http.StatusOK, models.Response{Code: 200, Data: gin.H{
+ "total_count": totalCount,
+ "today_count": todayCount,
+ "module_stats": moduleStats,
+ "action_stats": actionStats,
+ "daily_stats": dailyStats,
+ }}) // Logan.Li
}
// ClearAuditLogs 清理审计日志
@@ -341,10 +340,9 @@ func (h *AuditHandler) ClearAuditLogs(c *gin.Context) {
"deleted": result.RowsAffected,
})
- c.JSON(http.StatusOK, gin.H{
- "message": "清理成功",
- "deleted": result.RowsAffected,
- })
+ c.JSON(http.StatusOK, models.Response{Code: 200, Message: "清理成功", Data: gin.H{
+ "deleted": result.RowsAffected,
+ }}) // Logan.Li
}
// RecordAuditLog 记录审计日志
diff --git a/backend/internal/api/handlers/navigation.go b/backend/internal/api/handlers/navigation.go
index 304f30b..d99c465 100644
--- a/backend/internal/api/handlers/navigation.go
+++ b/backend/internal/api/handlers/navigation.go
@@ -1,7 +1,6 @@
package handlers
import (
- "encoding/json"
"net/http"
"strconv"
@@ -183,246 +182,5 @@ func (h *NavigationHandler) DeleteGroup(c *gin.Context) {
})
}
-// ListLinks 获取导航链接列表
-// @Summary 获取导航链接列表
-// @Description 获取所有导航链接列表
-// @Tags 站内导航
-// @Produce json
-// @Security Bearer
-// @Param group_id query int false "分组ID"
-// @Param keyword query string false "搜索关键词"
-// @Success 200 {object} models.Response{data=[]models.NavigationLink}
-// @Router /navigations/links [get]
-func (h *NavigationHandler) ListLinks(c *gin.Context) {
- db := h.db.Model(&models.NavigationLink{}).Preload("Group")
-
- // 分组过滤
- if groupID := c.Query("group_id"); groupID != "" {
- db = db.Where("group_id = ?", groupID)
- }
-
- // 关键词搜索
- if keyword := c.Query("keyword"); keyword != "" {
- db = db.Where("name LIKE ? OR description LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
- }
-
- var links []models.NavigationLink
- db.Order("sort ASC, created_at ASC").Find(&links)
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "success",
- Data: links,
- })
-}
-
-// CreateLink 创建导航链接
-// @Summary 创建导航链接
-// @Description 创建新导航链接
-// @Tags 站内导航
-// @Accept json
-// @Produce json
-// @Security Bearer
-// @Param request body models.CreateNavigationLinkRequest true "创建请求"
-// @Success 200 {object} models.Response{data=models.NavigationLink}
-// @Router /navigations/links [post]
-func (h *NavigationHandler) CreateLink(c *gin.Context) {
- var req models.CreateNavigationLinkRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, models.Response{
- Code: 400,
- Message: "请求参数错误: " + err.Error(),
- })
- return
- }
-
- tags, _ := json.Marshal(req.Tags)
-
- link := models.NavigationLink{
- GroupID: req.GroupID,
- Name: req.Name,
- URL: req.URL,
- Description: req.Description,
- Icon: req.Icon,
- Tags: string(tags),
- Sort: req.Sort,
- Enabled: true,
- }
-
- if err := h.db.Create(&link).Error; err != nil {
- c.JSON(http.StatusInternalServerError, models.Response{
- Code: 500,
- Message: "创建失败",
- })
- return
- }
-
- // 更新分组链接数
- h.db.Model(&models.NavigationGroup{}).Where("id = ?", req.GroupID).
- UpdateColumn("link_count", h.db.Model(&models.NavigationLink{}).Where("group_id = ?", req.GroupID).Select("COUNT(*)"))
-
- h.db.Preload("Group").First(&link, link.ID)
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "创建成功",
- Data: link,
- })
-}
-
-// UpdateLink 更新导航链接
-// @Summary 更新导航链接
-// @Description 更新指定导航链接
-// @Tags 站内导航
-// @Accept json
-// @Produce json
-// @Security Bearer
-// @Param id path int true "链接ID"
-// @Param request body models.UpdateNavigationLinkRequest true "更新请求"
-// @Success 200 {object} models.Response{data=models.NavigationLink}
-// @Router /navigations/links/{id} [put]
-func (h *NavigationHandler) UpdateLink(c *gin.Context) {
- id, _ := strconv.Atoi(c.Param("id"))
-
- var req models.UpdateNavigationLinkRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, models.Response{
- Code: 400,
- Message: "请求参数错误: " + err.Error(),
- })
- return
- }
-
- var link models.NavigationLink
- if err := h.db.First(&link, id).Error; err != nil {
- c.JSON(http.StatusNotFound, models.Response{
- Code: 404,
- Message: "链接不存在",
- })
- return
- }
-
- updates := map[string]interface{}{}
- if req.GroupID > 0 {
- updates["group_id"] = req.GroupID
- }
- if req.Name != "" {
- updates["name"] = req.Name
- }
- if req.URL != "" {
- updates["url"] = req.URL
- }
- if req.Description != "" {
- updates["description"] = req.Description
- }
- if req.Icon != "" {
- updates["icon"] = req.Icon
- }
- if len(req.Tags) > 0 {
- tags, _ := json.Marshal(req.Tags)
- updates["tags"] = string(tags)
- }
- updates["sort"] = req.Sort
- if req.Enabled != nil {
- updates["enabled"] = *req.Enabled
- }
-
- if err := h.db.Model(&link).Updates(updates).Error; err != nil {
- c.JSON(http.StatusInternalServerError, models.Response{
- Code: 500,
- Message: "更新失败",
- })
- return
- }
-
- h.db.Preload("Group").First(&link, id)
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "更新成功",
- Data: link,
- })
-}
-
-// DeleteLink 删除导航链接
-// @Summary 删除导航链接
-// @Description 删除指定导航链接
-// @Tags 站内导航
-// @Produce json
-// @Security Bearer
-// @Param id path int true "链接ID"
-// @Success 200 {object} models.Response
-// @Router /navigations/links/{id} [delete]
-func (h *NavigationHandler) DeleteLink(c *gin.Context) {
- id, _ := strconv.Atoi(c.Param("id"))
-
- var link models.NavigationLink
- h.db.First(&link, id)
-
- if err := h.db.Delete(&models.NavigationLink{}, id).Error; err != nil {
- c.JSON(http.StatusInternalServerError, models.Response{
- Code: 500,
- Message: "删除失败",
- })
- return
- }
-
- // 更新分组链接数
- if link.GroupID > 0 {
- h.db.Model(&models.NavigationGroup{}).Where("id = ?", link.GroupID).
- UpdateColumn("link_count", h.db.Model(&models.NavigationLink{}).Where("group_id = ?", link.GroupID).Select("COUNT(*)"))
- }
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "删除成功",
- })
-}
-
-// ToggleFavorite 切换收藏状态
-// @Summary 切换收藏状态
-// @Description 切换导航链接的收藏状态
-// @Tags 站内导航
-// @Accept json
-// @Produce json
-// @Security Bearer
-// @Param id path int true "链接ID"
-// @Param request body models.ToggleFavoriteRequest true "收藏请求"
-// @Success 200 {object} models.Response{data=models.NavigationLink}
-// @Router /navigations/links/{id}/favorite [post]
-func (h *NavigationHandler) ToggleFavorite(c *gin.Context) {
- id, _ := strconv.Atoi(c.Param("id"))
-
- var req models.ToggleFavoriteRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, models.Response{
- Code: 400,
- Message: "请求参数错误",
- })
- return
- }
-
- var link models.NavigationLink
- if err := h.db.First(&link, id).Error; err != nil {
- c.JSON(http.StatusNotFound, models.Response{
- Code: 404,
- Message: "链接不存在",
- })
- return
- }
-
- link.IsFavorite = req.IsFavorite
- if err := h.db.Save(&link).Error; err != nil {
- c.JSON(http.StatusInternalServerError, models.Response{
- Code: 500,
- Message: "操作失败",
- })
- return
- }
-
- c.JSON(http.StatusOK, models.Response{
- Code: 200,
- Message: "操作成功",
- Data: link,
- })
-}
+// 导航链接(Link)相关的 CRUD 及收藏功能处理器方法已拆分到 navigation_link.go
+// 涉及方法: ListLinks, CreateLink, UpdateLink, DeleteLink, ToggleFavorite
diff --git a/backend/internal/api/handlers/navigation_link.go b/backend/internal/api/handlers/navigation_link.go
new file mode 100644
index 0000000..ac136fa
--- /dev/null
+++ b/backend/internal/api/handlers/navigation_link.go
@@ -0,0 +1,259 @@
+// Logan.Li
+// 导航链接相关处理器方法
+// 从 navigation.go 拆分而来,包含导航链接的 CRUD 及收藏功能
+
+package handlers
+
+import (
+ "encoding/json"
+ "net/http"
+ "strconv"
+
+ "ezops/internal/models"
+
+ "github.com/gin-gonic/gin"
+)
+
+// ListLinks 获取导航链接列表
+// @Summary 获取导航链接列表
+// @Description 获取所有导航链接列表
+// @Tags 站内导航
+// @Produce json
+// @Security Bearer
+// @Param group_id query int false "分组ID"
+// @Param keyword query string false "搜索关键词"
+// @Success 200 {object} models.Response{data=[]models.NavigationLink}
+// @Router /navigations/links [get]
+func (h *NavigationHandler) ListLinks(c *gin.Context) {
+ db := h.db.Model(&models.NavigationLink{}).Preload("Group")
+
+ // 分组过滤
+ if groupID := c.Query("group_id"); groupID != "" {
+ db = db.Where("group_id = ?", groupID)
+ }
+
+ // 关键词搜索
+ if keyword := c.Query("keyword"); keyword != "" {
+ db = db.Where("name LIKE ? OR description LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
+ }
+
+ var links []models.NavigationLink
+ db.Order("sort ASC, created_at ASC").Find(&links)
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "success",
+ Data: links,
+ })
+}
+
+// CreateLink 创建导航链接
+// @Summary 创建导航链接
+// @Description 创建新导航链接
+// @Tags 站内导航
+// @Accept json
+// @Produce json
+// @Security Bearer
+// @Param request body models.CreateNavigationLinkRequest true "创建请求"
+// @Success 200 {object} models.Response{data=models.NavigationLink}
+// @Router /navigations/links [post]
+func (h *NavigationHandler) CreateLink(c *gin.Context) {
+ var req models.CreateNavigationLinkRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, models.Response{
+ Code: 400,
+ Message: "请求参数错误: " + err.Error(),
+ })
+ return
+ }
+
+ tags, _ := json.Marshal(req.Tags)
+
+ link := models.NavigationLink{
+ GroupID: req.GroupID,
+ Name: req.Name,
+ URL: req.URL,
+ Description: req.Description,
+ Icon: req.Icon,
+ Tags: string(tags),
+ Sort: req.Sort,
+ Enabled: true,
+ }
+
+ if err := h.db.Create(&link).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, models.Response{
+ Code: 500,
+ Message: "创建失败",
+ })
+ return
+ }
+
+ // 更新分组链接数
+ h.db.Model(&models.NavigationGroup{}).Where("id = ?", req.GroupID).
+ UpdateColumn("link_count", h.db.Model(&models.NavigationLink{}).Where("group_id = ?", req.GroupID).Select("COUNT(*)"))
+
+ h.db.Preload("Group").First(&link, link.ID)
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "创建成功",
+ Data: link,
+ })
+}
+
+// UpdateLink 更新导航链接
+// @Summary 更新导航链接
+// @Description 更新指定导航链接
+// @Tags 站内导航
+// @Accept json
+// @Produce json
+// @Security Bearer
+// @Param id path int true "链接ID"
+// @Param request body models.UpdateNavigationLinkRequest true "更新请求"
+// @Success 200 {object} models.Response{data=models.NavigationLink}
+// @Router /navigations/links/{id} [put]
+func (h *NavigationHandler) UpdateLink(c *gin.Context) {
+ id, _ := strconv.Atoi(c.Param("id"))
+
+ var req models.UpdateNavigationLinkRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, models.Response{
+ Code: 400,
+ Message: "请求参数错误: " + err.Error(),
+ })
+ return
+ }
+
+ var link models.NavigationLink
+ if err := h.db.First(&link, id).Error; err != nil {
+ c.JSON(http.StatusNotFound, models.Response{
+ Code: 404,
+ Message: "链接不存在",
+ })
+ return
+ }
+
+ updates := map[string]interface{}{}
+ if req.GroupID > 0 {
+ updates["group_id"] = req.GroupID
+ }
+ if req.Name != "" {
+ updates["name"] = req.Name
+ }
+ if req.URL != "" {
+ updates["url"] = req.URL
+ }
+ if req.Description != "" {
+ updates["description"] = req.Description
+ }
+ if req.Icon != "" {
+ updates["icon"] = req.Icon
+ }
+ if len(req.Tags) > 0 {
+ tags, _ := json.Marshal(req.Tags)
+ updates["tags"] = string(tags)
+ }
+ updates["sort"] = req.Sort
+ if req.Enabled != nil {
+ updates["enabled"] = *req.Enabled
+ }
+
+ if err := h.db.Model(&link).Updates(updates).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, models.Response{
+ Code: 500,
+ Message: "更新失败",
+ })
+ return
+ }
+
+ h.db.Preload("Group").First(&link, id)
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "更新成功",
+ Data: link,
+ })
+}
+
+// DeleteLink 删除导航链接
+// @Summary 删除导航链接
+// @Description 删除指定导航链接
+// @Tags 站内导航
+// @Produce json
+// @Security Bearer
+// @Param id path int true "链接ID"
+// @Success 200 {object} models.Response
+// @Router /navigations/links/{id} [delete]
+func (h *NavigationHandler) DeleteLink(c *gin.Context) {
+ id, _ := strconv.Atoi(c.Param("id"))
+
+ var link models.NavigationLink
+ h.db.First(&link, id)
+
+ if err := h.db.Delete(&models.NavigationLink{}, id).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, models.Response{
+ Code: 500,
+ Message: "删除失败",
+ })
+ return
+ }
+
+ // 更新分组链接数
+ if link.GroupID > 0 {
+ h.db.Model(&models.NavigationGroup{}).Where("id = ?", link.GroupID).
+ UpdateColumn("link_count", h.db.Model(&models.NavigationLink{}).Where("group_id = ?", link.GroupID).Select("COUNT(*)"))
+ }
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "删除成功",
+ })
+}
+
+// ToggleFavorite 切换收藏状态
+// @Summary 切换收藏状态
+// @Description 切换导航链接的收藏状态
+// @Tags 站内导航
+// @Accept json
+// @Produce json
+// @Security Bearer
+// @Param id path int true "链接ID"
+// @Param request body models.ToggleFavoriteRequest true "收藏请求"
+// @Success 200 {object} models.Response{data=models.NavigationLink}
+// @Router /navigations/links/{id}/favorite [post]
+func (h *NavigationHandler) ToggleFavorite(c *gin.Context) {
+ id, _ := strconv.Atoi(c.Param("id"))
+
+ var req models.ToggleFavoriteRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, models.Response{
+ Code: 400,
+ Message: "请求参数错误",
+ })
+ return
+ }
+
+ var link models.NavigationLink
+ if err := h.db.First(&link, id).Error; err != nil {
+ c.JSON(http.StatusNotFound, models.Response{
+ Code: 404,
+ Message: "链接不存在",
+ })
+ return
+ }
+
+ link.IsFavorite = req.IsFavorite
+ if err := h.db.Save(&link).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, models.Response{
+ Code: 500,
+ Message: "操作失败",
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, models.Response{
+ Code: 200,
+ Message: "操作成功",
+ Data: link,
+ })
+}
diff --git a/frontend/src/components/agent/AgentMonitor.tsx b/frontend/src/components/agent/AgentMonitor.tsx
index 6345550..bb3919b 100644
--- a/frontend/src/components/agent/AgentMonitor.tsx
+++ b/frontend/src/components/agent/AgentMonitor.tsx
@@ -553,6 +553,7 @@ function HostDetailDialog({
确定删除此云平台?此操作不可撤销。
服务器总数
-{servers.length}
-运行中
-- {servers.filter((s) => s.status === 'running').length} -
-维护中
-- {servers.filter((s) => s.status === 'maintenance').length} -
-已停止
-- {servers.filter((s) => s.status === 'stopped').length} -
-{server.hostname}
{server.os}
@@ -609,15 +570,13 @@ export function ServerManager() {服务器总数
+{servers.length}
+运行中
+{runningCount}
+维护中
+{maintenanceCount}
+已停止
+{stoppedCount}
+