package api import ( "strconv" "github.com/gin-gonic/gin" "eaisalestrain/backend/internal/model" "eaisalestrain/backend/internal/store" "eaisalestrain/backend/internal/web" ) // courseView 课程详情视图(含绑定产品) func courseView(c *gin.Context, co model.Course) { out := gin.H{ "id": co.ID, "code": co.Code, "name": co.Name, "category": co.Category, "target_customers": co.TargetCustomers, "forbidden_customers": co.ForbiddenCustomers, "scripts": co.Scripts, "sales_process": co.SalesProcess, "objection_handling": co.ObjectionHandling, "delivery_pitfalls": co.DeliveryPitfalls, "report_rules": co.ReportRules, "related_product_id": co.RelatedProductID, "status": co.Status, "created_at": co.CreatedAt, "updated_at": co.UpdatedAt, } if co.RelatedProductID != nil { var p model.Product if store.DB.Where("id = ? AND status != ?", *co.RelatedProductID, "inactive").First(&p).Error == nil { out["product"] = gin.H{"id": p.ID, "code": p.Code, "name": p.Name, "category": p.Category} } } var medias []model.MediaFile store.DB.Where("bind_type = ? AND bind_id = ? AND status = ?", "course", co.ID, "approved"). Order("id ASC").Find(&medias) if len(medias) > 0 { items := make([]gin.H, 0, len(medias)) for _, m := range medias { items = append(items, gin.H{ "id": m.ID, "filename": m.Filename, "file_ext": m.FileExt, "preview_url": "/api/media/preview/" + strconv.FormatUint(uint64(m.ID), 10), }) } out["medias"] = items } web.OK(c, out) } // ListCourses GET /api/courses?category=&status= func ListCourses(c *gin.Context) { q := store.DB.Model(&model.Course{}) if cat := c.Query("category"); cat != "" { q = q.Where("category = ?", cat) } switch st := c.Query("status"); st { case "": // 默认仅 active(员工浏览) q = q.Where("status = ?", "active") case "all": // 管理员维护全量 default: q = q.Where("status = ?", st) } var items []model.Course if err := q.Order("id ASC").Find(&items).Error; err != nil { web.Fail(c, web.NewBadRequest("查询课程失败")) return } web.OK(c, items) } // GetCourse GET /api/courses/{id} func GetCourse(c *gin.Context) { id, ok := parseID(c, "id") if !ok { return } var co model.Course if err := store.DB.First(&co, id).Error; err != nil { web.Fail(c, web.NewNotFoundError("课程不存在")) return } courseView(c, co) } // CreateCourse POST /api/courses (admin) func CreateCourse(c *gin.Context) { var co model.Course if err := c.ShouldBindJSON(&co); err != nil { web.Fail(c, web.NewBadRequest("请求参数错误")) return } if co.Code == "" || co.Name == "" || co.Category == "" { web.Fail(c, web.NewBadRequest("编号、名称、分类为必填")) return } var count int64 store.DB.Model(&model.Course{}).Where("code = ?", co.Code).Count(&count) if count > 0 { web.Fail(c, web.NewConflictError("课程编号已存在")) return } if co.Status == "" { co.Status = "active" } if err := store.DB.Create(&co).Error; err != nil { web.Fail(c, web.NewBadRequest("创建课程失败")) return } web.OK(c, co) } // UpdateCourse PUT /api/courses/{id} (admin) —— 支持绑定/解绑产品 func UpdateCourse(c *gin.Context) { id, ok := parseID(c, "id") if !ok { return } var co model.Course if err := store.DB.First(&co, id).Error; err != nil { web.Fail(c, web.NewNotFoundError("课程不存在")) return } var req model.Course if err := c.ShouldBindJSON(&req); err != nil { web.Fail(c, web.NewBadRequest("请求参数错误")) return } if req.Name == "" || req.Category == "" { web.Fail(c, web.NewBadRequest("名称、分类为必填")) return } if req.Status == "" { req.Status = "active" } if req.Code != "" && req.Code != co.Code { var count int64 store.DB.Model(&model.Course{}).Where("code = ? AND id <> ?", req.Code, id).Count(&count) if count > 0 { web.Fail(c, web.NewConflictError("课程编号已存在")) return } co.Code = req.Code } co.Name = req.Name co.Category = req.Category co.TargetCustomers = req.TargetCustomers co.ForbiddenCustomers = req.ForbiddenCustomers co.Scripts = req.Scripts co.SalesProcess = req.SalesProcess co.ObjectionHandling = req.ObjectionHandling co.DeliveryPitfalls = req.DeliveryPitfalls co.ReportRules = req.ReportRules co.RelatedProductID = req.RelatedProductID co.Status = req.Status if err := store.DB.Save(&co).Error; err != nil { web.Fail(c, web.NewBadRequest("更新课程失败")) return } courseView(c, co) }