package web import ( "fmt" "net/http" "net/url" "strconv" "github.com/gin-gonic/gin" "cmautobuy/admin/service" ) // PDD 商品页的处理器。 // // 本文件只做三件事:取参数 → 调 service → 渲染或跳转。 // 不写业务判断,不拼 SQL——那些在 service/pdd.go 和 repository/pdd.go 里。 // PddList 渲染 PDD 商品列表页。 func (h *Handler) PddList(c *gin.Context) { keyword := c.Query("q") status := service.ParseCollectStatus(c.Query("status")) pageNum := service.ParsePage(c.Query("page")) result, err := service.ListPddProducts(h.db, keyword, status, pageNum) if err != nil { fail(c, http.StatusInternalServerError, "读取 PDD 商品列表失败,数据没有被改动。刷新页面重试;一直失败请把这句话报给维护者。") return } // 底部状态条平时显示统计,刚做完操作时先显示操作结果。 // 操作结果是通过跳转带过来的 msg 参数传的(见 pddRedirect)。 statusLine := result.StatusLine() if msg := c.Query("msg"); msg != "" { statusLine = msg + " · " + statusLine } values := url.Values{} if keyword != "" { values.Set("q", keyword) } if status != "" { values.Set("status", string(status)) } detailValues := url.Values{} for key, entries := range values { detailValues[key] = append([]string(nil), entries...) } detailValues.Set("page", strconv.Itoa(result.Page)) c.HTML(http.StatusOK, "pdd/list", page(c, "pdd", "PDD 商品", gin.H{ "Rows": result.Rows, "Keyword": keyword, "Status": statusLine, "StatusFilter": string(status), "StatusOptions": service.CollectStatusOptions(), "HasAnyProducts": result.Total > 0, "IsFiltered": result.IsFiltered, "CurrentPage": result.Page, "Pagination": service.NewPaginationView(result.Page, result.TotalPages, values.Encode()), "DetailURL": "/pdd/detail?" + detailValues.Encode(), })) } // PddDetail 渲染双击行弹出的那个弹窗的**内容**(不是整页)。 // // 弹窗内容由服务端渲染,前端只负责把它放进弹窗壳子里显示出来。 // 不要改成"前端拿 JSON 自己拼表格"——规格表的维度顺序、 // 价格格式、null 的处理都是业务规则,散到前端就没人维护得住了。 func (h *Handler) PddDetail(c *gin.Context) { id, err := strconv.ParseInt(c.Query("id"), 10, 64) if err != nil { fail(c, http.StatusBadRequest, "商品编号不对,请刷新页面后重试。") return } detail, err := service.GetPddProductDetail(h.db, id) if err != nil { fail(c, http.StatusInternalServerError, "读取商品详情失败,数据没有被改动。") return } if detail == nil { fail(c, http.StatusNotFound, "这个商品不存在或已被删除,请刷新页面。") return } c.HTML(http.StatusOK, "pdd/edit_modal", gin.H{ "D": detail, "CSRFToken": csrfToken(c), "Keyword": c.Query("q"), "StatusFilter": c.Query("status"), "CurrentPage": service.ParsePage(c.Query("page")), }) } // PddCreate 按链接创建一条 PDD 商品。 // // 只填链接,其余字段全靠采集回填。 // 链接解析不出 goods_id 时**报错并且不写库**,见 service.ParsePddGoodsID。 func (h *Handler) PddCreate(c *gin.Context) { goodsID, err := service.CreatePddProduct(h.db, c.PostForm("url")) if err != nil { fail(c, http.StatusBadRequest, err.Error()+"。没有创建任何记录。") return } h.pddRedirect(c, fmt.Sprintf("已保存商品 %s,现在可以勾选它创建采集任务", goodsID)) } // PddSave 保存弹窗里改过的链接。 func (h *Handler) PddSave(c *gin.Context) { id, err := strconv.ParseInt(c.PostForm("id"), 10, 64) if err != nil { fail(c, http.StatusBadRequest, "商品编号不对,请刷新页面后重试。没有改动任何数据。") return } if err := service.UpdatePddProductURL(h.db, id, c.PostForm("url")); err != nil { fail(c, http.StatusBadRequest, err.Error()+"。没有改动任何数据。") return } h.pddRedirect(c, "链接已保存") } // PddCollect 为勾选的商品创建采集任务。 // // 按钮文案是「创建采集任务」而不是「采集」:它做的是建一个任务, // 不是立刻去采。真正的采集要等客户端来领、去手机上跑, // 可能几秒也可能几分钟。 func (h *Handler) PddCollect(c *gin.Context) { ids := c.PostFormArray("ids") if len(ids) == 0 { h.pddRedirect(c, "没有勾选任何商品,没有创建任务") return } result, err := service.CreatePddCollectTasks(h.db, ids) if err != nil { fail(c, http.StatusInternalServerError, "创建采集任务失败:"+err.Error()+"。这一批任务整体没有创建,可以直接重试。") return } // 跳过原因分类、"还要等多久"的措辞都是业务规则,交给 service 算, // 这里只负责把结果接过来发给操作员,见 service.FormatCollectTaskMessage。 h.pddRedirect(c, service.FormatCollectTaskMessage(result)) } // PddDelete 批量软删除。 func (h *Handler) PddDelete(c *gin.Context) { ids := c.PostFormArray("ids") if len(ids) == 0 { h.pddRedirect(c, "没有勾选任何商品,没有删除") return } n, err := service.DeletePddProducts(h.db, ids) if err != nil { fail(c, http.StatusInternalServerError, "删除失败:"+err.Error()+"。没有删除任何记录。") return } h.pddRedirect(c, fmt.Sprintf("已删除 %d 条", n)) } // pddRedirect 处理完写操作后跳回列表页。 // // 用 303 跳转而不是直接渲染,是为了让浏览器地址栏变成 GET /pdd—— // 否则用户按 F5 会重复提交刚才那个 POST(重复建任务、重复删除)。 // // 筛选条件一起带回去,免得操作员每做一次操作就要重新筛一遍。 func (h *Handler) pddRedirect(c *gin.Context, msg string) { params := url.Values{} if s := c.PostForm("status"); s != "" { params.Set("status", s) } if q := c.PostForm("q"); q != "" { params.Set("q", q) } if p := c.PostForm("page"); p != "" { params.Set("page", p) } if msg != "" { params.Set("msg", msg) } target := "/pdd" if len(params) > 0 { target += "?" + params.Encode() } c.Redirect(http.StatusSeeOther, target) }