diff --git a/backend/internal/modules/listing/receive_pricing.go b/backend/internal/modules/listing/receive_pricing.go index 38ffd50..a920f6b 100644 --- a/backend/internal/modules/listing/receive_pricing.go +++ b/backend/internal/modules/listing/receive_pricing.go @@ -98,6 +98,28 @@ func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.Publis buyerAnchorCoinBase = referenceSellerCoinBasePrice(coinWan, referenceRatio) } pricing := calculateExternalPlatformPricing(coinM, buyerAnchorRatio, buyerAnchorCoinBase, consumablePrice, config) + premiumRatioMarkup := 0.0 + if req.IsPremium { + premiumRatioMarkup = NormalizePremiumReviewRatioMarkup( + options.RatioConfig.PremiumReviewRatioMarkup, + ) + } + if premiumRatioMarkup > 0 && pricing.BuyerRatio > premiumRatioMarkup { + beforeRatio := pricing.BuyerRatio + afterRatio := roundRatio(beforeRatio - premiumRatioMarkup) + buyerCoinBasePrice := roundMoney(coinWan / afterRatio) + pricing = buildExternalPlatformPricing( + coinWan, + sellerTotalPrice, + consumablePrice, + buyerCoinBasePrice, + pricing.RuleType, + ) + breakdown["premium_review_ratio_markup"] = premiumRatioMarkup + breakdown["premium_review_ratio_before"] = beforeRatio + breakdown["premium_review_ratio_after"] = pricing.BuyerRatio + breakdown["premium_review_ratio_markup_applied"] = true + } breakdown["seller_reference_ratio"] = referenceRatio breakdown["seller_ratio"] = sellerRatio breakdown["seller_coin_base_price"] = sellerCoinBasePrice @@ -170,6 +192,17 @@ func NormalizeQualityOwnerRatioDiscount(value float64) float64 { return roundRatio(value) } +// NormalizePremiumReviewRatioMarkup 限制极品专区发布时额外增加的比例加价点数,最大 10。 +func NormalizePremiumReviewRatioMarkup(value float64) float64 { + if value < 0 || math.IsNaN(value) || math.IsInf(value, 0) { + return 0 + } + if value > maxAcceleratedSaleRatioIncrement { + return maxAcceleratedSaleRatioIncrement + } + return roundRatio(value) +} + // referenceSellerCoinBasePrice 计算主站参考比例下的号主基础价,用作买家定价锚点。 func referenceSellerCoinBasePrice(coinWan, referenceRatio float64) float64 { if referenceRatio <= 0 { diff --git a/backend/internal/modules/listing/receive_pricing_test.go b/backend/internal/modules/listing/receive_pricing_test.go index b3c44c5..e2a3a34 100644 --- a/backend/internal/modules/listing/receive_pricing_test.go +++ b/backend/internal/modules/listing/receive_pricing_test.go @@ -82,6 +82,28 @@ func TestApplyMainSiteReceivePricingKeepsAcceleratedRatioWithinMainSiteRange(t * } } +func TestApplyMainSiteReceivePricingIncludesPremiumReviewMarkup(t *testing.T) { + req := fullReceivePricingRequest(50) + req.IsPremium = true + applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, 0, false) + breakdown := ensurePriceBreakdown(req.AssetSummary) + if got := readSummaryNumber(breakdown["premium_review_ratio_markup"]); got != 1 { + t.Fatalf("premium review markup = %v, want 1", got) + } + if got := readSummaryNumber(breakdown["premium_review_ratio_before"]); got != 45 { + t.Fatalf("premium buyer ratio before = %v, want 45", got) + } + if got := readSummaryNumber(breakdown["premium_review_ratio_after"]); got != 44 { + t.Fatalf("premium buyer ratio after = %v, want 44", got) + } + if applied, _ := breakdown["premium_review_ratio_markup_applied"].(bool); !applied { + t.Fatal("premium review markup should be applied before review") + } + if req.PriceCent != 22730 { + t.Fatalf("premium PriceCent = %d, want final 22730", req.PriceCent) + } +} + func TestApplyMainSiteReceivePricingAllowsQualityOwnerExclusiveRatio(t *testing.T) { req := fullReceivePricingRequest(42) req.OwnerIsQualityOwner = true diff --git a/backend/internal/modules/systemconfig/dto.go b/backend/internal/modules/systemconfig/dto.go index 7687a77..fbfb2b8 100644 --- a/backend/internal/modules/systemconfig/dto.go +++ b/backend/internal/modules/systemconfig/dto.go @@ -183,6 +183,7 @@ type PublishDepositSkinGroupRule struct { type PublishRatioConfig struct { QualityOwnerRatioDiscount float64 `json:"quality_owner_ratio_discount"` + PremiumReviewRatioMarkup float64 `json:"premium_review_ratio_markup"` InsuranceBaseRatios []PublishInsuranceBaseRatio `json:"insurance_base_ratios"` ConfigItems []PublishRatioConfigItem `json:"config_items"` CoinCorrections []PublishCoinCorrection `json:"coin_corrections"` diff --git a/backend/internal/modules/systemconfig/normalize.go b/backend/internal/modules/systemconfig/normalize.go index 6dd3a3f..a5c6342 100644 --- a/backend/internal/modules/systemconfig/normalize.go +++ b/backend/internal/modules/systemconfig/normalize.go @@ -252,6 +252,19 @@ func normalizePublishOptions(options *PublishOptionsDTO) { if len(options.RatioConfig.CoinCorrections) == 0 { options.RatioConfig.CoinCorrections = defaults.RatioConfig.CoinCorrections } + options.RatioConfig.PremiumReviewRatioMarkup = normalizePremiumReviewRatioMarkup( + options.RatioConfig.PremiumReviewRatioMarkup, + ) +} + +func normalizePremiumReviewRatioMarkup(value float64) float64 { + if value < 0 || math.IsNaN(value) || math.IsInf(value, 0) { + return 0 + } + if value > 10 { + return 10 + } + return math.Round(value*10) / 10 } func normalizeSalePriceConfig(config *PublishSalePriceConfig) { diff --git a/backend/internal/modules/systemconfig/publish_options.go b/backend/internal/modules/systemconfig/publish_options.go index fa057db..33e51e2 100644 --- a/backend/internal/modules/systemconfig/publish_options.go +++ b/backend/internal/modules/systemconfig/publish_options.go @@ -106,6 +106,7 @@ func DefaultPublishOptions() PublishOptionsDTO { }, RatioConfig: PublishRatioConfig{ QualityOwnerRatioDiscount: 1, + PremiumReviewRatioMarkup: 1, InsuranceBaseRatios: []PublishInsuranceBaseRatio{ {Insurance: "3*3", Ratio: 40}, {Insurance: "2*3", Ratio: 42}, diff --git a/backend/internal/modules/systemconfig/publish_options_test.go b/backend/internal/modules/systemconfig/publish_options_test.go index fcbb1aa..08b9175 100644 --- a/backend/internal/modules/systemconfig/publish_options_test.go +++ b/backend/internal/modules/systemconfig/publish_options_test.go @@ -128,6 +128,36 @@ func TestNormalizePublishOptionsValueDefaultsQualityOwnerRatioDiscount(t *testin got.RatioConfig.QualityOwnerRatioDiscount, ) } + if got.RatioConfig.PremiumReviewRatioMarkup != 1 { + t.Fatalf( + "premium_review_ratio_markup = %v, want 1", + got.RatioConfig.PremiumReviewRatioMarkup, + ) + } +} + +func TestNormalizePublishOptionsValuePreservesExplicitZeroPremiumReviewRatioMarkup(t *testing.T) { + options := DefaultPublishOptions() + options.RatioConfig.PremiumReviewRatioMarkup = 0 + raw, err := json.Marshal(options) + if err != nil { + t.Fatalf("构造配置失败: %v", err) + } + + normalized, err := normalizePublishOptionsValue(string(raw)) + if err != nil { + t.Fatalf("规范化失败: %v", err) + } + var got PublishOptionsDTO + if err := json.Unmarshal([]byte(normalized), &got); err != nil { + t.Fatalf("解析规范化结果失败: %v", err) + } + if got.RatioConfig.PremiumReviewRatioMarkup != 0 { + t.Fatalf( + "premium_review_ratio_markup = %v, want explicit 0", + got.RatioConfig.PremiumReviewRatioMarkup, + ) + } } func TestNormalizePublishOptionsValuePreservesExplicitZeroQualityOwnerDiscount(t *testing.T) { diff --git a/backend/internal/modules/systemconfig/repository.go b/backend/internal/modules/systemconfig/repository.go index 989ffad..37fc41a 100644 --- a/backend/internal/modules/systemconfig/repository.go +++ b/backend/internal/modules/systemconfig/repository.go @@ -337,6 +337,10 @@ func normalizePublishOptionsValue(value string) (string, error) { options.RatioConfig.QualityOwnerRatioDiscount = DefaultPublishOptions().RatioConfig.QualityOwnerRatioDiscount } + if !hasNestedJSONField(fields, "ratio_config", "premium_review_ratio_markup") { + options.RatioConfig.PremiumReviewRatioMarkup = + DefaultPublishOptions().RatioConfig.PremiumReviewRatioMarkup + } originalVersion := options.SchemaVersion if options.SchemaVersion < publishOptionsSchemaVersion { if hasLegacyPublishQuantityItem(options.QuantityItems) { diff --git a/frontend/src/features/admin/components/PublishOptionsDialog.vue b/frontend/src/features/admin/components/PublishOptionsDialog.vue index 26e4966..a38f30d 100644 --- a/frontend/src/features/admin/components/PublishOptionsDialog.vue +++ b/frontend/src/features/admin/components/PublishOptionsDialog.vue @@ -412,6 +412,17 @@ async function handleSave() { 默认 1,表示优质号主可在基础比例上降低 1;普通号主不能使用该区间。 + + + + 默认 1;极品专区商品发布时预加该比例点数,审核页直接展示最终价格,审核通过不再改价。 + +
保险基础比例 添加保险比例 diff --git a/frontend/src/features/admin/views/AdminListingReviewView.css b/frontend/src/features/admin/views/AdminListingReviewView.css index 44a73b6..8edacb3 100644 --- a/frontend/src/features/admin/views/AdminListingReviewView.css +++ b/frontend/src/features/admin/views/AdminListingReviewView.css @@ -630,6 +630,11 @@ font-weight: 700; } +.premium-review-note { + color: #c2410c !important; + font-weight: 800; +} + .pipeline-card--buyer { border: 2px solid #2563eb; background: linear-gradient(135deg, #eff6ff 0%, #e0f2fe 100%); diff --git a/frontend/src/features/admin/views/AdminListingReviewView.vue b/frontend/src/features/admin/views/AdminListingReviewView.vue index 2f96239..a10494e 100644 --- a/frontend/src/features/admin/views/AdminListingReviewView.vue +++ b/frontend/src/features/admin/views/AdminListingReviewView.vue @@ -558,6 +558,18 @@ function adminAdjustReason(row: Listing) { return breakdownText(row, 'admin_adjust_reason') } +function premiumReviewMarkupApplied(row: Listing) { + return breakdownNumber(row, 'premium_review_ratio_markup') +} + +function premiumReviewRatioBefore(row: Listing) { + return breakdownNumber(row, 'premium_review_ratio_before') +} + +function premiumReviewRatioAfter(row: Listing) { + return breakdownNumber(row, 'premium_review_ratio_after') +} + function priceRuleText(row: Listing) { const markup = platformMarkupAmount(row) if (priceRuleType(row) === 'admin_adjusted') { @@ -1206,6 +1218,9 @@ async function handleScreenshotSaved(payload: { oldUrl: string; newUrl: string }

{{ selectedListing.title }}

+ + 极品专区 +
毛利率 ~{{ platformMarkupPercentage(selectedListing) }}% + + 已含审核加价 +{{ quantity(premiumReviewMarkupApplied(selectedListing)) }} · + {{ ratioText(premiumReviewRatioBefore(selectedListing)) }} → + {{ ratioText(premiumReviewRatioAfter(selectedListing)) }} +