package handler import ( "net/http" "strconv" "soul-api/internal/database" "soul-api/internal/model" "github.com/gin-gonic/gin" ) // DBCKBLeadList GET /api/db/ckb-leads 管理端-CKB线索明细 // mode=submitted: ckb_submit_records(join/match 提交) // mode=contact: ckb_lead_records(链接卡若留资,有 phone/wechat) func DBCKBLeadList(c *gin.Context) { db := database.DB() mode := c.DefaultQuery("mode", "submitted") matchType := c.Query("matchType") page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } if mode == "contact" { // ckb_lead_records:链接卡若留资 q := db.Model(&model.CkbLeadRecord{}) var total int64 q.Count(&total) var records []model.CkbLeadRecord if err := q.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } out := make([]gin.H, 0, len(records)) for _, r := range records { out = append(out, gin.H{ "id": r.ID, "userId": r.UserID, "userNickname": r.Nickname, "matchType": "lead", "phone": r.Phone, "wechatId": r.WechatID, "name": r.Name, "createdAt": r.CreatedAt, }) } c.JSON(http.StatusOK, gin.H{"success": true, "records": out, "total": total, "page": page, "pageSize": pageSize}) return } // mode=submitted: ckb_submit_records q := db.Model(&model.CkbSubmitRecord{}) if matchType != "" { // matchType 对应 action: join 时 type 在 params 中,match 时 matchType 在 params 中 // 简化:仅按 action 过滤,join 时 params 含 type if matchType == "join" || matchType == "match" { q = q.Where("action = ?", matchType) } } var total int64 q.Count(&total) var records []model.CkbSubmitRecord if err := q.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } userIDs := make(map[string]bool) for _, r := range records { if r.UserID != "" { userIDs[r.UserID] = true } } ids := make([]string, 0, len(userIDs)) for id := range userIDs { ids = append(ids, id) } var users []model.User if len(ids) > 0 { db.Where("id IN ?", ids).Find(&users) } userMap := make(map[string]*model.User) for i := range users { userMap[users[i].ID] = &users[i] } safeNickname := func(u *model.User) string { if u == nil || u.Nickname == nil { return "" } return *u.Nickname } out := make([]gin.H, 0, len(records)) for _, r := range records { out = append(out, gin.H{ "id": r.ID, "userId": r.UserID, "userNickname": safeNickname(userMap[r.UserID]), "matchType": r.Action, "nickname": r.Nickname, "params": r.Params, "createdAt": r.CreatedAt, }) } c.JSON(http.StatusOK, gin.H{"success": true, "records": out, "total": total, "page": page, "pageSize": pageSize}) } // CKBPersonLeadStats GET /api/db/ckb-person-leads 每个人物的获客线索统计及明细 func CKBPersonLeadStats(c *gin.Context) { db := database.DB() personToken := c.Query("token") if personToken != "" { // 返回某人物的线索明细(通过 token → Person → 用 PersonID 和 Token 匹配 CkbLeadRecord.TargetPersonID) var person model.Person if err := db.Where("token = ?", personToken).First(&person).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": "人物不存在"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } q := db.Model(&model.CkbLeadRecord{}).Where("target_person_id IN ?", []string{person.PersonID, person.Token}) var total int64 q.Count(&total) var records []model.CkbLeadRecord q.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records) out := make([]gin.H, 0, len(records)) for _, r := range records { out = append(out, gin.H{ "id": r.ID, "userId": r.UserID, "nickname": r.Nickname, "phone": r.Phone, "wechatId": r.WechatID, "name": r.Name, "source": r.Source, "createdAt": r.CreatedAt, }) } c.JSON(http.StatusOK, gin.H{ "success": true, "personName": person.Name, "records": out, "total": total, "page": page, "pageSize": pageSize, }) return } // 无 token 参数:返回所有人物的获客数量汇总 type PersonLeadStat struct { TargetPersonID string `gorm:"column:target_person_id"` Total int64 `gorm:"column:total"` } var stats []PersonLeadStat db.Raw("SELECT target_person_id, COUNT(*) as total FROM ckb_lead_records WHERE target_person_id != '' GROUP BY target_person_id").Scan(&stats) // 构建 personId/token → Person.Token 的映射,使前端能用 token 匹配 var persons []model.Person db.Select("person_id, token").Find(&persons) pidToToken := make(map[string]string, len(persons)) for _, p := range persons { pidToToken[p.PersonID] = p.Token pidToToken[p.Token] = p.Token } merged := make(map[string]int64) for _, s := range stats { key := pidToToken[s.TargetPersonID] if key == "" { key = s.TargetPersonID } merged[key] += s.Total } byPerson := make([]gin.H, 0, len(merged)) for token, total := range merged { byPerson = append(byPerson, gin.H{"token": token, "total": total}) } // 同时统计全局(无特定人物的)线索 var globalTotal int64 db.Model(&model.CkbLeadRecord{}).Where("target_person_id = '' OR target_person_id IS NULL").Count(&globalTotal) c.JSON(http.StatusOK, gin.H{ "success": true, "byPerson": byPerson, "globalLeads": globalTotal, }) } // CKBPlanStats GET /api/db/ckb-plan-stats 存客宝获客计划统计(基于 ckb_submit_records + ckb_lead_records) func CKBPlanStats(c *gin.Context) { db := database.DB() type TypeStat struct { Action string `gorm:"column:action" json:"matchType"` Total int64 `gorm:"column:total" json:"total"` } var submitStats []TypeStat db.Raw("SELECT action, COUNT(*) as total FROM ckb_submit_records GROUP BY action").Scan(&submitStats) var submitTotal int64 db.Model(&model.CkbSubmitRecord{}).Count(&submitTotal) var leadTotal int64 db.Model(&model.CkbLeadRecord{}).Count(&leadTotal) withContact := leadTotal // ckb_lead_records 均有 phone 或 wechat c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "ckbTotal": submitTotal + leadTotal, "withContact": withContact, "byType": submitStats, "ckbApiKey": "***", "ckbApiUrl": "https://ckbapi.quwanzhi.com/v1/api/scenarios", "docNotes": "", "docContent": "", "routes": gin.H{}, }, }) }