feat: 数据概览/操作日志/批量操作 + 首页合格档案数
后台新增「数据概览」(商品/合格/按状态/品牌/分类/待审核) 与「操作日志」(全局审计分页);商品列表支持多选批量改状态/分类。公开首页标题改为「天工」并展示合格档案数;新增公开接口 /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:
@@ -0,0 +1,61 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user