From b16c45ce6fb29af8b4077d0da100b0f49b441a9d Mon Sep 17 00:00:00 2001 From: yml2213 Date: Wed, 9 Sep 2026 19:34:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(distribution):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=88=86=E9=94=80=E7=BB=93=E8=B4=A6=E9=80=80=E6=AC=BE=E5=88=86?= =?UTF-8?q?=E6=88=90=E8=AE=A1=E7=AE=97=E9=80=BB=E8=BE=91=E4=B8=8E=E5=88=A9?= =?UTF-8?q?=E6=B6=A6=E7=BB=9F=E8=AE=A1=EF=BC=8C=E4=BC=98=E5=8C=96=E5=88=86?= =?UTF-8?q?=E9=94=80=E8=AE=A2=E5=8D=95=E8=A1=A8=E6=A0=BC=E5=88=97=E5=AE=BD?= =?UTF-8?q?=E4=B8=8E=E5=B1=95=E7=A4=BA=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/modules/distribution/dto.go | 1 + .../internal/modules/distribution/handler.go | 1 + .../modules/distribution/lifecycle.go | 83 ++++++- .../modules/distribution/lifecycle_test.go | 73 ++++++ .../modules/distribution/repository.go | 68 ++++-- .../modules/order/checkout_finalize.go | 4 + distribution-admin/src/AppShell.tsx | 6 +- distribution-admin/src/api.ts | 4 + distribution-admin/src/pages/Accounts.tsx | 14 +- distribution-admin/src/pages/Orders.tsx | 223 ++++++++++++++---- distribution-admin/src/pages/SiteSettings.tsx | 2 +- distribution-admin/src/style.css | 12 + 12 files changed, 403 insertions(+), 88 deletions(-) diff --git a/backend/internal/modules/distribution/dto.go b/backend/internal/modules/distribution/dto.go index be74a68..bd70273 100644 --- a/backend/internal/modules/distribution/dto.go +++ b/backend/internal/modules/distribution/dto.go @@ -139,6 +139,7 @@ type DistributionOrderDTO struct { type DistributionOrderListQuery struct { SiteID uint64 Kind string + Role string Status string Keyword string Page int diff --git a/backend/internal/modules/distribution/handler.go b/backend/internal/modules/distribution/handler.go index 4e075c4..3a4e1df 100644 --- a/backend/internal/modules/distribution/handler.go +++ b/backend/internal/modules/distribution/handler.go @@ -146,6 +146,7 @@ func (h *Handler) ListOrders(c *gin.Context) { result, err := h.service.ListOrders(c.Request.Context(), DistributionOrderListQuery{ SiteID: scoped, Kind: c.Query("kind"), + Role: c.Query("role"), Status: c.Query("status"), Keyword: c.Query("keyword"), Page: page, diff --git a/backend/internal/modules/distribution/lifecycle.go b/backend/internal/modules/distribution/lifecycle.go index a149c7f..ecc9186 100644 --- a/backend/internal/modules/distribution/lifecycle.go +++ b/backend/internal/modules/distribution/lifecycle.go @@ -240,7 +240,7 @@ func SnapshotPaidOrderTx(tx *gorm.DB, orderID uint64, grossPaidCent int64) error return tx.Create(&parties).Error } -// ApplyRefundTx 按累计成功退款金额重算手续费、分成基数和各方分成,不覆盖订单创建时的规则版本。 +// ApplyRefundTx 按累计成功退款或结账结算重算手续费、分成基数和各方分成,不覆盖订单创建时的规则版本。 func ApplyRefundTx(tx *gorm.DB, orderID uint64, refundAmountCent int64) error { if !distributionTablesPresent(tx) { return nil @@ -255,33 +255,96 @@ func ApplyRefundTx(tx *gorm.DB, orderID uint64, refundAmountCent int64) error { if refundAmountCent < 0 { return ErrInvalidAmount } - if refundAmountCent > financial.GrossPaidAmountCent { - refundAmountCent = financial.GrossPaidAmountCent - } + var rule model.DistributionRuleVersion if err := tx.First(&rule, financial.RuleVersionID).Error; err != nil { return err } + var order model.RentalOrder - ownerRent := int64(0) - if err := tx.Select("owner_rent_amount_cent").First(&order, orderID).Error; err == nil { - ownerRent = order.OwnerRentAmountCent + if err := tx.First(&order, orderID).Error; err != nil { + return err + } + + // 判定实际租金退款和号主应得租金: + // 1. 如果存在已达成一致的结账单(accepted 或 settled),以结账单中的实际消费租金与号主实际所得为准 + var checkout model.OrderCheckout + hasAcceptedCheckout := false + if err := tx.Where("order_id = ? AND status IN ('accepted', 'settled')", orderID). + Order("id DESC").First(&checkout).Error; err == nil { + hasAcceptedCheckout = true + } + + rentRefundCent := refundAmountCent + ownerRent := order.OwnerRentAmountCent + if ownerRent < 0 { + ownerRent = 0 + } + + if hasAcceptedCheckout { + // 结账单中,实际有效租金为 checkout.RentAmountCent + // 真实的租金退款为 订单初始租金 - 实际有效租金 + actualRentRefund := order.RentAmountCent - checkout.RentAmountCent + if actualRentRefund < 0 { + actualRentRefund = 0 + } + rentRefundCent = actualRentRefund + // 号主实际租金所得以结账单的实际租金收入为准 + ownerRent = checkout.OwnerRentAmountCent if ownerRent < 0 { ownerRent = 0 } + } else { + // 无结账单场景(例如交接超时、提前取消退款、维权全额退款): + // refundAmountCent 是租客收到的总退款(含押金与租金),押金不计入分销毛收入 + // 先抵扣押金退款,剩余部分才属于租金退款 + depositRefund := order.DepositAmountCent + if refundAmountCent < depositRefund { + depositRefund = refundAmountCent + } + actualRentRefund := refundAmountCent - depositRefund + if actualRentRefund < 0 { + actualRentRefund = 0 + } + rentRefundCent = actualRentRefund + // 如果租金全额退款,号主应得租金为 0 + if rentRefundCent >= financial.GrossPaidAmountCent { + ownerRent = 0 + } } - result, err := calculateForRule(rule, SettlementInput{GrossPaidAmountCent: financial.GrossPaidAmountCent, RefundAmountCent: refundAmountCent, OwnerRentAmountCent: ownerRent, MarkupAmountCent: financial.MarkupAmountCent}) + + if rentRefundCent > financial.GrossPaidAmountCent { + rentRefundCent = financial.GrossPaidAmountCent + } + + result, err := calculateForRule(rule, SettlementInput{ + GrossPaidAmountCent: financial.GrossPaidAmountCent, + RefundAmountCent: rentRefundCent, + OwnerRentAmountCent: ownerRent, + MarkupAmountCent: financial.MarkupAmountCent, + }) if err != nil { return err } splits := SplitSettlement(ruleFromVersion(rule), result.SplitBaseAmountCent, financial.MarkupAmountCent) - if err := tx.Model(&financial).Updates(map[string]any{"refund_amount_cent": refundAmountCent, "net_paid_amount_cent": result.NetPaidAmountCent, "payment_fee_cent": result.PaymentFeeCent, "service_fee_cent": result.ServiceFeeCent, "split_base_amount_cent": result.SplitBaseAmountCent, "rounding_adjustment_cent": SplitRoundingCent(result.SplitBaseAmountCent, result.ServiceFeeCent, financial.MarkupAmountCent, splits), "settlement_status": "pending"}).Error; err != nil { + if err := tx.Model(&financial).Updates(map[string]any{ + "refund_amount_cent": rentRefundCent, + "net_paid_amount_cent": result.NetPaidAmountCent, + "payment_fee_cent": result.PaymentFeeCent, + "service_fee_cent": result.ServiceFeeCent, + "split_base_amount_cent": result.SplitBaseAmountCent, + "rounding_adjustment_cent": SplitRoundingCent(result.SplitBaseAmountCent, result.ServiceFeeCent, financial.MarkupAmountCent, splits), + "settlement_status": "pending", + }).Error; err != nil { return err } for _, split := range splits { if err := tx.Model(&model.DistributionOrderParty{}). Where("order_id = ? AND party_role = ?", orderID, split.PartyRole). - Updates(map[string]any{"income_amount_cent": split.AmountCent, "profit_amount_cent": split.AmountCent}).Error; err != nil { + Updates(map[string]any{ + "income_amount_cent": split.AmountCent, + "profit_amount_cent": split.AmountCent, + }).Error; err != nil { return err } } diff --git a/backend/internal/modules/distribution/lifecycle_test.go b/backend/internal/modules/distribution/lifecycle_test.go index 5de39f9..4f365fc 100644 --- a/backend/internal/modules/distribution/lifecycle_test.go +++ b/backend/internal/modules/distribution/lifecycle_test.go @@ -399,3 +399,76 @@ func TestSnapshotPaidOrderTxSplitsPercentOfProfit(t *testing.T) { t.Fatalf("platform receive = %+v", platform) } } + +func TestApplyRefundTxWithCheckoutAndDeposit(t *testing.T) { + db := distributionTestDB(t) + if err := db.AutoMigrate(&model.OrderCheckout{}); err != nil { + t.Fatal(err) + } + now := time.Now() + site := model.DistributionSite{SiteCode: "checkout_site", SiteName: "结账测试站", Status: "active"} + if err := db.Create(&site).Error; err != nil { + t.Fatal(err) + } + rule := model.DistributionRuleVersion{ + SiteID: &site.ID, Mode: ModeTwoPartyDistributorReceive, Version: 1, Status: "active", + DistributorReceiveRatio: 35, PlatformSellRatio: 40, + ServiceFeeValue: 25, ServiceFeeUnit: ServiceFeeUnitPercent, PaymentFeeBPS: 50, + EffectiveFrom: now.Add(-time.Hour), + } + if err := db.Create(&rule).Error; err != nil { + t.Fatal(err) + } + order := model.RentalOrder{ + OrderNo: "ORD-CHECKOUT-REFUND", ListingID: 10, AccountID: 20, OwnerID: 30, RenterID: 40, Status: "renting", + RentAmountCent: 63130, DepositAmountCent: 15500, OwnerRentAmountCent: 55950, + DistributionSiteID: &site.ID, DistributionRuleVersionID: &rule.ID, DistributionMode: ModeTwoPartyDistributorReceive, + } + if err := db.Create(&order).Error; err != nil { + t.Fatal(err) + } + if err := SnapshotPaidOrderTx(db, order.ID, 63130); err != nil { + t.Fatal(err) + } + + checkout := model.OrderCheckout{ + OrderID: order.ID, + Status: "accepted", + RentAmountCent: 53330, + OwnerRentAmountCent: 46150, + DepositAmountCent: 15500, + RenterRefundAmountCent: 25300, + OwnerIncomeAmountCent: 46150, + } + if err := db.Create(&checkout).Error; err != nil { + t.Fatal(err) + } + + // 退款 25300(包含押金全退 15500 + 租金退款 9800) + if err := ApplyRefundTx(db, order.ID, 25300); err != nil { + t.Fatalf("ApplyRefundTx failed: %v", err) + } + + var financial model.DistributionOrderFinancial + if err := db.Where("order_id = ?", order.ID).First(&financial).Error; err != nil { + t.Fatal(err) + } + if financial.RefundAmountCent != 9800 { + t.Fatalf("financial.RefundAmountCent = %d, want 9800 (only rent refund)", financial.RefundAmountCent) + } + if financial.NetPaidAmountCent != 53330 { + t.Fatalf("financial.NetPaidAmountCent = %d, want 53330", financial.NetPaidAmountCent) + } + if financial.SplitBaseAmountCent != 6913 { + t.Fatalf("financial.SplitBaseAmountCent = %d, want 6913", financial.SplitBaseAmountCent) + } + + var distributorParty model.DistributionOrderParty + if err := db.Where("order_id = ? AND party_role = 'distributor_receive'", order.ID).First(&distributorParty).Error; err != nil { + t.Fatal(err) + } + if distributorParty.IncomeAmountCent != 2420 { + t.Fatalf("distributorParty.IncomeAmountCent = %d, want 2420", distributorParty.IncomeAmountCent) + } +} + diff --git a/backend/internal/modules/distribution/repository.go b/backend/internal/modules/distribution/repository.go index 98444f0..7c4cb83 100644 --- a/backend/internal/modules/distribution/repository.go +++ b/backend/internal/modules/distribution/repository.go @@ -424,7 +424,7 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList Joins("LEFT JOIN users u ON u.id = ro.renter_id") if query.SiteID > 0 { - base = base.Where("ro.distribution_site_id = ?", query.SiteID) + base = base.Where("(ro.distribution_site_id = ? OR EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND (p.site_id = ? OR p.party_id = ?)))", query.SiteID, query.SiteID, query.SiteID) } if query.Status != "" { base = base.Where("ro.status = ?", query.Status) @@ -435,7 +435,13 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList } 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'))") + if query.Role == "sell" { + 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'))") + } else if query.Role == "receive" { + base = base.Where("EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND p.party_role IN ('distributor_receive','distributor_a'))") + } else { + 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','distributor_receive','distributor_a'))") + } case "receive": base = base.Where("EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND p.party_role IN ('distributor_receive','distributor_a'))") case "", "all": @@ -486,7 +492,7 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList 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) + pq = pq.Where("(site_id = ? OR party_id = ?)", query.SiteID, query.SiteID) } if err := pq.Find(&parties).Error; err == nil { partiesByOrder := make(map[uint64][]model.DistributionOrderParty) @@ -500,6 +506,9 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList var splitCent int64 var roles []string for _, p := range plist { + if p.PartyType != PartyTypeSite { + continue + } totalEarning += p.IncomeAmountCent if p.PartyRole == "distributor_sell" || p.PartyRole == "distributor_b" { roles = append(roles, "sell") @@ -566,12 +575,15 @@ func (r *Repository) FindOrder(ctx context.Context, adminID, id uint64) (*Distri 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 { + if err := r.db.WithContext(ctx).Where("order_id = ? AND (site_id = ? OR party_id = ?)", item.OrderID, item.SiteID, item.SiteID).Find(&parties).Error; err == nil { var totalEarning int64 var markupCent int64 var splitCent int64 var roles []string for _, p := range parties { + if p.PartyType != PartyTypeSite { + continue + } totalEarning += p.IncomeAmountCent if p.PartyRole == "distributor_sell" || p.PartyRole == "distributor_b" { roles = append(roles, "sell") @@ -2079,12 +2091,12 @@ func (r *Repository) GetDashboardAnalytics(ctx context.Context, adminID, siteID var todayProfit int64 _ = r.db.WithContext(ctx).Model(&model.DistributionOrderParty{}). - Where("site_id = ? AND created_at >= ?", siteID, todayStr+" 00:00:00"). + Where("(site_id = ? OR party_id = ?) AND party_type = 'site' AND created_at >= ?", siteID, siteID, todayStr+" 00:00:00"). Select("COALESCE(SUM(profit_amount_cent), 0)").Scan(&todayProfit).Error var monthProfit int64 _ = r.db.WithContext(ctx).Model(&model.DistributionOrderParty{}). - Where("site_id = ? AND created_at >= ?", siteID, monthStr+"-01 00:00:00"). + Where("(site_id = ? OR party_id = ?) AND party_type = 'site' AND created_at >= ?", siteID, siteID, monthStr+"-01 00:00:00"). Select("COALESCE(SUM(profit_amount_cent), 0)").Scan(&monthProfit).Error var wallet model.WalletAccount @@ -2101,41 +2113,49 @@ func (r *Repository) GetDashboardAnalytics(ctx context.Context, adminID, siteID var tRows []trendRow startDate := time.Now().AddDate(0, 0, -6).Format("2006-01-02") dateFormatExpr := "DATE_FORMAT(ro.created_at, '%Y-%m-%d')" + dateFormatPartyExpr := "DATE_FORMAT(created_at, '%Y-%m-%d')" if r.db != nil && r.db.Dialector != nil && r.db.Dialector.Name() == "sqlite" { dateFormatExpr = "strftime('%Y-%m-%d', ro.created_at)" + dateFormatPartyExpr = "strftime('%Y-%m-%d', created_at)" } selectTrendSQL := fmt.Sprintf("%s AS date_str, COUNT(ro.id) AS orders, COALESCE(SUM(f.gross_paid_amount_cent), 0) AS gmv", dateFormatExpr) _ = r.db.WithContext(ctx).Table("rental_orders ro"). Joins("JOIN distribution_order_financials f ON f.order_id = ro.id"). - Where("ro.distribution_site_id = ? AND ro.created_at >= ?", siteID, startDate+" 00:00:00"). + Where("(ro.distribution_site_id = ? OR EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND (p.site_id = ? OR p.party_id = ?))) AND ro.created_at >= ?", siteID, siteID, siteID, startDate+" 00:00:00"). Select(selectTrendSQL). Group(dateFormatExpr). Order("date_str ASC"). Scan(&tRows).Error + type profitRow struct { + DateStr string `gorm:"column:date_str"` + Profit int64 `gorm:"column:profit"` + } + var pRows []profitRow + _ = r.db.WithContext(ctx).Model(&model.DistributionOrderParty{}). + Where("(site_id = ? OR party_id = ?) AND party_type = 'site' AND created_at >= ?", siteID, siteID, startDate+" 00:00:00"). + Select(fmt.Sprintf("%s AS date_str, COALESCE(SUM(profit_amount_cent), 0) AS profit", dateFormatPartyExpr)). + Group(dateFormatPartyExpr). + Scan(&pRows).Error + pRowMap := make(map[string]int64, len(pRows)) + for _, pr := range pRows { + pRowMap[pr.DateStr] = pr.Profit + } + tRowMap := make(map[string]trendRow, len(tRows)) for _, tr := range tRows { tRowMap[tr.DateStr] = tr } for i := -6; i <= 0; i++ { dStr := time.Now().AddDate(0, 0, i).Format("2006-01-02") - if tr, ok := tRowMap[dStr]; ok { - trendDays = append(trendDays, DistributionTrendPoint{ - Date: dStr, - ReceivedCount: tr.Received, - OrderCount: tr.Orders, - GMVCent: tr.GMV, - ProfitCent: tr.Profit, - }) - } else { - trendDays = append(trendDays, DistributionTrendPoint{ - Date: dStr, - ReceivedCount: 0, - OrderCount: 0, - GMVCent: 0, - ProfitCent: 0, - }) - } + tr := tRowMap[dStr] + trendDays = append(trendDays, DistributionTrendPoint{ + Date: dStr, + ReceivedCount: tr.Received, + OrderCount: tr.Orders, + GMVCent: tr.GMV, + ProfitCent: pRowMap[dStr], + }) } return &DistributionAnalyticsDTO{ diff --git a/backend/internal/modules/order/checkout_finalize.go b/backend/internal/modules/order/checkout_finalize.go index c070690..44fe375 100644 --- a/backend/internal/modules/order/checkout_finalize.go +++ b/backend/internal/modules/order/checkout_finalize.go @@ -4,6 +4,7 @@ import ( "time" "hfb_sys/backend/internal/model" + "hfb_sys/backend/internal/modules/distribution" "hfb_sys/backend/internal/modules/notification" "hfb_sys/backend/internal/modules/rentergrowth" "hfb_sys/backend/internal/modules/wallet" @@ -55,6 +56,9 @@ func (r *Repository) finalizeCheckout(tx *gorm.DB, order *model.RentalOrder, che if err := saveFinalizedCheckout(tx, order, checkout, listing, account); err != nil { return nil, err } + if order.DistributionSiteID != nil && *order.DistributionSiteID > 0 { + _ = distribution.ApplyRefundTx(tx, order.ID, 0) + } return refund, nil } diff --git a/distribution-admin/src/AppShell.tsx b/distribution-admin/src/AppShell.tsx index ed3eaf1..e35463f 100644 --- a/distribution-admin/src/AppShell.tsx +++ b/distribution-admin/src/AppShell.tsx @@ -9,6 +9,7 @@ import { EyeOutlined, FileSearchOutlined, LogoutOutlined, + OrderedListOutlined, SettingOutlined, ShopOutlined, SkinOutlined, @@ -39,8 +40,9 @@ function MainLayout({ member, onLogout }: { member: CurrentMember; onLogout: () ...(member.is_superadmin ? [{ key: '/sites', icon: , label: 分销站点管理 }] : []), - { key: '/receive', icon: , label: 分销收号 }, + { key: '/orders', icon: , label: 全部分销订单 }, { key: '/sold', icon: , label: 售出账号订单 }, + { key: '/receive', icon: , label: 分销收号管理 }, { key: '/accounts', icon: , label: 商品货架与加价 }, { key: '/wallet', icon: , label: 资金与提现 }, { @@ -59,7 +61,7 @@ function MainLayout({ member, onLogout }: { member: CurrentMember; onLogout: () }, ] - const selectedKey = menuItems.some((item) => item.key === location.pathname) + const selectedKey = menuItems.some((item) => item && 'key' in item && item.key === location.pathname) ? location.pathname : '/' diff --git a/distribution-admin/src/api.ts b/distribution-admin/src/api.ts index 193150e..23c80ba 100644 --- a/distribution-admin/src/api.ts +++ b/distribution-admin/src/api.ts @@ -357,15 +357,19 @@ export async function updateRuleStatus(id: number, status: string): Promise { const resp = await api.get>('/admin/distribution/orders', { params: { kind: params.kind || undefined, + role: params.role || undefined, status: params.status || undefined, + keyword: params.keyword || undefined, site_id: params.siteID || undefined, page: params.page || 1, page_size: params.pageSize || 20, diff --git a/distribution-admin/src/pages/Accounts.tsx b/distribution-admin/src/pages/Accounts.tsx index 9d62c8e..cc7aa59 100644 --- a/distribution-admin/src/pages/Accounts.tsx +++ b/distribution-admin/src/pages/Accounts.tsx @@ -72,7 +72,17 @@ const TX_OPTIONS = [ function formatGameName(name?: string) { if (!name) return '默认游戏' - if (name === 'delta_force') return '三角洲行动' + const lower = name.toLowerCase().trim() + if ( + lower === 'delta_force' || + lower === 'deltaforce' || + lower === 'delta' || + lower.includes('delta_force') || + lower.includes('delta') || + name.includes('三角洲') + ) { + return '三角洲' + } return name } @@ -882,7 +892,7 @@ export default function Accounts() { width={580} destroyOnClose > - + = { const SETTLEMENT_STATUS_MAP: Record = { settled: { label: '已结算入账', color: 'success' }, pending: { label: '履约中待结算', color: 'warning' }, + unsettled: { label: '未结算', color: 'default' }, + settling: { label: '结算中', color: 'processing' }, + disputed: { label: '争议冻结', color: 'magenta' }, failed: { label: '结算异常', color: 'error' }, } +const REFUND_STATUS_MAP: Record = { + none: { label: '未退款', color: 'default' }, + refunding: { label: '退款中', color: 'orange' }, + refunded: { label: '已退押金/差额', color: 'blue' }, + failed: { label: '退款失败', color: 'error' }, +} + const REVIEW_STATUS_LABELS: Record = { pending: { label: '待审核', color: 'orange' }, approved: { label: '审核通过', color: 'success' }, @@ -80,6 +90,22 @@ function maskPhone(phone?: string): string { return phone.slice(0, 3) + '****' + phone.slice(-4) } +function formatGameName(gameName?: string): string { + if (!gameName) return '' + const lower = gameName.toLowerCase().trim() + if ( + lower === 'delta_force' || + lower === 'deltaforce' || + lower === 'delta' || + lower.includes('delta_force') || + lower.includes('delta') || + gameName.includes('三角洲') + ) { + return '三角洲' + } + return gameName +} + export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) { const member = useMember() const [sites, setSites] = useState([]) @@ -223,14 +249,70 @@ export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) { const orderColumns = [ { - title: '订单号 / 时间', - width: 190, + title: '订单与商品编号', + width: 270, render: (_: unknown, row: DistributionOrder) => ( -
- - {row.order_no} - -
+
+
+ + 订单 + + + {row.order_no} + +
+ {row.listing_no ? ( +
+ + 商品 + + + {row.listing_no} + +
+ ) : null} +
{row.created_at ? new Date(row.created_at).toLocaleString('zh-CN', { hour12: false }) : '-'}
@@ -238,14 +320,18 @@ export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) { }, { title: '商品与游戏', - minWidth: 220, + width: 260, render: (_: unknown, row: DistributionOrder) => (
- - {row.title || (row.listing_no ? `商品 #${row.listing_no}` : `订单商品 #${row.order_id}`)} + + {row.title || `商品 #${row.listing_no || row.order_id}`} - {row.game_name && {row.game_name}} + {row.game_name && {formatGameName(row.game_name)}} {row.server_region && {row.server_region}} {row.login_platform && {row.login_platform}} {row.rent_duration_hours ? 租期: {row.rent_duration_hours}h : null} @@ -255,28 +341,30 @@ export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) { }, { title: '租客', - width: 140, + width: 130, render: (_: unknown, row: DistributionOrder) => (
-
{row.renter_nickname || '平台租客'}
- +
{row.renter_nickname || '平台租客'}
+ {maskPhone(row.renter_phone)}
), }, { - title: '实付租金 / 押金', + title: '实付 / 押金', width: 140, render: (_: unknown, row: DistributionOrder) => { const netPaid = row.net_paid_amount_cent || row.gross_paid_amount_cent || 0 return (
- + {formatMoney(netPaid)} {row.deposit_amount_cent ? ( -
押金: {formatMoney(row.deposit_amount_cent)}
+
+ 押金: {formatMoney(row.deposit_amount_cent)} +
) : null}
) @@ -284,30 +372,42 @@ export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) { }, { title: '本站净收益', - width: 180, + width: 150, render: (_: unknown, row: DistributionOrder) => { const earning = row.distributor_earning_cent ?? 0 + const isSell = row.distributor_role?.includes('sell') + const isRecv = row.distributor_role?.includes('receive') const roleLabel = - row.distributor_role === 'seller' - ? '售出方' - : row.distributor_role === 'receiver' - ? '收号方' - : row.distributor_role === 'two_party_distributor' - ? '分销方' + isSell && isRecv + ? '自收自售' + : isSell + ? '渠道代销' + : isRecv + ? '号源出租' : '本站收益' + const tagColor = + isSell && isRecv + ? 'purple' + : isSell + ? 'green' + : isRecv + ? 'blue' + : earning > 0 + ? 'green' + : 'default' return (
- + +{formatMoney(earning)}
- 0 ? 'green' : 'default'} style={{ fontSize: 11 }}> + {roleLabel}
{row.distributor_markup_cent && row.distributor_markup_cent > 0 ? ( -
- 含自主溢价: +{formatMoney(row.distributor_markup_cent)} +
+ 含加价: +{formatMoney(row.distributor_markup_cent)}
) : null}
@@ -315,22 +415,11 @@ export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) { }, }, { - title: '订单履约状态', - width: 120, + title: '履约状态', + width: 110, render: (_: unknown, row: DistributionOrder) => { const conf = ORDER_STATUS_MAP[row.status] || { label: row.status, color: 'default' } - return ( -
- {conf.label} - {row.refund_status && row.refund_status !== 'none' && ( -
- - 退款: {row.refund_status} - -
- )} -
- ) + return {conf.label} }, }, { @@ -346,12 +435,14 @@ export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) { }, { title: '操作', - width: 110, + width: 90, + fixed: 'right' as const, render: (_: unknown, row: DistributionOrder) => (
- +