更新输入框边距规范,增加资源对接弹窗的布局修正,确保在小程序开发中避免文字贴边问题。补充相关口诀以提升开发一致性,并在经验清单中记录最新最佳实践。调整项目索引以反映最新进展,增强文档的可用性与可追溯性。

This commit is contained in:
Alex-larget
2026-03-03 11:12:56 +08:00
parent 8e2ea9b7c1
commit 3097d796e0
18 changed files with 220 additions and 94 deletions

View File

@@ -6,6 +6,7 @@ import (
"net/http"
"strconv"
"strings"
"sync"
"time"
"soul-api/internal/database"
@@ -13,6 +14,7 @@ import (
"soul-api/internal/wechat"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// OrdersList GET /api/orders带用户昵称/头像/手机号,分销佣金按配置比例计算;支持分页 page、pageSize筛选 status搜索 search
@@ -29,50 +31,64 @@ func OrdersList(c *gin.Context) {
pageSize = 10
}
q := db.Model(&model.Order{})
if statusFilter != "" && statusFilter != "all" {
if statusFilter == "completed" {
q = q.Where("status IN ?", []string{"paid", "completed"})
} else {
q = q.Where("status = ?", statusFilter) // 含 refunded、pending、created、failed
// 预加载 referral_config避免订单循环内 N+1 查询
var refCfgRow model.SystemConfig
refCfg := (*model.SystemConfig)(nil)
if err := db.Where("config_key = ?", "referral_config").First(&refCfgRow).Error; err == nil {
refCfg = &refCfgRow
}
// 构建带筛选的查询count 与 list 共用条件)
applyOrdersFilter := func(q *gorm.DB) *gorm.DB {
if statusFilter != "" && statusFilter != "all" {
if statusFilter == "completed" {
q = q.Where("status IN ?", []string{"paid", "completed"})
} else {
q = q.Where("status = ?", statusFilter)
}
}
if search != "" {
pattern := "%" + search + "%"
q = q.Where("order_sn LIKE ? OR id LIKE ? OR user_id IN (SELECT id FROM users WHERE COALESCE(nickname,'') LIKE ? OR COALESCE(phone,'') LIKE ? OR id LIKE ?)",
pattern, pattern, pattern, pattern, pattern)
}
return q
}
if search != "" {
pattern := "%" + search + "%"
q = q.Where("order_sn LIKE ? OR id LIKE ? OR user_id IN (SELECT id FROM users WHERE COALESCE(nickname,'') LIKE ? OR COALESCE(phone,'') LIKE ? OR id LIKE ?)",
pattern, pattern, pattern, pattern, pattern)
}
var total int64
q.Count(&total)
var totalRevenue, todayRevenue float64
db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)").
Where("status IN ?", []string{"paid", "completed"}).Scan(&totalRevenue)
todayStart := time.Now().Truncate(24 * time.Hour)
todayEnd := todayStart.Add(24 * time.Hour)
db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)").
Where("status IN ? AND created_at >= ? AND created_at < ?", []string{"paid", "completed"}, todayStart, todayEnd).
Scan(&todayRevenue)
var orders []model.Order
query := db.Model(&model.Order{})
if statusFilter != "" && statusFilter != "all" {
if statusFilter == "completed" {
query = query.Where("status IN ?", []string{"paid", "completed"})
} else {
query = query.Where("status = ?", statusFilter)
}
}
if search != "" {
pattern := "%" + search + "%"
query = query.Where("order_sn LIKE ? OR id LIKE ? OR user_id IN (SELECT id FROM users WHERE COALESCE(nickname,'') LIKE ? OR COALESCE(phone,'') LIKE ? OR id LIKE ?)",
pattern, pattern, pattern, pattern, pattern)
}
if err := query.Order("created_at DESC").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&orders).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error(), "orders": []interface{}{}, "total": 0})
var ordersErr error
var wg sync.WaitGroup
// 并行count、营收统计、订单列表
wg.Add(3)
go func() {
defer wg.Done()
applyOrdersFilter(db.Model(&model.Order{})).Count(&total)
}()
go func() {
defer wg.Done()
db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)").
Where("status IN ?", []string{"paid", "completed"}).Scan(&totalRevenue)
todayStart := time.Now().Truncate(24 * time.Hour)
todayEnd := todayStart.Add(24 * time.Hour)
db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)").
Where("status IN ? AND created_at >= ? AND created_at < ?", []string{"paid", "completed"}, todayStart, todayEnd).
Scan(&todayRevenue)
}()
go func() {
defer wg.Done()
query := applyOrdersFilter(db.Model(&model.Order{}))
ordersErr = query.Order("created_at DESC").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&orders).Error
}()
wg.Wait()
if ordersErr != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": ordersErr.Error(), "orders": []interface{}{}, "total": 0})
return
}
totalPages := int(total) / pageSize
@@ -147,7 +163,7 @@ func OrdersList(c *gin.Context) {
if u := userMap[*o.ReferrerID]; u != nil {
refUser = u
}
m["referrerEarnings"] = computeOrderCommission(db, &o, refUser)
m["referrerEarnings"] = computeOrderCommission(db, &o, refUser, refCfg)
} else {
m["referrerEarnings"] = nil
}

