Merge branch 'main' into feature/distribution-system

合入 main 的推广追踪(adtracking)模块,并解决与分销业务的 8 处冲突。

冲突解决原则:两边的字段、菜单与路由全部保留,不删任何一方。

后端:
- model/order.go:保留分销字段注释块,追加 SourceTrackCode
- model/user.go:追加 RegisterTrackCode;TokenVersion 默认值保持 1
  (与本库 users.token_version 列默认值一致)
- auth/repository.go:两处建号结构体同时写入 RegisterTrackCode 与 TokenVersion
- order/dto.go:OrderDTO 同时暴露 distribution_site_id 与 source_track_code;
  CreateRequest 同时接受 distribution_site_code 与 track_code
- order/presenter.go:同时映射 DistributionSiteID 与 SourceTrackCode

前端:
- orders/api/orders.ts:createOrder 同时携带 distribution_site_code 与 track_code
- layouts/AdminLayout.vue:分销管理菜单组与推广追踪菜单组并列保留
- router/adminRoutes.ts:分销 4 条路由与 tracking-links 路由并列保留

迁移:feature 侧 000064-000067 已在前一提交顺延为 000065-000068,
为 main 的 000064_ad_tracking 让号,合并后编号 000063→000068 连续无重复。
This commit is contained in:
yml2213
2026-09-10 13:06:18 +08:00
28 changed files with 2461 additions and 55 deletions
+2
View File
@@ -81,6 +81,7 @@ type OrderDTO struct {
AdminActions *AdminActionsDTO `json:"admin_actions,omitempty"`
PaymentDeadlineAt *time.Time `json:"payment_deadline_at,omitempty"`
DistributionSiteID *uint64 `json:"distribution_site_id,omitempty"`
SourceTrackCode string `json:"source_track_code,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
@@ -153,6 +154,7 @@ type ActiveDisputeDTO struct {
type CreateRequest struct {
ListingID uint64 `json:"listing_id" binding:"required"`
DistributionSiteCode string `json:"distribution_site_code" binding:"omitempty,max=64"`
TrackCode string `json:"track_code,omitempty"`
}
type SubmitHandoffRequest struct {
@@ -112,6 +112,13 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
if err != nil {
return err
}
sourceTrackCode := sanitizeTrackCode(req.TrackCode)
if sourceTrackCode == "" {
var u model.User
if err := tx.Select("register_track_code").First(&u, renterID).Error; err == nil && u.RegisterTrackCode != "" {
sourceTrackCode = sanitizeTrackCode(u.RegisterTrackCode)
}
}
order := model.RentalOrder{
OrderNo: orderNo,
ListingID: listing.ID,
@@ -150,6 +157,7 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
ManagedAdminID: listing.ManagedAdminID,
SettlementStatus: settlementStatusUnsettled,
OfflineSettlementStatus: offlineSettlementStatusNone,
SourceTrackCode: sourceTrackCode,
}
if distributionSite != nil && distributionRule != nil {
order.DistributionSiteID = &distributionSite.ID
@@ -135,6 +135,7 @@ func (row orderRow) toAdminDTO() OrderDTO {
DepositHoldReleasedAt: row.DepositHoldReleasedAt,
AdminActions: adminActionsForOrder(row.RentalOrder),
DistributionSiteID: row.DistributionSiteID,
SourceTrackCode: row.SourceTrackCode,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
+12
View File
@@ -3,13 +3,25 @@ package order
import (
"crypto/rand"
"errors"
"regexp"
"strconv"
"strings"
"gorm.io/gorm"
"hfb_sys/backend/internal/auditlog"
"hfb_sys/backend/internal/timeutil"
)
var trackCodePattern = regexp.MustCompile(`^[a-zA-Z0-9_-]{1,64}$`)
func sanitizeTrackCode(code string) string {
s := strings.TrimSpace(code)
if len(s) == 0 || len(s) > 64 || !trackCodePattern.MatchString(s) {
return ""
}
return s
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if value != "" {