feat: 统一 Admin 主列表分页 (#64)

This commit is contained in:
chengma
2026-08-09 21:51:20 +08:00
parent 5c9ebd968c
commit 155dd3b4a2
23 changed files with 549 additions and 120 deletions
+53 -13
View File
@@ -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(`