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
+37
View File
@@ -3,6 +3,7 @@ package service
import (
"database/sql"
"errors"
"fmt"
"path/filepath"
"sync"
"testing"
@@ -12,6 +13,42 @@ import (
"cmautobuy/admin/repository"
)
func TestListClientPageForUser_分页统计不泄露其他采购员客户端(t *testing.T) {
db := newTestDB(t)
admin, buyerA, _ := prepareClientAssignmentUsers(t, db)
now := time.Now().UTC()
for i := 0; i < 22; i++ {
clientID := fmt.Sprintf("page-client-%02d", i)
if err := RegisterClient(db, model.Client{ClientID: clientID, Name: clientID}, true); err != nil {
t.Fatal(err)
}
if i < 21 {
if _, _, err := AssignClient(db, admin, clientID, buyerA.UserID, now); err != nil {
t.Fatalf("绑定 %s 失败: %v", clientID, err)
}
}
}
buyerPage, err := ListClientPageForUser(db, buyerA, "", time.Hour, 2)
if err != nil {
t.Fatal(err)
}
if len(buyerPage.Rows) != 1 || buyerPage.Total != 21 || buyerPage.Page != 2 || buyerPage.TotalPages != 2 {
t.Fatalf("采购员分页不对: rows=%d total=%d page=%d/%d",
len(buyerPage.Rows), buyerPage.Total, buyerPage.Page, buyerPage.TotalPages)
}
if buyerPage.Online+buyerPage.Offline != 21 {
t.Fatalf("采购员统计泄露或漏算: online=%d offline=%d", buyerPage.Online, buyerPage.Offline)
}
adminPage, err := ListClientPageForUser(db, admin, "", time.Hour, 999)
if err != nil {
t.Fatal(err)
}
if adminPage.Total != 22 || adminPage.Page != 2 || len(adminPage.Rows) != 2 {
t.Fatalf("管理员分页不对: rows=%d total=%d page=%d", len(adminPage.Rows), adminPage.Total, adminPage.Page)
}
}
// newTestDB 建一个临时库,测试结束自动删。
// 用真实的 migrations,这样表结构变了测试会跟着失败。
func newTestDB(t *testing.T) *sql.DB {
+26 -10
View File
@@ -238,26 +238,42 @@ type PddProductView struct {
// PddListResult 是列表页要的全部数据。
type PddListResult struct {
Rows []PddProductView
Total int // 全部未删除记录数,不受筛选影响
Counts map[model.CollectStatus]int
IsFiltered bool
Rows []PddProductView
Total int // 全部未删除记录数,不受筛选影响
FilteredTotal int // 当前筛选下的总数,用于页数和筛选状态
Page int
TotalPages int
Counts map[model.CollectStatus]int
IsFiltered bool
}
// ListPddProducts 查列表并把每一行翻成界面文字。
func ListPddProducts(db *sql.DB, keyword string, status model.CollectStatus) (*PddListResult, error) {
rows, err := repository.ListPddProducts(db, keyword, status)
func ListPddProducts(db *sql.DB, keyword string, status model.CollectStatus, requestedPage int) (*PddListResult, error) {
page := requestedPage
if page < 1 {
page = 1
}
rows, filteredTotal, err := repository.ListPddProducts(db, keyword, status, PageSize, (page-1)*PageSize)
if err != nil {
return nil, err
}
totalPages := TotalPages(filteredTotal)
clampedPage := ClampPage(page, totalPages)
if clampedPage != page {
page = clampedPage
rows, _, err = repository.ListPddProducts(db, keyword, status, PageSize, (page-1)*PageSize)
if err != nil {
return nil, err
}
}
counts, err := repository.CountPddProductsByStatus(db)
if err != nil {
return nil, err
}
result := &PddListResult{
Rows: make([]PddProductView, 0, len(rows)),
Counts: counts,
Rows: make([]PddProductView, 0, len(rows)), Counts: counts,
FilteredTotal: filteredTotal, Page: page, TotalPages: totalPages,
IsFiltered: strings.TrimSpace(keyword) != "" || status != "",
}
for _, n := range counts {
@@ -312,9 +328,9 @@ func (r *PddListResult) StatusLine() string {
}
line := strings.Join(parts, " · ")
if r.IsFiltered {
line = fmt.Sprintf("筛选出 %d 条 / %s", len(r.Rows), line)
line = fmt.Sprintf("筛选出 %d 条 / %s", r.FilteredTotal, line)
}
return line
return fmt.Sprintf("%s · 第 %d/%d 页", line, r.Page, r.TotalPages)
}
// ---------- 弹窗 ----------
+37 -9
View File
@@ -3,6 +3,7 @@ package service
import (
"database/sql"
"errors"
"fmt"
"strings"
"sync"
"testing"
@@ -12,6 +13,33 @@ import (
"cmautobuy/admin/repository"
)
func TestListPddProducts_统一每页20条并收敛越界页(t *testing.T) {
db := newTestDB(t)
for i := 0; i < 25; i++ {
goodsID := fmt.Sprintf("73711653%04d", i)
if _, err := CreatePddProduct(db,
"https://mobile.yangkeduo.com/goods.html?goods_id="+goodsID); err != nil {
t.Fatalf("创建第 %d 个商品失败: %v", i, err)
}
}
second, err := ListPddProducts(db, "", "", 2)
if err != nil {
t.Fatal(err)
}
if len(second.Rows) != 5 || second.FilteredTotal != 25 || second.Page != 2 || second.TotalPages != 2 {
t.Fatalf("第二页结果不对: rows=%d total=%d page=%d/%d",
len(second.Rows), second.FilteredTotal, second.Page, second.TotalPages)
}
overflow, err := ListPddProducts(db, "", "", 999)
if err != nil {
t.Fatal(err)
}
if overflow.Page != 2 || len(overflow.Rows) != 5 {
t.Fatalf("越界页应收敛到末页: page=%d rows=%d", overflow.Page, len(overflow.Rows))
}
}
// ── 链接解析 ───────────────────────────────────────────
func TestParsePddGoodsID_认得的写法(t *testing.T) {
@@ -259,7 +287,7 @@ func TestListPddProducts_规格数(t *testing.T) {
repository.SetCollectResult(db, "100000000002", "有规格的", "", sampleSkusJSON)
repository.SetCollectResult(db, "100000000003", "没规格的", "", `{"skus": []}`)
result, err := ListPddProducts(db, "", "")
result, err := ListPddProducts(db, "", "", 1)
if err != nil {
t.Fatalf("查列表失败: %v", err)
}
@@ -287,7 +315,7 @@ func TestListPddProducts_坏掉的采集结果不影响列表打开(t *testing.T
t.Fatalf("造坏数据失败: %v", err)
}
result, err := ListPddProducts(db, "", "")
result, err := ListPddProducts(db, "", "", 1)
if err != nil {
t.Fatalf("列表页应该照样打得开: %v", err)
}
@@ -302,7 +330,7 @@ func TestListPddProducts_按采集状态筛选(t *testing.T) {
createProduct(t, db, "100000000002")
repository.SetCollectResult(db, "100000000002", "已采的", "", sampleSkusJSON)
collected, err := ListPddProducts(db, "", model.CollectCollected)
collected, err := ListPddProducts(db, "", model.CollectCollected, 1)
if err != nil {
t.Fatalf("筛选失败: %v", err)
}
@@ -324,12 +352,12 @@ func TestListPddProducts_按商品ID和链接搜索(t *testing.T) {
createProduct(t, db, "737116531267")
createProduct(t, db, "999888777666")
byID, _ := ListPddProducts(db, "7371165", "")
byID, _ := ListPddProducts(db, "7371165", "", 1)
if len(byID.Rows) != 1 || byID.Rows[0].GoodsID != "737116531267" {
t.Errorf("按 ID 片段搜索失败,命中 %d 条", len(byID.Rows))
}
byURL, _ := ListPddProducts(db, "yangkeduo", "")
byURL, _ := ListPddProducts(db, "yangkeduo", "", 1)
if len(byURL.Rows) != 2 {
t.Errorf("按链接搜索应命中 2 条,实际 %d 条", len(byURL.Rows))
}
@@ -344,7 +372,7 @@ func TestListPddProducts_搜索词里的通配符不当通配符用(t *testing.T
createProduct(t, db, "737116531267")
for _, kw := range []string{"7_7", "%"} {
result, err := ListPddProducts(db, kw, "")
result, err := ListPddProducts(db, kw, "", 1)
if err != nil {
t.Fatalf("搜 %q 出错: %v", kw, err)
}
@@ -359,7 +387,7 @@ func TestListPddProducts_删除的查不到(t *testing.T) {
createProduct(t, db, "737116531267")
DeletePddProducts(db, []string{"737116531267"})
result, _ := ListPddProducts(db, "", "")
result, _ := ListPddProducts(db, "", "", 1)
if len(result.Rows) != 0 {
t.Errorf("软删除的不该出现在列表里,实际 %d 条", len(result.Rows))
}
@@ -374,7 +402,7 @@ func TestStatusLine_四个状态都列出来(t *testing.T) {
createProduct(t, db, "100000000002")
repository.SetCollectResult(db, "100000000002", "已采的", "", sampleSkusJSON)
result, _ := ListPddProducts(db, "", "")
result, _ := ListPddProducts(db, "", "", 1)
line := result.StatusLine()
for _, want := range []string{"共 2 条", "已采集 1", "未采集 1", "采集中 0", "采集失败 0"} {
if !strings.Contains(line, want) {
@@ -444,7 +472,7 @@ func TestPdd页面_显示店铺与按颜色采样标记(t *testing.T) {
repository.SetCollectResult(
db, "737116531267", "测试商品", "测试旗舰店", resultJSON)
list, err := ListPddProducts(db, "", "")
list, err := ListPddProducts(db, "", "", 1)
if err != nil {
t.Fatalf("读取列表失败: %v", err)
}
+63 -3
View File
@@ -131,17 +131,25 @@ func ListClientViews(db *sql.DB, keyword string, threshold time.Duration) ([]Cli
// ListClientViewsForUser 按网页登录身份限制可见范围:管理员全量,采购员只看自己。
func ListClientViewsForUser(db *sql.DB, actor *model.User, keyword string, threshold time.Duration) ([]ClientView, error) {
visibleUserID, err := visibleClientUserID(actor)
if err != nil {
return nil, err
}
return listClientViews(db, keyword, visibleUserID, threshold)
}
func visibleClientUserID(actor *model.User) (string, error) {
if actor == nil {
return nil, ErrUnauthenticated
return "", ErrUnauthenticated
}
visibleUserID := ""
if !actor.IsAdmin() {
if actor.Role != model.RolePurchaser {
return nil, ErrAdminRequired
return "", ErrAdminRequired
}
visibleUserID = actor.UserID
}
return listClientViews(db, keyword, visibleUserID, threshold)
return visibleUserID, nil
}
func listClientViews(db *sql.DB, keyword, visibleUserID string, threshold time.Duration) ([]ClientView, error) {
@@ -163,6 +171,58 @@ func listClientViews(db *sql.DB, keyword, visibleUserID string, threshold time.D
return views, nil
}
// ClientListResult 是客户端页面的一页数据和同一权限范围内的完整统计。
type ClientListResult struct {
Rows []ClientView
Page int
Total int
TotalPages int
Online int
Offline int
}
// ListClientPageForUser 按网页登录身份、搜索词和统一页大小返回客户端列表。
func ListClientPageForUser(db *sql.DB, actor *model.User, keyword string, threshold time.Duration, requestedPage int) (*ClientListResult, error) {
visibleUserID, err := visibleClientUserID(actor)
if err != nil {
return nil, err
}
now := time.Now().UTC()
page := requestedPage
if page < 1 {
page = 1
}
rows, total, err := repository.ListClientsForUserPage(db, keyword, visibleUserID, PageSize, (page-1)*PageSize)
if err != nil {
return nil, err
}
totalPages := TotalPages(total)
clampedPage := ClampPage(page, totalPages)
if clampedPage != page {
page = clampedPage
rows, _, err = repository.ListClientsForUserPage(db, keyword, visibleUserID, PageSize, (page-1)*PageSize)
if err != nil {
return nil, err
}
}
online, err := repository.CountOnlineClientsForUser(
db, keyword, visibleUserID, now.Add(-threshold).Format(model.TimeLayout))
if err != nil {
return nil, err
}
views := make([]ClientView, 0, len(rows))
for _, client := range rows {
views = append(views, ClientView{
Client: client.Client, Status: client.StatusText(now, threshold),
AssignedUserID: client.AssignedUserID, AssignedUsername: client.AssignedUsername,
})
}
return &ClientListResult{
Rows: views, Page: page, Total: total, TotalPages: totalPages,
Online: online, Offline: total - online,
}, nil
}
// ListAssignableClients 返回当前用户在采购任务页面可选择的客户端。
// 当前创建页面尚未实现,本函数固定未来入口也必须沿用相同权限边界。
func ListAssignableClients(db *sql.DB, actor *model.User, threshold time.Duration) ([]ClientView, error) {
+16 -11
View File
@@ -233,9 +233,11 @@ type TaskView struct {
// TaskListResult 是列表页要的全部数据。
type TaskListResult struct {
Rows []TaskView
Counts map[model.TaskStatus]int
Total int // 当前筛选下的总数(= Counts 求和),不是全库总数
Rows []TaskView
Counts map[model.TaskStatus]int
Total int // 当前筛选下的总数(= Counts 求和),不是全库总数
Page int
TotalPages int
// IsFiltered 为 false 时如果 Rows 也是空的,说明库里从来没建过任务
// (不筛选就是查全表,全表空自然等价于"从没有过");为 true 时
@@ -247,12 +249,18 @@ type TaskListResult struct {
//
// `[必须]` 底部统计要跟随当前筛选,所以 Counts 和 Rows 用的是**同一个**
// filter,见 #19。
func ListTasksView(db *sql.DB, filter repository.TaskFilter) (*TaskListResult, error) {
rows, err := repository.ListTasks(db, filter)
func ListTasksView(db *sql.DB, filter repository.TaskFilter, requestedPage int) (*TaskListResult, error) {
counts, err := repository.CountTasksByStatus(db, filter)
if err != nil {
return nil, err
}
counts, err := repository.CountTasksByStatus(db, filter)
total := 0
for _, n := range counts {
total += n
}
totalPages := TotalPages(total)
page := ClampPage(requestedPage, totalPages)
rows, err := repository.ListTasks(db, filter, PageSize, (page-1)*PageSize)
if err != nil {
return nil, err
}
@@ -260,13 +268,10 @@ func ListTasksView(db *sql.DB, filter repository.TaskFilter) (*TaskListResult, e
result := &TaskListResult{
Rows: make([]TaskView, 0, len(rows)),
Counts: counts,
Total: total, Page: page, TotalPages: totalPages,
IsFiltered: filter.Type != "" || filter.Status != "" ||
strings.TrimSpace(filter.Keyword) != "",
}
for _, n := range counts {
result.Total += n
}
for _, r := range rows {
result.Rows = append(result.Rows, TaskView{
TaskID: r.TaskID,
@@ -292,7 +297,7 @@ func (r *TaskListResult) StatusLine() string {
for _, s := range taskStatusOrder {
parts = append(parts, fmt.Sprintf("%s %d", taskStatusText(s), r.Counts[s]))
}
return strings.Join(parts, " · ")
return fmt.Sprintf("%s · 第 %d/%d 页", strings.Join(parts, " · "), r.Page, r.TotalPages)
}
// DeleteTasks 批量删除任务,返回实际删掉的条数。
+42 -16
View File
@@ -2,12 +2,38 @@ package service
import (
"database/sql"
"fmt"
"testing"
"cmautobuy/admin/model"
"cmautobuy/admin/repository"
)
func TestListTasksView_统一每页20条并收敛越界页(t *testing.T) {
db := newTestDB(t)
for i := 0; i < 25; i++ {
insertTestTask(t, db, testTaskParams{
taskID: fmt.Sprintf("PAGE-%02d", i), taskType: model.TaskCollect,
status: model.TaskPending, pddGoodsID: fmt.Sprintf("%d", i+1),
})
}
second, err := ListTasksView(db, repository.TaskFilter{}, 2)
if err != nil {
t.Fatal(err)
}
if len(second.Rows) != 5 || second.Total != 25 || second.Page != 2 || second.TotalPages != 2 {
t.Fatalf("第二页结果不对: rows=%d total=%d page=%d/%d",
len(second.Rows), second.Total, second.Page, second.TotalPages)
}
overflow, err := ListTasksView(db, repository.TaskFilter{}, 999)
if err != nil {
t.Fatal(err)
}
if overflow.Page != 2 || len(overflow.Rows) != 5 {
t.Fatalf("越界页应收敛到末页: page=%d rows=%d", overflow.Page, len(overflow.Rows))
}
}
// insertTestTask 插一条任务,字段按需覆盖默认值,供本文件的用例使用。
// 跟 client_test.go 里的 insertTask 不一样:那个专为"领取"场景写死了固定值,
// 这里需要覆盖类型、状态、options 等更多维度。
@@ -94,7 +120,7 @@ func TestListTasksView_同时显示采集和采购(t *testing.T) {
insertTestTask(t, db, testTaskParams{taskID: "COL-1", taskType: model.TaskCollect, status: model.TaskPending, pddGoodsID: "111"})
insertTestTask(t, db, testTaskParams{taskID: "PUR-1", taskType: model.TaskPurchase, status: model.TaskAssigned, orderNo: "SO-1"})
result, err := ListTasksView(db, repository.TaskFilter{})
result, err := ListTasksView(db, repository.TaskFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -110,7 +136,7 @@ func TestListTasksView_类型筛选生效(t *testing.T) {
insertTestTask(t, db, testTaskParams{taskID: "COL-1", taskType: model.TaskCollect, status: model.TaskPending, pddGoodsID: "111"})
insertTestTask(t, db, testTaskParams{taskID: "PUR-1", taskType: model.TaskPurchase, status: model.TaskAssigned, orderNo: "SO-1"})
result, err := ListTasksView(db, repository.TaskFilter{Type: model.TaskCollect})
result, err := ListTasksView(db, repository.TaskFilter{Type: model.TaskCollect}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -127,7 +153,7 @@ func TestListTasksView_状态筛选生效(t *testing.T) {
insertTestTask(t, db, testTaskParams{taskID: "COL-1", taskType: model.TaskCollect, status: model.TaskPending, pddGoodsID: "111"})
insertTestTask(t, db, testTaskParams{taskID: "COL-2", taskType: model.TaskCollect, status: model.TaskSucceeded, pddGoodsID: "222"})
result, err := ListTasksView(db, repository.TaskFilter{Status: model.TaskSucceeded})
result, err := ListTasksView(db, repository.TaskFilter{Status: model.TaskSucceeded}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -143,17 +169,17 @@ func TestListTasksView_关键词同时匹配任务编号订单号PDD商品ID(t *
insertTestTask(t, db, testTaskParams{taskID: "PUR-2", taskType: model.TaskPurchase, status: model.TaskAssigned, orderNo: "SO-OTHER"})
// 按任务编号命中
r, err := ListTasksView(db, repository.TaskFilter{Keyword: "ABC"})
r, err := ListTasksView(db, repository.TaskFilter{Keyword: "ABC"}, 1)
if err != nil || len(r.Rows) != 1 || r.Rows[0].TaskID != "COL-ABC" {
t.Fatalf("按任务编号搜索失败: err=%v rows=%+v", err, r)
}
// 按订单号命中
r, err = ListTasksView(db, repository.TaskFilter{Keyword: "XYZ"})
r, err = ListTasksView(db, repository.TaskFilter{Keyword: "XYZ"}, 1)
if err != nil || len(r.Rows) != 1 || r.Rows[0].TaskID != "PUR-1" {
t.Fatalf("按订单号搜索失败: err=%v rows=%+v", err, r)
}
// 按 PDD 商品 ID 命中
r, err = ListTasksView(db, repository.TaskFilter{Keyword: "737116531267"})
r, err = ListTasksView(db, repository.TaskFilter{Keyword: "737116531267"}, 1)
if err != nil || len(r.Rows) != 1 || r.Rows[0].TaskID != "COL-ABC" {
t.Fatalf("按 PDD 商品 ID 搜索失败: err=%v rows=%+v", err, r)
}
@@ -165,7 +191,7 @@ func TestListTasksView_目标列_采集任务显示PDD商品ID(t *testing.T) {
db := newTestDB(t)
insertTestTask(t, db, testTaskParams{taskID: "COL-1", taskType: model.TaskCollect, status: model.TaskPending, pddGoodsID: "737116531267"})
r, err := ListTasksView(db, repository.TaskFilter{})
r, err := ListTasksView(db, repository.TaskFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -185,7 +211,7 @@ func TestListTasksView_目标列_采集任务能join到标题时追加显示(t *
}
insertTestTask(t, db, testTaskParams{taskID: "COL-1", taskType: model.TaskCollect, status: model.TaskSucceeded, pddGoodsID: "737116531267"})
r, err := ListTasksView(db, repository.TaskFilter{})
r, err := ListTasksView(db, repository.TaskFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -206,7 +232,7 @@ func TestListTasksView_目标列_商品已软删除时退回显示商品ID(t *te
}
insertTestTask(t, db, testTaskParams{taskID: "COL-1", taskType: model.TaskCollect, status: model.TaskPending, pddGoodsID: "737116531267"})
r, err := ListTasksView(db, repository.TaskFilter{})
r, err := ListTasksView(db, repository.TaskFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -224,7 +250,7 @@ func TestListTasksView_目标列_采购任务显示订单号规格数量价格
quantity: 2, maxPriceCent: 4200,
})
r, err := ListTasksView(db, repository.TaskFilter{})
r, err := ListTasksView(db, repository.TaskFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -240,7 +266,7 @@ func TestListTasksView_无主任务客户端列显示占位符(t *testing.T) {
db := newTestDB(t)
insertTestTask(t, db, testTaskParams{taskID: "COL-1", taskType: model.TaskCollect, status: model.TaskPending, pddGoodsID: "111"})
r, err := ListTasksView(db, repository.TaskFilter{})
r, err := ListTasksView(db, repository.TaskFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -258,7 +284,7 @@ func TestListTasksView_统计跟随当前筛选(t *testing.T) {
insertTestTask(t, db, testTaskParams{taskID: "PUR-1", taskType: model.TaskPurchase, status: model.TaskAssigned, orderNo: "SO-1"})
insertTestTask(t, db, testTaskParams{taskID: "PUR-2", taskType: model.TaskPurchase, status: model.TaskAssigned, orderNo: "SO-2"})
all, err := ListTasksView(db, repository.TaskFilter{})
all, err := ListTasksView(db, repository.TaskFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -266,7 +292,7 @@ func TestListTasksView_统计跟随当前筛选(t *testing.T) {
t.Fatalf("不筛选时总数应该是 4,实际 %d", all.Total)
}
collectOnly, err := ListTasksView(db, repository.TaskFilter{Type: model.TaskCollect})
collectOnly, err := ListTasksView(db, repository.TaskFilter{Type: model.TaskCollect}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -285,7 +311,7 @@ func TestListTasksView_统计跟随当前筛选(t *testing.T) {
func TestListTasksView_从未建过任务时不算筛选无结果(t *testing.T) {
db := newTestDB(t)
r, err := ListTasksView(db, repository.TaskFilter{})
r, err := ListTasksView(db, repository.TaskFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -301,7 +327,7 @@ func TestListTasksView_筛选无结果时IsFiltered为true(t *testing.T) {
db := newTestDB(t)
insertTestTask(t, db, testTaskParams{taskID: "COL-1", taskType: model.TaskCollect, status: model.TaskPending, pddGoodsID: "111"})
r, err := ListTasksView(db, repository.TaskFilter{Type: model.TaskPurchase})
r, err := ListTasksView(db, repository.TaskFilter{Type: model.TaskPurchase}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
@@ -416,7 +442,7 @@ func TestDeleteTasks_批量删除(t *testing.T) {
t.Fatalf("应该删除 1 条,实际 %d", n)
}
r, err := ListTasksView(db, repository.TaskFilter{})
r, err := ListTasksView(db, repository.TaskFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}