优化快捷回复联想与个人调用统计,数据统计支持自定义日期
/ 按输入码与个人频次建议(最多10条,唯一自动填入),正文关键字联想;统计页可日历选区间并统一中文日历。
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -213,13 +214,22 @@ func (h *QuickReplyHandler) List(c *gin.Context) {
|
||||
middleware.JSONList(c, list, total, page, pageSize)
|
||||
}
|
||||
|
||||
// Suggest GET /api/quick-replies/suggest?prefix=
|
||||
// 供输入框 / 触发:匹配 shortcut 前缀或标题
|
||||
const suggestLimit = 10
|
||||
|
||||
// Suggest GET /api/quick-replies/suggest
|
||||
// mode=slash&prefix= → / 调用:按输入码前缀过滤;空前缀返回个人调用频次最高的 10 条
|
||||
// mode=keyword&q= → 普通输入:按标题/内容/输入码关键字匹配
|
||||
// 排序一律按当前坐席个人调用次数(非全租户)
|
||||
func (h *QuickReplyHandler) Suggest(c *gin.Context) {
|
||||
tenantID := middleware.GetTenantID(c)
|
||||
uid := middleware.GetUserID(c)
|
||||
mode := strings.TrimSpace(c.Query("mode"))
|
||||
if mode == "" {
|
||||
mode = "slash"
|
||||
}
|
||||
prefix := strings.TrimSpace(c.Query("prefix"))
|
||||
prefix = strings.TrimPrefix(strings.ToLower(prefix), "/")
|
||||
q := strings.TrimSpace(c.Query("q"))
|
||||
|
||||
db := model.DB.Model(&model.QuickReply{}).Where("tenant_id = ?", tenantID).Where(
|
||||
"(scope = ? AND status = ?) OR (scope = ? AND owner_user_id = ? AND status = ?)",
|
||||
@@ -227,19 +237,64 @@ func (h *QuickReplyHandler) Suggest(c *gin.Context) {
|
||||
quickReplyScopePersonal, uid, quickReplyStatusPub,
|
||||
)
|
||||
|
||||
if prefix != "" {
|
||||
like := prefix + "%"
|
||||
titleLike := "%" + prefix + "%"
|
||||
db = db.Where("shortcut LIKE ? OR title LIKE ?", like, titleLike)
|
||||
switch mode {
|
||||
case "slash":
|
||||
if prefix != "" {
|
||||
// 仅匹配输入码前缀(/n → shortcut 以 n 开头)
|
||||
db = db.Where("shortcut <> '' AND LOWER(shortcut) LIKE ?", prefix+"%")
|
||||
}
|
||||
case "keyword":
|
||||
if utf8.RuneCountInString(q) < 1 {
|
||||
middleware.JSON(c, []model.QuickReply{})
|
||||
return
|
||||
}
|
||||
like := "%" + q + "%"
|
||||
db = db.Where("title LIKE ? OR content LIKE ? OR shortcut LIKE ?", like, like, like)
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "mode 无效"})
|
||||
return
|
||||
}
|
||||
|
||||
// 多取一些再按个人频次排序截断(避免漏掉个人高频但全局低的条目)
|
||||
var list []model.QuickReply
|
||||
// 无前缀时返回高频
|
||||
order := "usage_count desc, sort_order asc, id desc"
|
||||
if err := db.Order(order).Limit(20).Find(&list).Error; err != nil {
|
||||
if err := db.Order("usage_count desc, sort_order asc, id desc").Limit(200).Find(&list).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "搜索失败"})
|
||||
return
|
||||
}
|
||||
if len(list) == 0 {
|
||||
middleware.JSON(c, []model.QuickReply{})
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]uint, len(list))
|
||||
for i, item := range list {
|
||||
ids[i] = item.ID
|
||||
}
|
||||
var usages []model.QuickReplyUserUsage
|
||||
_ = model.DB.Where("user_id = ? AND tenant_id = ? AND quick_reply_id IN ?", uid, tenantID, ids).Find(&usages).Error
|
||||
myMap := map[uint]int{}
|
||||
for _, u := range usages {
|
||||
myMap[u.QuickReplyID] = u.UsageCount
|
||||
}
|
||||
for i := range list {
|
||||
list[i].MyUsageCount = myMap[list[i].ID]
|
||||
}
|
||||
// 个人调用次数优先
|
||||
sort.SliceStable(list, func(i, j int) bool {
|
||||
if list[i].MyUsageCount != list[j].MyUsageCount {
|
||||
return list[i].MyUsageCount > list[j].MyUsageCount
|
||||
}
|
||||
if list[i].UsageCount != list[j].UsageCount {
|
||||
return list[i].UsageCount > list[j].UsageCount
|
||||
}
|
||||
if list[i].SortOrder != list[j].SortOrder {
|
||||
return list[i].SortOrder < list[j].SortOrder
|
||||
}
|
||||
return list[i].ID > list[j].ID
|
||||
})
|
||||
if len(list) > suggestLimit {
|
||||
list = list[:suggestLimit]
|
||||
}
|
||||
middleware.JSON(c, list)
|
||||
}
|
||||
|
||||
@@ -428,7 +483,7 @@ func (h *QuickReplyHandler) Unpublish(c *gin.Context) {
|
||||
middleware.JSON(c, item)
|
||||
}
|
||||
|
||||
// Use POST /api/quick-replies/:id/use 使用计数 +1
|
||||
// Use POST /api/quick-replies/:id/use 个人调用 +1(排序用),并累计全局 usage_count
|
||||
func (h *QuickReplyHandler) Use(c *gin.Context) {
|
||||
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
item, ok := loadQuickReply(c, uint(id64))
|
||||
@@ -437,6 +492,7 @@ func (h *QuickReplyHandler) Use(c *gin.Context) {
|
||||
}
|
||||
// 可见性:团队已发布,或本人个人
|
||||
uid := middleware.GetUserID(c)
|
||||
tenantID := middleware.GetTenantID(c)
|
||||
if item.Scope == quickReplyScopeTeam && item.Status != quickReplyStatusPub {
|
||||
if !middleware.HasAnyRole(c, "admin", "supervisor") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "该话术尚未发布"})
|
||||
@@ -447,8 +503,32 @@ func (h *QuickReplyHandler) Use(c *gin.Context) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权使用"})
|
||||
return
|
||||
}
|
||||
|
||||
// 全局累计(管理页)
|
||||
_ = model.DB.Model(item).UpdateColumn("usage_count", item.UsageCount+1).Error
|
||||
middleware.JSON(c, gin.H{"ok": true, "usage_count": item.UsageCount + 1})
|
||||
|
||||
// 个人累计(建议排序)
|
||||
var usage model.QuickReplyUserUsage
|
||||
err := model.DB.Where("user_id = ? AND quick_reply_id = ?", uid, item.ID).First(&usage).Error
|
||||
myCount := 1
|
||||
if err != nil {
|
||||
usage = model.QuickReplyUserUsage{
|
||||
TenantID: tenantID,
|
||||
UserID: uid,
|
||||
QuickReplyID: item.ID,
|
||||
UsageCount: 1,
|
||||
}
|
||||
_ = model.DB.Create(&usage).Error
|
||||
} else {
|
||||
myCount = usage.UsageCount + 1
|
||||
_ = model.DB.Model(&usage).UpdateColumn("usage_count", myCount).Error
|
||||
}
|
||||
|
||||
middleware.JSON(c, gin.H{
|
||||
"ok": true,
|
||||
"usage_count": item.UsageCount + 1,
|
||||
"my_usage_count": myCount,
|
||||
})
|
||||
}
|
||||
|
||||
// 快捷回复 CSV 固定四列(与导入模板一致)
|
||||
|
||||
Reference in New Issue
Block a user