feat: 完善 SYB 蝦皮主数据与软删除 (#203 #204 #205)

This commit is contained in:
chengma
2026-08-13 10:39:21 +08:00
parent 3af9bf245a
commit ad1ccc2c60
23 changed files with 864 additions and 90 deletions
+36 -6
View File
@@ -18,7 +18,7 @@ import (
// **商品级一行**(不是 SKU 级),数据来自 shopee_products 和 shopee_skus
// 聚合联查。列定义见 docs/admin/05-ui-specification.md §4.2、工单 #41。
func (h *Handler) ShopeeList(c *gin.Context) {
h.renderShopeeList(c, c.Query("goods_id"), c.Query("shop_name"), c.Query("status"), c.Query("shop"), c.Query("image"), c.Query("page"), c.Query("msg"))
h.renderShopeeList(c, c.Query("goods_id"), c.Query("shop_name"), c.Query("status"), c.Query("shop"), c.Query("image"), c.Query("deleted"), c.Query("page"), c.Query("msg"))
}
// ShopeeDetail 渲染双击行弹出的那个弹窗的**内容**(不是整页)。
@@ -57,7 +57,7 @@ func (h *Handler) ShopeeDetail(c *gin.Context) {
//
// `[必须]` 分页控件用 <a href> 纯 GET 导航,翻页要保留关键词和三个下拉筛选,
// 所以这里把归一后的有效值透传回模板去拼下一页的链接(工单 #43、#150)。
func (h *Handler) renderShopeeList(c *gin.Context, keyword, shopName, statusRaw, shopRaw, imageRaw, pageRaw, msg string) {
func (h *Handler) renderShopeeList(c *gin.Context, keyword, shopName, statusRaw, shopRaw, imageRaw, deletedRaw, pageRaw, msg string) {
filter := repository.ShopeeFilter{
Keyword: keyword,
ShopName: strings.TrimSpace(shopName),
@@ -65,6 +65,9 @@ func (h *Handler) renderShopeeList(c *gin.Context, keyword, shopName, statusRaw,
Shop: service.ParseShopeePresence(shopRaw),
Image: service.ParseShopeePresence(imageRaw),
}
if currentUser(c).IsAdmin() && deletedRaw == "1" {
filter.Deleted = true
}
// 不叫 page:本文件末尾要调用同名的 page(c, ...) 渲染辅助函数,
// 局部变量会把它遮住导致编译失败。
pageNum := service.ParsePage(pageRaw)
@@ -103,6 +106,9 @@ func (h *Handler) renderShopeeList(c *gin.Context, keyword, shopName, statusRaw,
if filter.Image != "" {
values.Set("image", filter.Image)
}
if filter.Deleted {
values.Set("deleted", "1")
}
c.HTML(http.StatusOK, "shopee/list", page(c, "shopee", "蝦皮数据", gin.H{
"Keyword": keyword,
@@ -113,6 +119,7 @@ func (h *Handler) renderShopeeList(c *gin.Context, keyword, shopName, statusRaw,
"ShopOptions": service.ShopeePresenceOptions("有店铺", "无店铺"),
"ImageFilter": filter.Image,
"ImageOptions": service.ShopeePresenceOptions("有图片", "无图片"),
"DeletedFilter": filter.Deleted,
"Rows": result.Rows,
"Status": status,
"HasAnyProducts": result.HasAnyProducts,
@@ -160,11 +167,31 @@ func (h *Handler) ShopeeSave(c *gin.Context) {
}
// ShopeeDelete 批量删除勾选的行。
//
// `[不做]` 本工单(#38)明确不实现这个接口,保持 501。
func (h *Handler) ShopeeDelete(c *gin.Context) {
// TODO(骨架): 取 ids,二次确认已在前端做过,这里直接删
fail(c, http.StatusNotImplemented, "删除功能尚未实现。")
count, err := service.DeleteShopeeProducts(h.db, currentUser(c), c.PostFormArray("ids"))
if err != nil {
if service.IsValidationError(err) {
h.shopeeRedirect(c, "删除失败:"+err.Error())
return
}
fail(c, http.StatusInternalServerError, "删除蝦皮商品失败,数据没有被改动。")
return
}
h.shopeeRedirect(c, fmt.Sprintf("已将 %d 个蝦皮商品移入已删除,可由管理员恢复", count))
}
// ShopeeRestore 恢复软删除商品及其原有 SKU、PDD 关联。
func (h *Handler) ShopeeRestore(c *gin.Context) {
count, err := service.RestoreShopeeProducts(h.db, currentUser(c), c.PostFormArray("ids"))
if err != nil {
if service.IsValidationError(err) {
h.shopeeRedirect(c, "恢复失败:"+err.Error())
return
}
fail(c, http.StatusInternalServerError, "恢复蝦皮商品失败,数据没有被改动。")
return
}
h.shopeeRedirect(c, fmt.Sprintf("已恢复 %d 个蝦皮商品", count))
}
// ShopeeCollect 发起采集任务。
@@ -226,6 +253,9 @@ func (h *Handler) shopeeRedirect(c *gin.Context, msg string) {
if value := service.ParseShopeePresence(c.PostForm("image")); value != "" {
params.Set("image", value)
}
if currentUser(c).IsAdmin() && c.PostForm("deleted") == "1" {
params.Set("deleted", "1")
}
if value := strings.TrimSpace(c.PostForm("page")); value != "" {
params.Set("page", value)
}
+3 -1
View File
@@ -57,9 +57,11 @@ func Register(r *gin.Engine, db *sql.DB, onlineThreshold time.Duration) {
pages.GET("/shopee", h.ShopeeList)
pages.GET("/shopee/detail", h.ShopeeDetail) // 双击行时前端来取弹窗内容
pages.POST("/shopee/save", h.ShopeeSave)
pages.POST("/shopee/delete", h.ShopeeDelete)
pages.POST("/shopee/collect", h.ShopeeCollect)
pages.POST("/shopee/collect-batch", h.ShopeeCollectBatch)
shopeeAdmin := pages.Group("/shopee", AdminRequired())
shopeeAdmin.POST("/delete", h.ShopeeDelete)
shopeeAdmin.POST("/restore", h.ShopeeRestore)
// 2. PDD 商品
pages.GET("/pdd", h.PddList)