package web import ( "net/http" "strconv" "strings" "daili/internal/domain" "daili/internal/store" ) type packagesData struct { List []store.Package } func (s *Server) handlePackages(w http.ResponseWriter, r *http.Request) { list, err := s.db.ListPackages(true) if err != nil { http.Error(w, "读取套餐失败", http.StatusInternalServerError) return } p := s.newPage(r, "套餐", "packages") p.D = &packagesData{List: list} s.render(w, r, "packages", p) } type packageFormData struct { Package *store.Package IsNew bool } func (s *Server) handlePackageNewPage(w http.ResponseWriter, r *http.Request) { p := s.newPage(r, "新增套餐", "packages") p.D = &packageFormData{ Package: &store.Package{Unit: domain.UnitDay, DefaultAmount: 30, Color: "#2F5D50"}, IsNew: true, } s.render(w, r, "package_form", p) } func readPackageForm(r *http.Request) *store.Package { p := &store.Package{ Name: strings.TrimSpace(r.PostFormValue("name")), Unit: strings.TrimSpace(r.PostFormValue("unit")), DefaultAmount: 0, Color: strings.TrimSpace(r.PostFormValue("color")), Note: strings.TrimSpace(r.PostFormValue("note")), SortOrder: 0, } if v, err := strconv.Atoi(strings.TrimSpace(r.PostFormValue("default_amount"))); err == nil { p.DefaultAmount = v } if v, err := strconv.Atoi(strings.TrimSpace(r.PostFormValue("sort_order"))); err == nil { p.SortOrder = v } if price := strings.TrimSpace(r.PostFormValue("price")); price != "" { if f, err := strconv.ParseFloat(price, 64); err == nil { p.PriceCents = int(f*100 + 0.5) p.HasPrice = true } } if p.Unit == "" { p.Unit = domain.UnitDay } return p } func validatePackage(p *store.Package) string { if p.Name == "" { return "套餐名称不能为空" } if len([]rune(p.Name)) > 32 { return "套餐名称最长 32 字" } if p.Unit != domain.UnitDay && p.Unit != domain.UnitCount { return "套餐类型不正确" } if p.DefaultAmount <= 0 { return "默认天数/次数必须大于 0" } if p.Color == "" || !strings.HasPrefix(p.Color, "#") { return "请选择套餐颜色" } return "" } func (s *Server) handlePackageCreate(w http.ResponseWriter, r *http.Request) { pkg := readPackageForm(r) p := s.newPage(r, "新增套餐", "packages") if msg := validatePackage(pkg); msg != "" { p.D = &packageFormData{Package: pkg, IsNew: true} s.fail(w, r, "package_form", msg, p) return } id, err := s.db.CreatePackage(pkg) if err != nil { p.D = &packageFormData{Package: pkg, IsNew: true} s.fail(w, r, "package_form", "保存失败:"+err.Error(), p) return } s.logOp(r, "新增套餐", "package", id, "新增套餐 "+pkg.Name) s.redirect(w, r, "/packages", "套餐已创建,现在可以给客户开通了") } func (s *Server) handlePackageEditPage(w http.ResponseWriter, r *http.Request) { id := pathID(r) pkg, err := s.db.Package(id) if err != nil { http.NotFound(w, r) return } p := s.newPage(r, "编辑套餐", "packages") p.D = &packageFormData{Package: pkg, IsNew: false} s.render(w, r, "package_form", p) } func (s *Server) handlePackageUpdate(w http.ResponseWriter, r *http.Request) { id := pathID(r) pkg := readPackageForm(r) pkg.ID = id p := s.newPage(r, "编辑套餐", "packages") if msg := validatePackage(pkg); msg != "" { p.D = &packageFormData{Package: pkg, IsNew: false} s.fail(w, r, "package_form", msg, p) return } if err := s.db.UpdatePackage(pkg); err != nil { p.D = &packageFormData{Package: pkg, IsNew: false} s.fail(w, r, "package_form", "保存失败:"+err.Error(), p) return } s.logOp(r, "编辑套餐", "package", id, "修改套餐 "+pkg.Name) s.redirect(w, r, "/packages", "套餐已保存") } func (s *Server) handlePackageArchive(w http.ResponseWriter, r *http.Request) { id := pathID(r) pkg, err := s.db.Package(id) if err != nil { http.NotFound(w, r) return } archived := !pkg.Archived if err := s.db.SetPackageArchived(id, archived); err != nil { s.redirect(w, r, "/packages", "操作失败:"+err.Error()) return } action := "归档套餐" msg := "套餐已归档,不再允许新订阅(已有订阅不受影响)" if !archived { action = "取消归档" msg = "套餐已恢复" } s.logOp(r, action, "package", id, action+" "+pkg.Name) s.redirect(w, r, "/packages", msg) }