From 95ded72b6609a7d47b855651150f4cf752dc24f6 Mon Sep 17 00:00:00 2001 From: yml Date: Sun, 24 May 2026 23:19:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=99=BA=E8=83=BD=E6=8A=BC=E9=87=91=E4=B8=8E33?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/modules/systemconfig/dto.go | 12 ++ .../modules/systemconfig/publish_options.go | 8 ++ .../internal/modules/systemconfig/service.go | 6 + frontend/src/api/listingOptions.ts | 51 +++++++ .../views/admin/AdminSystemConfigsView.vue | 43 ++++++ .../mobile/MobileSellerListingCreateView.css | 24 ++++ .../mobile/MobileSellerListingCreateView.vue | 95 +++++++++++++- .../views/seller/SellerListingCreateView.vue | 124 +++++++++++++++++- 8 files changed, 350 insertions(+), 13 deletions(-) diff --git a/backend/internal/modules/systemconfig/dto.go b/backend/internal/modules/systemconfig/dto.go index aeb24ac..39db6ef 100644 --- a/backend/internal/modules/systemconfig/dto.go +++ b/backend/internal/modules/systemconfig/dto.go @@ -51,6 +51,7 @@ type PublishOptionsDTO struct { BanEvidenceOptions []string `json:"ban_evidence_options"` FireLevelMin int `json:"fire_level_min"` PriceConfig PublishPriceConfig `json:"price_config"` + DepositRecommend PublishDepositRecommend `json:"deposit_recommend_config"` RatioConfig PublishRatioConfig `json:"ratio_config"` } @@ -80,6 +81,17 @@ type PublishPriceConfig struct { RatioDescription string `json:"ratio_description"` } +type PublishDepositRecommend struct { + BaseAmount float64 `json:"base_amount"` + SkinGroupRules []PublishDepositSkinGroupRule `json:"skin_group_rules"` +} + +type PublishDepositSkinGroupRule struct { + GroupKey string `json:"group_key"` + Label string `json:"label"` + AmountPerItem float64 `json:"amount_per_item"` +} + type PublishRatioConfig struct { InsuranceBaseRatios []PublishInsuranceBaseRatio `json:"insurance_base_ratios"` ConfigItems []PublishRatioConfigItem `json:"config_items"` diff --git a/backend/internal/modules/systemconfig/publish_options.go b/backend/internal/modules/systemconfig/publish_options.go index 19d3fc5..d3d1d6d 100644 --- a/backend/internal/modules/systemconfig/publish_options.go +++ b/backend/internal/modules/systemconfig/publish_options.go @@ -87,6 +87,14 @@ func DefaultPublishOptions() PublishOptionsDTO { PricePlaceholder: "填写币数、保险、体力和负重后自动计算;发布总价=纯币基础价+额外消耗品", RatioDescription: "1:xx 表示 1 元人民币(RMB)等价兑换 xx 万哈夫币", }, + DepositRecommend: PublishDepositRecommend{ + BaseAmount: 50, + SkinGroupRules: []PublishDepositSkinGroupRule{ + {GroupKey: "melee", Label: "刀皮", AmountPerItem: 5}, + {GroupKey: "operatorGold", Label: "干员金皮", AmountPerItem: 10}, + {GroupKey: "operatorRed", Label: "干员红皮", AmountPerItem: 30}, + }, + }, RatioConfig: PublishRatioConfig{ InsuranceBaseRatios: []PublishInsuranceBaseRatio{ {Insurance: "3*3", Ratio: 40}, diff --git a/backend/internal/modules/systemconfig/service.go b/backend/internal/modules/systemconfig/service.go index c2f303d..211c847 100644 --- a/backend/internal/modules/systemconfig/service.go +++ b/backend/internal/modules/systemconfig/service.go @@ -229,6 +229,12 @@ func normalizePublishOptions(options *PublishOptionsDTO) { if options.PriceConfig.RatioDescription == "" { options.PriceConfig.RatioDescription = defaults.PriceConfig.RatioDescription } + if options.DepositRecommend.BaseAmount <= 0 { + options.DepositRecommend.BaseAmount = defaults.DepositRecommend.BaseAmount + } + if len(options.DepositRecommend.SkinGroupRules) == 0 { + options.DepositRecommend.SkinGroupRules = defaults.DepositRecommend.SkinGroupRules + } if len(options.RatioConfig.InsuranceBaseRatios) == 0 { options.RatioConfig.InsuranceBaseRatios = defaults.RatioConfig.InsuranceBaseRatios } diff --git a/frontend/src/api/listingOptions.ts b/frontend/src/api/listingOptions.ts index 618c8fa..c6cc6da 100644 --- a/frontend/src/api/listingOptions.ts +++ b/frontend/src/api/listingOptions.ts @@ -34,6 +34,17 @@ export interface PublishPriceConfig { ratio_description: string } +export interface PublishDepositSkinGroupRule { + group_key: string + label: string + amount_per_item: number +} + +export interface PublishDepositRecommendConfig { + base_amount: number + skin_group_rules: PublishDepositSkinGroupRule[] +} + export interface PublishInsuranceBaseRatio { insurance: string ratio: number @@ -90,6 +101,7 @@ export interface ListingPublishOptions { ban_evidence_options: string[] fire_level_min: number price_config: PublishPriceConfig + deposit_recommend_config: PublishDepositRecommendConfig ratio_config: PublishRatioConfig } @@ -118,6 +130,14 @@ export const emptyListingPublishOptions: ListingPublishOptions = { price_placeholder: '', ratio_description: '', }, + deposit_recommend_config: { + base_amount: 50, + skin_group_rules: [ + { group_key: 'melee', label: '刀皮', amount_per_item: 5 }, + { group_key: 'operatorGold', label: '干员金皮', amount_per_item: 10 }, + { group_key: 'operatorRed', label: '干员红皮', amount_per_item: 30 }, + ], + }, ratio_config: { insurance_base_ratios: [], config_items: [], @@ -169,6 +189,7 @@ export function mergeListingPublishOptions(options?: Partial { + const row = isRecord(item) ? item : {} + return { + group_key: typeof row.group_key === 'string' ? row.group_key.trim() : '', + label: typeof row.label === 'string' ? row.label.trim() : '', + amount_per_item: readNumber(row.amount_per_item), + } + }) + .filter((item) => item.group_key && item.label && item.amount_per_item >= 0) +} + function normalizeRatioConfig(value?: unknown): PublishRatioConfig { const row = isRecord(value) ? value : {} return { diff --git a/frontend/src/views/admin/AdminSystemConfigsView.vue b/frontend/src/views/admin/AdminSystemConfigsView.vue index b6e8143..8931d64 100644 --- a/frontend/src/views/admin/AdminSystemConfigsView.vue +++ b/frontend/src/views/admin/AdminSystemConfigsView.vue @@ -325,6 +325,18 @@ function removeCoinCorrection(index: number) { publishOptionsDraft.value.ratio_config.coin_corrections.splice(index, 1) } +function addDepositSkinGroupRule() { + publishOptionsDraft.value.deposit_recommend_config.skin_group_rules.push({ + group_key: '', + label: '', + amount_per_item: 0, + }) +} + +function removeDepositSkinGroupRule(index: number) { + publishOptionsDraft.value.deposit_recommend_config.skin_group_rules.splice(index, 1) +} + function addSaleFixedMarkupRule() { salePriceConfigDraft.value.fixed_markup_rules.push({ min_m: 0, @@ -668,6 +680,37 @@ function readError(error: unknown, fallback: string) { +
+
+ 智能推荐押金 + 添加皮肤规则 +
+ + + + + + + + + + + + + + + + + +
+
比例计算配置 diff --git a/frontend/src/views/mobile/MobileSellerListingCreateView.css b/frontend/src/views/mobile/MobileSellerListingCreateView.css index 9ccffc5..446dc2c 100644 --- a/frontend/src/views/mobile/MobileSellerListingCreateView.css +++ b/frontend/src/views/mobile/MobileSellerListingCreateView.css @@ -257,6 +257,10 @@ margin-bottom: 8px; } +.quantity-row.disabled { + opacity: 0.62; +} + .quantity-card { display: grid; grid-template-columns: minmax(0, 1fr) 72px; @@ -314,6 +318,10 @@ border-color: #1477ff; } +.quantity-input:disabled { + cursor: not-allowed; +} + .mode-toggle { display: grid; grid-template-columns: 1fr; @@ -336,6 +344,10 @@ color: #e65f00; } +.mode-toggle button:disabled { + cursor: not-allowed; +} + .skin-groups { display: grid; gap: 10px; @@ -504,6 +516,18 @@ margin-bottom: 8px; } +.deposit-recommend-btn { + min-height: 32px; + margin: -2px 0 10px 88px; + padding: 0 12px; + border: 1px solid #ffba86; + border-radius: 16px; + background: #fff7f0; + color: #e65f00; + font-size: 12px; + font-weight: 800; +} + .ratio-reference { display: flex; align-items: center; diff --git a/frontend/src/views/mobile/MobileSellerListingCreateView.vue b/frontend/src/views/mobile/MobileSellerListingCreateView.vue index e594e4d..2bf7f6a 100644 --- a/frontend/src/views/mobile/MobileSellerListingCreateView.vue +++ b/frontend/src/views/mobile/MobileSellerListingCreateView.vue @@ -121,6 +121,7 @@ const regionOptions = computed(() => publishOptions.value.region_options); const banRecordOptions = computed(() => publishOptions.value.ban_record_options); const banEvidenceOptions = computed(() => publishOptions.value.ban_evidence_options); const priceConfig = computed(() => publishOptions.value.price_config); +const depositRecommendConfig = computed(() => publishOptions.value.deposit_recommend_config); const fireLevelMin = computed(() => publishOptions.value.fire_level_min || 38); const fireLevelPlaceholder = computed( () => `等级低于${fireLevelMin.value}级的号无法发布` @@ -161,6 +162,7 @@ const calculatedSellerPrice = computed(() => ); const calculatedPlatformPricing = computed(() => calculatePlatformPricing()); const calculatedFinalPrice = computed(() => calculatedPlatformPricing.value.buyerTotalPrice); +const recommendedDepositAmount = computed(() => calculateRecommendedDeposit()); const calculatedRatioText = computed(() => calculatedRatio.value > 0 ? `1:${formatNumber(calculatedRatio.value)}` : "" ); @@ -189,6 +191,18 @@ watch( { deep: true } ); +watch(recommendedDepositAmount, () => { + syncRecommendedDeposit(); +}, { immediate: true }); + +watch( + [() => form.season_insurance, quantityItems], + () => { + clearForbiddenQuantityItems(); + }, + { deep: true } +); + async function loadPublishOptions() { try { const [nextPublishOptions, nextSalePriceConfig] = await Promise.all([ @@ -397,6 +411,55 @@ function handleAcceleratedSaleRatioInput(value: string | number) { form.accelerated_sale_ratio = Number.isFinite(ratio) ? ratio : ""; } +function syncRecommendedDeposit() { + const recommended = recommendedDepositAmount.value; + if (recommended <= 0) return; + const current = Number(form.deposit_amount); + if (form.deposit_amount === "" || !Number.isFinite(current) || current < recommended) { + form.deposit_amount = recommended; + } +} + +function calculateRecommendedDeposit() { + const baseAmount = Number(depositRecommendConfig.value.base_amount || 0); + const skinAmount = depositRecommendConfig.value.skin_group_rules.reduce((sum, rule) => { + const group = skinGroups.value.find((item) => item.key === rule.group_key); + if (!group) return sum; + const selectedCount = group.options.filter((skin) => + selectedSkins.value.includes(skin) + ).length; + return sum + selectedCount * Number(rule.amount_per_item || 0); + }, 0); + return roundMoney(baseAmount + skinAmount); +} + +function useRecommendedDeposit() { + if (recommendedDepositAmount.value > 0) { + form.deposit_amount = recommendedDepositAmount.value; + } +} + +function isGridCardQuantityItem(item: { key: string; label: string }) { + return item.key === "gridCard9" || item.label.includes("9格体验卡"); +} + +function isQuantityItemDisabled(item: { key: string; label: string }) { + return form.season_insurance === "3*3" && isGridCardQuantityItem(item); +} + +function clearForbiddenQuantityItems() { + for (const item of quantityItems.value) { + if (!isQuantityItemDisabled(item)) continue; + quantityValues[item.key] = 0; + quantityModes[item.key] = "赠送"; + } +} + +function setQuantityMode(item: { key: string; label: string }, mode: ChargeMode) { + if (isQuantityItemDisabled(item)) return; + quantityModes[item.key] = mode; +} + function clampAcceleratedSaleRatioInput() { if (!hasAcceleratedSaleRatioInput() || calculatedDefaultSaleRatio.value <= 0) return; const ratio = Number(form.accelerated_sale_ratio); @@ -489,6 +552,9 @@ function validateForm() { if (!Number.isFinite(Number(form.deposit_amount))) { return "押金格式不正确"; } + if (recommendedDepositAmount.value > 0 && Number(form.deposit_amount) < recommendedDepositAmount.value) { + return `押金不能低于智能推荐 ¥${recommendedDepositAmount.value}`; + } if (calculatedConsumablePrice.value > 0 && Number(form.deposit_amount) <= calculatedConsumablePrice.value) { return `押金必须大于额外消耗品总价值 ¥${calculatedConsumablePrice.value}`; } @@ -503,6 +569,11 @@ function validateForm() { return `请上传${item.label}`; } } + for (const item of quantityItems.value) { + if (isQuantityItemDisabled(item) && Number(quantityValues[item.key] || 0) > 0) { + return "赛季保险选择 3*3 时不能填写 9格体验卡"; + } + } return ""; } @@ -542,8 +613,8 @@ function buildAssetSummary() { key: item.key, label: item.label, price: item.price, - quantity: Number(quantityValues[item.key] || 0), - mode: quantityModes[item.key] || "收费", + quantity: isQuantityItemDisabled(item) ? 0 : Number(quantityValues[item.key] || 0), + mode: isQuantityItemDisabled(item) ? "赠送" : quantityModes[item.key] || "收费", })), skin_groups: skinGroups.value.reduce>((groups, group) => { groups[group.key] = group.options.filter((skin) => @@ -569,6 +640,7 @@ function calculateConsumablePrice() { const total = quantityItems.value.reduce((sum, item) => { const quantity = Number(quantityValues[item.key] || 0); const mode = quantityModes[item.key] || "收费"; + if (isQuantityItemDisabled(item)) return sum; if (quantity <= 0 || mode !== "收费") return sum; return sum + quantity * readUnitPrice(item.price); }, 0); @@ -897,7 +969,12 @@ function readError(error: unknown, fallback: string) {

额外消耗品

-
+
*{{ item.label }} - {{ item.price }} + {{ isQuantityItemDisabled(item) ? "3*3 已包含,不能填写" : item.price }}
@@ -1100,6 +1180,9 @@ function readError(error: unknown, fallback: string) { 押金 +