feat(distribution): 重构分销后台为SaaS商户工作台并全面商业化优化

- 商品货架 (Accounts.tsx): 支持期望售价/固定金额/百分比多模式实时联动加价与批量调价,高亮100%独享溢价
- 订单中心 (Orders.tsx): 透传账号游戏租客信息,新增收益对账抽屉透明拆解底价、手续费、分成与自主溢价
- 结算与规则 (Settlements.tsx & Rules.tsx): 纠正角色倒错,商户视角展现周期对账单与分成合作政策
- 店铺装修 (SiteSettings.tsx): 废除手写JSON,提供横幅/公告/页脚可视化列表编辑器与前台实时模拟器
- 数据大盘 (Dashboard.tsx): 对齐最新订单字段,大字展示分销商净收益
- 后端数据层 (dto.go, repository.go, handler.go): 聚合计算分销商订单收益,支持商品与租客搜索
This commit is contained in:
yml2213
2026-09-09 14:44:02 +08:00
parent aa466051ac
commit f6ad7c34ee
12 changed files with 2257 additions and 465 deletions
@@ -417,13 +417,22 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList
if size > 100 {
size = 100
}
base := r.db.WithContext(ctx).Table("rental_orders ro").Joins("JOIN distribution_order_financials f ON f.order_id = ro.id")
base := r.db.WithContext(ctx).Table("rental_orders ro").
Joins("JOIN distribution_order_financials f ON f.order_id = ro.id").
Joins("LEFT JOIN rental_listings rl ON rl.id = ro.listing_id").
Joins("LEFT JOIN game_accounts ga ON ga.id = ro.account_id").
Joins("LEFT JOIN users u ON u.id = ro.renter_id")
if query.SiteID > 0 {
base = base.Where("ro.distribution_site_id = ?", query.SiteID)
}
if query.Status != "" {
base = base.Where("ro.status = ?", query.Status)
}
if strings.TrimSpace(query.Keyword) != "" {
kw := "%" + strings.TrimSpace(query.Keyword) + "%"
base = base.Where("(ro.order_no LIKE ? OR rl.listing_no LIKE ? OR ga.title LIKE ? OR u.phone LIKE ?)", kw, kw, kw, kw)
}
switch strings.TrimSpace(query.Kind) {
case "sold":
base = base.Where("EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND p.party_role IN ('distributor_sell','distributor_b'))")
@@ -438,16 +447,99 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList
return nil, err
}
items := make([]DistributionOrderDTO, 0)
selectSQL := "ro.id AS order_id, ro.order_no, ro.distribution_site_id AS site_id, ro.listing_id, ro.account_id, ro.owner_id, ro.renter_id, ro.status, ro.handoff_status, ro.refund_status, ro.settlement_status, ro.distribution_mode, f.gross_paid_amount_cent, f.refund_amount_cent, f.net_paid_amount_cent, f.payment_fee_cent, f.service_fee_cent, f.markup_amount_cent, f.tax_amount_cent, f.split_base_amount_cent, f.rounding_adjustment_cent, ro.created_at"
selectSQL := `
ro.id AS order_id, ro.order_no, ro.distribution_site_id AS site_id, ro.listing_id, ro.account_id, ro.owner_id, ro.renter_id,
ro.status, ro.handoff_status, ro.refund_status, ro.settlement_status, ro.distribution_mode,
f.gross_paid_amount_cent, f.refund_amount_cent, f.net_paid_amount_cent, f.payment_fee_cent, f.service_fee_cent,
f.markup_amount_cent, f.tax_amount_cent, f.split_base_amount_cent, f.rounding_adjustment_cent, ro.created_at,
COALESCE(rl.listing_no, '') AS listing_no,
COALESCE(ga.title, '') AS title,
COALESCE(ga.game_name, '') AS game_name,
COALESCE(ga.server_region, '') AS server_region,
COALESCE(ga.login_platform, '') AS login_platform,
ro.estimated_duration_hours AS rent_duration_hours,
ro.rented_at AS rented_at,
COALESCE(u.phone, '') AS renter_phone,
COALESCE(u.nickname, '') AS renter_nickname,
COALESCE(ro.owner_rent_amount_cent, 0) AS owner_rent_amount_cent,
COALESCE(ro.base_price_cent, 0) AS base_price_cent,
COALESCE(ro.shelf_markup_cent, 0) AS shelf_markup_cent,
COALESCE(ro.deposit_amount_cent, 0) AS deposit_amount_cent
`
if err := base.Select(selectSQL).Order("ro.id DESC").Offset((page - 1) * size).Limit(size).Scan(&items).Error; err != nil {
return nil, err
}
if len(items) > 0 {
orderIDs := make([]uint64, len(items))
for i, it := range items {
orderIDs[i] = it.OrderID
}
var parties []model.DistributionOrderParty
pq := r.db.WithContext(ctx).Where("order_id IN ?", orderIDs)
if query.SiteID > 0 {
pq = pq.Where("site_id = ?", query.SiteID)
}
if err := pq.Find(&parties).Error; err == nil {
partiesByOrder := make(map[uint64][]model.DistributionOrderParty)
for _, p := range parties {
partiesByOrder[p.OrderID] = append(partiesByOrder[p.OrderID], p)
}
for i := range items {
plist := partiesByOrder[items[i].OrderID]
var totalEarning int64
var markupCent int64
var splitCent int64
var roles []string
for _, p := range plist {
totalEarning += p.IncomeAmountCent
if p.PartyRole == "distributor_sell" || p.PartyRole == "distributor_b" {
roles = append(roles, "sell")
markupCent += items[i].ShelfMarkupCent
splitCent += (p.IncomeAmountCent - items[i].ShelfMarkupCent)
} else if p.PartyRole == "distributor_receive" || p.PartyRole == "distributor_a" {
roles = append(roles, "receive")
splitCent += p.IncomeAmountCent
}
}
items[i].DistributorEarningCent = totalEarning
items[i].DistributorMarkupCent = markupCent
items[i].DistributorSplitCent = splitCent
items[i].DistributorRole = strings.Join(roles, ",")
}
}
}
return &DistributionOrderPage{Items: items, Total: total, Page: page, PageSize: size}, nil
}
func (r *Repository) FindOrder(ctx context.Context, adminID, id uint64) (*DistributionOrderDTO, error) {
var item DistributionOrderDTO
err := r.db.WithContext(ctx).Table("rental_orders ro").Joins("JOIN distribution_order_financials f ON f.order_id = ro.id").Select("ro.id AS order_id, ro.order_no, ro.distribution_site_id AS site_id, ro.listing_id, ro.account_id, ro.owner_id, ro.renter_id, ro.status, ro.handoff_status, ro.refund_status, ro.settlement_status, ro.distribution_mode, f.gross_paid_amount_cent, f.refund_amount_cent, f.net_paid_amount_cent, f.payment_fee_cent, f.service_fee_cent, f.markup_amount_cent, f.tax_amount_cent, f.split_base_amount_cent, f.rounding_adjustment_cent, ro.created_at").Where("ro.id = ?", id).Scan(&item).Error
selectSQL := `
ro.id AS order_id, ro.order_no, ro.distribution_site_id AS site_id, ro.listing_id, ro.account_id, ro.owner_id, ro.renter_id,
ro.status, ro.handoff_status, ro.refund_status, ro.settlement_status, ro.distribution_mode,
f.gross_paid_amount_cent, f.refund_amount_cent, f.net_paid_amount_cent, f.payment_fee_cent, f.service_fee_cent,
f.markup_amount_cent, f.tax_amount_cent, f.split_base_amount_cent, f.rounding_adjustment_cent, ro.created_at,
COALESCE(rl.listing_no, '') AS listing_no,
COALESCE(ga.title, '') AS title,
COALESCE(ga.game_name, '') AS game_name,
COALESCE(ga.server_region, '') AS server_region,
COALESCE(ga.login_platform, '') AS login_platform,
ro.estimated_duration_hours AS rent_duration_hours,
ro.rented_at AS rented_at,
COALESCE(u.phone, '') AS renter_phone,
COALESCE(u.nickname, '') AS renter_nickname,
COALESCE(ro.owner_rent_amount_cent, 0) AS owner_rent_amount_cent,
COALESCE(ro.base_price_cent, 0) AS base_price_cent,
COALESCE(ro.shelf_markup_cent, 0) AS shelf_markup_cent,
COALESCE(ro.deposit_amount_cent, 0) AS deposit_amount_cent
`
err := r.db.WithContext(ctx).Table("rental_orders ro").
Joins("JOIN distribution_order_financials f ON f.order_id = ro.id").
Joins("LEFT JOIN rental_listings rl ON rl.id = ro.listing_id").
Joins("LEFT JOIN game_accounts ga ON ga.id = ro.account_id").
Joins("LEFT JOIN users u ON u.id = ro.renter_id").
Select(selectSQL).Where("ro.id = ?", id).Scan(&item).Error
if err != nil {
return nil, err
}
@@ -457,6 +549,28 @@ func (r *Repository) FindOrder(ctx context.Context, adminID, id uint64) (*Distri
if err := r.ensureSiteAccess(ctx, adminID, item.SiteID); err != nil {
return nil, err
}
var parties []model.DistributionOrderParty
if err := r.db.WithContext(ctx).Where("order_id = ? AND site_id = ?", item.OrderID, item.SiteID).Find(&parties).Error; err == nil {
var totalEarning int64
var markupCent int64
var splitCent int64
var roles []string
for _, p := range parties {
totalEarning += p.IncomeAmountCent
if p.PartyRole == "distributor_sell" || p.PartyRole == "distributor_b" {
roles = append(roles, "sell")
markupCent += item.ShelfMarkupCent
splitCent += (p.IncomeAmountCent - item.ShelfMarkupCent)
} else if p.PartyRole == "distributor_receive" || p.PartyRole == "distributor_a" {
roles = append(roles, "receive")
splitCent += p.IncomeAmountCent
}
}
item.DistributorEarningCent = totalEarning
item.DistributorMarkupCent = markupCent
item.DistributorSplitCent = splitCent
item.DistributorRole = strings.Join(roles, ",")
}
return &item, nil
}