优化快捷回复联想与个人调用统计,数据统计支持自定义日期
/ 按输入码与个人频次建议(最多10条,唯一自动填入),正文关键字联想;统计页可日历选区间并统一中文日历。
This commit is contained in:
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -22,9 +23,60 @@ func requireStatisticsAccess(c *gin.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func loadStatisticsData(tenantID uint) ([]model.Session, []model.Message, error) {
|
||||
// statsRange 统计时间窗:[From, To) 本地日界
|
||||
type statsRange struct {
|
||||
From time.Time
|
||||
To time.Time // exclusive
|
||||
}
|
||||
|
||||
// parseStatsRange 支持 from&to=YYYY-MM-DD,或 period=today|week|month(默认 week)
|
||||
func parseStatsRange(c *gin.Context) (statsRange, error) {
|
||||
loc := time.Now().Location()
|
||||
now := time.Now().In(loc)
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
|
||||
|
||||
fromStr := strings.TrimSpace(c.Query("from"))
|
||||
toStr := strings.TrimSpace(c.Query("to"))
|
||||
if fromStr != "" && toStr != "" {
|
||||
fromDay, err1 := time.ParseInLocation("2006-01-02", fromStr, loc)
|
||||
toDay, err2 := time.ParseInLocation("2006-01-02", toStr, loc)
|
||||
if err1 != nil || err2 != nil {
|
||||
return statsRange{}, errStr("日期格式应为 YYYY-MM-DD")
|
||||
}
|
||||
if toDay.Before(fromDay) {
|
||||
return statsRange{}, errStr("结束日期不能早于开始日期")
|
||||
}
|
||||
// 最多 366 天
|
||||
if toDay.Sub(fromDay) > 366*24*time.Hour {
|
||||
return statsRange{}, errStr("日期区间最长 366 天")
|
||||
}
|
||||
return statsRange{From: fromDay, To: toDay.AddDate(0, 0, 1)}, nil
|
||||
}
|
||||
|
||||
period := strings.TrimSpace(c.DefaultQuery("period", "week"))
|
||||
switch period {
|
||||
case "today":
|
||||
return statsRange{From: today, To: today.AddDate(0, 0, 1)}, nil
|
||||
case "month":
|
||||
// 本自然月
|
||||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc)
|
||||
nextMonth := monthStart.AddDate(0, 1, 0)
|
||||
return statsRange{From: monthStart, To: nextMonth}, nil
|
||||
case "week", "day":
|
||||
// 近 7 天(含今天)
|
||||
return statsRange{From: today.AddDate(0, 0, -6), To: today.AddDate(0, 0, 1)}, nil
|
||||
default:
|
||||
return statsRange{From: today.AddDate(0, 0, -6), To: today.AddDate(0, 0, 1)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func loadStatisticsData(tenantID uint, r statsRange) ([]model.Session, []model.Message, error) {
|
||||
var sessions []model.Session
|
||||
if err := model.DB.Where("tenant_id = ?", tenantID).Order("created_at asc").Find(&sessions).Error; err != nil {
|
||||
q := model.DB.Where("tenant_id = ?", tenantID)
|
||||
if !r.From.IsZero() {
|
||||
q = q.Where("created_at >= ? AND created_at < ?", r.From, r.To)
|
||||
}
|
||||
if err := q.Order("created_at asc").Find(&sessions).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(sessions) == 0 {
|
||||
@@ -101,7 +153,12 @@ func (h *StatisticsHandler) KPIs(c *gin.Context) {
|
||||
if !requireStatisticsAccess(c) {
|
||||
return
|
||||
}
|
||||
sessions, messages, err := loadStatisticsData(middleware.GetTenantID(c))
|
||||
r, err := parseStatsRange(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
sessions, messages, err := loadStatisticsData(middleware.GetTenantID(c), r)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "查询统计数据失败"})
|
||||
return
|
||||
@@ -141,23 +198,30 @@ func (h *StatisticsHandler) SessionTrend(c *gin.Context) {
|
||||
if !requireStatisticsAccess(c) {
|
||||
return
|
||||
}
|
||||
period := c.DefaultQuery("period", "day")
|
||||
r, err := parseStatsRange(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
// 是否有自定义区间:有 from/to 时按日粒度;本月预置可按月展示近 6 月
|
||||
hasCustom := strings.TrimSpace(c.Query("from")) != "" && strings.TrimSpace(c.Query("to")) != ""
|
||||
period := strings.TrimSpace(c.DefaultQuery("period", "week"))
|
||||
|
||||
var sessions []model.Session
|
||||
if err := model.DB.Where("tenant_id = ?", middleware.GetTenantID(c)).Find(&sessions).Error; err != nil {
|
||||
q := model.DB.Where("tenant_id = ?", middleware.GetTenantID(c))
|
||||
// 趋势:自定义/今日/本周用区间内数据;本月预置仍看近 6 个月走势
|
||||
if hasCustom || period == "today" || period == "week" || period == "day" {
|
||||
q = q.Where("created_at >= ? AND created_at < ?", r.From, r.To)
|
||||
}
|
||||
if err := q.Find(&sessions).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "查询会话趋势失败"})
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
data := make([]gin.H, 0)
|
||||
if period == "today" {
|
||||
count := 0
|
||||
for _, session := range sessions {
|
||||
if session.CreatedAt.Format("2006-01-02") == now.Format("2006-01-02") {
|
||||
count++
|
||||
}
|
||||
}
|
||||
data = append(data, gin.H{"date": now.Format("01-02"), "count": count})
|
||||
} else if period == "month" {
|
||||
if !hasCustom && period == "month" {
|
||||
// 近 6 个自然月
|
||||
now := time.Now()
|
||||
counts := make(map[string]int)
|
||||
for _, session := range sessions {
|
||||
counts[session.CreatedAt.Format("2006-01")]++
|
||||
@@ -168,14 +232,19 @@ func (h *StatisticsHandler) SessionTrend(c *gin.Context) {
|
||||
data = append(data, gin.H{"date": key, "count": counts[key]})
|
||||
}
|
||||
} else {
|
||||
// 按日:从 From 到 To(不含)
|
||||
counts := make(map[string]int)
|
||||
for _, session := range sessions {
|
||||
counts[session.CreatedAt.Format("01-02")]++
|
||||
counts[session.CreatedAt.Format("2006-01-02")]++
|
||||
}
|
||||
for offset := 6; offset >= 0; offset-- {
|
||||
day := now.AddDate(0, 0, -offset)
|
||||
key := day.Format("01-02")
|
||||
data = append(data, gin.H{"date": key, "count": counts[key]})
|
||||
for d := r.From; d.Before(r.To); d = d.AddDate(0, 0, 1) {
|
||||
key := d.Format("2006-01-02")
|
||||
label := d.Format("01-02")
|
||||
// 跨年区间显示完整日期
|
||||
if r.To.Sub(r.From) > 180*24*time.Hour || d.Year() != time.Now().Year() {
|
||||
label = d.Format("2006-01-02")
|
||||
}
|
||||
data = append(data, gin.H{"date": label, "count": counts[key]})
|
||||
}
|
||||
}
|
||||
middleware.JSON(c, data)
|
||||
@@ -185,7 +254,12 @@ func (h *StatisticsHandler) ResponseDistribution(c *gin.Context) {
|
||||
if !requireStatisticsAccess(c) {
|
||||
return
|
||||
}
|
||||
_, messages, err := loadStatisticsData(middleware.GetTenantID(c))
|
||||
r, err := parseStatsRange(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
_, messages, err := loadStatisticsData(middleware.GetTenantID(c), r)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "查询响应时长失败"})
|
||||
return
|
||||
@@ -216,8 +290,13 @@ func (h *StatisticsHandler) AgentPerformance(c *gin.Context) {
|
||||
if !requireStatisticsAccess(c) {
|
||||
return
|
||||
}
|
||||
r, err := parseStatsRange(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
tenantID := middleware.GetTenantID(c)
|
||||
sessions, messages, err := loadStatisticsData(tenantID)
|
||||
sessions, messages, err := loadStatisticsData(tenantID, r)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "查询客服绩效失败"})
|
||||
return
|
||||
@@ -280,10 +359,19 @@ func (h *StatisticsHandler) ChannelDistribution(c *gin.Context) {
|
||||
if !requireStatisticsAccess(c) {
|
||||
return
|
||||
}
|
||||
r, err := parseStatsRange(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
tenantID := middleware.GetTenantID(c)
|
||||
var sessions []model.Session
|
||||
sessions, _, err := loadStatisticsData(tenantID, r)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "查询渠道分布失败"})
|
||||
return
|
||||
}
|
||||
var channels []model.Channel
|
||||
if err := model.DB.Where("tenant_id = ?", tenantID).Find(&sessions).Error; err != nil {
|
||||
if err := model.DB.Where("tenant_id = ?", tenantID).Find(&channels).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "查询渠道分布失败"})
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user