From e6c4f58251a592836e5a2b58226e4e7dcddab539 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 17 Oct 2026 15:35:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(invitation):=20=E4=BF=AE=E5=A4=8D=E9=82=80?= =?UTF-8?q?=E8=AF=B7=E5=85=B3=E7=B3=BB=E6=89=8B=E6=9C=BA=E5=8F=B7=E4=B8=8E?= =?UTF-8?q?=E9=82=80=E8=AF=B7=E7=A0=81=E7=AD=9B=E9=80=89=E5=B9=B6=E9=A2=84?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/invitation/admin_filters_test.go | 151 ++++++++++++++++++ backend/internal/modules/invitation/dto.go | 7 +- .../internal/modules/invitation/repository.go | 17 +- .../features/admin/api/adminInvitations.ts | 7 + .../admin/views/AdminInvitationsView.vue | 42 +++-- 5 files changed, 201 insertions(+), 23 deletions(-) create mode 100644 backend/internal/modules/invitation/admin_filters_test.go diff --git a/backend/internal/modules/invitation/admin_filters_test.go b/backend/internal/modules/invitation/admin_filters_test.go new file mode 100644 index 0000000..47c1e53 --- /dev/null +++ b/backend/internal/modules/invitation/admin_filters_test.go @@ -0,0 +1,151 @@ +package invitation + +import ( + "context" + "testing" + "time" + + "hfb_sys/backend/internal/model" +) + +func TestListAdminInvitationsFiltersPhoneAndInviteCode(t *testing.T) { + db := setupTestDB(t) + repo := NewRepository(db) + ctx := context.Background() + now := time.Now() + + users := []model.User{ + {Phone: "13812345678", Nickname: "邀请人甲", Status: "active", RealnameStatus: "verified"}, + {Phone: "13911112222", Nickname: "被邀请人甲", Status: "active", RealnameStatus: "verified"}, + {Phone: "13700001111", Nickname: "邀请人乙", Status: "active", RealnameStatus: "verified"}, + {Phone: "13622223333", Nickname: "被邀请人乙", Status: "active", RealnameStatus: "verified"}, + } + for i := range users { + if err := db.Create(&users[i]).Error; err != nil { + t.Fatalf("创建测试用户失败: %v", err) + } + } + + records := []model.UserInvitation{ + { + InviterID: users[0].ID, + InviteeID: users[1].ID, + InviteCode: "alpha01", + Status: StatusActive, + CreatedAt: now, + }, + { + InviterID: users[2].ID, + InviteeID: users[3].ID, + InviteCode: "beta02", + Status: StatusActive, + CreatedAt: now, + }, + } + for i := range records { + if err := db.Create(&records[i]).Error; err != nil { + t.Fatalf("创建邀请关系失败: %v", err) + } + } + + cases := []struct { + name string + query AdminInvitationQuery + want int64 + }{ + {name: "通用手机号匹配邀请人", query: AdminInvitationQuery{Phone: "1234"}, want: 1}, + {name: "通用手机号匹配被邀请人", query: AdminInvitationQuery{Phone: "139111"}, want: 1}, + {name: "邀请码模糊匹配", query: AdminInvitationQuery{InviteCode: "beta"}, want: 1}, + {name: "邀请人手机号模糊匹配", query: AdminInvitationQuery{InviterPhone: "137"}, want: 1}, + {name: "被邀请人手机号模糊匹配", query: AdminInvitationQuery{InviteePhone: "1362"}, want: 1}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + tc.query.Page = 1 + tc.query.PageSize = 20 + _, total, err := repo.ListAdminInvitations(ctx, tc.query) + if err != nil { + t.Fatalf("查询邀请关系失败: %v", err) + } + if total != tc.want { + t.Fatalf("查询结果数量 = %d, want %d", total, tc.want) + } + }) + } +} + +func TestListAdminRebateLedgersFiltersPartialPhoneAndOrderNo(t *testing.T) { + db := setupTestDB(t) + repo := NewRepository(db) + ctx := context.Background() + now := time.Now() + + users := []model.User{ + {Phone: "13812345678", Nickname: "邀请人甲", Status: "active", RealnameStatus: "verified"}, + {Phone: "13911112222", Nickname: "被邀请人甲", Status: "active", RealnameStatus: "verified"}, + {Phone: "13700001111", Nickname: "邀请人乙", Status: "active", RealnameStatus: "verified"}, + {Phone: "13622223333", Nickname: "被邀请人乙", Status: "active", RealnameStatus: "verified"}, + } + for i := range users { + if err := db.Create(&users[i]).Error; err != nil { + t.Fatalf("创建测试用户失败: %v", err) + } + } + + ledgers := []model.InvitationRebateLedger{ + { + OrderID: 1001, + OrderNo: "ORDER-ALPHA-1001", + Source: "rental_order", + InviterID: users[0].ID, + InviteeID: users[1].ID, + TradeRole: "buyer", + RateApplied: 0.6, + CycleStage: CycleFirstMonth, + Status: "settled", + RebateAmountCent: 100, + SettledAt: now, + }, + { + OrderID: 1002, + OrderNo: "ORDER-BETA-1002", + Source: "pickup", + InviterID: users[2].ID, + InviteeID: users[3].ID, + TradeRole: "seller", + RateApplied: 0.3, + CycleStage: CycleSecondMonth, + Status: "settled", + RebateAmountCent: 200, + SettledAt: now, + }, + } + for i := range ledgers { + if err := db.Create(&ledgers[i]).Error; err != nil { + t.Fatalf("创建返利流水失败: %v", err) + } + } + + cases := []struct { + name string + query AdminRebateLedgerQuery + want int64 + }{ + {name: "订单号模糊匹配", query: AdminRebateLedgerQuery{OrderNo: "ALPHA"}, want: 1}, + {name: "邀请人手机号模糊匹配", query: AdminRebateLedgerQuery{InviterPhone: "1381"}, want: 1}, + {name: "被邀请人手机号模糊匹配", query: AdminRebateLedgerQuery{InviteePhone: "1362"}, want: 1}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + tc.query.Page = 1 + tc.query.PageSize = 20 + _, total, err := repo.ListAdminRebateLedgers(ctx, tc.query) + if err != nil { + t.Fatalf("查询返利流水失败: %v", err) + } + if total != tc.want { + t.Fatalf("查询结果数量 = %d, want %d", total, tc.want) + } + }) + } +} diff --git a/backend/internal/modules/invitation/dto.go b/backend/internal/modules/invitation/dto.go index 5d6e18d..0c6056c 100644 --- a/backend/internal/modules/invitation/dto.go +++ b/backend/internal/modules/invitation/dto.go @@ -76,8 +76,11 @@ type UpdateConfigRequest struct { // AdminInvitationQuery 后台查询邀请关系 type AdminInvitationQuery struct { - Page int `form:"page,default=1"` - PageSize int `form:"page_size,default=20"` + Page int `form:"page,default=1"` + PageSize int `form:"page_size,default=20"` + // Phone 为通用手机号搜索:同时匹配邀请人/被邀请人,支持模糊查询。 + Phone string `form:"phone"` + InviteCode string `form:"invite_code"` InviterPhone string `form:"inviter_phone"` InviteePhone string `form:"invitee_phone"` Status string `form:"status"` diff --git a/backend/internal/modules/invitation/repository.go b/backend/internal/modules/invitation/repository.go index 071c29a..d320c5f 100644 --- a/backend/internal/modules/invitation/repository.go +++ b/backend/internal/modules/invitation/repository.go @@ -845,11 +845,18 @@ func (r *Repository) ListAdminInvitations(ctx context.Context, query AdminInvita Joins("LEFT JOIN users inviter ON inviter.id = ui.inviter_id"). Joins("LEFT JOIN users invitee ON invitee.id = ui.invitee_id") + if p := strings.TrimSpace(query.Phone); p != "" { + like := "%" + p + "%" + db = db.Where("(inviter.phone LIKE ? OR invitee.phone LIKE ?)", like, like) + } if p := strings.TrimSpace(query.InviterPhone); p != "" { - db = db.Where("inviter.phone = ?", p) + db = db.Where("inviter.phone LIKE ?", "%"+p+"%") } if p := strings.TrimSpace(query.InviteePhone); p != "" { - db = db.Where("invitee.phone = ?", p) + db = db.Where("invitee.phone LIKE ?", "%"+p+"%") + } + if code := strings.TrimSpace(query.InviteCode); code != "" { + db = db.Where("ui.invite_code LIKE ?", "%"+code+"%") } if s := strings.TrimSpace(query.Status); s != "" { db = db.Where("ui.status = ?", s) @@ -928,13 +935,13 @@ func (r *Repository) ListAdminRebateLedgers(ctx context.Context, query AdminReba Joins("LEFT JOIN users invitee ON invitee.id = irl.invitee_id") if o := strings.TrimSpace(query.OrderNo); o != "" { - db = db.Where("irl.order_no = ?", o) + db = db.Where("irl.order_no LIKE ?", "%"+o+"%") } if p := strings.TrimSpace(query.InviterPhone); p != "" { - db = db.Where("inviter.phone = ?", p) + db = db.Where("inviter.phone LIKE ?", "%"+p+"%") } if p := strings.TrimSpace(query.InviteePhone); p != "" { - db = db.Where("invitee.phone = ?", p) + db = db.Where("invitee.phone LIKE ?", "%"+p+"%") } if t := strings.TrimSpace(query.TradeRole); t != "" { db = db.Where("irl.trade_role = ?", t) diff --git a/frontend/src/features/admin/api/adminInvitations.ts b/frontend/src/features/admin/api/adminInvitations.ts index b137c12..84f0e2f 100644 --- a/frontend/src/features/admin/api/adminInvitations.ts +++ b/frontend/src/features/admin/api/adminInvitations.ts @@ -42,6 +42,13 @@ export interface AdminRebateRow { } export interface AdminInvitationQuery { + phone?: string + invite_code?: string + inviter_phone?: string + invitee_phone?: string + order_no?: string + trade_role?: string + distribution_site_id?: number inviter_id?: number invitee_id?: number page?: number diff --git a/frontend/src/features/admin/views/AdminInvitationsView.vue b/frontend/src/features/admin/views/AdminInvitationsView.vue index 0026d25..f882d33 100644 --- a/frontend/src/features/admin/views/AdminInvitationsView.vue +++ b/frontend/src/features/admin/views/AdminInvitationsView.vue @@ -55,9 +55,9 @@ const rebateQuery = reactive({ const rebates = ref([]) const rebatesTotal = ref(0) -onMounted(async () => { - await loadSites() - await loadConfig() +onMounted(() => { + // 三个页签的数据一起预加载,避免邀请关系/返利流水标签在首次进入时显示 0。 + void Promise.all([loadSites(), loadConfig(), loadInvitations(), loadRebates()]) }) async function loadSites() { @@ -169,6 +169,16 @@ function handleTabChange(tab: 'config' | 'invitations' | 'rebates') { } } +function searchInvitations() { + invitationQuery.page = 1 + void loadInvitations() +} + +function searchRebates() { + rebateQuery.page = 1 + void loadRebates() +} + function cycleLabel(cycle: string) { switch (cycle) { case 'first_month': @@ -295,20 +305,20 @@ function cycleLabel(cycle: string) {
- 查询 - 刷新 + 查询 + 刷新
- 查询 - 刷新 + 查询 + 刷新