内部加价分离, 不展示

This commit is contained in:
yml2213
2026-05-23 21:07:40 +08:00
parent 9f15609afd
commit 78257097f1
11 changed files with 347 additions and 117 deletions
@@ -5,10 +5,13 @@ import { showDialog, showToast } from "vant";
import { fetchFileBlobByURL, uploadFile } from "@/api/files";
import {
emptyListingSalePriceConfig,
emptyListingPublishOptions,
fetchListingPublishOptions,
fetchListingSalePriceConfig,
type ChargeMode,
type ListingPublishOptions,
type PublishSalePriceConfig,
type QuantityKey,
type ScreenshotKey,
} from "@/api/listingOptions";
@@ -21,6 +24,8 @@ type PublishForm = {
rank_level: string;
secret_kd: string;
fire_level: number | "";
daily_loss_m: number | "";
accelerated_sale_ratio: number | "";
season_insurance: string;
stamina_level: string;
load_level: string;
@@ -42,12 +47,14 @@ interface PublishDraft {
}
const draftKey = "hfb.mobile.publish.draft";
const dailyLossOptions = [10, 20, 30, 40, 50];
const router = useRouter();
const route = useRoute();
const loading = ref(false);
const uploading = ref(false);
const suppressDraftSave = ref(false);
const publishOptions = ref<ListingPublishOptions>(emptyListingPublishOptions);
const salePriceConfig = ref<PublishSalePriceConfig>(emptyListingSalePriceConfig);
function isNavActive(path: string) {
if (path === "/m") return route.path === "/m";
@@ -62,6 +69,8 @@ function defaultForm(): PublishForm {
rank_level: "",
secret_kd: "",
fire_level: "",
daily_loss_m: 10,
accelerated_sale_ratio: "",
season_insurance: "",
stamina_level: "",
load_level: "",
@@ -117,7 +126,6 @@ const fireLevelPlaceholder = computed(
() => `等级低于${fireLevelMin.value}级的号无法发布`
);
const ratioConfig = computed(() => publishOptions.value.ratio_config);
const salePriceConfig = computed(() => publishOptions.value.sale_price_config);
const skinGroups = computed(() => publishOptions.value.skin_groups);
const quantityItems = computed(() => publishOptions.value.quantity_items);
const screenshotSlots = computed(() => publishOptions.value.screenshot_slots);
@@ -128,11 +136,26 @@ const screenshotUrls = computed(() =>
);
const coinMAmount = computed(() => Number(form.haf_coin_amount || 0));
const coinWanAmount = computed(() => coinMAmount.value * 100);
const dailyLossMAmount = computed(() => Number(form.daily_loss_m || 10));
const dailyLossRatioAdjustment = computed(() =>
Math.min(Math.max(Math.floor((dailyLossMAmount.value - 10) / 10), 0), 4)
);
const calculatedRecycleRatio = computed(() => calculatePublishRatio());
const calculatedSalePricing = computed(() => calculateSalePricing());
const calculatedRatio = computed(() => calculatedSalePricing.value.saleRatio);
const calculatedCoinBasePrice = computed(() => calculatedSalePricing.value.coinBasePrice);
const calculatedSaleRuleText = computed(() => calculatedSalePricing.value.ruleText);
const calculatedDefaultSaleRatio = computed(() => calculatedSalePricing.value.saleRatio);
const maxAcceleratedSaleRatio = computed(() =>
calculatedDefaultSaleRatio.value > 0 ? roundRatio(calculatedDefaultSaleRatio.value + 10) : 0
);
const calculatedRatio = computed(() => readFinalSaleRatio());
const calculatedCoinBasePrice = computed(() => {
if (calculatedRatio.value <= 0) return 0;
if (!hasAcceleratedSaleRatioInput()) return calculatedSalePricing.value.coinBasePrice;
return roundMoney(coinWanAmount.value / calculatedRatio.value);
});
const acceleratedSaleRatioPlaceholder = computed(() => {
if (calculatedDefaultSaleRatio.value <= 0) return "填写资料后自动生成可设置范围";
return `默认 ${formatNumber(calculatedDefaultSaleRatio.value)},最高 ${formatNumber(maxAcceleratedSaleRatio.value)}`;
});
const calculatedConsumablePrice = computed(() => calculateConsumablePrice());
const calculatedFinalPrice = computed(() =>
calculatedRatio.value > 0
@@ -162,9 +185,15 @@ watch(
async function loadPublishOptions() {
try {
publishOptions.value = await fetchListingPublishOptions();
const [nextPublishOptions, nextSalePriceConfig] = await Promise.all([
fetchListingPublishOptions(),
fetchListingSalePriceConfig(),
]);
publishOptions.value = nextPublishOptions;
salePriceConfig.value = nextSalePriceConfig;
} catch {
publishOptions.value = emptyListingPublishOptions;
salePriceConfig.value = emptyListingSalePriceConfig;
}
}
@@ -353,6 +382,27 @@ function handleFireLevelInput(value: string | number) {
form.fire_level = Math.trunc(level);
}
function handleAcceleratedSaleRatioInput(value: string | number) {
if (value === "") {
form.accelerated_sale_ratio = "";
return;
}
const ratio = Number(value);
form.accelerated_sale_ratio = Number.isFinite(ratio) ? ratio : "";
}
function clampAcceleratedSaleRatioInput() {
if (!hasAcceleratedSaleRatioInput() || calculatedDefaultSaleRatio.value <= 0) return;
const ratio = Number(form.accelerated_sale_ratio);
if (!Number.isFinite(ratio)) {
form.accelerated_sale_ratio = "";
return;
}
const minRatio = calculatedDefaultSaleRatio.value;
const maxRatio = maxAcceleratedSaleRatio.value;
form.accelerated_sale_ratio = roundRatio(Math.min(Math.max(ratio, minRatio), maxRatio));
}
async function handleSubmit() {
const error = validateForm();
if (error) {
@@ -409,6 +459,17 @@ function validateForm() {
if (!form.season_insurance) return "请选择赛季保险";
if (!form.stamina_level) return "请选择体力等级";
if (!form.load_level) return "请选择负重等级";
if (!dailyLossOptions.includes(dailyLossMAmount.value)) return "请选择每日损耗";
if (hasAcceleratedSaleRatioInput()) {
const ratio = Number(form.accelerated_sale_ratio);
if (!Number.isFinite(ratio) || ratio <= 0) return "加速出售比例格式不正确";
if (calculatedDefaultSaleRatio.value > 0 && ratio < calculatedDefaultSaleRatio.value) {
return `加速出售比例不能低于默认比例 1:${formatNumber(calculatedDefaultSaleRatio.value)}`;
}
if (maxAcceleratedSaleRatio.value > 0 && ratio > maxAcceleratedSaleRatio.value) {
return `加速出售比例不能超过 1:${formatNumber(maxAcceleratedSaleRatio.value)}`;
}
}
if (banRecordOptions.value.length && !form.ban_record) return "请选择封禁记录";
if (form.deposit_amount === "" || Number(form.deposit_amount) < 0) {
return "请填写押金";
@@ -443,16 +504,20 @@ function buildAssetSummary() {
face_owner: form.face_owner,
secret_kd: form.secret_kd,
fire_level: Number(form.fire_level),
daily_loss_m: dailyLossMAmount.value,
publish_ratio: calculatedRatio.value,
price_breakdown: {
recycle_coin_base_price: calculatedSalePricing.value.recycleCoinBasePrice,
default_coin_base_price: calculatedSalePricing.value.coinBasePrice,
coin_base_price: calculatedCoinBasePrice.value,
consumable_price: calculatedConsumablePrice.value,
final_price: calculatedFinalPrice.value,
recycle_ratio: calculatedRecycleRatio.value,
daily_loss_ratio_adjustment: dailyLossRatioAdjustment.value,
default_sale_ratio: calculatedDefaultSaleRatio.value,
sale_ratio: calculatedRatio.value,
accelerated_sale_ratio: hasAcceleratedSaleRatioInput() ? Number(form.accelerated_sale_ratio) : calculatedDefaultSaleRatio.value,
sale_rule_type: calculatedSalePricing.value.ruleType,
sale_rule_text: calculatedSalePricing.value.ruleText,
},
season_insurance: form.season_insurance,
stamina_level: form.stamina_level,
@@ -518,7 +583,25 @@ function calculatePublishRatio() {
const baseRatio = getInsuranceBaseRatio(form.season_insurance);
if (baseRatio <= 0) return 0;
return baseRatio + calculateConfigPenalty() + getCoinCorrection(coinMAmount.value);
return (
baseRatio +
calculateConfigPenalty() +
getCoinCorrection(coinMAmount.value) +
dailyLossRatioAdjustment.value
);
}
function readFinalSaleRatio() {
const defaultRatio = calculatedDefaultSaleRatio.value;
if (defaultRatio <= 0) return 0;
if (!hasAcceleratedSaleRatioInput()) return defaultRatio;
const ratio = Number(form.accelerated_sale_ratio);
if (!Number.isFinite(ratio) || ratio <= 0) return defaultRatio;
return roundRatio(Math.min(Math.max(ratio, defaultRatio), maxAcceleratedSaleRatio.value));
}
function hasAcceleratedSaleRatioInput() {
return form.accelerated_sale_ratio !== "" && form.accelerated_sale_ratio !== null;
}
function calculateSalePricing() {
@@ -535,7 +618,6 @@ function calculateSalePricing() {
coinBasePrice,
saleRatio: calculateEffectiveRatio(coinBasePrice),
ruleType: "fixed_markup",
ruleText: `${formatSaleRange(fixedRule)} 加价 ${formatNumber(fixedRule.markup_amount)}`,
};
}
@@ -550,9 +632,6 @@ function calculateSalePricing() {
coinBasePrice: roundMoney(coinWanAmount.value / saleRatio),
saleRatio,
ruleType: ratioRule ? "ratio_subtract" : "none",
ruleText: ratioRule
? `${formatSaleRange(ratioRule)} 出售比例=回收比例-${formatNumber(ratioSubtract)}`
: "未匹配出售加价规则,按回收比例计算",
};
}
@@ -562,7 +641,6 @@ function emptySalePricing(recycleCoinBasePrice = 0) {
coinBasePrice: 0,
saleRatio: 0,
ruleType: "none",
ruleText: "",
};
}
@@ -585,13 +663,7 @@ function isCoinInSaleRange(item: { min_m: number; max_m: number }) {
function calculateEffectiveRatio(price: number) {
if (price <= 0) return 0;
return Math.round((coinWanAmount.value / price) * 10) / 10;
}
function formatSaleRange(item: { min_m: number; max_m: number }) {
const minM = formatNumber(Number(item.min_m || 0));
const maxM = Number(item.max_m || 0);
return maxM > 0 ? `${minM}M-${formatNumber(maxM)}M` : `${minM}M以上`;
return roundRatio(coinWanAmount.value / price);
}
function getInsuranceBaseRatio(insurance: string) {
@@ -641,6 +713,10 @@ function formatNumber(value: number) {
return Number.isInteger(value) ? `${value}` : `${Math.round(value * 10) / 10}`;
}
function roundRatio(value: number) {
return Math.round(value * 10) / 10;
}
function readError(error: unknown, fallback: string) {
if (typeof error === "object" && error && "response" in error) {
const response = (error as { response?: { data?: { message?: string } } })
@@ -997,6 +1073,37 @@ function readError(error: unknown, fallback: string) {
<span class="hint-label" :data-hint="priceConfig.deposit_placeholder" tabindex="0">押金</span>
</template>
</van-field>
<van-field label="每日损耗" required class="publish-field">
<template #input>
<div class="radio-group">
<button
v-for="loss in dailyLossOptions"
:key="loss"
type="button"
class="radio-btn"
:class="{ active: dailyLossMAmount === loss }"
@click="form.daily_loss_m = loss"
>
{{ loss }}M/
</button>
</div>
</template>
</van-field>
<p class="field-hint">
比例调整在默认10M/每增加10M出租比例相应+1请根据实际需求合理设置感谢您的配合
</p>
<van-field
:model-value="form.accelerated_sale_ratio"
label="加速出售比例"
type="number"
:placeholder="acceleratedSaleRatioPlaceholder"
class="publish-field"
@update:model-value="handleAcceleratedSaleRatioInput"
@blur="clampAcceleratedSaleRatioInput"
/>
<p class="field-hint">
请根据实际需求合理设置感谢您的配合
</p>
<div class="result-grid">
<van-field
:model-value="calculatedRatioText"
@@ -1009,12 +1116,6 @@ function readError(error: unknown, fallback: string) {
<span class="hint-label" :data-hint="priceConfig.ratio_description" tabindex="0">出售比例</span>
</template>
</van-field>
<van-field
:model-value="calculatedSaleRuleText"
label="出售规则"
readonly
class="publish-field result-field muted-result-field"
/>
<van-field
:model-value="calculatedCoinBasePrice ? `¥${calculatedCoinBasePrice}` : ''"
label="纯币基础价"
@@ -1043,7 +1144,7 @@ function readError(error: unknown, fallback: string) {
/>
</div>
<p class="price-breakdown-hint">
纯币基础价先按回收比例计算再套出售加价规则发布总价 = 纯币基础价 + 额外消耗品
发布总价 = 纯币基础价 + 额外消耗品加速比例仅影响纯币基础价
</p>
<van-field