feat: 数据概览/操作日志/批量操作 + 首页合格档案数
CI / Go (api) (pull_request) Successful in 13s
CI / Python (ingestion) (pull_request) Successful in 9s
CI / Migrations (postgres) (pull_request) Successful in 14s

后台新增「数据概览」(商品/合格/按状态/品牌/分类/待审核) 与「操作日志」(全局审计分页);商品列表支持多选批量改状态/分类。公开首页标题改为「天工」并展示合格档案数;新增公开接口 /api/v1/stats 与后台 /api/stats、/api/audit、/api/products/bulk。合格口径=quality_score≥0.6 且在用。

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
sulaimaannaasif6866
2026-06-21 01:45:28 +00:00
parent a36700076e
commit f382c27200
15 changed files with 847 additions and 9 deletions
+73
View File
@@ -64,8 +64,11 @@ func (h *Handler) Router() http.Handler {
r.Group(func(r chi.Router) {
r.Use(h.authn.Middleware)
r.Get("/api/me", h.Me)
r.Get("/api/stats", h.Stats)
r.Get("/api/audit", h.ListAllAudit)
r.Get("/api/products", h.ListProducts)
r.Post("/api/products", h.CreateProduct)
r.Post("/api/products/bulk", h.BulkProducts)
r.Get("/api/products/{id}", h.GetProduct)
r.Put("/api/products/{id}", h.UpdateProduct)
r.Get("/api/products/{id}/audit", h.ListAudit)
@@ -203,6 +206,76 @@ func (h *Handler) CreateProduct(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, d)
}
// Stats returns the dashboard overview counters.
func (h *Handler) Stats(w http.ResponseWriter, r *http.Request) {
st, err := h.store.Stats(r.Context())
if h.handleErr(w, err) {
return
}
writeJSON(w, http.StatusOK, st)
}
// ListAllAudit returns a page of the global operations audit log.
func (h *Handler) ListAllAudit(w http.ResponseWriter, r *http.Request) {
page, size := pageParams(r)
items, total, err := h.store.ListAllAudit(r.Context(), size, (page-1)*size)
if h.handleErr(w, err) {
return
}
writeJSON(w, http.StatusOK, map[string]any{
"items": items, "page": page, "size": size, "total": total,
})
}
type bulkInput struct {
IDs []string `json:"ids"`
Action string `json:"action"`
Status string `json:"status"`
CategoryID *string `json:"category_id"`
}
// BulkProducts applies a status or category change to many products at once.
func (h *Handler) BulkProducts(w http.ResponseWriter, r *http.Request) {
var in bulkInput
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
writeError(w, http.StatusBadRequest, "bad_request", "invalid body")
return
}
if len(in.IDs) == 0 {
writeError(w, http.StatusBadRequest, "bad_request", "未选择任何商品")
return
}
actor := auth.UserFrom(r.Context())
var (
affected int
err error
)
switch in.Action {
case "status":
affected, err = h.store.BulkSetStatus(r.Context(), actor, in.IDs, in.Status)
case "category":
affected, err = h.store.BulkSetCategory(r.Context(), actor, in.IDs, in.CategoryID)
default:
writeError(w, http.StatusBadRequest, "bad_request", "未知的批量操作")
return
}
switch {
case errors.Is(err, adminstore.ErrInvalidStatus):
writeError(w, http.StatusBadRequest, "invalid_status", "无效的状态值")
return
case errors.Is(err, adminstore.ErrInvalidParent):
writeError(w, http.StatusBadRequest, "invalid_parent", "目标分类无效")
return
case errors.Is(err, adminstore.ErrNoTargets):
writeError(w, http.StatusBadRequest, "bad_request", "未选择任何商品")
return
}
if h.handleErr(w, err) {
return
}
writeJSON(w, http.StatusOK, map[string]any{"status": "ok", "affected": affected})
}
// UpdateProduct applies an edit.
func (h *Handler) UpdateProduct(w http.ResponseWriter, r *http.Request) {
var in adminstore.ProductInput