feat: 统一 Admin 主列表分页 (#64)
This commit is contained in:
+53
-13
@@ -126,16 +126,8 @@ func ListClients(db *sql.DB, keyword string) ([]model.Client, error) {
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ListClientsForUser 返回带当前负责人的客户端。
|
||||
// visibleUserID 为空表示管理员全量视图,否则只返回当前绑定给该用户的客户端。
|
||||
func ListClientsForUser(db *sql.DB, keyword, visibleUserID string) ([]ClientWithAssignee, error) {
|
||||
query := `SELECT c.client_id, c.name, c.device_address, c.platform, c.pdd_package,
|
||||
c.capabilities, c.last_seen_at, c.created_at, c.updated_at,
|
||||
a.user_id, u.username
|
||||
FROM clients c
|
||||
LEFT JOIN client_user_assignments a
|
||||
ON a.client_id = c.client_id AND a.ended_at IS NULL
|
||||
LEFT JOIN users u ON u.user_id = a.user_id`
|
||||
// clientListFilter 统一生成客户端分页、总数和在线统计的可见范围。
|
||||
func clientListFilter(keyword, visibleUserID string) (string, []any) {
|
||||
where := make([]string, 0, 2)
|
||||
args := make([]any, 0, 3)
|
||||
if visibleUserID != "" {
|
||||
@@ -147,10 +139,24 @@ func ListClientsForUser(db *sql.DB, keyword, visibleUserID string) ([]ClientWith
|
||||
like := "%" + kw + "%"
|
||||
args = append(args, like, like)
|
||||
}
|
||||
if len(where) > 0 {
|
||||
query += ` WHERE ` + strings.Join(where, ` AND `)
|
||||
if len(where) == 0 {
|
||||
return "", args
|
||||
}
|
||||
query += ` ORDER BY c.last_seen_at DESC, c.client_id`
|
||||
return ` WHERE ` + strings.Join(where, ` AND `), args
|
||||
}
|
||||
|
||||
const clientListFrom = ` FROM clients c
|
||||
LEFT JOIN client_user_assignments a
|
||||
ON a.client_id = c.client_id AND a.ended_at IS NULL
|
||||
LEFT JOIN users u ON u.user_id = a.user_id`
|
||||
|
||||
func listClientsForUserPage(db *sql.DB, keyword, visibleUserID string, limit, offset int) ([]ClientWithAssignee, error) {
|
||||
where, args := clientListFilter(keyword, visibleUserID)
|
||||
query := `SELECT c.client_id, c.name, c.device_address, c.platform, c.pdd_package,
|
||||
c.capabilities, c.last_seen_at, c.created_at, c.updated_at,
|
||||
a.user_id, u.username` + clientListFrom + where +
|
||||
` ORDER BY c.last_seen_at DESC, c.client_id LIMIT ? OFFSET ?`
|
||||
args = append(args, limit, offset)
|
||||
|
||||
rows, err := db.Query(query, args...)
|
||||
if err != nil {
|
||||
@@ -173,6 +179,40 @@ func ListClientsForUser(db *sql.DB, keyword, visibleUserID string) ([]ClientWith
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ListClientsForUser 返回带当前负责人的全部客户端,供需要完整候选集的业务使用。
|
||||
func ListClientsForUser(db *sql.DB, keyword, visibleUserID string) ([]ClientWithAssignee, error) {
|
||||
return listClientsForUserPage(db, keyword, visibleUserID, -1, 0)
|
||||
}
|
||||
|
||||
// ListClientsForUserPage 返回一页客户端和同一可见范围内的总数。
|
||||
func ListClientsForUserPage(db *sql.DB, keyword, visibleUserID string, limit, offset int) ([]ClientWithAssignee, int, error) {
|
||||
where, args := clientListFilter(keyword, visibleUserID)
|
||||
var total int
|
||||
if err := db.QueryRow(`SELECT COUNT(*)`+clientListFrom+where, args...).Scan(&total); err != nil {
|
||||
return nil, 0, fmt.Errorf("统计客户端列表失败: %w", err)
|
||||
}
|
||||
rows, err := listClientsForUserPage(db, keyword, visibleUserID, limit, offset)
|
||||
return rows, total, err
|
||||
}
|
||||
|
||||
// CountOnlineClientsForUser 统计同一筛选和权限范围内的在线客户端。
|
||||
// julianday 解析失败会得到 NULL,自然按离线处理,与 model.Client.IsOnline 一致。
|
||||
func CountOnlineClientsForUser(db *sql.DB, keyword, visibleUserID, cutoffISO string) (int, error) {
|
||||
where, args := clientListFilter(keyword, visibleUserID)
|
||||
args = append(args, cutoffISO)
|
||||
var online int
|
||||
query := `SELECT COUNT(*)` + clientListFrom + where
|
||||
if where == "" {
|
||||
query += ` WHERE julianday(c.last_seen_at) > julianday(?)`
|
||||
} else {
|
||||
query += ` AND julianday(c.last_seen_at) > julianday(?)`
|
||||
}
|
||||
if err := db.QueryRow(query, args...).Scan(&online); err != nil {
|
||||
return 0, fmt.Errorf("统计在线客户端失败: %w", err)
|
||||
}
|
||||
return online, nil
|
||||
}
|
||||
|
||||
// ListActivePurchasers 返回可成为新负责人的启用采购员。
|
||||
func ListActivePurchasers(db *sql.DB) ([]model.User, error) {
|
||||
rows, err := db.Query(`
|
||||
|
||||
+33
-20
@@ -174,33 +174,43 @@ const pddSkuCountExpr = `
|
||||
ELSE COALESCE(json_array_length(skus_json, '$.skus'), -1)
|
||||
END`
|
||||
|
||||
// ListPddProducts 查列表。keyword 匹配 goods_id 或链接,status 为空表示不筛选。
|
||||
//
|
||||
// 只返回未删除的。软删除的记录还在库里(sku_mappings 指着它),
|
||||
// 但界面上不该看见。
|
||||
func ListPddProducts(q Execer, keyword string, status model.CollectStatus) ([]PddProductRow, error) {
|
||||
sqlText := `SELECT ` + pddColumns + `,` + pddSkuCountExpr + `
|
||||
FROM pdd_products
|
||||
WHERE deleted_at IS NULL`
|
||||
var args []any
|
||||
|
||||
// pddListFilter 统一生成 PDD 列表和总数查询的筛选条件。
|
||||
// 两个查询必须共用这一处,避免新增筛选时只改列表、漏改页数。
|
||||
func pddListFilter(keyword string, status model.CollectStatus) (string, []any) {
|
||||
where := ` WHERE deleted_at IS NULL`
|
||||
args := make([]any, 0, 3)
|
||||
if keyword = strings.TrimSpace(keyword); keyword != "" {
|
||||
// ESCAPE 不能省:操作员搜 "_" 时,LIKE 会把它当"任意一个字符",
|
||||
// 结果是全部行都命中,看着像搜索坏了。
|
||||
pattern := "%" + escapeLike(keyword) + "%"
|
||||
sqlText += ` AND (goods_id LIKE ? ESCAPE '\' OR url LIKE ? ESCAPE '\')`
|
||||
where += ` AND (goods_id LIKE ? ESCAPE '\' OR url LIKE ? ESCAPE '\')`
|
||||
args = append(args, pattern, pattern)
|
||||
}
|
||||
if status != "" {
|
||||
sqlText += ` AND collect_status = ?`
|
||||
where += ` AND collect_status = ?`
|
||||
args = append(args, string(status))
|
||||
}
|
||||
// 刚动过的排前面,操作员点完"创建采集任务"一眼能看到结果
|
||||
sqlText += ` ORDER BY updated_at DESC, id DESC`
|
||||
return where, args
|
||||
}
|
||||
|
||||
rows, err := q.Query(sqlText, args...)
|
||||
// ListPddProducts 查一页列表和当前筛选下的总数。
|
||||
// keyword 匹配 goods_id 或链接,status 为空表示不筛选。
|
||||
//
|
||||
// 只返回未删除的。软删除的记录还在库里(sku_mappings 指着它),
|
||||
// 但界面上不该看见。
|
||||
func ListPddProducts(q Execer, keyword string, status model.CollectStatus, limit, offset int) ([]PddProductRow, int, error) {
|
||||
where, args := pddListFilter(keyword, status)
|
||||
var total int
|
||||
if err := q.QueryRow(`SELECT COUNT(*) FROM pdd_products`+where, args...).Scan(&total); err != nil {
|
||||
return nil, 0, fmt.Errorf("统计 PDD 商品列表失败: %w", err)
|
||||
}
|
||||
sqlText := `SELECT ` + pddColumns + `,` + pddSkuCountExpr + `
|
||||
FROM pdd_products` + where
|
||||
// 刚动过的排前面,操作员点完"创建采集任务"一眼能看到结果
|
||||
sqlText += ` ORDER BY updated_at DESC, id DESC LIMIT ? OFFSET ?`
|
||||
listArgs := append(append([]any{}, args...), limit, offset)
|
||||
|
||||
rows, err := q.Query(sqlText, listArgs...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询 PDD 商品列表失败: %w", err)
|
||||
return nil, 0, fmt.Errorf("查询 PDD 商品列表失败: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
@@ -209,12 +219,15 @@ func ListPddProducts(q Execer, keyword string, status model.CollectStatus) ([]Pd
|
||||
var r PddProductRow
|
||||
p, err := scanPddProduct(rows, &r.SkuCount)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取 PDD 商品列表失败: %w", err)
|
||||
return nil, 0, fmt.Errorf("读取 PDD 商品列表失败: %w", err)
|
||||
}
|
||||
r.PddProduct = *p
|
||||
list = append(list, r)
|
||||
}
|
||||
return list, rows.Err()
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// escapeLike 把 LIKE 的三个特殊字符转义掉,配合 ESCAPE '\' 使用。
|
||||
|
||||
@@ -329,7 +329,7 @@ func taskFilterClause(filter TaskFilter) (string, []any) {
|
||||
// 「目标」列更好读;join 条件里的 `deleted_at IS NULL` 让"join 不到"和
|
||||
// "商品已软删除"这两种情况都自然落到 PddTitle 为空,service 层退回显示
|
||||
// PddGoodsID 即可,不用在 service 里再判断一次已删除。
|
||||
func ListTasks(q Execer, filter TaskFilter) ([]TaskListRow, error) {
|
||||
func ListTasks(q Execer, filter TaskFilter, limit, offset int) ([]TaskListRow, error) {
|
||||
where, args := taskFilterClause(filter)
|
||||
sqlText := `
|
||||
SELECT t.task_id, t.task_type, t.status, t.assigned_client,
|
||||
@@ -338,7 +338,8 @@ func ListTasks(q Execer, filter TaskFilter) ([]TaskListRow, error) {
|
||||
FROM tasks t
|
||||
LEFT JOIN pdd_products p
|
||||
ON p.goods_id = t.pdd_goods_id AND p.deleted_at IS NULL` +
|
||||
where + ` ORDER BY t.updated_at DESC, t.task_id DESC`
|
||||
where + ` ORDER BY t.updated_at DESC, t.task_id DESC LIMIT ? OFFSET ?`
|
||||
args = append(args, limit, offset)
|
||||
|
||||
rows, err := q.Query(sqlText, args...)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user