View File

@@ -13,7 +13,8 @@ import (
// 会员订单:推广者会员 20%、非会员 10%内容订单90%(好友优惠 5% 仅针对内容)
// order: 已支付订单,需有 product_type、amount、referrer_id
// referrerUser: 推广者用户信息,用于判断 is_vip可为 nil会查库
func computeOrderCommission(db *gorm.DB, order *model.Order, referrerUser *model.User) float64 {
// preloadConfig: 可选,预加载的 referral_config避免 N+1 查询
func computeOrderCommission(db *gorm.DB, order *model.Order, referrerUser *model.User, preloadConfig ...*model.SystemConfig) float64 {
if order == nil || order.ReferrerID == nil || *order.ReferrerID == "" {
return 0
}
@@ -22,8 +23,17 @@ func computeOrderCommission(db *gorm.DB, order *model.Order, referrerUser *model
userDiscount := 0.0
vipOrderShareVip := 20.0
vipOrderShareNonVip := 10.0
var cfg model.SystemConfig
if err := db.Where("config_key = ?", "referral_config").First(&cfg).Error; err == nil {
var cfg *model.SystemConfig
if len(preloadConfig) > 0 && preloadConfig[0] != nil {
cfg = preloadConfig[0]
} else if row, err := (func() (*model.SystemConfig, error) {
var r model.SystemConfig
e := db.Where("config_key = ?", "referral_config").First(&r).Error
return &r, e
})(); err == nil {
cfg = row
}
if cfg != nil {
var config map[string]interface{}
if err := json.Unmarshal(cfg.ConfigValue, &config); err == nil {
if share, ok := config["distributorShare"].(float64); ok {

View File

@@ -271,20 +271,26 @@ func VipMembers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "data": list, "total": len(list)})
}
// formatVipMember 仅从 vip_* 字段构建会员展示数据,不混入用户信息
// 用于创业老板排行等场景;未填会员资料时 name 显示「创业者」占位
// formatVipMember 构建会员展示数据;优先 vip_*,无则回退到用户 nickname/avatar
// 用于首页超级个体、创业老板排行等场景,展示真实用户头像和昵称
func formatVipMember(u *model.User, isVip bool) gin.H {
name := ""
if u.VipName != nil {
if u.VipName != nil && *u.VipName != "" {
name = *u.VipName
}
if name == "" && u.Nickname != nil && *u.Nickname != "" {
name = *u.Nickname
}
if name == "" {
name = "创业者"
}
avatar := ""
if u.VipAvatar != nil {
if u.VipAvatar != nil && *u.VipAvatar != "" {
avatar = *u.VipAvatar
}
if avatar == "" && u.Avatar != nil && *u.Avatar != "" {
avatar = *u.Avatar
}
project := ""
if u.VipProject != nil {
project = *u.VipProject
@@ -302,17 +308,19 @@ func formatVipMember(u *model.User, isVip bool) gin.H {
vipRole = *u.VipRole
}
return gin.H{
"id": u.ID,
"name": name,
"nickname": name,
"avatar": avatar,
"vipName": name,
"vipRole": vipRole,
"vipAvatar": avatar,
"vipProject": project,
"vipContact": contact,
"vipBio": bio,
"is_vip": isVip,
"id": u.ID,
"name": name,
"nickname": name,
"avatar": avatar,
"vip_name": name,
"vipName": name,
"vipRole": vipRole,
"vip_avatar": avatar,
"vipAvatar": avatar,
"vipProject": project,
"vipContact": contact,
"vipBio": bio,
"is_vip": isVip,
}
}