- 超级个体:去掉首位特例;列表仅展示有头像且非微信默认昵称(vip.go) - 个人资料:居中头像、低调联系方式、点头像优先走存客宝 lead(ckbLeadToken) - 阅读页分享朋友圈复制与 toast 去重 - soul-api: miniprogram users 带 ckbLeadToken;其它 handler 与路由调整 - 脚本:content_upload、miniprogram 上传辅助等 Made-with: Cursor
103 lines
2.8 KiB
Go
103 lines
2.8 KiB
Go
package handler
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"soul-api/internal/database"
|
||
"soul-api/internal/model"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// DBCKBPersonLeads GET /api/db/ckb-person-leads
|
||
// - 不带 token:返回每个 Person 的获客数(基于 ckb_lead_records.target_person_id)
|
||
// - 带 token:返回该 Person 的获客明细(分页)
|
||
func DBCKBPersonLeads(c *gin.Context) {
|
||
db := database.DB()
|
||
token := strings.TrimSpace(c.Query("token"))
|
||
|
||
// 1) 汇总:每个人物的获客数(按 user_id 去重,同一用户多次留资只算 1 人)
|
||
if token == "" {
|
||
type Row struct {
|
||
Token string `json:"token"`
|
||
Total int64 `json:"total"`
|
||
}
|
||
var rows []Row
|
||
if err := db.Raw(`
|
||
SELECT p.token AS token, COUNT(DISTINCT l.user_id) AS total
|
||
FROM persons p
|
||
LEFT JOIN ckb_lead_records l ON l.target_person_id = p.person_id
|
||
GROUP BY p.token
|
||
`).Scan(&rows).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "byPerson": rows})
|
||
return
|
||
}
|
||
|
||
// 2) 明细:某个人物的获客列表
|
||
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
|
||
}
|
||
|
||
var person model.Person
|
||
// token 是管理端/小程序统一引用的主键;兜底允许传 personId(便于排查/手工调用)
|
||
if err := db.Where("token = ? OR person_id = ?", token, token).First(&person).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "未找到人物"})
|
||
return
|
||
}
|
||
|
||
var total int64
|
||
db.Model(&model.CkbLeadRecord{}).Where("target_person_id = ?", person.PersonID).
|
||
Select("COUNT(DISTINCT user_id)").Scan(&total)
|
||
|
||
var records []model.CkbLeadRecord
|
||
if err := db.Raw(`
|
||
SELECT l.*
|
||
FROM ckb_lead_records l
|
||
INNER JOIN (
|
||
SELECT user_id, MAX(created_at) AS max_at
|
||
FROM ckb_lead_records
|
||
WHERE target_person_id = ?
|
||
GROUP BY user_id
|
||
) latest ON l.user_id = latest.user_id AND l.created_at = latest.max_at
|
||
WHERE l.target_person_id = ?
|
||
ORDER BY l.created_at DESC
|
||
LIMIT ? OFFSET ?
|
||
`, person.PersonID, person.PersonID, pageSize, (page-1)*pageSize).Scan(&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,
|
||
"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,
|
||
"records": out,
|
||
"total": total,
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
"personName": person.Name,
|
||
})
|
||
}
|