diff --git a/backend/internal/modules/listing/dto.go b/backend/internal/modules/listing/dto.go
index 92f38b3..0880563 100644
--- a/backend/internal/modules/listing/dto.go
+++ b/backend/internal/modules/listing/dto.go
@@ -51,19 +51,20 @@ type ListingDTO struct {
}
type CreateRequest struct {
- Title string `json:"title" binding:"required"`
- Description string `json:"description"`
- ServerRegion string `json:"server_region" binding:"required"`
- LoginPlatform string `json:"login_platform"`
- RankLevel string `json:"rank_level"`
- HafCoinAmount int64 `json:"haf_coin_amount"`
- AssetSummary map[string]any `json:"asset_summary"`
- ScreenshotURLS []string `json:"screenshot_urls"`
- PriceCent int64 `json:"price_cent"`
- DepositAmountCent int64 `json:"deposit_amount_cent"`
- IsPremium bool `json:"is_premium"`
- CompensationPlan string `json:"compensation_plan" binding:"omitempty,oneof=normal insurance"`
- InsuranceFeeBps int `json:"-"` // 服务端按配置和用户资格生成,不接受前端指定。
+ Title string `json:"title" binding:"required"`
+ Description string `json:"description"`
+ ServerRegion string `json:"server_region" binding:"required"`
+ LoginPlatform string `json:"login_platform"`
+ RankLevel string `json:"rank_level"`
+ HafCoinAmount int64 `json:"haf_coin_amount"`
+ AssetSummary map[string]any `json:"asset_summary"`
+ ScreenshotURLS []string `json:"screenshot_urls"`
+ PriceCent int64 `json:"price_cent"`
+ DepositAmountCent int64 `json:"deposit_amount_cent"`
+ IsPremium bool `json:"is_premium"`
+ CompensationPlan string `json:"compensation_plan" binding:"omitempty,oneof=normal insurance"`
+ InsuranceFeeBps int `json:"-"` // 服务端按配置和用户资格生成,不接受前端指定。
+ OwnerIsQualityOwner bool `json:"-"` // 服务端鉴权结果,用于校验优质号主专享比例。
AgreedVirtualAssetSale bool `json:"agreed_virtual_asset_sale"`
AgreedSellerAgreement bool `json:"agreed_seller_agreement"`
diff --git a/backend/internal/modules/listing/handler_error.go b/backend/internal/modules/listing/handler_error.go
index dfa3236..fa881af 100644
--- a/backend/internal/modules/listing/handler_error.go
+++ b/backend/internal/modules/listing/handler_error.go
@@ -59,6 +59,8 @@ func writeListingError(c *gin.Context, err error) {
response.BadRequest(c, "分销站点无效或已停用")
case errors.Is(err, ErrPremiumOwnerRequired):
response.Error(c, http.StatusForbidden, "quality_owner_required", "仅优质号主可以发布到极品专区")
+ case errors.Is(err, ErrQualityRatioPremiumOnly):
+ response.BadRequest(c, "优质号主专享比例仅可用于极品专区")
case isUploadValidationError(err):
response.BadRequest(c, err.Error())
case isFireLevelTooLow(err):
diff --git a/backend/internal/modules/listing/quality_owner_entitlement_test.go b/backend/internal/modules/listing/quality_owner_entitlement_test.go
index c8e5d9a..9902d7c 100644
--- a/backend/internal/modules/listing/quality_owner_entitlement_test.go
+++ b/backend/internal/modules/listing/quality_owner_entitlement_test.go
@@ -34,6 +34,9 @@ func TestApplyPublishEntitlementsPremiumInsurance(t *testing.T) {
if req.InsuranceFeeBps != 400 {
t.Fatalf("极品专区保险费率 = %d, want 400", req.InsuranceFeeBps)
}
+ if !req.OwnerIsQualityOwner {
+ t.Fatal("优质号主发布请求未标记专享比例资格")
+ }
ordinary := CreateRequest{CompensationPlan: CompensationPlanInsurance}
if err := service.applyPublishEntitlements(t.Context(), owner.ID, &ordinary); err != nil {
diff --git a/backend/internal/modules/listing/receive_pricing.go b/backend/internal/modules/listing/receive_pricing.go
index 7f90e7f..38ffd50 100644
--- a/backend/internal/modules/listing/receive_pricing.go
+++ b/backend/internal/modules/listing/receive_pricing.go
@@ -49,7 +49,7 @@ func (s *Service) applyMainSiteReceivePricing(ctx context.Context, req *CreateRe
return err
}
applyMainSiteReceivePricing(req, options, config, receiveRatioDelta, receiveCostDelta, onDistributionSite)
- return nil
+ return validateQualityOwnerPricingMode(req, onDistributionSite)
}
func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.PublishOptionsDTO, config salePriceConfig, receiveRatioDelta, receiveCostDelta float64, onDistributionSite bool) {
@@ -71,9 +71,16 @@ func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.Publis
if requestedRatio <= 0 {
requestedRatio = readSummaryNumber(breakdown["accelerated_sale_ratio"])
}
+ minSaleRatio := referenceRatio
+ if req.OwnerIsQualityOwner {
+ minSaleRatio = roundRatio(referenceRatio - NormalizeQualityOwnerRatioDiscount(options.RatioConfig.QualityOwnerRatioDiscount))
+ if minSaleRatio < minSiteReceiveRatio {
+ minSaleRatio = minSiteReceiveRatio
+ }
+ }
costDelta := NormalizeSiteReceiveCostDelta(receiveCostDelta)
ratioDelta := NormalizeSiteReceiveRatioDelta(receiveRatioDelta)
- sellerRatio := clampSaleRatio(referenceRatio, requestedRatio)
+ sellerRatio := clampSaleRatio(referenceRatio, requestedRatio, minSaleRatio)
// 分站新口径:收号比例 = R + 压价点数(收号价更低),买家定价锚定主站参考比例 R,不随收号价联动。
if onDistributionSite {
sellerRatio = ApplySiteReceiveCostDelta(referenceRatio, costDelta)
@@ -98,6 +105,10 @@ func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.Publis
breakdown["consumable_price"] = consumablePrice
breakdown["daily_loss_ratio_adjustment"] = calculateDailyLossRatioAdjustment(dailyLoss)
breakdown["accelerated_sale_ratio"] = sellerRatio
+ if sellerRatio < referenceRatio {
+ breakdown["quality_owner_ratio_discount"] = roundRatio(referenceRatio - sellerRatio)
+ breakdown["pricing_mode"] = "quality_owner_exclusive"
+ }
if onDistributionSite {
breakdown["site_receive_cost_delta"] = costDelta
breakdown["pricing_model"] = "site_cost_decoupled_v1"
@@ -114,6 +125,18 @@ func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.Publis
req.PriceCent = yuanToCent(pricing.BuyerTotalPrice)
}
+func validateQualityOwnerPricingMode(req *CreateRequest, onDistributionSite bool) error {
+ if req == nil || onDistributionSite || !req.OwnerIsQualityOwner || req.IsPremium {
+ return nil
+ }
+ breakdown := ensurePriceBreakdown(req.AssetSummary)
+ if readSummaryNumber(breakdown["seller_ratio"]) <
+ readSummaryNumber(breakdown["seller_reference_ratio"]) {
+ return ErrQualityRatioPremiumOnly
+ }
+ return nil
+}
+
// NormalizeSiteReceiveCostDelta 将分站收号压价点数限制在 [0, 20]。
// 点数越大,分站收号比例越高(相对主站参考比例),收号价越低;0 表示与主站同价收号。
func NormalizeSiteReceiveCostDelta(delta float64) float64 {
@@ -136,6 +159,17 @@ func ApplySiteReceiveCostDelta(referenceRatio, delta float64) float64 {
return siteRatio
}
+// NormalizeQualityOwnerRatioDiscount 限制优质号主专享比例下调值,默认 1、最大 10。
+func NormalizeQualityOwnerRatioDiscount(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 {
@@ -182,7 +216,7 @@ func (s *Service) publishRatioOptions(ctx context.Context) (systemconfig.Publish
if strings.TrimSpace(value) == "" {
return options, nil
}
- var parsed systemconfig.PublishOptionsDTO
+ parsed := options
if err := json.Unmarshal([]byte(value), &parsed); err != nil {
return options, nil
}
@@ -225,13 +259,13 @@ func calculateSellerReferenceRatio(coinM float64, summary map[string]any, option
calculateDailyLossRatioAdjustment(dailyLoss)
}
-func clampSaleRatio(referenceRatio, requestedRatio float64) float64 {
+func clampSaleRatio(referenceRatio, requestedRatio, minRatio float64) float64 {
maxRatio := roundRatio(referenceRatio + maxAcceleratedSaleRatioIncrement)
if requestedRatio <= 0 {
return referenceRatio
}
- if requestedRatio < referenceRatio {
- return referenceRatio
+ if requestedRatio < minRatio {
+ return minRatio
}
if requestedRatio > maxRatio {
return maxRatio
diff --git a/backend/internal/modules/listing/receive_pricing_test.go b/backend/internal/modules/listing/receive_pricing_test.go
index 4009f39..b3c44c5 100644
--- a/backend/internal/modules/listing/receive_pricing_test.go
+++ b/backend/internal/modules/listing/receive_pricing_test.go
@@ -1,6 +1,7 @@
package listing
import (
+ "errors"
"testing"
"hfb_sys/backend/internal/modules/systemconfig"
@@ -81,6 +82,63 @@ func TestApplyMainSiteReceivePricingKeepsAcceleratedRatioWithinMainSiteRange(t *
}
}
+func TestApplyMainSiteReceivePricingAllowsQualityOwnerExclusiveRatio(t *testing.T) {
+ req := fullReceivePricingRequest(42)
+ req.OwnerIsQualityOwner = true
+ options := systemconfig.DefaultPublishOptions()
+ applyMainSiteReceivePricing(&req, options, defaultSalePriceConfig(), 0, 0, false)
+ breakdown := ensurePriceBreakdown(req.AssetSummary)
+ if readSummaryNumber(breakdown["seller_ratio"]) != 42 {
+ t.Fatalf("quality owner seller_ratio = %v, want 42", breakdown["seller_ratio"])
+ }
+ if readSummaryNumber(breakdown["quality_owner_ratio_discount"]) != 1 {
+ t.Fatalf(
+ "quality owner discount = %v, want 1",
+ breakdown["quality_owner_ratio_discount"],
+ )
+ }
+
+ ordinary := fullReceivePricingRequest(42)
+ applyMainSiteReceivePricing(&ordinary, options, defaultSalePriceConfig(), 0, 0, false)
+ ordinaryBreakdown := ensurePriceBreakdown(ordinary.AssetSummary)
+ if readSummaryNumber(ordinaryBreakdown["seller_ratio"]) != 43 {
+ t.Fatalf("ordinary seller_ratio = %v, want reference 43", ordinaryBreakdown["seller_ratio"])
+ }
+}
+
+func TestQualityOwnerExclusiveRatioUsesConfiguredDiscount(t *testing.T) {
+ req := fullReceivePricingRequest(40)
+ req.OwnerIsQualityOwner = true
+ options := systemconfig.DefaultPublishOptions()
+ options.RatioConfig.QualityOwnerRatioDiscount = 3
+ applyMainSiteReceivePricing(&req, options, defaultSalePriceConfig(), 0, 0, false)
+ breakdown := ensurePriceBreakdown(req.AssetSummary)
+ if readSummaryNumber(breakdown["seller_ratio"]) != 40 {
+ t.Fatalf("configured quality owner seller_ratio = %v, want 40", breakdown["seller_ratio"])
+ }
+}
+
+func TestQualityOwnerExclusiveRatioRequiresPremiumZone(t *testing.T) {
+ req := fullReceivePricingRequest(42)
+ req.OwnerIsQualityOwner = true
+ applyMainSiteReceivePricing(
+ &req,
+ systemconfig.DefaultPublishOptions(),
+ defaultSalePriceConfig(),
+ 0,
+ 0,
+ false,
+ )
+ if err := validateQualityOwnerPricingMode(&req, false); !errors.Is(err, ErrQualityRatioPremiumOnly) {
+ t.Fatalf("error = %v, want ErrQualityRatioPremiumOnly", err)
+ }
+
+ req.IsPremium = true
+ if err := validateQualityOwnerPricingMode(&req, false); err != nil {
+ t.Fatalf("极品专区发布不应拒绝优质号主专享比例: %v", err)
+ }
+}
+
func TestDistributionReceiveCostDeltaZeroKeepsMainSitePrice(t *testing.T) {
req := fullReceivePricingRequest(50)
applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, 0, true)
@@ -146,10 +204,10 @@ func TestApplySiteReceiveRatioDeltaCapsAtMainSite(t *testing.T) {
}
func TestClampSaleRatioRejectsBelowReference(t *testing.T) {
- if got := clampSaleRatio(43, 10); got != 43 {
+ if got := clampSaleRatio(43, 10, 43); got != 43 {
t.Fatalf("clamp below reference = %v, want 43", got)
}
- if got := clampSaleRatio(43, 80); got != 53 {
+ if got := clampSaleRatio(43, 80, 43); got != 53 {
t.Fatalf("clamp above max = %v, want 53", got)
}
}
diff --git a/backend/internal/modules/listing/service.go b/backend/internal/modules/listing/service.go
index 1ecc367..8966156 100644
--- a/backend/internal/modules/listing/service.go
+++ b/backend/internal/modules/listing/service.go
@@ -34,6 +34,7 @@ var (
ErrSaleRatioOutOfRange = errors.New("sale ratio out of range")
ErrDistributionSiteInvalid = errors.New("invalid distribution site")
ErrPremiumOwnerRequired = errors.New("premium listing requires quality owner")
+ ErrQualityRatioPremiumOnly = errors.New("quality owner exclusive ratio requires premium zone")
)
type Service struct {
diff --git a/backend/internal/modules/listing/service_mutation.go b/backend/internal/modules/listing/service_mutation.go
index 178fb7e..fa8d7c5 100644
--- a/backend/internal/modules/listing/service_mutation.go
+++ b/backend/internal/modules/listing/service_mutation.go
@@ -18,6 +18,9 @@ func (s *Service) Create(ctx context.Context, ownerID uint64, req CreateRequest,
if len(siteCode) > 0 {
effectiveSiteCode = strings.TrimSpace(siteCode[0])
}
+ if err := s.applyPublishEntitlements(ctx, ownerID, &req); err != nil {
+ return nil, err
+ }
delta, onDistribution := s.repo.resolveSiteReceiveCostDelta(ctx, 0, effectiveSiteCode)
if err := s.applyMainSiteReceivePricing(ctx, &req, 0, delta, onDistribution); err != nil {
return nil, err
@@ -32,9 +35,6 @@ func (s *Service) Create(ctx context.Context, ownerID uint64, req CreateRequest,
if err := validateRequiredOnlineTime(req); err != nil {
return nil, err
}
- if err := s.applyPublishEntitlements(ctx, ownerID, &req); err != nil {
- return nil, err
- }
cooldown, err := s.publishCooldown(ctx)
if err != nil {
return nil, err
@@ -54,6 +54,9 @@ func (s *Service) Update(ctx context.Context, ownerID uint64, id uint64, req Upd
if len(siteCode) > 0 {
effectiveSiteCode = strings.TrimSpace(siteCode[0])
}
+ if err := s.applyPublishEntitlements(ctx, ownerID, &req); err != nil {
+ return nil, err
+ }
delta, onDistribution := s.repo.resolveSiteReceiveCostDelta(ctx, id, effectiveSiteCode)
if err := s.applyMainSiteReceivePricing(ctx, &req, 0, delta, onDistribution); err != nil {
return nil, err
@@ -68,9 +71,6 @@ func (s *Service) Update(ctx context.Context, ownerID uint64, id uint64, req Upd
if err := validateRequiredOnlineTime(req); err != nil {
return nil, err
}
- if err := s.applyPublishEntitlements(ctx, ownerID, &req); err != nil {
- return nil, err
- }
reviewRequired, err := s.reviewRequired(ctx)
if err != nil {
return nil, err
@@ -94,6 +94,7 @@ func (s *Service) applyPublishEntitlements(ctx context.Context, ownerID uint64,
if req.IsPremium && !isQualityOwner {
return ErrPremiumOwnerRequired
}
+ req.OwnerIsQualityOwner = isQualityOwner
req.InsuranceFeeBps = 0
if req.CompensationPlan != CompensationPlanInsurance {
diff --git a/backend/internal/modules/systemconfig/dto.go b/backend/internal/modules/systemconfig/dto.go
index b3da7fb..7687a77 100644
--- a/backend/internal/modules/systemconfig/dto.go
+++ b/backend/internal/modules/systemconfig/dto.go
@@ -182,9 +182,10 @@ type PublishDepositSkinGroupRule struct {
}
type PublishRatioConfig struct {
- InsuranceBaseRatios []PublishInsuranceBaseRatio `json:"insurance_base_ratios"`
- ConfigItems []PublishRatioConfigItem `json:"config_items"`
- CoinCorrections []PublishCoinCorrection `json:"coin_corrections"`
+ QualityOwnerRatioDiscount float64 `json:"quality_owner_ratio_discount"`
+ InsuranceBaseRatios []PublishInsuranceBaseRatio `json:"insurance_base_ratios"`
+ ConfigItems []PublishRatioConfigItem `json:"config_items"`
+ CoinCorrections []PublishCoinCorrection `json:"coin_corrections"`
}
type PublishInsuranceBaseRatio struct {
diff --git a/backend/internal/modules/systemconfig/publish_options.go b/backend/internal/modules/systemconfig/publish_options.go
index 732e31c..fa057db 100644
--- a/backend/internal/modules/systemconfig/publish_options.go
+++ b/backend/internal/modules/systemconfig/publish_options.go
@@ -105,6 +105,7 @@ func DefaultPublishOptions() PublishOptionsDTO {
},
},
RatioConfig: PublishRatioConfig{
+ QualityOwnerRatioDiscount: 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 6d321ce..fcbb1aa 100644
--- a/backend/internal/modules/systemconfig/publish_options_test.go
+++ b/backend/internal/modules/systemconfig/publish_options_test.go
@@ -106,6 +106,54 @@ func TestNormalizePublishOptionsValuePreservesCurrentSchema(t *testing.T) {
}
}
+func TestNormalizePublishOptionsValueDefaultsQualityOwnerRatioDiscount(t *testing.T) {
+ legacy := `{
+ "schema_version": 2,
+ "ratio_config": {
+ "insurance_base_ratios": [{"insurance": "3*3", "ratio": 40}]
+ }
+ }`
+
+ normalized, err := normalizePublishOptionsValue(legacy)
+ 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.QualityOwnerRatioDiscount != 1 {
+ t.Fatalf(
+ "quality_owner_ratio_discount = %v, want 1",
+ got.RatioConfig.QualityOwnerRatioDiscount,
+ )
+ }
+}
+
+func TestNormalizePublishOptionsValuePreservesExplicitZeroQualityOwnerDiscount(t *testing.T) {
+ options := DefaultPublishOptions()
+ options.RatioConfig.QualityOwnerRatioDiscount = 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.QualityOwnerRatioDiscount != 0 {
+ t.Fatalf(
+ "quality_owner_ratio_discount = %v, want explicit 0",
+ got.RatioConfig.QualityOwnerRatioDiscount,
+ )
+ }
+}
+
func TestNormalizePublishOptionsValueMigratesSchemaV1Switches(t *testing.T) {
legacy := `{"schema_version":1,"next_season_pass_enabled":false,"server_options":["QQ"]}`
diff --git a/backend/internal/modules/systemconfig/repository.go b/backend/internal/modules/systemconfig/repository.go
index 3433df0..989ffad 100644
--- a/backend/internal/modules/systemconfig/repository.go
+++ b/backend/internal/modules/systemconfig/repository.go
@@ -333,6 +333,10 @@ func normalizePublishOptionsValue(value string) (string, error) {
if err := json.Unmarshal([]byte(value), &fields); err != nil {
return "", err
}
+ if !hasNestedJSONField(fields, "ratio_config", "quality_owner_ratio_discount") {
+ options.RatioConfig.QualityOwnerRatioDiscount =
+ DefaultPublishOptions().RatioConfig.QualityOwnerRatioDiscount
+ }
originalVersion := options.SchemaVersion
if options.SchemaVersion < publishOptionsSchemaVersion {
if hasLegacyPublishQuantityItem(options.QuantityItems) {
@@ -360,6 +364,19 @@ func normalizePublishOptionsValue(value string) (string, error) {
return string(raw), nil
}
+func hasNestedJSONField(fields map[string]json.RawMessage, parent, child string) bool {
+ raw, ok := fields[parent]
+ if !ok {
+ return false
+ }
+ var nested map[string]json.RawMessage
+ if err := json.Unmarshal(raw, &nested); err != nil {
+ return false
+ }
+ _, ok = nested[child]
+ return ok
+}
+
func hasLegacyPublishQuantityItem(items []PublishQuantityItem) bool {
for _, item := range items {
if item.Key == "kit5" {
diff --git a/frontend/src/features/admin/components/PublishOptionsDialog.vue b/frontend/src/features/admin/components/PublishOptionsDialog.vue
index 9832746..26e4966 100644
--- a/frontend/src/features/admin/components/PublishOptionsDialog.vue
+++ b/frontend/src/features/admin/components/PublishOptionsDialog.vue
@@ -401,6 +401,17 @@ async function handleSave() {
比例计算配置
+
+
+
+ 默认 1,表示优质号主可在基础比例上降低 1;普通号主不能使用该区间。
+
+
保险基础比例
添加保险比例
@@ -635,6 +646,13 @@ async function handleSave() {
font-size: 13px;
}
+.inline-config-hint {
+ margin-left: 10px;
+ color: #8b95a5;
+ font-size: 12px;
+ line-height: 1.5;
+}
+
.skin-config-row {
display: grid;
grid-template-columns: 140px 180px minmax(0, 1fr) 72px;
diff --git a/frontend/src/features/listings/api/listingOptions.ts b/frontend/src/features/listings/api/listingOptions.ts
index 9e01305..af1564d 100644
--- a/frontend/src/features/listings/api/listingOptions.ts
+++ b/frontend/src/features/listings/api/listingOptions.ts
@@ -78,6 +78,7 @@ export interface PublishSaleRatioAdjustmentRule {
}
export interface PublishRatioConfig {
+ quality_owner_ratio_discount: number
insurance_base_ratios: PublishInsuranceBaseRatio[]
config_items: PublishRatioConfigItem[]
coin_corrections: PublishCoinCorrection[]
@@ -151,6 +152,7 @@ export const emptyListingPublishOptions: ListingPublishOptions = {
],
},
ratio_config: {
+ quality_owner_ratio_discount: 1,
insurance_base_ratios: [],
config_items: [],
coin_corrections: [],
@@ -363,6 +365,10 @@ function normalizeDepositSkinGroupRules(values: unknown[]): PublishDepositSkinGr
function normalizeRatioConfig(value?: unknown): PublishRatioConfig {
const row = isRecord(value) ? value : {}
return {
+ quality_owner_ratio_discount: Math.min(
+ 10,
+ Math.max(0, readNumber(row.quality_owner_ratio_discount ?? 1))
+ ),
insurance_base_ratios: normalizeInsuranceBaseRatios(
Array.isArray(row.insurance_base_ratios) ? row.insurance_base_ratios : []
),
diff --git a/frontend/src/features/seller/composables/usePublishDraft.ts b/frontend/src/features/seller/composables/usePublishDraft.ts
index 9939b21..efc3d15 100644
--- a/frontend/src/features/seller/composables/usePublishDraft.ts
+++ b/frontend/src/features/seller/composables/usePublishDraft.ts
@@ -37,6 +37,7 @@ export function buildPublishDraft(options: {
quantityModes: Record
screenshotFiles: Record
selectedSkins: string[]
+ qualityOwnerRatioDefaultApplied: boolean
}): PublishDraft {
return {
form: {
@@ -47,6 +48,7 @@ export function buildPublishDraft(options: {
quantityModes: { ...options.quantityModes },
screenshotFiles: cloneScreenshotFiles(options.screenshotFiles),
selectedSkins: [...options.selectedSkins],
+ qualityOwnerRatioDefaultApplied: options.qualityOwnerRatioDefaultApplied,
}
}
@@ -63,6 +65,10 @@ export function readPublishDraft(draftKey: string) {
selectedSkins: Array.isArray(draft.selectedSkins)
? draft.selectedSkins.filter((skin): skin is string => typeof skin === 'string')
: [],
+ qualityOwnerRatioDefaultApplied:
+ typeof draft.qualityOwnerRatioDefaultApplied === 'boolean'
+ ? draft.qualityOwnerRatioDefaultApplied
+ : undefined,
}
} catch {
localStorage.removeItem(draftKey)
diff --git a/frontend/src/features/seller/composables/usePublishForm.ts b/frontend/src/features/seller/composables/usePublishForm.ts
index a2c3bd8..b430c1c 100644
--- a/frontend/src/features/seller/composables/usePublishForm.ts
+++ b/frontend/src/features/seller/composables/usePublishForm.ts
@@ -92,6 +92,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
const unlockDemulanEnabled = computed(() => publishOptions.value.unlock_demulan_enabled)
const nextSeasonPassEnabled = computed(() => publishOptions.value.next_season_pass_enabled)
const isQualityOwner = computed(() => qualityOwnerStatus.value?.active === true)
+ const qualityOwnerRatioDefaultApplied = ref(true)
const insuranceFeeBps = computed(() =>
form.is_premium && isQualityOwner.value
? Number(qualityOwnerStatus.value?.premium_insurance_fee_bps || 400)
@@ -110,7 +111,43 @@ export function usePublishForm(options: UsePublishFormOptions) {
isDistribution,
receiveRatioDelta,
receiveCostDelta,
+ isQualityOwner,
})
+ const qualityOwnerSaleRatioSelected = computed(
+ () =>
+ isQualityOwner.value &&
+ pricing.qualityOwnerRatioDiscount.value > 0 &&
+ pricing.qualityOwnerSaleRatio.value > 0 &&
+ Math.abs(Number(form.accelerated_sale_ratio) - pricing.qualityOwnerSaleRatio.value) < 0.05
+ )
+ const isQualityOwnerExclusiveRatioActive = computed(
+ () =>
+ isQualityOwner.value &&
+ pricing.calculatedSellerReferenceRatio.value > 0 &&
+ pricing.hasAcceleratedSaleRatioInput() &&
+ Number(form.accelerated_sale_ratio) < pricing.calculatedSellerReferenceRatio.value
+ )
+
+ watch(
+ [isQualityOwner, pricing.qualityOwnerRatioDiscount, pricing.qualityOwnerSaleRatio, isEditMode],
+ ([owner, discount, ratio, editing]) => {
+ if (editing || !owner || discount <= 0 || !qualityOwnerRatioDefaultApplied.value) return
+ if (ratio <= 0) return
+ if (Number(form.accelerated_sale_ratio) !== ratio) {
+ form.accelerated_sale_ratio = ratio
+ }
+ },
+ { immediate: true }
+ )
+ watch(
+ [isQualityOwnerExclusiveRatioActive, () => form.is_premium],
+ ([exclusiveRatio, premium]) => {
+ if (exclusiveRatio && !premium) {
+ form.is_premium = true
+ }
+ },
+ { immediate: true }
+ )
// ========== 上号方式互斥 ==========
// 微信区账号只能扫码登录:选择微信区后,「账号密码」不可选,默认固定为「扫码」。
@@ -279,6 +316,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
quantityModes,
screenshotFiles,
selectedSkins: selectedSkins.value,
+ qualityOwnerRatioDefaultApplied: qualityOwnerRatioDefaultApplied.value,
})
)
}
@@ -304,6 +342,8 @@ export function usePublishForm(options: UsePublishFormOptions) {
Object.assign(quantityModes, draft.quantityModes)
Object.assign(screenshotFiles, draft.screenshotFiles)
selectedSkins.value = draft.selectedSkins
+ qualityOwnerRatioDefaultApplied.value =
+ draft.qualityOwnerRatioDefaultApplied ?? (form.accelerated_sale_ratio === '' ? true : false)
hydrateScreenshotPreviews()
}
@@ -368,6 +408,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
}
selectedSkins.value = Object.values(skinGroups).flatMap(value => stringArray(value))
+ qualityOwnerRatioDefaultApplied.value = false
virtualAssetSaleAgreementChecked.value = true
sellerAgreementChecked.value = true
passwordAndDeviceConfirmed.value = true
@@ -385,6 +426,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
sellerAgreementChecked.value = false
passwordAndDeviceConfirmed.value = false
activeUploadKey.value = 'coin'
+ qualityOwnerRatioDefaultApplied.value = true
// Also re-initialize quantityValues to 0
for (const item of publishOptions.value.quantity_items) {
@@ -588,6 +630,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
}
function handleAcceleratedSaleRatioInput(value: string | number | undefined) {
+ qualityOwnerRatioDefaultApplied.value = false
if (value === '' || value === undefined) {
form.accelerated_sale_ratio = ''
return
@@ -645,21 +688,36 @@ export function usePublishForm(options: UsePublishFormOptions) {
}
form.accelerated_sale_ratio = roundRatio(
Math.min(
- Math.max(ratio, pricing.calculatedDefaultSaleRatio.value),
+ Math.max(ratio, pricing.minAcceleratedSaleRatio.value),
pricing.maxAcceleratedSaleRatio.value
)
)
}
function useReferenceSaleRatio() {
+ qualityOwnerRatioDefaultApplied.value = false
form.accelerated_sale_ratio = ''
}
function useMaxAcceleratedSaleRatio() {
if (pricing.maxAcceleratedSaleRatio.value <= 0) return
+ qualityOwnerRatioDefaultApplied.value = false
form.accelerated_sale_ratio = pricing.maxAcceleratedSaleRatio.value
}
+ function useQualityOwnerSaleRatio() {
+ if (
+ !isQualityOwner.value ||
+ pricing.qualityOwnerRatioDiscount.value <= 0 ||
+ pricing.qualityOwnerSaleRatio.value <= 0
+ ) {
+ return
+ }
+ qualityOwnerRatioDefaultApplied.value = true
+ form.accelerated_sale_ratio = pricing.qualityOwnerSaleRatio.value
+ form.is_premium = true
+ }
+
async function handleSubmit() {
normalizeOnlineTime()
const error = validateForm()
@@ -740,10 +798,12 @@ export function usePublishForm(options: UsePublishFormOptions) {
const ratio = Number(form.accelerated_sale_ratio)
if (!Number.isFinite(ratio) || ratio <= 0) return '加速出售比例格式不正确'
if (
- pricing.calculatedDefaultSaleRatio.value > 0 &&
- ratio < pricing.calculatedDefaultSaleRatio.value
+ pricing.minAcceleratedSaleRatio.value > 0 &&
+ ratio < pricing.minAcceleratedSaleRatio.value
) {
- return `加速出售比例不能低于默认比例 1:${formatNumber(pricing.calculatedDefaultSaleRatio.value)}`
+ return isQualityOwner.value && pricing.qualityOwnerRatioDiscount.value > 0
+ ? `优质号主专享比例不能低于 1:${formatNumber(pricing.minAcceleratedSaleRatio.value)}`
+ : `加速出售比例不能低于默认比例 1:${formatNumber(pricing.calculatedDefaultSaleRatio.value)}`
}
if (
pricing.maxAcceleratedSaleRatio.value > 0 &&
@@ -779,6 +839,9 @@ export function usePublishForm(options: UsePublishFormOptions) {
if (!Number.isFinite(pricing.calculatedFinalPrice.value))
return '发布价格计算异常,请检查填写内容'
if (form.is_premium && !isQualityOwner.value) return '仅优质号主可以发布到极品专区'
+ if (isQualityOwnerExclusiveRatioActive.value && !form.is_premium) {
+ return '优质号主专享比例仅可用于极品专区'
+ }
if (!virtualAssetSaleAgreementChecked.value || !sellerAgreementChecked.value) {
return '请先阅读并勾选两份发布协议'
}
@@ -837,6 +900,16 @@ export function usePublishForm(options: UsePublishFormOptions) {
buyer_ratio: pricing.calculatedPlatformPricing.value.buyerRatio,
platform_markup_amount: pricing.calculatedPlatformPricing.value.platformMarkupAmount,
platform_rule_type: pricing.calculatedPlatformPricing.value.ruleType,
+ quality_owner_ratio_discount:
+ pricing.calculatedRatio.value < pricing.calculatedSellerReferenceRatio.value
+ ? roundRatio(
+ pricing.calculatedSellerReferenceRatio.value - pricing.calculatedRatio.value
+ )
+ : 0,
+ pricing_mode:
+ pricing.calculatedRatio.value < pricing.calculatedSellerReferenceRatio.value
+ ? 'quality_owner_exclusive'
+ : 'standard',
},
season_insurance: form.season_insurance,
stamina_level: form.stamina_level,
@@ -955,6 +1028,9 @@ export function usePublishForm(options: UsePublishFormOptions) {
publishAgreements,
qualityOwnerStatus,
isQualityOwner,
+ qualityOwnerRatioDefaultApplied,
+ qualityOwnerSaleRatioSelected,
+ isQualityOwnerExclusiveRatioActive,
insuranceFeeBps,
insuranceFeePercent,
virtualAssetSaleAgreementChecked,
@@ -1013,6 +1089,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
clampAcceleratedSaleRatioInput,
useReferenceSaleRatio,
useMaxAcceleratedSaleRatio,
+ useQualityOwnerSaleRatio,
handleSubmit,
validateForm,
isScreenshotRequired,
diff --git a/frontend/src/features/seller/views/MobileSellerListingCreateView.css b/frontend/src/features/seller/views/MobileSellerListingCreateView.css
index cd111cd..632f093 100644
--- a/frontend/src/features/seller/views/MobileSellerListingCreateView.css
+++ b/frontend/src/features/seller/views/MobileSellerListingCreateView.css
@@ -706,6 +706,19 @@
gap: 8px;
}
+.ratio-mode-grid.has-quality-owner .ratio-mode-btn:nth-of-type(1) {
+ order: 1;
+}
+
+.ratio-mode-grid.has-quality-owner .ratio-mode-btn:nth-of-type(2) {
+ order: 3;
+ grid-column: 1 / -1;
+}
+
+.ratio-mode-grid.has-quality-owner .ratio-mode-btn:nth-of-type(3) {
+ order: 2;
+}
+
.ratio-mode-btn {
display: grid;
gap: 5px;
@@ -737,6 +750,44 @@
line-height: 1.25;
}
+.ratio-mode-btn em {
+ width: max-content;
+ margin: 0 auto;
+ padding: 2px 7px;
+ border-radius: 9px;
+ background: #fff3d6;
+ color: #91600d;
+ font-size: 10px;
+ font-style: normal;
+ font-weight: 900;
+}
+
+.ratio-mode-btn--quality {
+ border-color: #dfc98c;
+ background: linear-gradient(135deg, #fffaf0 0%, #faf6ff 100%);
+ text-align: left;
+}
+
+.ratio-mode-btn--quality strong {
+ color: #7a4b0d;
+}
+
+.ratio-mode-btn.ratio-mode-btn--quality.active {
+ border-color: #b7791f;
+ background: linear-gradient(135deg, #fff4d8 0%, #f5edff 100%);
+ box-shadow: 0 0 0 2px rgba(183, 121, 31, 0.12);
+ color: #7a4b0d;
+}
+
+.ratio-mode-btn.ratio-mode-btn--quality.active em {
+ background: #b7791f;
+ color: #fff;
+}
+
+.ratio-mode-btn.ratio-mode-btn--quality.active span {
+ color: #7a4b0d;
+}
+
.ratio-mode-btn.active {
border-color: #ff6a00;
background: #fff7f0;
@@ -880,6 +931,16 @@
background: #f8fafc;
}
+.premium-coverage-card.is-quality-owner {
+ border-color: #e6d7b5;
+ background: linear-gradient(145deg, #fffdf8 0%, #faf8ff 100%);
+}
+
+.premium-coverage-card.is-premium-active {
+ border-color: #d4ad5e;
+ box-shadow: 0 8px 20px rgba(139, 92, 20, 0.08);
+}
+
.premium-zone-row,
.coverage-heading {
display: flex;
@@ -888,6 +949,34 @@
gap: 12px;
}
+.premium-zone-copy {
+ display: grid;
+ min-width: 0;
+ gap: 4px;
+}
+
+.premium-zone-title {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.premium-zone-title em {
+ padding: 2px 7px;
+ border-radius: 9px;
+ background: #fff3d6;
+ color: #8b5a0d;
+ font-size: 9px;
+ font-style: normal;
+ font-weight: 900;
+}
+
+.premium-zone-title em.is-locked {
+ background: #f1f3f6;
+ color: #7b8492;
+}
+
.premium-zone-row strong,
.coverage-heading strong {
display: block;
@@ -905,16 +994,46 @@
line-height: 1.45;
}
-.quality-owner-link {
- justify-self: start;
- padding: 0;
- border: none;
- background: transparent;
- color: #7c3aed;
- font-size: 12px;
+.premium-zone-action {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ gap: 7px;
+}
+
+.premium-zone-action > span {
+ margin: 0;
+ font-size: 10px;
font-weight: 800;
}
+.premium-zone-action.is-active > span {
+ color: #6d35c7;
+}
+
+.premium-zone-action :deep(.van-switch--on) {
+ background: #7c3aed;
+}
+
+.premium-zone-action :deep(.van-switch--disabled) {
+ opacity: 0.78;
+}
+
+.quality-owner-link {
+ display: inline-flex;
+ align-items: center;
+ justify-self: start;
+ gap: 4px;
+ min-height: 30px;
+ padding: 0 10px;
+ border: 1px solid #ddd0f2;
+ border-radius: 15px;
+ background: #faf5ff;
+ color: #6d35c7;
+ font-size: 12px;
+ font-weight: 900;
+}
+
.coverage-option-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -939,6 +1058,32 @@
font-weight: 800;
}
+.coverage-option > .coverage-option-top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 6px;
+ color: inherit;
+ font-size: inherit;
+ line-height: normal;
+}
+
+.coverage-option .coverage-rate {
+ flex-shrink: 0;
+ padding: 2px 6px;
+ border-radius: 8px;
+ background: #f0f2f5;
+ color: #505a68;
+ font-size: 10px;
+ font-style: normal;
+ font-weight: 900;
+}
+
+.coverage-option .coverage-rate.is-insurance {
+ background: #f3ebff;
+ color: #6d35c7;
+}
+
.coverage-option span {
color: #858c96;
font-size: 10px;
@@ -952,6 +1097,13 @@
font-weight: 800;
}
+.coverage-option .coverage-special-rate {
+ width: max-content;
+ padding: 2px 6px;
+ border-radius: 8px;
+ background: #f3ebff;
+}
+
.coverage-option.active {
border-color: #7c3aed;
background: #faf5ff;
diff --git a/frontend/src/features/seller/views/MobileSellerListingCreateView.vue b/frontend/src/features/seller/views/MobileSellerListingCreateView.vue
index 4ac370a..80e9495 100644
--- a/frontend/src/features/seller/views/MobileSellerListingCreateView.vue
+++ b/frontend/src/features/seller/views/MobileSellerListingCreateView.vue
@@ -27,7 +27,12 @@ const {
passwordAndDeviceConfirmed,
canPublishAfterAgreements,
isQualityOwner,
+ isQualityOwnerExclusiveRatioActive,
+ qualityOwnerSaleRatioSelected,
qualityOwnerStatus,
+ qualityOwnerRatioDiscount,
+ qualityOwnerSaleRatio,
+ minAcceleratedSaleRatio,
insuranceFeePercent,
showBanRecordRiskHint,
unlockDemulanEnabled,
@@ -91,6 +96,7 @@ const {
clampAcceleratedSaleRatioInput,
useReferenceSaleRatio,
useMaxAcceleratedSaleRatio,
+ useQualityOwnerSaleRatio,
handleSubmit,
isScreenshotRequired,
} = usePublishForm({
@@ -732,7 +738,11 @@ const screenshotGuides: Record = {
{{ saleRatioRangeText
}}{{ isDistribution ? '。' : ',比例越高出租速度越快,自行选择。' }}
-
+
+
@@ -824,21 +853,46 @@ const screenshotGuides: Record = {
卖家价格 = 纯币基础价 + 额外消耗品。
-
+
-
-
极品专区
+
+
+ 极品专区
+ 优质号主已认证
+ 优质号主专享
+
{{
isQualityOwner
- ? '优质号主专享曝光专区'
- : `保证金 ¥${formatMoney(
+ ? '发布后获得首页极品专区曝光'
+ : `缴纳 ¥${formatMoney(
(qualityOwnerStatus?.required_deposit_cent || 20000) / 100
- )}`
+ )} 保证金后可开通`
}}
-
+
+ {{
+ isQualityOwnerExclusiveRatioActive
+ ? '专享比例已关联专区'
+ : form.is_premium
+ ? '已开启极品专区'
+ : isQualityOwner
+ ? '开启极品专区'
+ : '开通后可发布'
+ }}
+
+
@@ -860,7 +915,10 @@ const screenshotGuides: Record = {
:class="{ active: form.compensation_plan === 'normal' }"
@click="form.compensation_plan = 'normal'"
>
- 普通赔付
+
+ 普通赔付
+ 0%
+
租金 + 押金规则赔付
- 无忧租号保险在普通赔付基础上额外保障账号市场价值;选择后按号主实际到手租金扣除服务费。
+ 无忧租号保险在普通赔付基础上增加账号市场价值保障,服务费按实际到手租金扣除。
diff --git a/frontend/src/features/seller/views/SellerListingCreateView.css b/frontend/src/features/seller/views/SellerListingCreateView.css
index 020eea8..af91cf0 100644
--- a/frontend/src/features/seller/views/SellerListingCreateView.css
+++ b/frontend/src/features/seller/views/SellerListingCreateView.css
@@ -705,7 +705,7 @@
.ratio-mode-grid {
display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
+ grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
@@ -727,6 +727,50 @@
font-size: 12px;
}
+.ratio-mode-btn em {
+ width: max-content;
+ padding: 3px 7px;
+ border-radius: 10px;
+ background: #fff3d6;
+ color: #9a6700;
+ font-size: 10px;
+ font-style: normal;
+ font-weight: 900;
+ letter-spacing: 0;
+}
+
+.ratio-mode-btn--quality {
+ position: relative;
+ overflow: hidden;
+ border-color: #dfc98c;
+ background: linear-gradient(135deg, #fffaf0 0%, #f8f3ff 100%);
+}
+
+.ratio-mode-btn--quality strong {
+ color: #7a4b0d;
+}
+
+.ratio-mode-btn--quality > * {
+ position: relative;
+ z-index: 1;
+}
+
+.ratio-mode-btn--quality.active {
+ border-color: #b7791f;
+ background: linear-gradient(135deg, #fff4d8 0%, #f5edff 100%);
+ color: #7a4b0d;
+ box-shadow: 0 0 0 2px rgba(183, 121, 31, 0.12);
+}
+
+.ratio-mode-btn--quality.active em {
+ background: #b7791f;
+ color: #fff;
+}
+
+.ratio-mode-btn--quality.active span {
+ color: #7a4b0d;
+}
+
.ratio-custom-card {
display: flex;
flex-direction: column;
@@ -1022,6 +1066,16 @@
background: #f8fafc;
}
+.premium-coverage-panel.is-quality-owner {
+ border-color: #e6d7b5;
+ background: linear-gradient(145deg, #fffdf8 0%, #faf8ff 100%);
+}
+
+.premium-coverage-panel.is-premium-active {
+ border-color: #d4ad5e;
+ box-shadow: 0 10px 28px rgba(139, 92, 20, 0.08);
+}
+
.premium-zone-row,
.coverage-heading {
display: flex;
@@ -1035,6 +1089,33 @@
min-width: 0;
}
+.premium-zone-copy {
+ display: grid;
+ gap: 5px;
+}
+
+.premium-zone-title {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.premium-zone-title em {
+ padding: 3px 8px;
+ border-radius: 10px;
+ background: #fff3d6;
+ color: #8b5a0d;
+ font-size: 10px;
+ font-style: normal;
+ font-weight: 900;
+}
+
+.premium-zone-title em.is-locked {
+ background: #f1f3f6;
+ color: #7b8492;
+}
+
.premium-zone-row strong,
.coverage-heading strong {
display: block;
@@ -1051,17 +1132,52 @@
line-height: 1.5;
}
-.quality-owner-link {
- justify-self: start;
- padding: 0;
- border: none;
- background: transparent;
- color: #7c3aed;
- font-size: 12px;
+.premium-zone-action {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ gap: 9px;
+}
+
+.premium-zone-action > span {
font-weight: 800;
+}
+
+.premium-zone-action.is-active > span {
+ color: #6d35c7;
+}
+
+.premium-zone-action :deep(.el-switch.is-checked .el-switch__core) {
+ border-color: #7c3aed;
+ background: #7c3aed;
+ box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.1);
+}
+
+.premium-zone-action :deep(.el-switch.is-disabled) {
+ opacity: 0.78;
+}
+
+.quality-owner-link {
+ display: inline-flex;
+ align-items: center;
+ justify-self: start;
+ gap: 4px;
+ min-height: 30px;
+ padding: 0 10px;
+ border: 1px solid #ddd0f2;
+ border-radius: 15px;
+ background: #faf5ff;
+ color: #6d35c7;
+ font-size: 12px;
+ font-weight: 900;
cursor: pointer;
}
+.quality-owner-link:hover {
+ border-color: #c7acdf;
+ background: #f4ebff;
+}
+
.coverage-option-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -1086,6 +1202,33 @@
font-weight: 900;
}
+.coverage-option > .coverage-option-top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ color: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ line-height: normal;
+}
+
+.coverage-option .coverage-rate {
+ flex-shrink: 0;
+ padding: 3px 8px;
+ border-radius: 10px;
+ background: #f0f2f5;
+ color: #505a68;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 900;
+}
+
+.coverage-option .coverage-rate.is-insurance {
+ background: #f3ebff;
+ color: #6d35c7;
+}
+
.coverage-option span {
color: #6b7280;
font-size: 12px;
@@ -1100,6 +1243,13 @@
font-weight: 800;
}
+.coverage-option .coverage-special-rate {
+ width: max-content;
+ padding: 3px 7px;
+ border-radius: 9px;
+ background: #f3ebff;
+}
+
.coverage-option.active {
border-color: #7c3aed;
background: #faf5ff;
diff --git a/frontend/src/features/seller/views/SellerListingCreateView.vue b/frontend/src/features/seller/views/SellerListingCreateView.vue
index 62efac3..a637cb2 100644
--- a/frontend/src/features/seller/views/SellerListingCreateView.vue
+++ b/frontend/src/features/seller/views/SellerListingCreateView.vue
@@ -1,5 +1,6 @@