diff --git a/README.md b/README.md index a369adc..8c74d0e 100644 --- a/README.md +++ b/README.md @@ -85,3 +85,4 @@ npm run dev - [数据库设计](docs/database.md) - [接口规划](docs/api.md) - [业务规则](docs/business-rules.md) +- [分销网管理后台设计方案](docs/分销网管理后台设计方案.md) diff --git a/backend/internal/model/distribution.go b/backend/internal/model/distribution.go new file mode 100644 index 0000000..c449808 --- /dev/null +++ b/backend/internal/model/distribution.go @@ -0,0 +1,164 @@ +package model + +import "time" + +// DistributionSite 表示一个独立分销网站主体。 +type DistributionSite struct { + ID uint64 `gorm:"primaryKey" json:"id"` + SiteCode string `gorm:"size:64;not null;uniqueIndex" json:"site_code"` + SiteName string `gorm:"size:128;not null" json:"site_name"` + Domain string `gorm:"size:255;not null;uniqueIndex" json:"domain"` + Status string `gorm:"size:16;not null;default:'active';index" json:"status"` + DisplayAccountEnabled bool `gorm:"not null;default:true" json:"display_account_enabled"` + DefaultSupportGroupID *uint64 `gorm:"index" json:"default_support_group_id,omitempty"` + TaxEnabled bool `gorm:"not null;default:false" json:"tax_enabled"` + TaxRateBPS int64 `gorm:"not null;default:100" json:"tax_rate_bps"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (DistributionSite) TableName() string { return "distribution_sites" } + +// DistributionSiteMember 将现有后台管理员映射到分销网站。 +type DistributionSiteMember struct { + ID uint64 `gorm:"primaryKey" json:"id"` + SiteID uint64 `gorm:"not null;uniqueIndex:uk_distribution_site_member;index" json:"site_id"` + AdminUserID uint64 `gorm:"not null;uniqueIndex:uk_distribution_site_member;index" json:"admin_user_id"` + MemberType string `gorm:"size:32;not null" json:"member_type"` + Status string `gorm:"size:16;not null;default:'active';index" json:"status"` + DataScope string `gorm:"size:32;not null;default:'site'" json:"data_scope"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (DistributionSiteMember) TableName() string { return "distribution_site_members" } + +// DistributionSiteAccount 控制账号在分销网站的展示状态。 +type DistributionSiteAccount struct { + ID uint64 `gorm:"primaryKey" json:"id"` + SiteID uint64 `gorm:"not null;uniqueIndex:uk_distribution_site_account;index" json:"site_id"` + AccountID uint64 `gorm:"not null;uniqueIndex:uk_distribution_site_account;index" json:"account_id"` + ListingID uint64 `gorm:"not null;index" json:"listing_id"` + DisplayStatus string `gorm:"size:16;not null;default:'enabled';index" json:"display_status"` + DisplayStartedAt *time.Time `json:"display_started_at,omitempty"` + DisplayEndedAt *time.Time `json:"display_ended_at,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (DistributionSiteAccount) TableName() string { return "distribution_site_accounts" } + +// DistributionRuleVersion 是新订单使用的分账规则快照来源。 +type DistributionRuleVersion struct { + ID uint64 `gorm:"primaryKey" json:"id"` + SiteID *uint64 `gorm:"index" json:"site_id,omitempty"` + Mode string `gorm:"size:32;not null;index" json:"mode"` + Version int `gorm:"not null" json:"version"` + Status string `gorm:"size:16;not null;default:'draft';index" json:"status"` + PlatformReceiveRatio float64 `gorm:"type:decimal(10,2);not null;default:0" json:"platform_receive_ratio"` + PlatformSellRatio float64 `gorm:"type:decimal(10,2);not null;default:0" json:"platform_sell_ratio"` + DistributorReceiveRatio float64 `gorm:"type:decimal(10,2);not null;default:0" json:"distributor_receive_ratio"` + DistributorSellRatio float64 `gorm:"type:decimal(10,2);not null;default:0" json:"distributor_sell_ratio"` + DistributorARatio float64 `gorm:"type:decimal(10,2);not null;default:0" json:"distributor_a_ratio"` + DistributorBRatio float64 `gorm:"type:decimal(10,2);not null;default:0" json:"distributor_b_ratio"` + ServiceFeeValue float64 `gorm:"type:decimal(12,4);not null;default:0" json:"service_fee_value"` + ServiceFeeUnit string `gorm:"size:24;not null;default:'percent'" json:"service_fee_unit"` + PaymentFeeBPS int64 `gorm:"not null;default:50" json:"payment_fee_bps"` + TaxEnabled bool `gorm:"not null;default:false" json:"tax_enabled"` + TaxRateBPS int64 `gorm:"not null;default:100" json:"tax_rate_bps"` + EffectiveFrom time.Time `gorm:"index" json:"effective_from"` + EffectiveTo *time.Time `gorm:"index" json:"effective_to,omitempty"` + CreatedBy *uint64 `json:"created_by,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +func (DistributionRuleVersion) TableName() string { return "distribution_rule_versions" } + +// DistributionOrderFinancial 保存正常订单的分销财务快照。 +type DistributionOrderFinancial struct { + ID uint64 `gorm:"primaryKey" json:"id"` + OrderID uint64 `gorm:"not null;uniqueIndex" json:"order_id"` + SiteID uint64 `gorm:"not null;index" json:"site_id"` + RuleVersionID uint64 `gorm:"not null" json:"rule_version_id"` + GrossPaidAmountCent int64 `gorm:"not null;default:0" json:"gross_paid_amount_cent"` + RefundAmountCent int64 `gorm:"not null;default:0" json:"refund_amount_cent"` + NetPaidAmountCent int64 `gorm:"not null;default:0" json:"net_paid_amount_cent"` + PaymentFeeBPS int64 `gorm:"not null;default:50" json:"payment_fee_bps"` + PaymentFeeCent int64 `gorm:"not null;default:0" json:"payment_fee_cent"` + ServiceFeeValue float64 `gorm:"type:decimal(12,4);not null;default:0" json:"service_fee_value"` + ServiceFeeUnit string `gorm:"size:24;not null;default:'percent'" json:"service_fee_unit"` + ServiceFeeCent int64 `gorm:"not null;default:0" json:"service_fee_cent"` + MarkupAmountCent int64 `gorm:"not null;default:0" json:"markup_amount_cent"` + TaxEnabled bool `gorm:"not null;default:false" json:"tax_enabled"` + TaxRateBPS int64 `gorm:"not null;default:100" json:"tax_rate_bps"` + TaxAmountCent int64 `gorm:"not null;default:0" json:"tax_amount_cent"` + SplitBaseAmountCent int64 `gorm:"not null;default:0" json:"split_base_amount_cent"` + RoundingAdjustmentCent int64 `gorm:"not null;default:0" json:"rounding_adjustment_cent"` + SettlementStatus string `gorm:"size:24;not null;default:'pending';index" json:"settlement_status"` + SettledAt *time.Time `json:"settled_at,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (DistributionOrderFinancial) TableName() string { return "distribution_order_financials" } + +// DistributionOrderParty 保存订单创建时的参与方和金额快照,避免规则或站点变更影响历史订单。 +type DistributionOrderParty struct { + ID uint64 `gorm:"primaryKey" json:"id"` + OrderID uint64 `gorm:"not null;index" json:"order_id"` + SiteID uint64 `gorm:"not null;index" json:"site_id"` + PartyRole string `gorm:"size:32;not null;index" json:"party_role"` + PartyType string `gorm:"size:16;not null" json:"party_type"` + PartyID uint64 `gorm:"not null" json:"party_id"` + Ratio float64 `gorm:"type:decimal(10,2);not null;default:0" json:"ratio"` + IncomeAmountCent int64 `gorm:"not null;default:0" json:"income_amount_cent"` + CostAmountCent int64 `gorm:"not null;default:0" json:"cost_amount_cent"` + ProfitAmountCent int64 `gorm:"not null;default:0" json:"profit_amount_cent"` + TaxAmountCent int64 `gorm:"not null;default:0" json:"tax_amount_cent"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (DistributionOrderParty) TableName() string { return "distribution_order_parties" } + +// DistributionSettlementBatch 是按站点生成的结算批次。 +type DistributionSettlementBatch struct { + ID uint64 `gorm:"primaryKey" json:"id"` + SiteID uint64 `gorm:"not null;index" json:"site_id"` + BatchNo string `gorm:"size:64;not null;uniqueIndex" json:"batch_no"` + PeriodStart time.Time `gorm:"not null" json:"period_start"` + PeriodEnd time.Time `gorm:"not null" json:"period_end"` + TotalAmountCent int64 `gorm:"not null;default:0" json:"total_amount_cent"` + Status string `gorm:"size:16;not null;default:'draft';index" json:"status"` + CreatedBy *uint64 `json:"created_by,omitempty"` + SettledAt *time.Time `json:"settled_at,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (DistributionSettlementBatch) TableName() string { return "distribution_settlement_batches" } + +type DistributionSettlementItem struct { + ID uint64 `gorm:"primaryKey" json:"id"` + BatchID uint64 `gorm:"not null;index" json:"batch_id"` + OrderID uint64 `gorm:"not null;index" json:"order_id"` + PartyID uint64 `gorm:"not null;index" json:"party_id"` + AmountCent int64 `gorm:"not null;default:0" json:"amount_cent"` + Status string `gorm:"size:16;not null;default:'pending'" json:"status"` + CreatedAt time.Time `json:"created_at"` +} + +func (DistributionSettlementItem) TableName() string { return "distribution_settlement_items" } + +// DistributionSitePaymentChannel 将一个或多个现有支付商户配置绑定到分销站点。 +type DistributionSitePaymentChannel struct { + ID uint64 `gorm:"primaryKey" json:"id"` + SiteID uint64 `gorm:"not null;uniqueIndex:uk_distribution_site_payment;index" json:"site_id"` + ConfigID uint64 `gorm:"not null;uniqueIndex:uk_distribution_site_payment;index" json:"config_id"` + Priority int `gorm:"not null;default:0" json:"priority"` + Status string `gorm:"size:16;not null;default:'active';index" json:"status"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (DistributionSitePaymentChannel) TableName() string { return "distribution_site_payment_channels" } diff --git a/backend/internal/model/order.go b/backend/internal/model/order.go index a49ec76..50ff007 100644 --- a/backend/internal/model/order.go +++ b/backend/internal/model/order.go @@ -41,30 +41,36 @@ type RentalOrder struct { GrowthPointsAwarded int64 `gorm:"not null;default:0" json:"growth_points_awarded"` GrowthPointsAwardedAt *time.Time `json:"growth_points_awarded_at"` AccountSnapshot datatypes.JSON `json:"account_snapshot"` - Status string `gorm:"size:32;not null;default:'pending_payment'" json:"status"` - HandoffStatus string `gorm:"size:32;not null;default:'none'" json:"handoff_status"` - HandoffMode string `gorm:"size:16;not null;default:'owner';index" json:"handoff_mode"` - SettlementMode string `gorm:"size:32;not null;default:'owner_wallet'" json:"settlement_mode"` - ManagedAdminID *uint64 `gorm:"index" json:"managed_admin_id"` - SettlementStatus string `gorm:"size:32;not null;default:'unsettled'" json:"settlement_status"` - OfflineSettlementStatus string `gorm:"size:16;not null;default:'none';index" json:"offline_settlement_status"` - OfflineSettlementAmountCent int64 `gorm:"not null;default:0" json:"offline_settlement_amount_cent"` - OfflineSettlementRemark string `gorm:"size:255;not null;default:''" json:"offline_settlement_remark"` - OfflineSettledBy *uint64 `json:"offline_settled_by"` - OfflineSettledAt *time.Time `json:"offline_settled_at"` - RefundStatus string `gorm:"size:32;not null;default:'none';index" json:"refund_status"` - RefundAmountCent int64 `gorm:"not null;default:0" json:"refund_amount_cent"` - RefundedAt *time.Time `json:"refunded_at"` - DepositHoldStatus string `gorm:"size:16;not null;default:'none';index" json:"deposit_hold_status"` - DepositHoldAmountCent int64 `gorm:"not null;default:0" json:"deposit_hold_amount_cent"` - DepositHoldReason string `gorm:"size:255;not null;default:''" json:"deposit_hold_reason"` - DepositHeldBy *uint64 `json:"deposit_held_by"` - DepositHeldAt *time.Time `json:"deposit_held_at"` - DepositHoldReleasedAt *time.Time `json:"deposit_hold_released_at"` - OwnerSettledAt *time.Time `json:"owner_settled_at"` - SettledAt *time.Time `json:"settled_at"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + AccountSource string `gorm:"size:32;not null;default:'internal';index" json:"account_source"` + SourceChannel string `gorm:"size:32;not null;default:''" json:"source_channel"` + // 分销正常订单字段:仅在卖家和租客都通过分销网站完成交易时填写。 + DistributionSiteID *uint64 `gorm:"index" json:"distribution_site_id,omitempty"` + DistributionMode string `gorm:"size:32;not null;default:'';index" json:"distribution_mode"` + DistributionRuleVersionID *uint64 `gorm:"index" json:"distribution_rule_version_id,omitempty"` + Status string `gorm:"size:32;not null;default:'pending_payment'" json:"status"` + HandoffStatus string `gorm:"size:32;not null;default:'none'" json:"handoff_status"` + HandoffMode string `gorm:"size:16;not null;default:'owner';index" json:"handoff_mode"` + SettlementMode string `gorm:"size:32;not null;default:'owner_wallet'" json:"settlement_mode"` + ManagedAdminID *uint64 `gorm:"index" json:"managed_admin_id"` + SettlementStatus string `gorm:"size:32;not null;default:'unsettled'" json:"settlement_status"` + OfflineSettlementStatus string `gorm:"size:16;not null;default:'none';index" json:"offline_settlement_status"` + OfflineSettlementAmountCent int64 `gorm:"not null;default:0" json:"offline_settlement_amount_cent"` + OfflineSettlementRemark string `gorm:"size:255;not null;default:''" json:"offline_settlement_remark"` + OfflineSettledBy *uint64 `json:"offline_settled_by"` + OfflineSettledAt *time.Time `json:"offline_settled_at"` + RefundStatus string `gorm:"size:32;not null;default:'none';index" json:"refund_status"` + RefundAmountCent int64 `gorm:"not null;default:0" json:"refund_amount_cent"` + RefundedAt *time.Time `json:"refunded_at"` + DepositHoldStatus string `gorm:"size:16;not null;default:'none';index" json:"deposit_hold_status"` + DepositHoldAmountCent int64 `gorm:"not null;default:0" json:"deposit_hold_amount_cent"` + DepositHoldReason string `gorm:"size:255;not null;default:''" json:"deposit_hold_reason"` + DepositHeldBy *uint64 `json:"deposit_held_by"` + DepositHeldAt *time.Time `json:"deposit_held_at"` + DepositHoldReleasedAt *time.Time `json:"deposit_hold_released_at"` + OwnerSettledAt *time.Time `json:"owner_settled_at"` + SettledAt *time.Time `json:"settled_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } func (RentalOrder) TableName() string { diff --git a/backend/internal/modules/distribution/dto.go b/backend/internal/modules/distribution/dto.go new file mode 100644 index 0000000..c8fab62 --- /dev/null +++ b/backend/internal/modules/distribution/dto.go @@ -0,0 +1,179 @@ +package distribution + +import "time" + +type SiteDTO struct { + ID uint64 `json:"id"` + SiteCode string `json:"site_code"` + SiteName string `json:"site_name"` + Domain string `json:"domain"` + Status string `json:"status"` + DisplayAccountEnabled bool `json:"display_account_enabled"` + DefaultSupportGroupID *uint64 `json:"default_support_group_id,omitempty"` + TaxEnabled bool `json:"tax_enabled"` + TaxRateBPS int64 `json:"tax_rate_bps"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type CreateSiteRequest struct { + SiteCode string `json:"site_code" binding:"required,max=64"` + SiteName string `json:"site_name" binding:"required,max=128"` + Domain string `json:"domain" binding:"required,max=255"` + DisplayAccountEnabled *bool `json:"display_account_enabled"` + DefaultSupportGroupID *uint64 `json:"default_support_group_id"` + TaxEnabled *bool `json:"tax_enabled"` + TaxRateBPS *int64 `json:"tax_rate_bps"` +} + +type UpdateSiteRequest struct { + SiteName *string `json:"site_name"` + Domain *string `json:"domain"` + Status *string `json:"status"` + DisplayAccountEnabled *bool `json:"display_account_enabled"` + DefaultSupportGroupID *uint64 `json:"default_support_group_id"` + TaxEnabled *bool `json:"tax_enabled"` + TaxRateBPS *int64 `json:"tax_rate_bps"` +} + +type RuleDTO struct { + ID uint64 `json:"id"` + SiteID *uint64 `json:"site_id,omitempty"` + Mode string `json:"mode"` + Version int `json:"version"` + Status string `json:"status"` + PlatformReceiveRatio float64 `json:"platform_receive_ratio"` + PlatformSellRatio float64 `json:"platform_sell_ratio"` + DistributorReceiveRatio float64 `json:"distributor_receive_ratio"` + DistributorSellRatio float64 `json:"distributor_sell_ratio"` + DistributorARatio float64 `json:"distributor_a_ratio"` + DistributorBRatio float64 `json:"distributor_b_ratio"` + ServiceFeeValue float64 `json:"service_fee_value"` + ServiceFeeUnit string `json:"service_fee_unit"` + PaymentFeeBPS int64 `json:"payment_fee_bps"` + TaxEnabled bool `json:"tax_enabled"` + TaxRateBPS int64 `json:"tax_rate_bps"` + EffectiveFrom time.Time `json:"effective_from"` + EffectiveTo *time.Time `json:"effective_to,omitempty"` + CreatedBy *uint64 `json:"created_by,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +type CreateRuleRequest struct { + SiteID *uint64 `json:"site_id"` + Mode string `json:"mode" binding:"required"` + PlatformReceiveRatio float64 `json:"platform_receive_ratio"` + PlatformSellRatio float64 `json:"platform_sell_ratio"` + DistributorReceiveRatio float64 `json:"distributor_receive_ratio"` + DistributorSellRatio float64 `json:"distributor_sell_ratio"` + DistributorARatio float64 `json:"distributor_a_ratio"` + DistributorBRatio float64 `json:"distributor_b_ratio"` + ServiceFeeValue float64 `json:"service_fee_value"` + ServiceFeeUnit string `json:"service_fee_unit" binding:"required"` + PaymentFeeBPS int64 `json:"payment_fee_bps"` + TaxEnabled bool `json:"tax_enabled"` + TaxRateBPS int64 `json:"tax_rate_bps"` + EffectiveFrom time.Time `json:"effective_from"` +} + +type UpdateRuleStatusRequest struct { + Status string `json:"status" binding:"required,oneof=draft active disabled"` +} + +type DistributionOrderDTO struct { + OrderID uint64 `json:"order_id"` + OrderNo string `json:"order_no"` + SiteID uint64 `json:"site_id"` + ListingID uint64 `json:"listing_id"` + AccountID uint64 `json:"account_id"` + OwnerID uint64 `json:"owner_id"` + RenterID uint64 `json:"renter_id"` + Status string `json:"status"` + HandoffStatus string `json:"handoff_status"` + RefundStatus string `json:"refund_status"` + SettlementStatus string `json:"settlement_status"` + DistributionMode string `json:"distribution_mode"` + GrossPaidAmountCent int64 `json:"gross_paid_amount_cent"` + RefundAmountCent int64 `json:"refund_amount_cent"` + NetPaidAmountCent int64 `json:"net_paid_amount_cent"` + PaymentFeeCent int64 `json:"payment_fee_cent"` + ServiceFeeCent int64 `json:"service_fee_cent"` + MarkupAmountCent int64 `json:"markup_amount_cent"` + TaxAmountCent int64 `json:"tax_amount_cent"` + SplitBaseAmountCent int64 `json:"split_base_amount_cent"` + CreatedAt time.Time `json:"created_at"` +} + +type DistributionOrderListQuery struct { + SiteID uint64 + Kind string + Status string + Page int + Size int +} + +type DistributionOrderPage struct { + Items []DistributionOrderDTO `json:"items"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` +} + +type AccountDisplayDTO struct { + ID uint64 `json:"id"` + SiteID uint64 `json:"site_id"` + AccountID uint64 `json:"account_id"` + ListingID uint64 `json:"listing_id"` + DisplayStatus string `json:"display_status"` +} + +type UpdateAccountDisplayRequest struct { + DisplayStatus string `json:"display_status" binding:"required,oneof=enabled disabled sold offline"` +} + +type PaymentChannelDTO struct { + ID uint64 `json:"id"` + SiteID uint64 `json:"site_id"` + ConfigID uint64 `json:"config_id"` + Priority int `json:"priority"` + Status string `json:"status"` +} + +type BindPaymentChannelRequest struct { + ConfigID uint64 `json:"config_id" binding:"required"` + Priority int `json:"priority"` +} + +type SettlementBatchDTO struct { + ID uint64 `json:"id"` + SiteID uint64 `json:"site_id"` + BatchNo string `json:"batch_no"` + PeriodStart time.Time `json:"period_start"` + PeriodEnd time.Time `json:"period_end"` + TotalAmountCent int64 `json:"total_amount_cent"` + Status string `json:"status"` +} + +type CreateSettlementBatchRequest struct { + SiteID uint64 `json:"site_id" binding:"required"` + PeriodStart time.Time `json:"period_start" binding:"required"` + PeriodEnd time.Time `json:"period_end" binding:"required"` +} + +type SnapshotPartyRequest struct { + PartyRole string `json:"party_role" binding:"required"` + PartyType string `json:"party_type" binding:"required"` + PartyID uint64 `json:"party_id"` + Ratio float64 `json:"ratio"` + IncomeAmountCent int64 `json:"income_amount_cent"` + CostAmountCent int64 `json:"cost_amount_cent"` +} + +type CreateFinancialSnapshotRequest struct { + SiteID uint64 `json:"site_id" binding:"required"` + RuleVersionID uint64 `json:"rule_version_id" binding:"required"` + GrossPaidAmountCent int64 `json:"gross_paid_amount_cent"` + RefundAmountCent int64 `json:"refund_amount_cent"` + MarkupAmountCent int64 `json:"markup_amount_cent"` + Parties []SnapshotPartyRequest `json:"parties"` +} diff --git a/backend/internal/modules/distribution/handler.go b/backend/internal/modules/distribution/handler.go new file mode 100644 index 0000000..c0313ca --- /dev/null +++ b/backend/internal/modules/distribution/handler.go @@ -0,0 +1,343 @@ +package distribution + +import ( + "errors" + "net/http" + "strconv" + "strings" + + "hfb_sys/backend/internal/middleware" + "hfb_sys/backend/pkg/response" + + "github.com/gin-gonic/gin" +) + +type Handler struct{ service *Service } + +func NewHandler(service *Service) *Handler { return &Handler{service: service} } + +func (h *Handler) ListSites(c *gin.Context) { + items, err := h.service.ListSites(c.Request.Context()) + if err != nil { + writeError(c, err) + return + } + response.OK(c, gin.H{"items": items}) +} + +func (h *Handler) GetSite(c *gin.Context) { + id, ok := parseID(c) + if !ok { + return + } + item, err := h.service.FindSite(c.Request.Context(), id) + if err != nil { + writeError(c, err) + return + } + response.OK(c, item) +} + +func (h *Handler) CreateSite(c *gin.Context) { + var req CreateSiteRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "分销网站参数不正确") + return + } + item, err := h.service.CreateSite(c.Request.Context(), req) + if err != nil { + writeError(c, err) + return + } + response.Created(c, item) +} + +func (h *Handler) UpdateSite(c *gin.Context) { + id, ok := parseID(c) + if !ok { + return + } + var req UpdateSiteRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "分销网站参数不正确") + return + } + item, err := h.service.UpdateSite(c.Request.Context(), id, req) + if err != nil { + writeError(c, err) + return + } + response.OK(c, item) +} + +func (h *Handler) ListRules(c *gin.Context) { + var siteID *uint64 + if raw := strings.TrimSpace(c.Query("site_id")); raw != "" { + id, err := strconv.ParseUint(raw, 10, 64) + if err != nil || id == 0 { + response.BadRequest(c, "site_id 不正确") + return + } + siteID = &id + } + items, err := h.service.ListRules(c.Request.Context(), siteID, c.Query("mode")) + if err != nil { + writeError(c, err) + return + } + response.OK(c, gin.H{"items": items}) +} + +func (h *Handler) CreateRule(c *gin.Context) { + adminID, ok := currentAdminID(c) + if !ok { + response.Unauthorized(c, "缺少管理员上下文") + return + } + var req CreateRuleRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "分销规则参数不正确") + return + } + item, err := h.service.CreateRule(c.Request.Context(), adminID, req) + if err != nil { + writeError(c, err) + return + } + response.Created(c, item) +} + +func (h *Handler) UpdateRuleStatus(c *gin.Context) { + id, ok := parseID(c) + if !ok { + return + } + var req UpdateRuleStatusRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "规则状态不正确") + return + } + item, err := h.service.UpdateRuleStatus(c.Request.Context(), id, req.Status) + if err != nil { + writeError(c, err) + return + } + response.OK(c, item) +} + +func (h *Handler) ListOrders(c *gin.Context) { + siteID := parseQueryUint(c, "site_id") + if siteID < 0 { + return + } + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) + result, err := h.service.ListOrders(c.Request.Context(), DistributionOrderListQuery{SiteID: uint64(siteID), Kind: c.Query("kind"), Status: c.Query("status"), Page: page, Size: size}) + if err != nil { + writeError(c, err) + return + } + response.OK(c, result) +} + +func (h *Handler) FindOrder(c *gin.Context) { + id, ok := parseID(c) + if !ok { + return + } + item, err := h.service.FindOrder(c.Request.Context(), id) + if err != nil { + writeError(c, err) + return + } + response.OK(c, item) +} + +func (h *Handler) CreateFinancialSnapshot(c *gin.Context) { + id, ok := parseID(c) + if !ok { + return + } + var req CreateFinancialSnapshotRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "分销财务快照参数不正确") + return + } + item, err := h.service.CreateFinancialSnapshot(c.Request.Context(), id, req) + if err != nil { + writeError(c, err) + return + } + response.Created(c, item) +} + +func (h *Handler) ListAccountDisplays(c *gin.Context) { + siteID := parseQueryUint(c, "site_id") + if siteID <= 0 { + if siteID == 0 { + response.BadRequest(c, "site_id 不正确") + } + return + } + items, err := h.service.ListAccountDisplays(c.Request.Context(), uint64(siteID), c.Query("status")) + if err != nil { + writeError(c, err) + return + } + response.OK(c, gin.H{"items": items}) +} + +func (h *Handler) UpdateAccountDisplay(c *gin.Context) { + id, ok := parseID(c) + if !ok { + return + } + var req UpdateAccountDisplayRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "账号展示状态不正确") + return + } + item, err := h.service.UpdateAccountDisplay(c.Request.Context(), id, req.DisplayStatus) + if err != nil { + writeError(c, err) + return + } + response.OK(c, item) +} + +func (h *Handler) ListPaymentChannels(c *gin.Context) { + siteID := parseQueryUint(c, "site_id") + if siteID <= 0 { + response.BadRequest(c, "site_id 不正确") + return + } + items, err := h.service.ListPaymentChannels(c.Request.Context(), uint64(siteID)) + if err != nil { + writeError(c, err) + return + } + response.OK(c, gin.H{"items": items}) +} + +func (h *Handler) BindPaymentChannel(c *gin.Context) { + siteID := parseQueryUint(c, "site_id") + if siteID <= 0 { + response.BadRequest(c, "site_id 不正确") + return + } + var req BindPaymentChannelRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "支付通道参数不正确") + return + } + item, err := h.service.BindPaymentChannel(c.Request.Context(), uint64(siteID), req) + if err != nil { + writeError(c, err) + return + } + response.Created(c, item) +} + +func (h *Handler) UnbindPaymentChannel(c *gin.Context) { + id, ok := parseID(c) + if !ok { + return + } + if err := h.service.UnbindPaymentChannel(c.Request.Context(), id); err != nil { + writeError(c, err) + return + } + response.OK(c, gin.H{"deleted": true}) +} + +func (h *Handler) ListSettlementBatches(c *gin.Context) { + siteID := parseQueryUint(c, "site_id") + if siteID <= 0 { + response.BadRequest(c, "site_id 不正确") + return + } + items, err := h.service.ListSettlementBatches(c.Request.Context(), uint64(siteID)) + if err != nil { + writeError(c, err) + return + } + response.OK(c, gin.H{"items": items}) +} + +func (h *Handler) CreateSettlementBatch(c *gin.Context) { + adminID, ok := currentAdminID(c) + if !ok { + response.Unauthorized(c, "缺少管理员上下文") + return + } + var req CreateSettlementBatchRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "结算周期参数不正确") + return + } + item, err := h.service.CreateSettlementBatch(c.Request.Context(), adminID, req) + if err != nil { + writeError(c, err) + return + } + response.Created(c, item) +} + +func (h *Handler) MarkSettlementBatch(c *gin.Context) { + id, ok := parseID(c) + if !ok { + return + } + item, err := h.service.MarkSettlementBatch(c.Request.Context(), id) + if err != nil { + writeError(c, err) + return + } + response.OK(c, item) +} + +// parseQueryUint 返回 -1 表示格式错误,0 表示未传入。 +func parseQueryUint(c *gin.Context, key string) int64 { + raw := strings.TrimSpace(c.Query(key)) + if raw == "" { + return 0 + } + id, err := strconv.ParseUint(raw, 10, 64) + if err != nil || id == 0 { + response.BadRequest(c, key+" 不正确") + return -1 + } + return int64(id) +} + +func currentAdminID(c *gin.Context) (uint64, bool) { + value, ok := c.Get(middleware.ContextAdminID) + if !ok { + return 0, false + } + id, ok := value.(uint64) + return id, ok && id > 0 +} + +func parseID(c *gin.Context) (uint64, bool) { + id, err := strconv.ParseUint(c.Param("id"), 10, 64) + if err != nil || id == 0 { + response.BadRequest(c, "ID 不正确") + return 0, false + } + return id, true +} + +func writeError(c *gin.Context, err error) { + switch { + case errors.Is(err, ErrSiteNotFound), errors.Is(err, ErrRuleNotFound), errors.Is(err, ErrOrderNotFound), errors.Is(err, ErrAccountDisplayNotFound), errors.Is(err, ErrPaymentChannelNotFound), errors.Is(err, ErrSettlementBatchNotFound): + response.NotFound(c, "分销资源不存在") + case errors.Is(err, ErrInvalidAmount), errors.Is(err, ErrInvalidRate), errors.Is(err, ErrInvalidServiceUnit), errors.Is(err, ErrUnsupportedMode), errors.Is(err, ErrInvalidOrderKind), errors.Is(err, ErrInvalidPeriod), errors.Is(err, ErrBatchAlreadySettled): + response.BadRequest(c, "分销规则参数不正确") + case errors.Is(err, ErrDependencyUnavailable): + response.ServiceUnavailable(c, "分销服务暂时不可用") + default: + response.RecordError(c, err) + response.Error(c, http.StatusInternalServerError, "distribution_error", "分销服务暂时不可用") + } +} diff --git a/backend/internal/modules/distribution/pricing.go b/backend/internal/modules/distribution/pricing.go new file mode 100644 index 0000000..9c1ed3e --- /dev/null +++ b/backend/internal/modules/distribution/pricing.go @@ -0,0 +1,162 @@ +package distribution + +import ( + "errors" + "math" +) + +const ( + ModeTwoPartyDistributorReceive = "two_party_distributor_receive" + ModeTwoPartyPlatformReceive = "two_party_platform_receive" + ModeThreeParty = "three_party" + + ServiceFeeUnitPercent = "percent" + ServiceFeeUnitFixedAmount = "fixed_amount" + ServiceFeeUnitRatio = "ratio" +) + +var ( + ErrInvalidAmount = errors.New("invalid distribution amount") + ErrInvalidRate = errors.New("invalid distribution rate") + ErrInvalidServiceUnit = errors.New("invalid service fee unit") + ErrUnsupportedMode = errors.New("unsupported distribution mode") +) + +// Rule 是后台可编辑的分账规则。比例字段按参与方角色命名,避免方向歧义。 +type Rule struct { + Mode string + PlatformReceiveRatio float64 + PlatformSellRatio float64 + DistributorReceiveRatio float64 + DistributorSellRatio float64 + DistributorARatio float64 + DistributorBRatio float64 + ServiceFeeValue float64 + ServiceFeeUnit string + PaymentFeeBPS int64 + TaxEnabled bool + TaxRateBPS int64 +} + +// SettlementInput 使用支付单和退款单的整数分快照计算分销费用。 +type SettlementInput struct { + GrossPaidAmountCent int64 + RefundAmountCent int64 + MarkupAmountCent int64 + GrossProfitByParty []PartyProfitInput +} + +type PartyProfitInput struct { + PartyRole string + IncomeAmountCent int64 + CostAmountCent int64 +} + +type PartyTaxResult struct { + PartyRole string `json:"party_role"` + GrossProfitCent int64 `json:"gross_profit_cent"` + TaxAmountCent int64 `json:"tax_amount_cent"` +} + +type SettlementResult struct { + GrossPaidAmountCent int64 + RefundAmountCent int64 + NetPaidAmountCent int64 + PaymentFeeCent int64 + ServiceFeeCent int64 + MarkupAmountCent int64 + SplitBaseAmountCent int64 + TaxAmountCent int64 + TaxByParty []PartyTaxResult +} + +// DefaultRule 返回文档中约定的第一版默认值;调用方应将其写入规则表而非写死到业务流程。 +func DefaultRule(mode string) (Rule, error) { + base := Rule{ + Mode: mode, ServiceFeeValue: 2.5, ServiceFeeUnit: ServiceFeeUnitRatio, + PaymentFeeBPS: 50, TaxEnabled: false, TaxRateBPS: 100, + } + switch mode { + case ModeTwoPartyDistributorReceive: + base.DistributorReceiveRatio = 3.5 + base.PlatformSellRatio = 4 + case ModeTwoPartyPlatformReceive: + base.PlatformReceiveRatio = 3.5 + base.DistributorSellRatio = 4 + case ModeThreeParty: + base.DistributorARatio = 3.5 + base.DistributorBRatio = 4 + default: + return Rule{}, ErrUnsupportedMode + } + return base, nil +} + +// CalculateSettlement 先扣支付手续费,再计算服务费和分成基数;退款金额不能超过支付金额。 +func CalculateSettlement(rule Rule, input SettlementInput) (SettlementResult, error) { + if input.GrossPaidAmountCent < 0 || input.RefundAmountCent < 0 || input.MarkupAmountCent < 0 || input.RefundAmountCent > input.GrossPaidAmountCent { + return SettlementResult{}, ErrInvalidAmount + } + if rule.PaymentFeeBPS < 0 || rule.PaymentFeeBPS > 10000 || rule.TaxRateBPS < 0 || rule.TaxRateBPS > 10000 || rule.ServiceFeeValue < 0 { + return SettlementResult{}, ErrInvalidRate + } + if rule.ServiceFeeUnit != ServiceFeeUnitPercent && rule.ServiceFeeUnit != ServiceFeeUnitFixedAmount && rule.ServiceFeeUnit != ServiceFeeUnitRatio { + return SettlementResult{}, ErrInvalidServiceUnit + } + netPaid := input.GrossPaidAmountCent - input.RefundAmountCent + paymentFee := roundHalfUp(float64(netPaid) * float64(rule.PaymentFeeBPS) / 10000) + serviceFee, err := serviceFeeCent(rule, netPaid) + if err != nil { + return SettlementResult{}, err + } + splitBase := netPaid - paymentFee - serviceFee + if splitBase < 0 { + splitBase = 0 + } + result := SettlementResult{ + GrossPaidAmountCent: input.GrossPaidAmountCent, + RefundAmountCent: input.RefundAmountCent, + NetPaidAmountCent: netPaid, + PaymentFeeCent: paymentFee, + ServiceFeeCent: serviceFee, + MarkupAmountCent: input.MarkupAmountCent, + SplitBaseAmountCent: splitBase, + } + if rule.TaxEnabled { + result.TaxByParty = make([]PartyTaxResult, 0, len(input.GrossProfitByParty)) + for _, party := range input.GrossProfitByParty { + profit := party.IncomeAmountCent - party.CostAmountCent + tax := roundHalfUp(float64(max(profit, 0)) * float64(rule.TaxRateBPS) / 10000) + result.TaxAmountCent += tax + result.TaxByParty = append(result.TaxByParty, PartyTaxResult{PartyRole: party.PartyRole, GrossProfitCent: profit, TaxAmountCent: tax}) + } + } + return result, nil +} + +func serviceFeeCent(rule Rule, netPaid int64) (int64, error) { + switch rule.ServiceFeeUnit { + case ServiceFeeUnitPercent: + return roundHalfUp(float64(netPaid) * rule.ServiceFeeValue / 100), nil + case ServiceFeeUnitFixedAmount: + return roundHalfUp(rule.ServiceFeeValue * 100), nil + case ServiceFeeUnitRatio: + if rule.ServiceFeeValue <= 0 { + return 0, nil + } + return roundHalfUp(float64(netPaid) / rule.ServiceFeeValue), nil + default: + return 0, ErrInvalidServiceUnit + } +} + +func roundHalfUp(value float64) int64 { + return int64(math.Floor(value + 0.5)) +} + +func max(a, b int64) int64 { + if a > b { + return a + } + return b +} diff --git a/backend/internal/modules/distribution/pricing_test.go b/backend/internal/modules/distribution/pricing_test.go new file mode 100644 index 0000000..7abd1dd --- /dev/null +++ b/backend/internal/modules/distribution/pricing_test.go @@ -0,0 +1,55 @@ +package distribution + +import "testing" + +func TestDefaultRules(t *testing.T) { + cases := []struct { + mode string + check func(Rule) bool + }{ + {ModeTwoPartyDistributorReceive, func(r Rule) bool { return r.DistributorReceiveRatio == 3.5 && r.PlatformSellRatio == 4 }}, + {ModeTwoPartyPlatformReceive, func(r Rule) bool { return r.PlatformReceiveRatio == 3.5 && r.DistributorSellRatio == 4 }}, + {ModeThreeParty, func(r Rule) bool { return r.DistributorARatio == 3.5 && r.DistributorBRatio == 4 }}, + } + for _, item := range cases { + rule, err := DefaultRule(item.mode) + if err != nil || !item.check(rule) || rule.ServiceFeeValue != 2.5 || rule.PaymentFeeBPS != 50 { + t.Fatalf("default rule %s = %#v, err=%v", item.mode, rule, err) + } + } +} + +func TestCalculateSettlementDeductsFeeBeforeSplit(t *testing.T) { + rule := Rule{ServiceFeeValue: 0, ServiceFeeUnit: ServiceFeeUnitFixedAmount, PaymentFeeBPS: 50} + got, err := CalculateSettlement(rule, SettlementInput{GrossPaidAmountCent: 10001, RefundAmountCent: 1000, MarkupAmountCent: 300}) + if err != nil { + t.Fatal(err) + } + if got.NetPaidAmountCent != 9001 || got.PaymentFeeCent != 45 || got.SplitBaseAmountCent != 8956 { + t.Fatalf("unexpected settlement: %#v", got) + } +} + +func TestCalculateSettlementTaxesEachPositivePartyProfit(t *testing.T) { + rule := Rule{ServiceFeeUnit: ServiceFeeUnitFixedAmount, PaymentFeeBPS: 0, TaxEnabled: true, TaxRateBPS: 100} + got, err := CalculateSettlement(rule, SettlementInput{ + GrossPaidAmountCent: 10000, + GrossProfitByParty: []PartyProfitInput{ + {PartyRole: "distributor_a", IncomeAmountCent: 1000, CostAmountCent: 400}, + {PartyRole: "distributor_b", IncomeAmountCent: 500, CostAmountCent: 700}, + }, + }) + if err != nil { + t.Fatal(err) + } + if got.TaxAmountCent != 6 || len(got.TaxByParty) != 2 || got.TaxByParty[0].TaxAmountCent != 6 || got.TaxByParty[1].TaxAmountCent != 0 { + t.Fatalf("unexpected tax result: %#v", got) + } +} + +func TestCalculateSettlementRejectsRefundOverPayment(t *testing.T) { + _, err := CalculateSettlement(Rule{ServiceFeeUnit: ServiceFeeUnitFixedAmount}, SettlementInput{GrossPaidAmountCent: 1, RefundAmountCent: 2}) + if err != ErrInvalidAmount { + t.Fatalf("err = %v", err) + } +} diff --git a/backend/internal/modules/distribution/repository.go b/backend/internal/modules/distribution/repository.go new file mode 100644 index 0000000..ed5251f --- /dev/null +++ b/backend/internal/modules/distribution/repository.go @@ -0,0 +1,399 @@ +package distribution + +import ( + "context" + "errors" + "strings" + "time" + + "hfb_sys/backend/internal/model" + + "gorm.io/gorm" +) + +var ( + ErrSiteNotFound = errors.New("distribution site not found") + ErrRuleNotFound = errors.New("distribution rule not found") + ErrOrderNotFound = errors.New("distribution order not found") + ErrInvalidOrderKind = errors.New("invalid distribution order kind") + ErrAccountDisplayNotFound = errors.New("distribution account display not found") + ErrPaymentChannelNotFound = errors.New("distribution payment channel not found") + ErrInvalidPeriod = errors.New("invalid distribution settlement period") + ErrSettlementBatchNotFound = errors.New("distribution settlement batch not found") + ErrBatchAlreadySettled = errors.New("distribution settlement batch already settled") +) + +type Repository struct{ db *gorm.DB } + +func NewRepository(db *gorm.DB) *Repository { return &Repository{db: db} } + +func (r *Repository) ListSites(ctx context.Context) ([]SiteDTO, error) { + var rows []model.DistributionSite + if err := r.db.WithContext(ctx).Order("id ASC").Find(&rows).Error; err != nil { + return nil, err + } + items := make([]SiteDTO, 0, len(rows)) + for _, row := range rows { + items = append(items, siteDTO(row)) + } + return items, nil +} + +func (r *Repository) FindSite(ctx context.Context, id uint64) (*SiteDTO, error) { + var row model.DistributionSite + if err := r.db.WithContext(ctx).First(&row, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrSiteNotFound + } + return nil, err + } + item := siteDTO(row) + return &item, nil +} + +func (r *Repository) CreateSite(ctx context.Context, req CreateSiteRequest) (*SiteDTO, error) { + row := model.DistributionSite{ + SiteCode: strings.TrimSpace(req.SiteCode), SiteName: strings.TrimSpace(req.SiteName), Domain: strings.TrimSpace(req.Domain), + Status: "active", DisplayAccountEnabled: true, TaxRateBPS: 100, + } + if req.DisplayAccountEnabled != nil { + row.DisplayAccountEnabled = *req.DisplayAccountEnabled + } + if req.DefaultSupportGroupID != nil { + row.DefaultSupportGroupID = req.DefaultSupportGroupID + } + if req.TaxEnabled != nil { + row.TaxEnabled = *req.TaxEnabled + } + if req.TaxRateBPS != nil { + row.TaxRateBPS = *req.TaxRateBPS + } + if err := r.db.WithContext(ctx).Create(&row).Error; err != nil { + return nil, err + } + item := siteDTO(row) + return &item, nil +} + +func (r *Repository) UpdateSite(ctx context.Context, id uint64, req UpdateSiteRequest) (*SiteDTO, error) { + var row model.DistributionSite + if err := r.db.WithContext(ctx).First(&row, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrSiteNotFound + } + return nil, err + } + if req.SiteName != nil { + row.SiteName = strings.TrimSpace(*req.SiteName) + } + if req.Domain != nil { + row.Domain = strings.TrimSpace(*req.Domain) + } + if req.Status != nil { + row.Status = strings.TrimSpace(*req.Status) + } + if req.DisplayAccountEnabled != nil { + row.DisplayAccountEnabled = *req.DisplayAccountEnabled + } + if req.DefaultSupportGroupID != nil { + row.DefaultSupportGroupID = req.DefaultSupportGroupID + } + if req.TaxEnabled != nil { + row.TaxEnabled = *req.TaxEnabled + } + if req.TaxRateBPS != nil { + row.TaxRateBPS = *req.TaxRateBPS + } + if err := r.db.WithContext(ctx).Save(&row).Error; err != nil { + return nil, err + } + item := siteDTO(row) + return &item, nil +} + +func (r *Repository) ListRules(ctx context.Context, siteID *uint64, mode string) ([]RuleDTO, error) { + db := r.db.WithContext(ctx).Model(&model.DistributionRuleVersion{}).Order("effective_from DESC, id DESC") + if siteID == nil { + db = db.Where("site_id IS NULL") + } else { + db = db.Where("site_id = ?", *siteID) + } + if mode = strings.TrimSpace(mode); mode != "" { + db = db.Where("mode = ?", mode) + } + var rows []model.DistributionRuleVersion + if err := db.Find(&rows).Error; err != nil { + return nil, err + } + items := make([]RuleDTO, 0, len(rows)) + for _, row := range rows { + items = append(items, ruleDTO(row)) + } + return items, nil +} + +func (r *Repository) CreateRule(ctx context.Context, adminID uint64, req CreateRuleRequest) (*RuleDTO, error) { + version := 1 + var maxVersion int + db := r.db.WithContext(ctx).Model(&model.DistributionRuleVersion{}).Where("mode = ?", req.Mode) + if req.SiteID == nil { + db = db.Where("site_id IS NULL") + } else { + db = db.Where("site_id = ?", *req.SiteID) + } + if err := db.Select("COALESCE(MAX(version), 0)").Scan(&maxVersion).Error; err != nil { + return nil, err + } + version = maxVersion + 1 + effectiveFrom := req.EffectiveFrom + if effectiveFrom.IsZero() { + effectiveFrom = time.Now() + } + row := model.DistributionRuleVersion{ + SiteID: req.SiteID, Mode: strings.TrimSpace(req.Mode), Version: version, Status: "draft", + PlatformReceiveRatio: req.PlatformReceiveRatio, PlatformSellRatio: req.PlatformSellRatio, + DistributorReceiveRatio: req.DistributorReceiveRatio, DistributorSellRatio: req.DistributorSellRatio, + DistributorARatio: req.DistributorARatio, DistributorBRatio: req.DistributorBRatio, + ServiceFeeValue: req.ServiceFeeValue, ServiceFeeUnit: strings.TrimSpace(req.ServiceFeeUnit), + PaymentFeeBPS: req.PaymentFeeBPS, TaxEnabled: req.TaxEnabled, TaxRateBPS: req.TaxRateBPS, + EffectiveFrom: effectiveFrom, CreatedBy: &adminID, + } + if row.PaymentFeeBPS == 0 { + row.PaymentFeeBPS = 50 + } + if row.TaxRateBPS == 0 { + row.TaxRateBPS = 100 + } + if err := r.db.WithContext(ctx).Create(&row).Error; err != nil { + return nil, err + } + item := ruleDTO(row) + return &item, nil +} + +func (r *Repository) UpdateRuleStatus(ctx context.Context, id uint64, status string) (*RuleDTO, error) { + var row model.DistributionRuleVersion + if err := r.db.WithContext(ctx).First(&row, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrRuleNotFound + } + return nil, err + } + row.Status = strings.TrimSpace(status) + if err := r.db.WithContext(ctx).Save(&row).Error; err != nil { + return nil, err + } + item := ruleDTO(row) + return &item, nil +} + +func siteDTO(row model.DistributionSite) SiteDTO { + return SiteDTO{ID: row.ID, SiteCode: row.SiteCode, SiteName: row.SiteName, Domain: row.Domain, Status: row.Status, DisplayAccountEnabled: row.DisplayAccountEnabled, DefaultSupportGroupID: row.DefaultSupportGroupID, TaxEnabled: row.TaxEnabled, TaxRateBPS: row.TaxRateBPS, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt} +} + +func ruleDTO(row model.DistributionRuleVersion) RuleDTO { + return RuleDTO{ID: row.ID, SiteID: row.SiteID, Mode: row.Mode, Version: row.Version, Status: row.Status, PlatformReceiveRatio: row.PlatformReceiveRatio, PlatformSellRatio: row.PlatformSellRatio, DistributorReceiveRatio: row.DistributorReceiveRatio, DistributorSellRatio: row.DistributorSellRatio, DistributorARatio: row.DistributorARatio, DistributorBRatio: row.DistributorBRatio, ServiceFeeValue: row.ServiceFeeValue, ServiceFeeUnit: row.ServiceFeeUnit, PaymentFeeBPS: row.PaymentFeeBPS, TaxEnabled: row.TaxEnabled, TaxRateBPS: row.TaxRateBPS, EffectiveFrom: row.EffectiveFrom, EffectiveTo: row.EffectiveTo, CreatedBy: row.CreatedBy, CreatedAt: row.CreatedAt} +} + +func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderListQuery) (*DistributionOrderPage, error) { + page, size := query.Page, query.Size + if page < 1 { + page = 1 + } + if size < 1 { + size = 20 + } + 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") + if query.SiteID > 0 { + base = base.Where("ro.distribution_site_id = ?", query.SiteID) + } + if query.Status != "" { + base = base.Where("ro.status = ?", query.Status) + } + 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'))") + 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": + default: + return nil, ErrInvalidOrderKind + } + var total int64 + if err := base.Session(&gorm.Session{}).Select("ro.id").Count(&total).Error; err != nil { + 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, ro.created_at" + if err := base.Select(selectSQL).Order("ro.id DESC").Offset((page - 1) * size).Limit(size).Scan(&items).Error; err != nil { + return nil, err + } + return &DistributionOrderPage{Items: items, Total: total, Page: page, PageSize: size}, nil +} + +func (r *Repository) FindOrder(ctx context.Context, 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, ro.created_at").Where("ro.id = ?", id).Scan(&item).Error + if err != nil { + return nil, err + } + if item.OrderID == 0 { + return nil, ErrOrderNotFound + } + return &item, nil +} + +func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, status string) ([]AccountDisplayDTO, error) { + var rows []model.DistributionSiteAccount + db := r.db.WithContext(ctx).Where("site_id = ?", siteID).Order("id DESC") + if status != "" { + db = db.Where("display_status = ?", status) + } + if err := db.Find(&rows).Error; err != nil { + return nil, err + } + items := make([]AccountDisplayDTO, 0, len(rows)) + for _, row := range rows { + items = append(items, AccountDisplayDTO{ID: row.ID, SiteID: row.SiteID, AccountID: row.AccountID, ListingID: row.ListingID, DisplayStatus: row.DisplayStatus}) + } + return items, nil +} + +func (r *Repository) UpdateAccountDisplay(ctx context.Context, id uint64, status string) (*AccountDisplayDTO, error) { + var row model.DistributionSiteAccount + if err := r.db.WithContext(ctx).First(&row, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrAccountDisplayNotFound + } + return nil, err + } + row.DisplayStatus = strings.TrimSpace(status) + if err := r.db.WithContext(ctx).Save(&row).Error; err != nil { + return nil, err + } + item := &AccountDisplayDTO{ID: row.ID, SiteID: row.SiteID, AccountID: row.AccountID, ListingID: row.ListingID, DisplayStatus: row.DisplayStatus} + return item, nil +} + +func (r *Repository) ListPaymentChannels(ctx context.Context, siteID uint64) ([]PaymentChannelDTO, error) { + var rows []model.DistributionSitePaymentChannel + if err := r.db.WithContext(ctx).Where("site_id = ?", siteID).Order("priority ASC, id ASC").Find(&rows).Error; err != nil { + return nil, err + } + items := make([]PaymentChannelDTO, 0, len(rows)) + for _, row := range rows { + items = append(items, PaymentChannelDTO{ID: row.ID, SiteID: row.SiteID, ConfigID: row.ConfigID, Priority: row.Priority, Status: row.Status}) + } + return items, nil +} + +func (r *Repository) BindPaymentChannel(ctx context.Context, siteID uint64, req BindPaymentChannelRequest) (*PaymentChannelDTO, error) { + row := model.DistributionSitePaymentChannel{SiteID: siteID, ConfigID: req.ConfigID, Priority: req.Priority, Status: "active"} + if err := r.db.WithContext(ctx).Where("site_id = ? AND config_id = ?", siteID, req.ConfigID).FirstOrCreate(&row).Error; err != nil { + return nil, err + } + return &PaymentChannelDTO{ID: row.ID, SiteID: row.SiteID, ConfigID: row.ConfigID, Priority: row.Priority, Status: row.Status}, nil +} + +func (r *Repository) UnbindPaymentChannel(ctx context.Context, id uint64) error { + result := r.db.WithContext(ctx).Delete(&model.DistributionSitePaymentChannel{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return ErrPaymentChannelNotFound + } + return nil +} + +func (r *Repository) ListSettlementBatches(ctx context.Context, siteID uint64) ([]SettlementBatchDTO, error) { + var rows []model.DistributionSettlementBatch + if err := r.db.WithContext(ctx).Where("site_id = ?", siteID).Order("id DESC").Find(&rows).Error; err != nil { + return nil, err + } + items := make([]SettlementBatchDTO, 0, len(rows)) + for _, row := range rows { + items = append(items, SettlementBatchDTO{ID: row.ID, SiteID: row.SiteID, BatchNo: row.BatchNo, PeriodStart: row.PeriodStart, PeriodEnd: row.PeriodEnd, TotalAmountCent: row.TotalAmountCent, Status: row.Status}) + } + return items, nil +} + +func (r *Repository) CreateSettlementBatch(ctx context.Context, adminID uint64, req CreateSettlementBatchRequest) (*SettlementBatchDTO, error) { + if req.PeriodEnd.Before(req.PeriodStart) { + return nil, ErrInvalidPeriod + } + row := model.DistributionSettlementBatch{SiteID: req.SiteID, BatchNo: "DS" + time.Now().Format("20060102150405.000000000"), PeriodStart: req.PeriodStart, PeriodEnd: req.PeriodEnd, Status: "draft", CreatedBy: &adminID} + if err := r.db.WithContext(ctx).Create(&row).Error; err != nil { + return nil, err + } + return &SettlementBatchDTO{ID: row.ID, SiteID: row.SiteID, BatchNo: row.BatchNo, PeriodStart: row.PeriodStart, PeriodEnd: row.PeriodEnd, TotalAmountCent: row.TotalAmountCent, Status: row.Status}, nil +} + +func (r *Repository) MarkSettlementBatch(ctx context.Context, id uint64) (*SettlementBatchDTO, error) { + var row model.DistributionSettlementBatch + if err := r.db.WithContext(ctx).First(&row, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrSettlementBatchNotFound + } + return nil, err + } + if row.Status == "settled" { + return nil, ErrBatchAlreadySettled + } + now := time.Now() + row.Status = "settled" + row.SettledAt = &now + if err := r.db.WithContext(ctx).Save(&row).Error; err != nil { + return nil, err + } + return &SettlementBatchDTO{ID: row.ID, SiteID: row.SiteID, BatchNo: row.BatchNo, PeriodStart: row.PeriodStart, PeriodEnd: row.PeriodEnd, TotalAmountCent: row.TotalAmountCent, Status: row.Status}, nil +} + +func (r *Repository) CreateFinancialSnapshot(ctx context.Context, orderID uint64, req CreateFinancialSnapshotRequest) (*DistributionOrderDTO, error) { + var rule model.DistributionRuleVersion + if err := r.db.WithContext(ctx).First(&rule, req.RuleVersionID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrRuleNotFound + } + return nil, err + } + if req.SiteID == 0 || orderID == 0 { + return nil, ErrInvalidAmount + } + partyInputs := make([]PartyProfitInput, 0, len(req.Parties)) + for _, party := range req.Parties { + partyInputs = append(partyInputs, PartyProfitInput{PartyRole: party.PartyRole, IncomeAmountCent: party.IncomeAmountCent, CostAmountCent: party.CostAmountCent}) + } + result, err := CalculateSettlement(Rule{ServiceFeeValue: rule.ServiceFeeValue, ServiceFeeUnit: rule.ServiceFeeUnit, PaymentFeeBPS: rule.PaymentFeeBPS, TaxEnabled: rule.TaxEnabled, TaxRateBPS: rule.TaxRateBPS}, SettlementInput{GrossPaidAmountCent: req.GrossPaidAmountCent, RefundAmountCent: req.RefundAmountCent, MarkupAmountCent: req.MarkupAmountCent, GrossProfitByParty: partyInputs}) + if err != nil { + return nil, err + } + err = r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + financial := model.DistributionOrderFinancial{OrderID: orderID, SiteID: req.SiteID, RuleVersionID: req.RuleVersionID, GrossPaidAmountCent: result.GrossPaidAmountCent, RefundAmountCent: result.RefundAmountCent, NetPaidAmountCent: result.NetPaidAmountCent, PaymentFeeBPS: rule.PaymentFeeBPS, PaymentFeeCent: result.PaymentFeeCent, ServiceFeeValue: rule.ServiceFeeValue, ServiceFeeUnit: rule.ServiceFeeUnit, ServiceFeeCent: result.ServiceFeeCent, MarkupAmountCent: result.MarkupAmountCent, TaxEnabled: rule.TaxEnabled, TaxRateBPS: rule.TaxRateBPS, TaxAmountCent: result.TaxAmountCent, SplitBaseAmountCent: result.SplitBaseAmountCent, SettlementStatus: "pending"} + if err := tx.Where("order_id = ?", orderID).Assign(financial).FirstOrCreate(&financial).Error; err != nil { + return err + } + if err := tx.Where("order_id = ?", orderID).Delete(&model.DistributionOrderParty{}).Error; err != nil { + return err + } + for idx, party := range req.Parties { + tax := int64(0) + if idx < len(result.TaxByParty) { + tax = result.TaxByParty[idx].TaxAmountCent + } + profit := party.IncomeAmountCent - party.CostAmountCent + row := model.DistributionOrderParty{OrderID: orderID, SiteID: req.SiteID, PartyRole: party.PartyRole, PartyType: party.PartyType, PartyID: party.PartyID, Ratio: party.Ratio, IncomeAmountCent: party.IncomeAmountCent, CostAmountCent: party.CostAmountCent, ProfitAmountCent: profit, TaxAmountCent: tax} + if err := tx.Create(&row).Error; err != nil { + return err + } + } + return tx.Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{"distribution_site_id": req.SiteID, "distribution_rule_version_id": req.RuleVersionID}).Error + }) + if err != nil { + return nil, err + } + return r.FindOrder(ctx, orderID) +} diff --git a/backend/internal/modules/distribution/repository_test.go b/backend/internal/modules/distribution/repository_test.go new file mode 100644 index 0000000..bf98979 --- /dev/null +++ b/backend/internal/modules/distribution/repository_test.go @@ -0,0 +1,87 @@ +package distribution + +import ( + "testing" + "time" + + "hfb_sys/backend/internal/model" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func distributionTestDB(t *testing.T) *gorm.DB { + t.Helper() + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + if err != nil { + t.Fatal(err) + } + if err := db.AutoMigrate(&model.ChatConversation{}, &model.RentalOrder{}, &model.DistributionSite{}, &model.DistributionRuleVersion{}, &model.DistributionOrderFinancial{}, &model.DistributionOrderParty{}, &model.DistributionSiteAccount{}, &model.DistributionSitePaymentChannel{}, &model.DistributionSettlementBatch{}, &model.DistributionSettlementItem{}); err != nil { + t.Fatal(err) + } + return db +} + +func TestDistributionOrderSnapshotAndQueries(t *testing.T) { + db := distributionTestDB(t) + repo := NewRepository(db) + site := model.DistributionSite{SiteCode: "demo", SiteName: "演示站", Domain: "distribution.example.com", DisplayAccountEnabled: true, TaxRateBPS: 100} + if err := db.Create(&site).Error; err != nil { + t.Fatal(err) + } + now := time.Now() + rule := model.DistributionRuleVersion{SiteID: &site.ID, Mode: ModeTwoPartyDistributorReceive, Version: 1, Status: "active", ServiceFeeValue: 0, ServiceFeeUnit: ServiceFeeUnitFixedAmount, PaymentFeeBPS: 50, TaxRateBPS: 100, EffectiveFrom: now} + if err := db.Create(&rule).Error; err != nil { + t.Fatal(err) + } + order := model.RentalOrder{OrderNo: "D-1", ListingID: 1, AccountID: 2, OwnerID: 3, RenterID: 4, Status: "completed"} + if err := db.Create(&order).Error; err != nil { + t.Fatal(err) + } + _, err := repo.CreateFinancialSnapshot(t.Context(), order.ID, CreateFinancialSnapshotRequest{SiteID: site.ID, RuleVersionID: rule.ID, GrossPaidAmountCent: 10001, RefundAmountCent: 1000, MarkupAmountCent: 300, Parties: []SnapshotPartyRequest{{PartyRole: "distributor_receive", PartyType: "site", PartyID: site.ID, IncomeAmountCent: 1000, CostAmountCent: 400}}}) + if err != nil { + t.Fatal(err) + } + page, err := repo.ListOrders(t.Context(), DistributionOrderListQuery{SiteID: site.ID, Kind: "receive", Page: 1, Size: 20}) + if err != nil { + t.Fatal(err) + } + if page.Total != 1 || len(page.Items) != 1 || page.Items[0].PaymentFeeCent != 45 || page.Items[0].NetPaidAmountCent != 9001 { + t.Fatalf("unexpected page: %#v", page) + } + if _, err := repo.FindOrder(t.Context(), order.ID); err != nil { + t.Fatal(err) + } +} + +func TestDistributionPaymentChannelAndSettlementBatch(t *testing.T) { + db := distributionTestDB(t) + repo := NewRepository(db) + site := model.DistributionSite{SiteCode: "demo", SiteName: "演示站", Domain: "distribution.example.com", TaxRateBPS: 100} + if err := db.Create(&site).Error; err != nil { + t.Fatal(err) + } + channel, err := repo.BindPaymentChannel(t.Context(), site.ID, BindPaymentChannelRequest{ConfigID: 9, Priority: 1}) + if err != nil { + t.Fatal(err) + } + items, err := repo.ListPaymentChannels(t.Context(), site.ID) + if err != nil || len(items) != 1 || items[0].ConfigID != 9 { + t.Fatalf("channels = %#v, err=%v", items, err) + } + if err := repo.UnbindPaymentChannel(t.Context(), channel.ID); err != nil { + t.Fatal(err) + } + start := time.Now().Add(-time.Hour) + batch, err := repo.CreateSettlementBatch(t.Context(), 1, CreateSettlementBatchRequest{SiteID: site.ID, PeriodStart: start, PeriodEnd: time.Now()}) + if err != nil { + t.Fatal(err) + } + settled, err := repo.MarkSettlementBatch(t.Context(), batch.ID) + if err != nil || settled.Status != "settled" { + t.Fatalf("settled = %#v, err=%v", settled, err) + } + if _, err := repo.MarkSettlementBatch(t.Context(), batch.ID); err != ErrBatchAlreadySettled { + t.Fatalf("second settle err = %v", err) + } +} diff --git a/backend/internal/modules/distribution/service.go b/backend/internal/modules/distribution/service.go new file mode 100644 index 0000000..881aa7b --- /dev/null +++ b/backend/internal/modules/distribution/service.go @@ -0,0 +1,129 @@ +package distribution + +import ( + "context" + "errors" +) + +var ErrDependencyUnavailable = errors.New("distribution dependency unavailable") + +type Service struct{ repo *Repository } + +func NewService(repo *Repository) *Service { return &Service{repo: repo} } + +func (s *Service) available() error { + if s == nil || s.repo == nil { + return ErrDependencyUnavailable + } + return nil +} +func (s *Service) ListSites(ctx context.Context) ([]SiteDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.ListSites(ctx) +} +func (s *Service) FindSite(ctx context.Context, id uint64) (*SiteDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.FindSite(ctx, id) +} +func (s *Service) CreateSite(ctx context.Context, req CreateSiteRequest) (*SiteDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.CreateSite(ctx, req) +} +func (s *Service) UpdateSite(ctx context.Context, id uint64, req UpdateSiteRequest) (*SiteDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.UpdateSite(ctx, id, req) +} +func (s *Service) ListRules(ctx context.Context, siteID *uint64, mode string) ([]RuleDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.ListRules(ctx, siteID, mode) +} +func (s *Service) CreateRule(ctx context.Context, adminID uint64, req CreateRuleRequest) (*RuleDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.CreateRule(ctx, adminID, req) +} +func (s *Service) UpdateRuleStatus(ctx context.Context, id uint64, status string) (*RuleDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.UpdateRuleStatus(ctx, id, status) +} + +func (s *Service) ListOrders(ctx context.Context, query DistributionOrderListQuery) (*DistributionOrderPage, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.ListOrders(ctx, query) +} +func (s *Service) FindOrder(ctx context.Context, id uint64) (*DistributionOrderDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.FindOrder(ctx, id) +} +func (s *Service) ListAccountDisplays(ctx context.Context, siteID uint64, status string) ([]AccountDisplayDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.ListAccountDisplays(ctx, siteID, status) +} +func (s *Service) UpdateAccountDisplay(ctx context.Context, id uint64, status string) (*AccountDisplayDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.UpdateAccountDisplay(ctx, id, status) +} +func (s *Service) ListPaymentChannels(ctx context.Context, siteID uint64) ([]PaymentChannelDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.ListPaymentChannels(ctx, siteID) +} +func (s *Service) BindPaymentChannel(ctx context.Context, siteID uint64, req BindPaymentChannelRequest) (*PaymentChannelDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.BindPaymentChannel(ctx, siteID, req) +} +func (s *Service) UnbindPaymentChannel(ctx context.Context, id uint64) error { + if err := s.available(); err != nil { + return err + } + return s.repo.UnbindPaymentChannel(ctx, id) +} +func (s *Service) ListSettlementBatches(ctx context.Context, siteID uint64) ([]SettlementBatchDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.ListSettlementBatches(ctx, siteID) +} +func (s *Service) CreateSettlementBatch(ctx context.Context, adminID uint64, req CreateSettlementBatchRequest) (*SettlementBatchDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.CreateSettlementBatch(ctx, adminID, req) +} +func (s *Service) MarkSettlementBatch(ctx context.Context, id uint64) (*SettlementBatchDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.MarkSettlementBatch(ctx, id) +} + +func (s *Service) CreateFinancialSnapshot(ctx context.Context, orderID uint64, req CreateFinancialSnapshotRequest) (*DistributionOrderDTO, error) { + if err := s.available(); err != nil { + return nil, err + } + return s.repo.CreateFinancialSnapshot(ctx, orderID, req) +} diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index 615d855..06fc017 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -22,6 +22,7 @@ import ( "hfb_sys/backend/internal/modules/chat" "hfb_sys/backend/internal/modules/chathub" "hfb_sys/backend/internal/modules/dispute" + "hfb_sys/backend/internal/modules/distribution" filemodule "hfb_sys/backend/internal/modules/file" "hfb_sys/backend/internal/modules/listing" "hfb_sys/backend/internal/modules/mohong" @@ -308,6 +309,13 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { } systemConfigService := systemconfig.NewService(systemConfigRepo) systemConfigHandler := systemconfig.NewHandler(systemConfigService) + var distributionRepo *distribution.Repository + if deps.DB != nil { + distributionRepo = distribution.NewRepository(deps.DB) + } + distributionService := distribution.NewService(distributionRepo) + distributionHandler := distribution.NewHandler(distributionService) + backupMonitorHandler := backupmonitor.NewHandler(cfg.BackupStatusFile) var adminRoleRepo *adminrole.Repository if deps.DB != nil { adminRoleRepo = adminrole.NewRepository(deps.DB, deps.Redis) @@ -588,6 +596,24 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { adminRoutes.POST("/auth/logout", adminAuthHandler.Logout) adminRoutes.POST("/files/upload", fileHandler.Upload) adminRoutes.GET("/dashboard", requirePerm("dashboard:view"), adminDashboardHandler.Summary) + adminRoutes.GET("/distribution/sites", requirePerm("distribution:site_view"), distributionHandler.ListSites) + adminRoutes.GET("/distribution/sites/:id", requirePerm("distribution:site_view"), distributionHandler.GetSite) + adminRoutes.POST("/distribution/sites", requirePerm("distribution:site_manage"), distributionHandler.CreateSite) + adminRoutes.PUT("/distribution/sites/:id", requirePerm("distribution:site_manage"), distributionHandler.UpdateSite) + adminRoutes.GET("/distribution/rules", requirePerm("distribution:rule_view"), distributionHandler.ListRules) + adminRoutes.POST("/distribution/rules", requirePerm("distribution:rule_manage"), distributionHandler.CreateRule) + adminRoutes.PUT("/distribution/rules/:id/status", requirePerm("distribution:rule_manage"), distributionHandler.UpdateRuleStatus) + adminRoutes.GET("/distribution/orders", requirePerm("distribution:order_view"), distributionHandler.ListOrders) + adminRoutes.GET("/distribution/orders/:id", requirePerm("distribution:order_view"), distributionHandler.FindOrder) + adminRoutes.POST("/distribution/orders/:id/financial-snapshot", requirePerm("distribution:settlement_manage"), distributionHandler.CreateFinancialSnapshot) + adminRoutes.GET("/distribution/accounts", requirePerm("distribution:account_view"), distributionHandler.ListAccountDisplays) + adminRoutes.PUT("/distribution/accounts/:id/display", requirePerm("distribution:account_display"), distributionHandler.UpdateAccountDisplay) + adminRoutes.GET("/distribution/payment-channels", requirePerm("distribution:payment_view"), distributionHandler.ListPaymentChannels) + adminRoutes.POST("/distribution/payment-channels", requirePerm("distribution:payment_manage"), distributionHandler.BindPaymentChannel) + adminRoutes.DELETE("/distribution/payment-channels/:id", requirePerm("distribution:payment_manage"), distributionHandler.UnbindPaymentChannel) + adminRoutes.GET("/distribution/settlement-batches", requirePerm("distribution:settlement_view"), distributionHandler.ListSettlementBatches) + adminRoutes.POST("/distribution/settlement-batches", requirePerm("distribution:settlement_manage"), distributionHandler.CreateSettlementBatch) + adminRoutes.POST("/distribution/settlement-batches/:id/settle", requirePerm("distribution:settlement_manage"), distributionHandler.MarkSettlementBatch) adminRoutes.GET("/files/object", fileHandler.Object) adminRoutes.GET("/users", requirePerm("user:view"), adminUserHandler.List) adminRoutes.POST("/users/:id/freeze", requirePerm("user:freeze"), adminUserHandler.Freeze) diff --git a/backend/migrations/000062_distribution_init.sql b/backend/migrations/000062_distribution_init.sql new file mode 100644 index 0000000..b371a7e --- /dev/null +++ b/backend/migrations/000062_distribution_init.sql @@ -0,0 +1,259 @@ +-- +goose Up + +CREATE TABLE distribution_sites ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + site_code VARCHAR(64) NOT NULL, + site_name VARCHAR(128) NOT NULL, + domain VARCHAR(255) NOT NULL, + status VARCHAR(16) NOT NULL DEFAULT 'active', + display_account_enabled TINYINT(1) NOT NULL DEFAULT 1, + default_support_group_id BIGINT UNSIGNED NULL, + tax_enabled TINYINT(1) NOT NULL DEFAULT 0, + tax_rate_bps BIGINT NOT NULL DEFAULT 100, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_distribution_sites_code (site_code), + UNIQUE KEY uk_distribution_sites_domain (domain), + KEY idx_distribution_sites_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销网站主体'; + +CREATE TABLE distribution_site_members ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + site_id BIGINT UNSIGNED NOT NULL, + admin_user_id BIGINT UNSIGNED NOT NULL, + member_type VARCHAR(32) NOT NULL, + status VARCHAR(16) NOT NULL DEFAULT 'active', + data_scope VARCHAR(32) NOT NULL DEFAULT 'site', + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_distribution_site_member (site_id, admin_user_id), + KEY idx_distribution_site_members_admin (admin_user_id, status), + CONSTRAINT fk_distribution_site_members_site FOREIGN KEY (site_id) REFERENCES distribution_sites (id), + CONSTRAINT fk_distribution_site_members_admin FOREIGN KEY (admin_user_id) REFERENCES admin_users (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销网站后台成员'; + +CREATE TABLE distribution_site_accounts ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + site_id BIGINT UNSIGNED NOT NULL, + account_id BIGINT UNSIGNED NOT NULL, + listing_id BIGINT UNSIGNED NOT NULL, + display_status VARCHAR(16) NOT NULL DEFAULT 'enabled', + display_started_at DATETIME NULL, + display_ended_at DATETIME NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_distribution_site_account (site_id, account_id), + KEY idx_distribution_site_accounts_listing (listing_id), + KEY idx_distribution_site_accounts_display (site_id, display_status), + CONSTRAINT fk_distribution_site_accounts_site FOREIGN KEY (site_id) REFERENCES distribution_sites (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销网站账号展示关系'; + +CREATE TABLE distribution_rule_versions ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + site_id BIGINT UNSIGNED NULL, + mode VARCHAR(32) NOT NULL, + version INT NOT NULL, + status VARCHAR(16) NOT NULL DEFAULT 'draft', + platform_receive_ratio DECIMAL(10,2) NOT NULL DEFAULT 0, + platform_sell_ratio DECIMAL(10,2) NOT NULL DEFAULT 0, + distributor_receive_ratio DECIMAL(10,2) NOT NULL DEFAULT 0, + distributor_sell_ratio DECIMAL(10,2) NOT NULL DEFAULT 0, + distributor_a_ratio DECIMAL(10,2) NOT NULL DEFAULT 0, + distributor_b_ratio DECIMAL(10,2) NOT NULL DEFAULT 0, + service_fee_value DECIMAL(12,4) NOT NULL DEFAULT 0, + service_fee_unit VARCHAR(24) NOT NULL DEFAULT 'ratio', + payment_fee_bps BIGINT NOT NULL DEFAULT 50, + tax_enabled TINYINT(1) NOT NULL DEFAULT 0, + tax_rate_bps BIGINT NOT NULL DEFAULT 100, + effective_from DATETIME NOT NULL, + effective_to DATETIME NULL, + created_by BIGINT UNSIGNED NULL, + created_at DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_distribution_rule_version (site_id, mode, version), + KEY idx_distribution_rule_effective (site_id, mode, status, effective_from), + CONSTRAINT fk_distribution_rule_site FOREIGN KEY (site_id) REFERENCES distribution_sites (id), + CONSTRAINT fk_distribution_rule_admin FOREIGN KEY (created_by) REFERENCES admin_users (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销分账规则版本'; + +CREATE TABLE distribution_order_financials ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + order_id BIGINT UNSIGNED NOT NULL, + site_id BIGINT UNSIGNED NOT NULL, + rule_version_id BIGINT UNSIGNED NOT NULL, + gross_paid_amount_cent BIGINT NOT NULL DEFAULT 0, + refund_amount_cent BIGINT NOT NULL DEFAULT 0, + net_paid_amount_cent BIGINT NOT NULL DEFAULT 0, + payment_fee_bps BIGINT NOT NULL DEFAULT 50, + payment_fee_cent BIGINT NOT NULL DEFAULT 0, + service_fee_value DECIMAL(12,4) NOT NULL DEFAULT 0, + service_fee_unit VARCHAR(24) NOT NULL DEFAULT 'ratio', + service_fee_cent BIGINT NOT NULL DEFAULT 0, + markup_amount_cent BIGINT NOT NULL DEFAULT 0, + tax_enabled TINYINT(1) NOT NULL DEFAULT 0, + tax_rate_bps BIGINT NOT NULL DEFAULT 100, + tax_amount_cent BIGINT NOT NULL DEFAULT 0, + split_base_amount_cent BIGINT NOT NULL DEFAULT 0, + rounding_adjustment_cent BIGINT NOT NULL DEFAULT 0, + settlement_status VARCHAR(24) NOT NULL DEFAULT 'pending', + settled_at DATETIME NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_distribution_order_financial_order (order_id), + KEY idx_distribution_order_financial_site_status (site_id, settlement_status), + CONSTRAINT fk_distribution_order_financial_order FOREIGN KEY (order_id) REFERENCES rental_orders (id), + CONSTRAINT fk_distribution_order_financial_site FOREIGN KEY (site_id) REFERENCES distribution_sites (id), + CONSTRAINT fk_distribution_order_financial_rule FOREIGN KEY (rule_version_id) REFERENCES distribution_rule_versions (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销正常订单财务快照'; + +CREATE TABLE distribution_order_parties ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + order_id BIGINT UNSIGNED NOT NULL, + site_id BIGINT UNSIGNED NOT NULL, + party_role VARCHAR(32) NOT NULL, + party_type VARCHAR(16) NOT NULL, + party_id BIGINT UNSIGNED NOT NULL, + ratio DECIMAL(10,2) NOT NULL DEFAULT 0, + income_amount_cent BIGINT NOT NULL DEFAULT 0, + cost_amount_cent BIGINT NOT NULL DEFAULT 0, + profit_amount_cent BIGINT NOT NULL DEFAULT 0, + tax_amount_cent BIGINT NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + PRIMARY KEY (id), + KEY idx_distribution_order_parties_order (order_id), + KEY idx_distribution_order_parties_site_role (site_id, party_role), + CONSTRAINT fk_distribution_order_parties_order FOREIGN KEY (order_id) REFERENCES rental_orders (id), + CONSTRAINT fk_distribution_order_parties_site FOREIGN KEY (site_id) REFERENCES distribution_sites (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销订单参与方快照'; + +CREATE TABLE distribution_settlement_batches ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + site_id BIGINT UNSIGNED NOT NULL, + batch_no VARCHAR(64) NOT NULL, + period_start DATETIME NOT NULL, + period_end DATETIME NOT NULL, + total_amount_cent BIGINT NOT NULL DEFAULT 0, + status VARCHAR(16) NOT NULL DEFAULT 'draft', + created_by BIGINT UNSIGNED NULL, + settled_at DATETIME NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_distribution_settlement_batch_no (batch_no), + KEY idx_distribution_settlement_batches_site_status (site_id, status), + CONSTRAINT fk_distribution_settlement_batches_site FOREIGN KEY (site_id) REFERENCES distribution_sites (id), + CONSTRAINT fk_distribution_settlement_batches_admin FOREIGN KEY (created_by) REFERENCES admin_users (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销结算批次'; + +CREATE TABLE distribution_settlement_items ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + batch_id BIGINT UNSIGNED NOT NULL, + order_id BIGINT UNSIGNED NOT NULL, + party_id BIGINT UNSIGNED NOT NULL, + amount_cent BIGINT NOT NULL DEFAULT 0, + status VARCHAR(16) NOT NULL DEFAULT 'pending', + created_at DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_distribution_settlement_item (batch_id, order_id, party_id), + KEY idx_distribution_settlement_items_order (order_id), + CONSTRAINT fk_distribution_settlement_items_batch FOREIGN KEY (batch_id) REFERENCES distribution_settlement_batches (id), + CONSTRAINT fk_distribution_settlement_items_order FOREIGN KEY (order_id) REFERENCES rental_orders (id), + CONSTRAINT fk_distribution_settlement_items_party FOREIGN KEY (party_id) REFERENCES distribution_order_parties (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销结算明细'; + +CREATE TABLE distribution_site_payment_channels ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + site_id BIGINT UNSIGNED NOT NULL, + config_id BIGINT UNSIGNED NOT NULL, + priority INT NOT NULL DEFAULT 0, + status VARCHAR(16) NOT NULL DEFAULT 'active', + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_distribution_site_payment (site_id, config_id), + KEY idx_distribution_site_payment_status (site_id, status, priority), + CONSTRAINT fk_distribution_site_payment_site FOREIGN KEY (site_id) REFERENCES distribution_sites (id), + CONSTRAINT fk_distribution_site_payment_config FOREIGN KEY (config_id) REFERENCES payment_merchant_configs (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销网站收款通道绑定'; + +ALTER TABLE rental_orders + ADD COLUMN distribution_site_id BIGINT UNSIGNED NULL AFTER source_channel, + ADD COLUMN distribution_mode VARCHAR(32) NOT NULL DEFAULT '' AFTER distribution_site_id, + ADD COLUMN distribution_rule_version_id BIGINT UNSIGNED NULL AFTER distribution_mode, + ADD KEY idx_rental_orders_distribution_site (distribution_site_id, distribution_mode), + ADD CONSTRAINT fk_rental_orders_distribution_site FOREIGN KEY (distribution_site_id) REFERENCES distribution_sites (id), + ADD CONSTRAINT fk_rental_orders_distribution_rule FOREIGN KEY (distribution_rule_version_id) REFERENCES distribution_rule_versions (id); + +INSERT INTO permissions (code, name, resource, action) VALUES +('distribution:site_view', '查看分销网站', 'distribution', 'site_view'), +('distribution:site_manage', '管理分销网站', 'distribution', 'site_manage'), +('distribution:order_view', '查看分销订单', 'distribution', 'order_view'), +('distribution:process_view', '查看分销订单过程', 'distribution', 'process_view'), +('distribution:finance_view', '查看分销财务', 'distribution', 'finance_view'), +('distribution:settlement_view', '查看分销结算', 'distribution', 'settlement_view'), +('distribution:settlement_manage', '管理分销结算', 'distribution', 'settlement_manage'), +('distribution:rule_view', '查看分销规则', 'distribution', 'rule_view'), +('distribution:rule_manage', '管理分销规则', 'distribution', 'rule_manage'), +('distribution:account_view', '查看分销账号', 'distribution', 'account_view'), +('distribution:account_display', '管理分销账号展示', 'distribution', 'account_display'), +('distribution:payment_view', '查看分销支付通道', 'distribution', 'payment_view'), +('distribution:payment_manage', '管理分销支付通道', 'distribution', 'payment_manage'), +('distribution:chat_view', '查看分销客服消息', 'distribution', 'chat_view'), +('distribution:chat_send', '发送分销客服消息', 'distribution', 'chat_send') +ON DUPLICATE KEY UPDATE name = VALUES(name), resource = VALUES(resource), action = VALUES(action); + +INSERT IGNORE INTO role_permissions (role_id, permission_id) +SELECT r.id, p.id FROM roles r CROSS JOIN permissions p +WHERE r.code = 'super_admin' AND p.code LIKE 'distribution:%'; + +INSERT INTO distribution_rule_versions ( + site_id, mode, version, status, + platform_receive_ratio, platform_sell_ratio, + distributor_receive_ratio, distributor_sell_ratio, + distributor_a_ratio, distributor_b_ratio, + service_fee_value, service_fee_unit, payment_fee_bps, + tax_enabled, tax_rate_bps, effective_from, created_at +) VALUES + (NULL, 'two_party_distributor_receive', 1, 'active', 0, 4, 3.5, 0, 0, 0, 2.5, 'ratio', 50, 0, 100, NOW(), NOW()), + (NULL, 'two_party_platform_receive', 1, 'active', 3.5, 0, 0, 4, 0, 0, 2.5, 'ratio', 50, 0, 100, NOW(), NOW()), + (NULL, 'three_party', 1, 'active', 0, 0, 0, 0, 3.5, 4, 2.5, 'ratio', 50, 0, 100, NOW(), NOW()) +ON DUPLICATE KEY UPDATE + status = VALUES(status), + service_fee_value = VALUES(service_fee_value), + service_fee_unit = VALUES(service_fee_unit), + payment_fee_bps = VALUES(payment_fee_bps); + +-- +goose Down + +DROP TABLE IF EXISTS distribution_order_financials; +DROP TABLE IF EXISTS distribution_site_payment_channels; +DROP TABLE IF EXISTS distribution_settlement_items; +DROP TABLE IF EXISTS distribution_settlement_batches; +DROP TABLE IF EXISTS distribution_order_parties; +ALTER TABLE rental_orders + DROP FOREIGN KEY fk_rental_orders_distribution_rule, + DROP FOREIGN KEY fk_rental_orders_distribution_site, + DROP KEY idx_rental_orders_distribution_site, + DROP COLUMN distribution_rule_version_id, + DROP COLUMN distribution_mode, + DROP COLUMN distribution_site_id; +DROP TABLE IF EXISTS distribution_rule_versions; +DROP TABLE IF EXISTS distribution_site_accounts; +DROP TABLE IF EXISTS distribution_site_members; +DROP TABLE IF EXISTS distribution_sites; + +DELETE rp FROM role_permissions rp +JOIN permissions p ON p.id = rp.permission_id +WHERE p.code LIKE 'distribution:%'; + +DELETE FROM permissions WHERE code IN ( + 'distribution:site_view', 'distribution:site_manage', 'distribution:order_view', 'distribution:process_view', + 'distribution:finance_view', 'distribution:settlement_view', 'distribution:settlement_manage', + 'distribution:rule_view', 'distribution:rule_manage', 'distribution:account_view', 'distribution:account_display', + 'distribution:payment_view', 'distribution:payment_manage', 'distribution:chat_view', 'distribution:chat_send' +); diff --git a/deploy/caddy/Caddyfile b/deploy/caddy/Caddyfile index 9bc5cc0..3c513db 100644 --- a/deploy/caddy/Caddyfile +++ b/deploy/caddy/Caddyfile @@ -68,6 +68,17 @@ } } +# 分销管理后台使用独立 React + Ant Design 静态资源,API 与主站复用 backend。 +distribution.{$CADDY_DOMAIN} { + encode zstd gzip + import public_api + handle { + root * /srv/distribution-admin + try_files {path} /index.html + file_server + } +} + # 选号网(hfb_show):仅当 CADDY_SHOW_DOMAIN 有值时由部署脚本启用 conf.d/show.caddy。 # 未配置时 conf.d 为空,不影响主站。 import /etc/caddy/conf.d/*.caddy @@ -86,3 +97,8 @@ monitor.{$CADDY_DOMAIN} { } } } + +# gitea 自托管代码库 +gitea.hao858.com { + reverse_proxy gitea:3000 +} diff --git a/deploy/caddy/Dockerfile b/deploy/caddy/Dockerfile index b058afb..a8dd135 100644 --- a/deploy/caddy/Dockerfile +++ b/deploy/caddy/Dockerfile @@ -10,11 +10,21 @@ RUN npm install COPY frontend/ . RUN npm run build -# 第二阶段:Caddy 作为公网入口,托管主站 + 选号网静态资源 +# 第二套独立分销管理后台(React + Ant Design) +FROM node:24-alpine AS distribution-build +WORKDIR /src +COPY distribution-admin/package*.json ./ +RUN --mount=type=cache,target=/root/.npm \ + npm ci --prefer-offline --no-audit --fund=false +COPY distribution-admin/ . +RUN npm run build + +# 第三阶段:Caddy 作为公网入口,托管主站、分销后台和选号网静态资源 FROM caddy:2.10-alpine COPY --from=build /src/dist /srv/dist # show-dist 由 deploy-prod.sh 在 build 前准备(hfb_show 构建产物或占位页) COPY deploy/caddy/show-dist /srv/show-dist +COPY --from=distribution-build /src/dist /srv/distribution-admin EXPOSE 80 443 diff --git a/distribution-admin/Dockerfile b/distribution-admin/Dockerfile new file mode 100644 index 0000000..72bdc90 --- /dev/null +++ b/distribution-admin/Dockerfile @@ -0,0 +1,10 @@ +FROM node:22-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +FROM nginx:1.27-alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/distribution-admin/index.html b/distribution-admin/index.html new file mode 100644 index 0000000..5da73cf --- /dev/null +++ b/distribution-admin/index.html @@ -0,0 +1,12 @@ + + + + + + 分销网管理后台 + + +
+ + + diff --git a/distribution-admin/nginx.conf b/distribution-admin/nginx.conf new file mode 100644 index 0000000..a60520b --- /dev/null +++ b/distribution-admin/nginx.conf @@ -0,0 +1,7 @@ +server { + listen 80; + root /usr/share/nginx/html; + index index.html; + location /api/ { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } + location / { try_files $uri /index.html; } +} diff --git a/distribution-admin/package-lock.json b/distribution-admin/package-lock.json new file mode 100644 index 0000000..96b3c81 --- /dev/null +++ b/distribution-admin/package-lock.json @@ -0,0 +1,3353 @@ +{ + "name": "hfb-distribution-admin", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hfb-distribution-admin", + "version": "0.1.0", + "dependencies": { + "@ant-design/icons": "^6.0.0", + "antd": "^5.27.6", + "axios": "^1.13.2", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-router-dom": "^7.9.4" + }, + "devDependencies": { + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.4", + "typescript": "~5.9.3", + "vite": "^7.2.4" + } + }, + "node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz", + "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.3.4.tgz", + "integrity": "sha512-kDoUlppczVyCUTFthF6cHmPzMBYqNZQhu9j6EyIX/YIdM9j9CZNZoJUjzVw7/1v1VO8WwVMmPp15r/uwIAS0AQ==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/icons-svg": "^4.6.0", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.6.0.tgz", + "integrity": "sha512-PRomU725ABMf/lnQp5HiB7my1kjEbFY0D10N4lXYxK6TIB1gKjIVD5MRThpDaezLgw1D774J8eOeeDB0M6wHrQ==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.2.tgz", + "integrity": "sha512-WYbrZSjzznU1ekD0qFq2qRxt309VoS61MTG5npnFQlKYcoy9IzU8T+ZCIhq5bGAXRbXysABFWTspicMfmWFwow==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/color-picker/node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.4.tgz", + "integrity": "sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.3.tgz", + "integrity": "sha512-aGv6alnn4HbDEsURzKP+jv13rbi1VxmAYfBNZr5GKF1iohMNWy5tAVoJ1E3cOvzMB1kbUPvCXchM6zSFlRGPhA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.1.tgz", + "integrity": "sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.13.0.tgz", + "integrity": "sha512-IXf2zBfZrbQGT+G8LxnoICDBGnMHIPIiq5HCRn3EDfKUx28x1rnbZBj46ULJf4sM8ImTG1SepqfNBdxZziqQ4w==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^19.2.7" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/antd": { + "version": "5.29.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.3.tgz", + "integrity": "sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.1.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.3.0", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.1", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.9", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.54.0", + "rc-tabs": "~15.7.0", + "rc-textarea": "~1.10.2", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.11.0", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/@ant-design/colors": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/antd/node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/antd/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", + "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.422", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz", + "integrity": "sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.1.tgz", + "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.5.0.tgz", + "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.1.tgz", + "integrity": "sha512-izj1Nw/Dw2Vb7EVr+D/E9lUTkBe+kKC+SAFSU9zqr7WV2W5Ktaa9Gc7cB2jTqgk8GROJayltaec+DBlYKc6d+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.8", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.9.tgz", + "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.54.0", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.54.0.tgz", + "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz", + "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz", + "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.11.0.tgz", + "integrity": "sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/rc-virtual-list": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz", + "integrity": "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.3.tgz", + "integrity": "sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.3.tgz", + "integrity": "sha512-ytVbyBBM7vMfRCam25r0WMhSVSom909A8p+8m0/f1w853dz/xfFu6etAT2SEbVoSnI+ZoPRDqIsQXVT89gp7kg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/distribution-admin/package.json b/distribution-admin/package.json new file mode 100644 index 0000000..d5ed216 --- /dev/null +++ b/distribution-admin/package.json @@ -0,0 +1,27 @@ +{ + "name": "hfb-distribution-admin", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "tsc --noEmit && vite build", + "preview": "vite preview --host 0.0.0.0", + "test": "tsc --noEmit" + }, + "dependencies": { + "@ant-design/icons": "^6.0.0", + "antd": "^5.27.6", + "axios": "^1.13.2", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-router-dom": "^7.9.4" + }, + "devDependencies": { + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.4", + "typescript": "~5.9.3", + "vite": "^7.2.4" + } +} diff --git a/distribution-admin/src/main.tsx b/distribution-admin/src/main.tsx new file mode 100644 index 0000000..ff7fdc7 --- /dev/null +++ b/distribution-admin/src/main.tsx @@ -0,0 +1,40 @@ +import React, { useEffect, useState } from 'react' +import { createRoot } from 'react-dom/client' +import { App, Card, ConfigProvider, Layout, Menu, Space, Statistic, Table, Tag, Typography, message } from 'antd' +import { AccountBookOutlined, ApiOutlined, DashboardOutlined, FileSearchOutlined, SettingOutlined, ShopOutlined } from '@ant-design/icons' +import axios from 'axios' +import './style.css' + +const api = axios.create({ baseURL: '/api' }) +type Site = { id: number; site_name: string; site_code: string; domain: string; status: string; display_account_enabled: boolean; tax_enabled: boolean; tax_rate_bps: number } +type Order = { order_id: number; order_no: string; site_id: number; status: string; settlement_status: string; net_paid_amount_cent: number; payment_fee_cent: number; service_fee_cent: number; tax_amount_cent: number; created_at: string } + +function Dashboard() { + const [sites, setSites] = useState([]) + const [orders, setOrders] = useState([]) + useEffect(() => { Promise.all([api.get('/admin/distribution/sites'), api.get('/admin/distribution/orders?kind=all')]).then(([a, b]) => { setSites(a.data.data.items ?? []); setOrders(b.data.data.items ?? []) }).catch(() => message.error('数据加载失败')) }, []) + const total = orders.reduce((sum, item) => sum + item.net_paid_amount_cent, 0) + return 分销运营概览
{v} }, { title: '净支付', dataIndex: 'net_paid_amount_cent', render: (v) => `¥${(v / 100).toFixed(2)}` }, { title: '创建时间', dataIndex: 'created_at' }]} /> +} + +function Sites() { + const [items, setItems] = useState([]) + useEffect(() => { api.get('/admin/distribution/sites').then((r) => setItems(r.data.data.items ?? [])).catch(() => message.error('站点加载失败')) }, []) + return 分销网站
v ? 开启 : 关闭 }, { title: '税费', render: (_, r) => r.tax_enabled ? `${r.tax_rate_bps / 100}%` : '关闭' }]} /> +} + +function Orders({ kind }: { kind: 'sold' | 'receive' }) { + const [items, setItems] = useState([]) + useEffect(() => { api.get(`/admin/distribution/orders?kind=${kind}`).then((r) => setItems(r.data.data.items ?? [])).catch(() => message.error('订单加载失败')) }, [kind]) + return {kind === 'sold' ? '售出账号订单' : '分销收号订单'}
`¥${(v / 100).toFixed(2)}` }, { title: '手续费', dataIndex: 'payment_fee_cent', render: (v) => `¥${(v / 100).toFixed(2)}` }, { title: '服务费', dataIndex: 'service_fee_cent', render: (v) => `¥${(v / 100).toFixed(2)}` }, { title: '结算', dataIndex: 'settlement_status' }]} /> +} + +function Placeholder({ title }: { title: string }) { return {title}该模块已预留接口入口,可在后台继续配置。 } + +function Shell() { + const [key, setKey] = useState('dashboard') + const content = key === 'dashboard' ? : key === 'sites' ? : key === 'sold' ? : key === 'receive' ? : )[key] ?? '分销后台'} /> + return
HFB 分销后台
setKey(e.key)} items={[{ key: 'dashboard', icon: , label: '仪表盘' }, { key: 'sites', icon: , label: '分销网站' }, { key: 'sold', icon: , label: '售出账号' }, { key: 'receive', icon: , label: '分销收号' }, { key: 'settlements', icon: , label: '结算管理' }, { key: 'payments', icon: , label: '支付通道' }, { key: 'accounts', icon: , label: '账号展示' }, { key: 'rules', icon: , label: '比例与税费' }, { key: 'qrcodes', icon: , label: '客服二维码' }, { key: 'chats', icon: , label: '客服消息' }]} />主站 hao858.com · 分销子域名 distribution.hao858.com{content} +} + +createRoot(document.getElementById('root')!).render() diff --git a/distribution-admin/src/style.css b/distribution-admin/src/style.css new file mode 100644 index 0000000..8a8d8f0 --- /dev/null +++ b/distribution-admin/src/style.css @@ -0,0 +1,9 @@ +:root { font-family: Inter, "PingFang SC", sans-serif; color: #1f2937; background: #f5f7fa; } +* { box-sizing: border-box; } +body { margin: 0; } +.app { min-height: 100vh; } +.brand { height: 64px; padding: 20px 18px; color: #fff; font-weight: 600; letter-spacing: 0; } +.header { display: flex; align-items: center; background: #fff; padding: 0 24px; border-bottom: 1px solid #edf0f5; } +.page { width: 100%; padding: 24px; } +.stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; width: 100%; } +@media (max-width: 720px) { .page { padding: 16px; } .stats { grid-template-columns: 1fr; } } diff --git a/distribution-admin/tsconfig.json b/distribution-admin/tsconfig.json new file mode 100644 index 0000000..e935337 --- /dev/null +++ b/distribution-admin/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/distribution-admin/vite.config.ts b/distribution-admin/vite.config.ts new file mode 100644 index 0000000..42431f1 --- /dev/null +++ b/distribution-admin/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { port: 5190, proxy: { '/api': 'http://localhost:8080' } }, +}) diff --git a/docs/分销网管理后台设计方案.md b/docs/分销网管理后台设计方案.md new file mode 100644 index 0000000..03e3f84 --- /dev/null +++ b/docs/分销网管理后台设计方案.md @@ -0,0 +1,776 @@ +# 分销网管理后台设计方案 + +> 状态:草案 v0.2 +> 更新时间:2026-09-04 +> 适用范围:分销网站正常租赁订单管理、订单分账、分销客服与支付配置 + +## 1. 文档目的 + +本文档记录分销网管理后台的产品计划、领域模型、分账口径、接口边界、部署方案和实施顺序。 + +本文档是持续维护的设计基线。业务规则尚未确认的部分使用“待确认”标记,进入开发前必须补齐;已创建的订单必须使用规则和金额快照,不能因后续规则变更而重算历史账目。 + +## 2. 现有系统基础 + +当前 HFB Sys 是 Go + Gin + GORM + MySQL + Redis 后端,Vue 3 + Element Plus 用户端/运营后台前端,生产通过 Caddy 托管静态文件并反代单个 backend 服务。 + +可复用的现有领域: + +- 用户:`users`、实名认证、用户状态。 +- 商品和账号:`rental_listings`、`game_accounts`、账号资产快照。 +- 正常订单:`rental_orders`、交接、归还、结账、退款和订单过程事件。 +- 订单来源:`rental_orders` 的正常租赁交易;分销网只处理卖家和租客都在网站内完成的正常订单。 +- 资金:`wallet_accounts`、`wallet_ledger`、`withdrawal_requests`。 +- 支付:`payment_merchant_configs`、支付单、退款单和支付配置使用日志。 +- 客服:`chat_conversations`、`chat_messages`、客服组和二维码池。 +- 管理权限:`admin_users`、`roles`、`permissions`、角色权限关联和审计日志。 + +现有系统的价格快照已包含 `seller_ratio`、`buyer_ratio`、`seller_total_price`、`buyer_total_price` 和 `platform_markup_amount`。但现有默认语义是平台加价归平台,分销计划要求销售加价归卖方,因此分销必须使用独立的分账快照,不能直接改变旧字段含义。 + +## 3. 总体架构 + +### 3.1 独立前端、复用后端 + +分销网管理后台独立为新的前端工程,推荐 React + TypeScript + Vite + Ant Design。现有 Vue + Element Plus 用户端和运营后台保持不变。 + +```text +主站:https://hao858.com/ +现有运营后台:https://hao858.com/manage-xxx +分销后台:暂定使用主域名下的独立子域名,例如 `distribution.hao858.com` +``` + +分销后台通过独立域名提供静态资源,`/api/*` 反代到现有 Go backend: + +```text +distribution.hao858.com(暂定) + ├── / 独立 Ant Design 静态资源 + └── /api/* reverse_proxy backend:8080 +``` + +这样可以避免在同一个 Vue 应用中同时引入 Element Plus 和 Ant Design,也避免跨域 Cookie 和 CORS 配置。 + +### 3.2 后端暂不拆服务 + +第一阶段不新建独立分销后端服务。分销模块继续使用现有 MySQL、Redis、钱包、订单、支付、客服和权限体系,以便在同一事务中处理支付、退款、分成、税费和钱包流水。 + +建议新增后端目录: + +```text +backend/internal/modules/distribution/ +``` + +建议文件: + +```text +dto.go +handler.go +service.go +repository.go +query.go +mutation.go +pricing.go +settlement.go +payment.go +qrcode.go +chat.go +presenter.go +audit.go +``` + +## 4. 业务目标与后台功能 + +### 4.1 分销后台功能 + +第一版需要覆盖(仅限正常租赁订单): + +1. 显示正常订单中已售出账号的详情。 +2. 显示分销网站自己通过正常订单收购/接收的账号详情(卖家在分销网站上架,租客完成交易)。 +3. 配置分销网站在正常订单中的收号比例;主站卖出比例统一或按规则版本管理。 +4. 分销网站使用指定客服组和指定二维码池的拉群码。 +5. 查看手续费、服务费、加价、税费、分成和结算金额。 +6. 配置分销网站是否展示账号,并可单独关闭某个账号。 +7. 配置分销网站可使用的一个或多个收款支付渠道。 +8. 分销客服只读订单信息和过程记录,并可按权限处理客服消息。 +9. 主站查看所有分销网站的收售订单、收款金额和结算数据。 + +### 4.2 建议页面 + +```text +仪表盘 +分销网站管理 +正常订单售出账号 +分销收售订单 +佣金/分成明细 +结算管理 +比例与税费规则 +支付通道 +客服组与二维码 +账号展示管理 +报表 +操作日志 +``` + +## 5. 业务主体和权限 + +### 5.1 分销网站 + +建议使用独立主体表 `distribution_sites`。分销网站不是普通用户,也不直接等同于平台管理员。 + +分销网站至少需要保存: + +- 编码、名称、域名和状态。 +- 联系人信息。 +- 是否允许自动展示账号。 +- 默认客服组和专属二维码池。 +- 税费开关和税率。 +- 创建、停用和更新时间。 + +### 5.2 分销网站成员 + +建议使用 `distribution_site_members` 将现有 `admin_users` 映射到一个或多个分销网站。 + +成员类型建议包括: + +- `operator`:查看业务和账号展示。 +- `finance`:查看分成、结算和收款金额。 +- `customer_service`:查看订单、过程记录和客服消息。 + +分销网站成员只能看到所属站点的数据。前端传入的 `site_id` 不能扩大数据范围,后端必须从当前管理员成员关系中推导并校验。 + +### 5.3 权限码 + +建议新增: + +```text +distribution:site_view +distribution:site_manage +distribution:account_view +distribution:account_display +distribution:order_view +distribution:process_view +distribution:finance_view +distribution:settlement_view +distribution:settlement_manage +distribution:rule_view +distribution:rule_manage +distribution:payment_view +distribution:payment_manage +distribution:qrcode_view +distribution:qrcode_manage +distribution:chat_view +distribution:chat_send +distribution:all_view +``` + +分销客服默认只拥有: + +```text +distribution:order_view +distribution:process_view +distribution:chat_view +distribution:chat_send +``` + +客服禁止修改比例、支付配置、展示状态、退款、订单状态和结算结果。 + +## 6. 订单和账号展示 + +### 6.1 售出账号详情 + +分销后台需要显示: + +- 订单号、商品编号、账号基本信息和账号快照。 +- 分销网站、收号方、售出方和租客。 +- 收号比例、销售比例、销售基础价、销售加价和实际支付金额。 +- 退款、手续费、服务费、税费、各方收入和结算状态。 +- 交接、租用、归还、结账、退款、仲裁等过程事件。 + +账号密码、实名信息等敏感字段必须按照现有后台脱敏和权限规则返回。是否允许分销成员查看完整登录信息:待确认。 + +### 6.2 分销网站正常订单收号详情 + +分销网的收号不是线下提号记录,而是正常租赁订单中的一方角色:卖家在分销网站上架账号,租客在分销网站完成支付、交接、租用和归还。该类记录应从 `rental_orders` 及分销订单参与方快照查询,不能使用 `admin_pickups` 作为数据来源。 + +正常订单需要保存分销站点和参与方快照,例如: + +```text +distribution_site_id +seller_party_id +renter_party_id +receive_ratio +receive_amount_cent +rule_version +``` + +不新增独立的线下收号流程;如需展示“分销网收号记录”,通过正常订单中的 `distributor_receive` 角色和订单状态筛选实现。 + +### 6.3 账号展示开关 + +网站级配置: + +```text +distribution_sites.display_account_enabled +``` + +账号级配置: + +```text +distribution_site_accounts.display_status +``` + +账号状态建议为: + +```text +enabled +disabled +sold +offline +``` + +关闭展示不删除账号、不影响历史订单,只阻止新展示或新订单,并写入审计日志。 + +一个账号是否允许同时挂载多个分销网站:待确认。第一版建议一个账号在同一时刻只归属一个可售分销网站,避免重复出售和归因冲突。 + +### 6.4 不纳入分销网的业务 + +以下业务不属于分销网正常订单,不进入分销订单分账: + +- 平台或管理员线下提号(`admin_pickups`)。 +- 平台代管账号及线下交接、线下结算。 +- 撞车商品及撞车订单(`mohong`/`crash`)。 +- 分销网站之外的外部导入、私下交易或人工补录订单。 + +分销网订单必须同时满足:订单来源站点是分销网站、卖家和租客均属于该正常订单链路、支付和订单状态在平台系统内可追踪。 + +## 7. 比例、费用和分账口径 + +### 7.1 初步默认规则(可后台调整) + +第一版先按以下规则建立默认配置,后续由平台后台调整。默认配置不是写死在代码中的常量,必须保存为可启用/停用、可版本化的后台规则: + +```text +两方:分销网收 3.5,主站卖出 4,服务费 2.5 +两方:分销网卖 4,主站收 3.5,服务费 2.5 +三方:主站服务费 2.5,分销 A 上架 3.5,分销 B 卖出 4 +``` + +初步配置含义: + +| 模式 | 参与方 | 初始比例/费用 | +| --- | --- | --- | +| 两方-分销网收号 | 分销网收号、主站卖出 | 分销网 `3.5`,主站 `4`,服务费 `2.5` | +| 两方-主站收号 | 分销网卖出、主站收号 | 分销网 `4`,主站 `3.5`,服务费 `2.5` | +| 三方 | 主站服务费、分销 A 上架、分销 B 卖出 | 主站服务费 `2.5`,分销 A `3.5`,分销 B `4` | + +以上是当前业务的初始值。后台需要允许平台管理员按分销网站、交易模式和生效时间修改比例及费用;新订单读取当前生效规则并写入快照,历史订单不跟随后台修改。 + +现有系统的比例语义是“哈夫币数量 / 比例 = 价格”,比例越大,价格越低。3.5 和 4 在分销规则中先分别作为“收号/上架方比例”和“卖出方比例”保存;如果后续确认比例含义需要调整,只影响规则解释和新订单,已结算订单仍以金额快照为准。 + +### 7.2 必须确认的口径 + +以下项目在 v0.2 中分为“已有初始值”和“仍待确认”: + +| 项目 | 待确认内容 | +| --- | --- | +| 两方/三方初始比例 | 已按 7.1 建立默认值,后台可调整 | +| 分销网站收号比例 | 已确定可按站点调整;是否设置上下限待确认 | +| 主站统一卖出比例 | 初步按规则版本配置;是否全站固定待确认 | +| 三方分销 | 已确定保留 A 上架、B 卖出角色;结算顺序和亏损承担待确认 | +| 3.5 和 4 的最终业务含义 | 初步按收号/上架方与卖出方比例保存,最终文字口径待确认 | +| 服务费 2.5 | 百分比、金额,还是比例值 | +| 销售加价 | 是否计入服务费和税费基数 | +| 税费税基 | 毛利、收入,还是分成后金额 | +| 退款处理 | 重算剩余分成,还是生成反向冲正流水 | +| 四舍五入尾差 | 统一归主站还是按最大分成方承担 | + +### 7.3 后台调整要求 + +后台调整规则时必须: + +1. 只允许具备 `distribution:rule_manage` 权限的管理员操作。 +2. 新增规则版本,不直接覆盖已生效版本的历史内容。 +3. 支持按分销网站、交易模式配置收号比例、卖出比例、服务费、手续费和税费开关。 +4. 支持设置生效时间;同一站点同一模式同一时刻只能有一个生效版本。 +5. 修改前展示影响范围和新旧值,确认后写入审计日志。 +6. 已创建、已支付或已结算订单继续使用其规则快照。 +7. 比例、费率和金额必须由后端校验范围,前端输入不能绕过校验。 + +### 7.4 推荐的金额模型 + +比例只负责生成金额,最终结算全部以整数分金额为准。每笔订单保存: + +```text +ratio_snapshot +amount_snapshot +rule_version +``` + +建议所有金额字段使用 `*_cent BIGINT`,所有费率使用基点 `*_bps INT`: + +```text +0.5% = 50 bps +1% = 100 bps +``` + +### 7.5 手续费 + +计划要求手续费在分成前扣除,按退款后的实际支付金额计算,费率为 0.5%,结果四舍五入。 + +推荐公式: + +```text +实际支付金额 = 支付金额 - 成功退款金额 +手续费 = round_half_up(实际支付金额 × 50 / 10000) +分成基数 = 实际支付金额 - 手续费 +``` + +“服务费 2.5”是否属于同一手续费层级:待确认。实现时必须将 `payment_fee`、`service_fee` 分成独立字段,不能合并为一个不可解释的费用。 + +### 7.6 销售加价 + +计划要求商家加价销售时,加价全部归卖方。 + +建议保存: + +```text +markup_amount_cent +markup_beneficiary_type +markup_beneficiary_id +``` + +建议第一版按以下方式实现: + +```text +销售总价 - 销售基础价 = 销售加价 +销售加价全部计入卖方收入 +``` + +加价是否计入服务费和税费基数:待确认。无明确规则前,结算引擎必须分别展示基础分成和加价,避免隐含扣费。 + +### 7.7 税费 + +计划要求对分销所得利益按毛利的 1% 计税,并支持开关和可调税率;每个主体承担自己的分成税费。 + +推荐配置: + +```text +tax_enabled +tax_rate_bps +``` + +推荐公式: + +```text +毛利 = 该主体收入 - 该主体收号成本 - 该主体承担的费用 +税额 = round_half_up(毛利 × tax_rate_bps / 10000) +``` + +每个主体分别计算税费,主站、分销 A、分销 B 不合并税基。毛利为负时税额暂按 0 处理,亏损如何结转:待确认。 + +### 7.8 账务不变量 + +每笔分销订单的结算必须满足: + +```text +实际支付金额 += 手续费 + + 平台服务费 + + 各参与方收入 + + 税费 + + 固定规则下的舍入尾差 +``` + +退款、拒付、仲裁和结算撤销必须生成反向明细,不能直接覆盖原结算记录。 + +## 8. 建议数据模型 + +### 8.1 分销网站 + +```text +distribution_sites +``` + +建议字段: + +```text +id, site_code, site_name, domain, status +display_account_enabled +default_support_group_id +tax_enabled, tax_rate_bps +created_at, updated_at +``` + +### 8.2 成员关系 + +```text +distribution_site_members +``` + +```text +id, site_id, admin_user_id, member_type, status, data_scope +created_at, updated_at +``` + +### 8.3 账号展示关系 + +```text +distribution_site_accounts +``` + +```text +id, site_id, account_id, listing_id, display_status +display_started_at, display_ended_at +created_at, updated_at +``` + +### 8.4 规则版本 + +```text +distribution_rules +distribution_rule_versions +``` + +规则需要保存: + +```text +site_id +mode: two_party / three_party +platform_receive_ratio +platform_sell_ratio +distributor_receive_ratio +distributor_sell_ratio +distributor_a_ratio +distributor_b_ratio +service_fee_value +service_fee_unit: percent / fixed_amount / ratio +payment_fee_bps +tax_enabled +tax_rate_bps +effective_from, effective_to +version, status +``` + +首批默认规则按第 7.1 节初始化: + +```text +two_party_distributor_receive: distributor_receive_ratio=3.5, platform_sell_ratio=4, service_fee_value=2.5 +two_party_platform_receive: platform_receive_ratio=3.5, distributor_sell_ratio=4, service_fee_value=2.5 +three_party: distributor_a_ratio=3.5, distributor_b_ratio=4, service_fee_value=2.5 +``` + +其中 `two_party_platform_receive` 的参与方方向为分销网卖出、主站收号;比例初始值为分销网 `4`、主站 `3.5`。规则字段使用参与方角色命名,避免只写 `receive_ratio`/`sell_ratio` 造成方向歧义。上面的服务费 `2.5` 先作为规则配置值保存,具体单位由后台规则字段和后续口径确认决定。默认规则必须通过迁移或初始化数据写入,不写死在前端或结算代码中。 + +### 8.5 订单参与方 + +```text +distribution_order_parties +``` + +```text +order_id, site_id, party_role, user_id +ratio, base_amount_cent, markup_amount_cent +gross_amount_cent, fee_amount_cent, tax_amount_cent +net_amount_cent, status +``` + +`party_role` 建议支持: + +```text +platform +distributor_a +distributor_b +distributor_receive +seller +buyer +``` + +### 8.6 订单财务快照 + +```text +distribution_order_financials +``` + +```text +order_id UNIQUE +gross_paid_amount_cent +refund_amount_cent +net_paid_amount_cent +payment_fee_bps, payment_fee_cent +service_fee_bps, service_fee_cent +markup_amount_cent +tax_enabled, tax_rate_bps, tax_amount_cent +split_base_amount_cent +settlement_status +rule_version +created_at, settled_at +``` + +### 8.7 结算批次和明细 + +```text +distribution_settlements +distribution_settlement_items +``` + +结算批次保存周期、状态、审核人、付款时间和失败原因;明细保存每个站点、参与方、收入、成本、毛利、手续费、税费和钱包流水关联。 + +### 8.8 支付配置绑定 + +```text +distribution_site_payment_configs +``` + +一个分销网站可以绑定一个或多个现有 `payment_merchant_configs`。支付下单时保存 `site_id` 和 `payment_config_id` 快照,回调不重新依赖当前配置。 + +### 8.9 二维码和客服组 + +建议给二维码池或关联表增加: + +```text +distribution_site_id +support_group_id +``` + +取码优先匹配“分销网站 + 客服组”的专属二维码池。站点已配置专属池时,不回退到其他站点二维码。 + +## 9. API 规划 + +### 9.1 平台管理员 + +```text +GET /api/admin/distribution/sites +POST /api/admin/distribution/sites +GET /api/admin/distribution/sites/:id +PUT /api/admin/distribution/sites/:id +POST /api/admin/distribution/sites/:id/enable +POST /api/admin/distribution/sites/:id/disable + +GET /api/admin/distribution/sold-orders +GET /api/admin/distribution/sold-orders/:id +GET /api/admin/distribution/receive-orders +GET /api/admin/distribution/receive-orders/:id + +GET /api/admin/distribution/rules +POST /api/admin/distribution/rules +PUT /api/admin/distribution/rules/:id + +GET /api/admin/distribution/settlements +GET /api/admin/distribution/settlements/:id +POST /api/admin/distribution/settlements/:id/approve +POST /api/admin/distribution/settlements/:id/reject + +GET /api/admin/distribution/reports/summary +GET /api/admin/distribution/reports/orders +GET /api/admin/distribution/reports/finance +``` + +### 9.2 分销网站成员 + +```text +GET /api/admin/distribution/me +GET /api/admin/distribution/orders +GET /api/admin/distribution/orders/:id +GET /api/admin/distribution/receive-orders +GET /api/admin/distribution/chats +GET /api/admin/distribution/chats/:id/messages +POST /api/admin/distribution/chats/:id/messages +``` + +同一套后端接口通过成员关系限制 `site_id`。平台管理员可以显式选择站点或查看全部;分销成员不能自行传入其他站点 ID。 + +## 10. 支付和域名上下文 + +生产环境建议通过请求 Host 解析分销网站: + +```text +请求域名 -> 服务端解析 site_id -> 校验站点状态 -> 选择绑定支付配置 +``` + +开发环境可以临时使用 `site_code`,但生产支付下单不能完全信任前端提交的站点参数。 + +支付配置选择规则: + +1. 只读取当前站点启用的支付配置。 +2. 支持一个或多个配置,按优先级或健康状态选择。 +3. 支付单保存站点和支付配置快照。 +4. 回调、查询和退款使用支付单快照,不因后台配置变更而切换通道。 + +## 11. 独立前端工程 + +建议目录: + +```text +distribution-admin/ +├── package.json +├── vite.config.ts +├── tsconfig.json +├── Dockerfile +└── src/ + ├── main.tsx + ├── router/ + ├── layouts/ + ├── stores/ + ├── services/ + ├── types/ + └── features/ + ├── dashboard/ + ├── sites/ + ├── sold-orders/ + ├── receive-orders/ + ├── settlements/ + ├── payment-configs/ + ├── qrcodes/ + ├── chats/ + └── reports/ +``` + +建议继续复用现有 API 响应格式: + +```json +{ + "code": "ok", + "message": "ok", + "data": {} +} +``` + +独立后台第一阶段可复用现有管理员登录接口和 HttpOnly Cookie,但不与现有前端共享 localStorage。是否建设分销员独立登录:待确认;如果分销员不是平台管理员,应新增独立主体和认证体系。 + +## 12. 部署方案 + +新增独立前端构建单元: + +```text +distribution-admin/Dockerfile +``` + +Caddy 增加独立子域名站点。子域名名称暂定,最终以 DNS、证书和运营规划确认的名称为准: + +```text +distribution.hao858.com { + handle /api/* { + reverse_proxy backend:8080 + } + + handle { + root * /srv/distribution-admin + try_files {path} /index.html + file_server + } +} +``` + +主站、现有运营后台和分销后台分别构建、分别发布。数据库迁移仍由现有生产发布流程执行,后端和前端发布需要保持兼容窗口。 + +## 13. 实施阶段 + +### 阶段 0:确认业务口径 + +- 确认 3.5、4、2.5 的单位和方向。 +- 确认两方、三方参与方及亏损承担。 +- 确认加价是否计入服务费和税费。 +- 确认税费毛利计算口径。 +- 确认退款采用重算还是冲正。 +- 确认分销员和分销客服的身份模型。 +- 确认账号是否允许多站点展示。 + +### 阶段 1:数据库和计算引擎 + +新增 Goose 迁移,建议从 `000062_distribution_init.sql` 开始,包含站点、成员、展示关系、默认规则版本、订单参与方、财务快照、结算、支付绑定、二维码绑定和权限初始化。默认分账值先按第 7.1 节写入,后续通过后台规则页面调整。 + +先完成纯函数计算和单元测试,不接真实订单。 + +### 阶段 2:订单生命周期接入 + +接入: + +```text +支付成功 +退款成功 +订单完成 +订单仲裁 +订单取消 +``` + +要求支付回调、退款回调、订单完成和结算操作幂等;规则修改不影响已有订单;钱包流水和分销结算明细金额一致。 + +### 阶段 3:后台 API + +完成站点、售出订单、正常订单收号记录、比例规则、结算、支付通道、二维码客服和账号展示接口,并补齐站点数据隔离测试。 + +### 阶段 4:Ant Design 前端 + +开发顺序:登录和权限、仪表盘、售出订单、正常订单收号记录、站点配置、规则、结算、支付、客服二维码、报表。 + +### 阶段 5:独立生产部署 + +接入独立子域名、DNS、Caddy 配置、前端镜像和发布脚本,完成健康检查、登录、订单查询、支付配置和客服消息冒烟测试。 + +## 14. 测试和验收标准 + +### 权限隔离 + +- 分销网站 A 不能查询分销网站 B。 +- 分销客服不能修改比例、支付配置、订单状态和结算。 +- 主站财务可查看全部站点资金数据。 +- 超级管理员可管理全部站点。 + +### 账务正确性 + +- 手续费按退款后的实际支付金额计算。 +- 手续费先扣除,再进行分成。 +- 0.5% 使用四舍五入并以分保存。 +- 销售加价归指定卖方。 +- 各主体独立计算税费。 +- 重复支付、退款和结算请求不会重复入账。 +- 分销结算明细与钱包流水一致。 + +### 业务正确性 + +- 售出订单和正常订单收号记录均可查询。 +- 线下提号、平台代管和撞车订单不会进入分销订单列表或分账。 +- 账号关闭展示后不影响历史订单。 +- 规则修改只影响新订单。 +- 支付只使用当前分销网站绑定的通道。 +- 分销网站使用指定客服组和二维码池。 +- 客服消息按站点隔离。 + +### 发布标准 + +- `./scripts/check.sh` 通过。 +- 新迁移具备 Up/Down 并通过 SQL 门禁。 +- 金额、退款、分账、税费和状态机变更包含回归测试。 +- 生产环境完成独立域名、后台登录、订单查询和支付配置冒烟验证。 + +## 15. 待确认清单 + +以下问题确认后,更新本文档版本并进入详细表结构和 API 契约设计: + +1. 分销网站是平台合作方,还是普通用户下的分销身份? +2. 分销客服是否使用现有 `admin_users`? +3. 3.5、4、2.5 的确切单位和计算方向是什么? +4. 服务费和支付手续费是否都按实际支付净额计算? +5. 加价是否计入服务费、税费和退款基数? +6. 税费是否只对正毛利计提?亏损是否结转? +7. 退款后佣金是重算剩余金额,还是追加负向冲正? +8. 一个账号能否同时展示在多个分销网站? +9. 分销后台子域名最终使用哪个名称(当前暂定 `distribution.hao858.com`)? +10. 分销网站是否需要独立登录入口和独立账号体系? +11. 分销网站是否能查看完整账号登录资料? + +--- + +后续每次确认业务规则后,优先更新第 7、15 节,再同步更新第 8、9、13、14 节,确保规则、数据、接口、实施和验收标准保持一致。 + +## 16. 当前实现基线(v0.3) + +本分支已落地第一批可运行功能,主站域名为 `https://hao858.com/`,独立后台工程位于 `distribution-admin/`,子域名暂定 `https://distribution.hao858.com/`: + +- Go 后端新增 `distribution_sites`、成员、账号展示、规则版本、订单财务快照、订单参与方、结算批次/明细、支付通道绑定表。 +- `rental_orders` 增加分销站点、模式和规则版本字段;只有正常租赁订单才允许写入这些字段。 +- 分账计算引擎已实现退款后净支付、0.5% 基点手续费先扣、服务费三种单位、加价单独展示、各参与方毛利税费和四舍五入。 +- 管理 API 已覆盖站点、规则、售出/收号订单查询、财务快照、账号展示开关、支付通道绑定和结算批次。 +- 后台权限使用 `distribution:*` 权限码;后续接入成员站点数据范围校验时,不能使用请求参数扩大范围。 +- `distribution-admin` 使用 React + TypeScript + Vite + Ant Design,已提供仪表盘、站点、售出订单、收号订单及结算/支付/账号/规则/客服入口和 Docker 镜像。 + +当前仍需在下一迭代接入真实支付回调、退款/订单完成结算、钱包流水、客服二维码池和成员级站点隔离;在这些链路接通前,财务快照接口只作为后台受控的幂等写入入口。