f382c27200
后台新增「数据概览」(商品/合格/按状态/品牌/分类/待审核) 与「操作日志」(全局审计分页);商品列表支持多选批量改状态/分类。公开首页标题改为「天工」并展示合格档案数;新增公开接口 /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>
62 lines
1.9 KiB
Go
62 lines
1.9 KiB
Go
package adminstore
|
|
|
|
import "context"
|
|
|
|
// QualifiedMinScore is the quality_score threshold at or above which a product
|
|
// counts as "qualified" (合格) in admin and public stats.
|
|
const QualifiedMinScore = 0.6
|
|
|
|
// AdminStats summarizes the catalog for the admin overview dashboard.
|
|
type AdminStats struct {
|
|
Products int `json:"products"`
|
|
Qualified int `json:"qualified"`
|
|
MinScore float64 `json:"min_score"`
|
|
ByStatus map[string]int `json:"by_status"`
|
|
Brands int `json:"brands"`
|
|
Categories int `json:"categories"`
|
|
Pending int `json:"pending_submissions"`
|
|
AvgQuality float64 `json:"avg_quality"`
|
|
}
|
|
|
|
// Stats gathers the dashboard counters in a handful of aggregate queries.
|
|
func (s *Store) Stats(ctx context.Context) (*AdminStats, error) {
|
|
out := &AdminStats{MinScore: QualifiedMinScore, ByStatus: map[string]int{}}
|
|
|
|
if err := s.pool.QueryRow(ctx, `
|
|
SELECT count(*),
|
|
count(*) FILTER (WHERE quality_score >= $1 AND status = 'active'),
|
|
COALESCE(avg(quality_score), 0)
|
|
FROM product`, QualifiedMinScore).Scan(&out.Products, &out.Qualified, &out.AvgQuality); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows, err := s.pool.Query(ctx, "SELECT status, count(*) FROM product GROUP BY status")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var st string
|
|
var n int
|
|
if err := rows.Scan(&st, &n); err != nil {
|
|
return nil, err
|
|
}
|
|
out.ByStatus[st] = n
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := s.pool.QueryRow(ctx, "SELECT count(*) FROM brand").Scan(&out.Brands); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.pool.QueryRow(ctx, "SELECT count(*) FROM category").Scan(&out.Categories); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.pool.QueryRow(ctx,
|
|
"SELECT count(*) FROM submission WHERE status = 'pending'").Scan(&out.Pending); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|