修复定价错误
This commit is contained in:
@@ -493,6 +493,9 @@ func publicListings(items []ListingDTO) []ListingDTO {
|
|||||||
|
|
||||||
func applyPublicListingURLs(item *ListingDTO) {
|
func applyPublicListingURLs(item *ListingDTO) {
|
||||||
item.ScreenshotURLS = publicScreenshotURLs(item.ID, item.ScreenshotURLS, item.Status, item.ReviewStatus)
|
item.ScreenshotURLS = publicScreenshotURLs(item.ID, item.ScreenshotURLS, item.Status, item.ReviewStatus)
|
||||||
|
if item.AssetSummary != nil {
|
||||||
|
delete(item.AssetSummary, "price_breakdown")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (row listingRow) toDTO() ListingDTO {
|
func (row listingRow) toDTO() ListingDTO {
|
||||||
|
|||||||
@@ -126,8 +126,21 @@ export const emptyListingPublishOptions: ListingPublishOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const emptyListingSalePriceConfig: PublishSalePriceConfig = {
|
export const emptyListingSalePriceConfig: PublishSalePriceConfig = {
|
||||||
fixed_markup_rules: [],
|
fixed_markup_rules: [
|
||||||
ratio_adjustment_rules: [],
|
{ min_m: 10, max_m: 30, markup_amount: 28 },
|
||||||
|
{ min_m: 30, max_m: 50, markup_amount: 31 },
|
||||||
|
{ min_m: 50, max_m: 70, markup_amount: 34 },
|
||||||
|
{ min_m: 70, max_m: 90, markup_amount: 38 },
|
||||||
|
],
|
||||||
|
ratio_adjustment_rules: [
|
||||||
|
{ min_m: 90, max_m: 150, ratio_subtract: 5 },
|
||||||
|
{ min_m: 150, max_m: 230, ratio_subtract: 4 },
|
||||||
|
{ min_m: 230, max_m: 310, ratio_subtract: 3.5 },
|
||||||
|
{ min_m: 310, max_m: 390, ratio_subtract: 3 },
|
||||||
|
{ min_m: 390, max_m: 470, ratio_subtract: 0 },
|
||||||
|
{ min_m: 470, max_m: 550, ratio_subtract: 0 },
|
||||||
|
{ min_m: 550, max_m: 0, ratio_subtract: 0 },
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchListingPublishOptions() {
|
export async function fetchListingPublishOptions() {
|
||||||
@@ -240,7 +253,7 @@ function normalizeRatioConfig(value?: unknown): PublishRatioConfig {
|
|||||||
|
|
||||||
function normalizeSalePriceConfig(value?: unknown): PublishSalePriceConfig {
|
function normalizeSalePriceConfig(value?: unknown): PublishSalePriceConfig {
|
||||||
const row = isRecord(value) ? value : {}
|
const row = isRecord(value) ? value : {}
|
||||||
return {
|
const config = {
|
||||||
fixed_markup_rules: normalizeSaleFixedMarkupRules(
|
fixed_markup_rules: normalizeSaleFixedMarkupRules(
|
||||||
Array.isArray(row.fixed_markup_rules) ? row.fixed_markup_rules : [],
|
Array.isArray(row.fixed_markup_rules) ? row.fixed_markup_rules : [],
|
||||||
),
|
),
|
||||||
@@ -248,6 +261,10 @@ function normalizeSalePriceConfig(value?: unknown): PublishSalePriceConfig {
|
|||||||
Array.isArray(row.ratio_adjustment_rules) ? row.ratio_adjustment_rules : [],
|
Array.isArray(row.ratio_adjustment_rules) ? row.ratio_adjustment_rules : [],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
if (config.fixed_markup_rules.length === 0 && config.ratio_adjustment_rules.length === 0) {
|
||||||
|
return JSON.parse(JSON.stringify(emptyListingSalePriceConfig)) as PublishSalePriceConfig
|
||||||
|
}
|
||||||
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeInsuranceBaseRatios(values: unknown[]): PublishInsuranceBaseRatio[] {
|
function normalizeInsuranceBaseRatios(values: unknown[]): PublishInsuranceBaseRatio[] {
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Sale Pricing</p>
|
<p class="eyebrow">Sale Pricing</p>
|
||||||
<h2>内部出售定价规则</h2>
|
<h2>内部出售定价规则</h2>
|
||||||
<span>管理出售加价和比例扣减规则,仅用于平台内部定价计算,不在发布页展示。</span>
|
<span>管理出售专用比例计算规则,仅用于平台内部定价计算,不在发布页展示。</span>
|
||||||
</div>
|
</div>
|
||||||
<el-button type="primary" @click="openEdit(salePriceConfig)">编辑出售规则</el-button>
|
<el-button type="primary" @click="openEdit(salePriceConfig)">编辑出售规则</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -474,7 +474,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="publish-stat">
|
<div class="publish-stat">
|
||||||
<strong>{{ salePriceStats.ratioCount }}</strong>
|
<strong>{{ salePriceStats.ratioCount }}</strong>
|
||||||
<span>比例扣减</span>
|
<span>比例修正</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="publish-stat">
|
<div class="publish-stat">
|
||||||
<strong>内部</strong>
|
<strong>内部</strong>
|
||||||
@@ -843,8 +843,8 @@ function readError(error: unknown, fallback: string) {
|
|||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<div class="editor-block-title subtle-title">
|
<div class="editor-block-title subtle-title">
|
||||||
<span>90M 以上比例调整:出售比例 = 回收比例 - 比例扣减;最大 M 为 0 表示无上限</span>
|
<span>90M 以上比例修正:出售比例 = 回收比例 - 比例修正;最大 M 为 0 表示无上限</span>
|
||||||
<el-button size="small" @click="addSaleRatioAdjustmentRule">添加比例调整</el-button>
|
<el-button size="small" @click="addSaleRatioAdjustmentRule">添加比例修正</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="salePriceConfigDraft.ratio_adjustment_rules" size="small" border>
|
<el-table :data="salePriceConfigDraft.ratio_adjustment_rules" size="small" border>
|
||||||
<el-table-column label="最小 M" min-width="120">
|
<el-table-column label="最小 M" min-width="120">
|
||||||
@@ -853,7 +853,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
<el-table-column label="最大 M" min-width="120">
|
<el-table-column label="最大 M" min-width="120">
|
||||||
<template #default="{ row }"><el-input-number v-model="row.max_m" :min="0" :step="10" /></template>
|
<template #default="{ row }"><el-input-number v-model="row.max_m" :min="0" :step="10" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="比例扣减" min-width="130">
|
<el-table-column label="比例修正" min-width="130">
|
||||||
<template #default="{ row }"><el-input-number v-model="row.ratio_subtract" :min="0" :step="0.5" /></template>
|
<template #default="{ row }"><el-input-number v-model="row.ratio_subtract" :min="0" :step="0.5" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="90">
|
<el-table-column label="操作" width="90">
|
||||||
|
|||||||
@@ -498,6 +498,104 @@
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ratio-panel {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-reference {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-reference span {
|
||||||
|
color: #30343a;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-reference strong {
|
||||||
|
color: #ff6a00;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-range {
|
||||||
|
margin: 0;
|
||||||
|
color: #858c96;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-mode-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-mode-btn {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 74px;
|
||||||
|
padding: 12px 8px;
|
||||||
|
border: 1px solid #d7dce3;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
color: #30343a;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-mode-btn strong,
|
||||||
|
.ratio-mode-btn span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-mode-btn strong {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-mode-btn span {
|
||||||
|
color: #858c96;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-mode-btn.active {
|
||||||
|
border-color: #ff6a00;
|
||||||
|
background: #fff7f0;
|
||||||
|
color: #ff6a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-mode-btn.active span {
|
||||||
|
color: #ff6a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-mode-btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.58;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-input-field {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-input-hint {
|
||||||
|
margin: -2px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
.total-price-field :deep(.van-field__control) {
|
.total-price-field :deep(.van-field__control) {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ const fireLevelMin = computed(() => publishOptions.value.fire_level_min || 38);
|
|||||||
const fireLevelPlaceholder = computed(
|
const fireLevelPlaceholder = computed(
|
||||||
() => `等级低于${fireLevelMin.value}级的号无法发布`
|
() => `等级低于${fireLevelMin.value}级的号无法发布`
|
||||||
);
|
);
|
||||||
const ratioConfig = computed(() => publishOptions.value.ratio_config);
|
|
||||||
const skinGroups = computed(() => publishOptions.value.skin_groups);
|
const skinGroups = computed(() => publishOptions.value.skin_groups);
|
||||||
const quantityItems = computed(() => publishOptions.value.quantity_items);
|
const quantityItems = computed(() => publishOptions.value.quantity_items);
|
||||||
const screenshotSlots = computed(() => publishOptions.value.screenshot_slots);
|
const screenshotSlots = computed(() => publishOptions.value.screenshot_slots);
|
||||||
@@ -140,16 +139,14 @@ const dailyLossMAmount = computed(() => Number(form.daily_loss_m || 10));
|
|||||||
const dailyLossRatioAdjustment = computed(() =>
|
const dailyLossRatioAdjustment = computed(() =>
|
||||||
Math.min(Math.max(Math.floor((dailyLossMAmount.value - 10) / 10), 0), 4)
|
Math.min(Math.max(Math.floor((dailyLossMAmount.value - 10) / 10), 0), 4)
|
||||||
);
|
);
|
||||||
const calculatedRecycleRatio = computed(() => calculatePublishRatio());
|
const calculatedSellerReferenceRatio = computed(() => calculateSellerReferenceRatio());
|
||||||
const calculatedSalePricing = computed(() => calculateSalePricing());
|
const calculatedDefaultSaleRatio = computed(() => calculatedSellerReferenceRatio.value);
|
||||||
const calculatedDefaultSaleRatio = computed(() => calculatedSalePricing.value.saleRatio);
|
|
||||||
const maxAcceleratedSaleRatio = computed(() =>
|
const maxAcceleratedSaleRatio = computed(() =>
|
||||||
calculatedDefaultSaleRatio.value > 0 ? roundRatio(calculatedDefaultSaleRatio.value + 10) : 0
|
calculatedDefaultSaleRatio.value > 0 ? roundRatio(calculatedDefaultSaleRatio.value + 10) : 0
|
||||||
);
|
);
|
||||||
const calculatedRatio = computed(() => readFinalSaleRatio());
|
const calculatedRatio = computed(() => readFinalSaleRatio());
|
||||||
const calculatedCoinBasePrice = computed(() => {
|
const calculatedCoinBasePrice = computed(() => {
|
||||||
if (calculatedRatio.value <= 0) return 0;
|
if (calculatedRatio.value <= 0) return 0;
|
||||||
if (!hasAcceleratedSaleRatioInput()) return calculatedSalePricing.value.coinBasePrice;
|
|
||||||
return roundMoney(coinWanAmount.value / calculatedRatio.value);
|
return roundMoney(coinWanAmount.value / calculatedRatio.value);
|
||||||
});
|
});
|
||||||
const acceleratedSaleRatioPlaceholder = computed(() => {
|
const acceleratedSaleRatioPlaceholder = computed(() => {
|
||||||
@@ -157,14 +154,23 @@ const acceleratedSaleRatioPlaceholder = computed(() => {
|
|||||||
return `默认 ${formatNumber(calculatedDefaultSaleRatio.value)},最高 ${formatNumber(maxAcceleratedSaleRatio.value)}`;
|
return `默认 ${formatNumber(calculatedDefaultSaleRatio.value)},最高 ${formatNumber(maxAcceleratedSaleRatio.value)}`;
|
||||||
});
|
});
|
||||||
const calculatedConsumablePrice = computed(() => calculateConsumablePrice());
|
const calculatedConsumablePrice = computed(() => calculateConsumablePrice());
|
||||||
const calculatedFinalPrice = computed(() =>
|
const calculatedSellerPrice = computed(() =>
|
||||||
calculatedRatio.value > 0
|
calculatedRatio.value > 0
|
||||||
? roundMoney(calculatedCoinBasePrice.value + calculatedConsumablePrice.value)
|
? roundMoney(calculatedCoinBasePrice.value + calculatedConsumablePrice.value)
|
||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
|
const calculatedPlatformPricing = computed(() => calculatePlatformPricing());
|
||||||
|
const calculatedFinalPrice = computed(() => calculatedPlatformPricing.value.buyerTotalPrice);
|
||||||
const calculatedRatioText = computed(() =>
|
const calculatedRatioText = computed(() =>
|
||||||
calculatedRatio.value > 0 ? `1:${formatNumber(calculatedRatio.value)}` : ""
|
calculatedRatio.value > 0 ? `1:${formatNumber(calculatedRatio.value)}` : ""
|
||||||
);
|
);
|
||||||
|
const calculatedDefaultSaleRatioText = computed(() =>
|
||||||
|
calculatedDefaultSaleRatio.value > 0 ? `1:${formatNumber(calculatedDefaultSaleRatio.value)}` : "--"
|
||||||
|
);
|
||||||
|
const saleRatioRangeText = computed(() => {
|
||||||
|
if (calculatedDefaultSaleRatio.value <= 0) return "完成基础信息后自动计算参考比例";
|
||||||
|
return `可设置 1:${formatNumber(calculatedDefaultSaleRatio.value)} ~ 1:${formatNumber(maxAcceleratedSaleRatio.value)}`;
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
restoreDraft();
|
restoreDraft();
|
||||||
@@ -403,6 +409,15 @@ function clampAcceleratedSaleRatioInput() {
|
|||||||
form.accelerated_sale_ratio = roundRatio(Math.min(Math.max(ratio, minRatio), maxRatio));
|
form.accelerated_sale_ratio = roundRatio(Math.min(Math.max(ratio, minRatio), maxRatio));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useReferenceSaleRatio() {
|
||||||
|
form.accelerated_sale_ratio = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function useMaxAcceleratedSaleRatio() {
|
||||||
|
if (maxAcceleratedSaleRatio.value <= 0) return;
|
||||||
|
form.accelerated_sale_ratio = maxAcceleratedSaleRatio.value;
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
const error = validateForm();
|
const error = validateForm();
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -505,19 +520,20 @@ function buildAssetSummary() {
|
|||||||
secret_kd: form.secret_kd,
|
secret_kd: form.secret_kd,
|
||||||
fire_level: Number(form.fire_level),
|
fire_level: Number(form.fire_level),
|
||||||
daily_loss_m: dailyLossMAmount.value,
|
daily_loss_m: dailyLossMAmount.value,
|
||||||
publish_ratio: calculatedRatio.value,
|
publish_ratio: calculatedPlatformPricing.value.buyerRatio,
|
||||||
price_breakdown: {
|
price_breakdown: {
|
||||||
recycle_coin_base_price: calculatedSalePricing.value.recycleCoinBasePrice,
|
seller_reference_ratio: calculatedSellerReferenceRatio.value,
|
||||||
default_coin_base_price: calculatedSalePricing.value.coinBasePrice,
|
seller_ratio: calculatedRatio.value,
|
||||||
coin_base_price: calculatedCoinBasePrice.value,
|
seller_coin_base_price: calculatedCoinBasePrice.value,
|
||||||
|
seller_total_price: calculatedSellerPrice.value,
|
||||||
consumable_price: calculatedConsumablePrice.value,
|
consumable_price: calculatedConsumablePrice.value,
|
||||||
final_price: calculatedFinalPrice.value,
|
|
||||||
recycle_ratio: calculatedRecycleRatio.value,
|
|
||||||
daily_loss_ratio_adjustment: dailyLossRatioAdjustment.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,
|
accelerated_sale_ratio: hasAcceleratedSaleRatioInput() ? Number(form.accelerated_sale_ratio) : calculatedDefaultSaleRatio.value,
|
||||||
sale_rule_type: calculatedSalePricing.value.ruleType,
|
buyer_coin_base_price: calculatedPlatformPricing.value.buyerCoinBasePrice,
|
||||||
|
buyer_total_price: calculatedFinalPrice.value,
|
||||||
|
buyer_ratio: calculatedPlatformPricing.value.buyerRatio,
|
||||||
|
platform_markup_amount: calculatedPlatformPricing.value.platformMarkupAmount,
|
||||||
|
platform_rule_type: calculatedPlatformPricing.value.ruleType,
|
||||||
},
|
},
|
||||||
season_insurance: form.season_insurance,
|
season_insurance: form.season_insurance,
|
||||||
stamina_level: form.stamina_level,
|
stamina_level: form.stamina_level,
|
||||||
@@ -571,7 +587,7 @@ function readUnitPrice(priceText: string) {
|
|||||||
return singleMatch ? Number(singleMatch[1]) : 0;
|
return singleMatch ? Number(singleMatch[1]) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculatePublishRatio() {
|
function calculateSellerReferenceRatio() {
|
||||||
if (
|
if (
|
||||||
coinMAmount.value <= 0 ||
|
coinMAmount.value <= 0 ||
|
||||||
!form.season_insurance ||
|
!form.season_insurance ||
|
||||||
@@ -580,13 +596,13 @@ function calculatePublishRatio() {
|
|||||||
) {
|
) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const baseRatio = getInsuranceBaseRatio(form.season_insurance);
|
const baseRatio = getInsuranceBaseRatio(publishOptions.value.ratio_config, form.season_insurance);
|
||||||
if (baseRatio <= 0) return 0;
|
if (baseRatio <= 0) return 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
baseRatio +
|
baseRatio +
|
||||||
calculateConfigPenalty() +
|
calculateConfigPenalty(publishOptions.value.ratio_config) +
|
||||||
getCoinCorrection(coinMAmount.value) +
|
getCoinCorrection(publishOptions.value.ratio_config, coinMAmount.value) +
|
||||||
dailyLossRatioAdjustment.value
|
dailyLossRatioAdjustment.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -604,42 +620,44 @@ function hasAcceleratedSaleRatioInput() {
|
|||||||
return form.accelerated_sale_ratio !== "" && form.accelerated_sale_ratio !== null;
|
return form.accelerated_sale_ratio !== "" && form.accelerated_sale_ratio !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateSalePricing() {
|
function calculatePlatformPricing() {
|
||||||
const recycleRatio = calculatedRecycleRatio.value;
|
if (calculatedRatio.value <= 0 || calculatedCoinBasePrice.value <= 0) {
|
||||||
if (recycleRatio <= 0 || coinWanAmount.value <= 0) {
|
return emptyPlatformPricing();
|
||||||
return emptySalePricing();
|
|
||||||
}
|
}
|
||||||
const recycleCoinBasePrice = roundMoney(coinWanAmount.value / recycleRatio);
|
|
||||||
const fixedRule = findSaleFixedMarkupRule();
|
const fixedRule = findSaleFixedMarkupRule();
|
||||||
if (fixedRule) {
|
if (fixedRule) {
|
||||||
const coinBasePrice = roundMoney(recycleCoinBasePrice + Number(fixedRule.markup_amount || 0));
|
return buildPlatformPricing(
|
||||||
return {
|
roundMoney(calculatedCoinBasePrice.value + Number(fixedRule.markup_amount || 0)),
|
||||||
recycleCoinBasePrice,
|
"fixed_markup",
|
||||||
coinBasePrice,
|
);
|
||||||
saleRatio: calculateEffectiveRatio(coinBasePrice),
|
|
||||||
ruleType: "fixed_markup",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratioRule = findSaleRatioAdjustmentRule();
|
const ratioRule = findSaleRatioAdjustmentRule();
|
||||||
const ratioSubtract = ratioRule ? Number(ratioRule.ratio_subtract || 0) : 0;
|
const ratioSubtract = ratioRule ? Number(ratioRule.ratio_subtract || 0) : 0;
|
||||||
const saleRatio = recycleRatio - ratioSubtract;
|
const buyerRatio = calculatedRatio.value - ratioSubtract;
|
||||||
if (saleRatio <= 0) {
|
if (buyerRatio > 0 && ratioRule) {
|
||||||
return emptySalePricing(recycleCoinBasePrice);
|
return buildPlatformPricing(roundMoney(coinWanAmount.value / buyerRatio), "ratio_subtract");
|
||||||
}
|
}
|
||||||
|
return buildPlatformPricing(calculatedCoinBasePrice.value, "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPlatformPricing(buyerCoinBasePrice: number, ruleType: string) {
|
||||||
|
const buyerTotalPrice = roundMoney(buyerCoinBasePrice + calculatedConsumablePrice.value);
|
||||||
return {
|
return {
|
||||||
recycleCoinBasePrice,
|
buyerCoinBasePrice,
|
||||||
coinBasePrice: roundMoney(coinWanAmount.value / saleRatio),
|
buyerTotalPrice,
|
||||||
saleRatio,
|
buyerRatio: calculateEffectiveRatio(buyerCoinBasePrice),
|
||||||
ruleType: ratioRule ? "ratio_subtract" : "none",
|
platformMarkupAmount: roundMoney(buyerTotalPrice - calculatedSellerPrice.value),
|
||||||
|
ruleType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function emptySalePricing(recycleCoinBasePrice = 0) {
|
function emptyPlatformPricing() {
|
||||||
return {
|
return {
|
||||||
recycleCoinBasePrice,
|
buyerCoinBasePrice: 0,
|
||||||
coinBasePrice: 0,
|
buyerTotalPrice: 0,
|
||||||
saleRatio: 0,
|
buyerRatio: 0,
|
||||||
|
platformMarkupAmount: 0,
|
||||||
ruleType: "none",
|
ruleType: "none",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -647,18 +665,27 @@ function emptySalePricing(recycleCoinBasePrice = 0) {
|
|||||||
function findSaleFixedMarkupRule() {
|
function findSaleFixedMarkupRule() {
|
||||||
return [...salePriceConfig.value.fixed_markup_rules]
|
return [...salePriceConfig.value.fixed_markup_rules]
|
||||||
.sort((a, b) => a.min_m - b.min_m)
|
.sort((a, b) => a.min_m - b.min_m)
|
||||||
.find((item) => isCoinInSaleRange(item));
|
.find((item, index, rules) => isCoinInSaleRange(item, index, rules, { includeLastMax: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function findSaleRatioAdjustmentRule() {
|
function findSaleRatioAdjustmentRule() {
|
||||||
return [...salePriceConfig.value.ratio_adjustment_rules]
|
return [...salePriceConfig.value.ratio_adjustment_rules]
|
||||||
.sort((a, b) => a.min_m - b.min_m)
|
.sort((a, b) => a.min_m - b.min_m)
|
||||||
.find((item) => isCoinInSaleRange(item));
|
.find((item, index, rules) => isCoinInSaleRange(item, index, rules, { excludeFirstMin: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCoinInSaleRange(item: { min_m: number; max_m: number }) {
|
function isCoinInSaleRange(
|
||||||
|
item: { min_m: number; max_m: number },
|
||||||
|
index: number,
|
||||||
|
rules: Array<{ min_m: number; max_m: number }>,
|
||||||
|
options: { includeLastMax?: boolean; excludeFirstMin?: boolean } = {},
|
||||||
|
) {
|
||||||
const maxM = Number(item.max_m || 0);
|
const maxM = Number(item.max_m || 0);
|
||||||
return coinMAmount.value >= Number(item.min_m || 0) && (maxM <= 0 || coinMAmount.value <= maxM);
|
const minM = Number(item.min_m || 0);
|
||||||
|
const minMatched = options.excludeFirstMin && index === 0 ? coinMAmount.value > minM : coinMAmount.value >= minM;
|
||||||
|
const isLastRule = index === rules.length - 1;
|
||||||
|
const maxMatched = maxM <= 0 || coinMAmount.value < maxM || (options.includeLastMax && isLastRule && coinMAmount.value <= maxM);
|
||||||
|
return minMatched && maxMatched;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateEffectiveRatio(price: number) {
|
function calculateEffectiveRatio(price: number) {
|
||||||
@@ -666,14 +693,14 @@ function calculateEffectiveRatio(price: number) {
|
|||||||
return roundRatio(coinWanAmount.value / price);
|
return roundRatio(coinWanAmount.value / price);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInsuranceBaseRatio(insurance: string) {
|
function getInsuranceBaseRatio(config: { insurance_base_ratios: Array<{ insurance: string; ratio: number }> }, insurance: string) {
|
||||||
return ratioConfig.value.insurance_base_ratios.find(
|
return config.insurance_base_ratios.find(
|
||||||
(item) => item.insurance === insurance
|
(item) => item.insurance === insurance
|
||||||
)?.ratio || 0;
|
)?.ratio || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateConfigPenalty() {
|
function calculateConfigPenalty(config: { config_items: Array<{ kind: string; group_key?: string; missing_penalty: number }> }) {
|
||||||
return ratioConfig.value.config_items.reduce((sum, item) => {
|
return config.config_items.reduce((sum, item) => {
|
||||||
return isRatioConfigItemMatched(item) ? sum : sum + Number(item.missing_penalty || 0);
|
return isRatioConfigItemMatched(item) ? sum : sum + Number(item.missing_penalty || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
@@ -703,8 +730,8 @@ function readLevelNumber(value: string) {
|
|||||||
return match ? Number(match[0]) : 0;
|
return match ? Number(match[0]) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCoinCorrection(coinM: number) {
|
function getCoinCorrection(config: { coin_corrections: Array<{ threshold_m: number; correction: number }> }, coinM: number) {
|
||||||
return [...ratioConfig.value.coin_corrections]
|
return [...config.coin_corrections]
|
||||||
.sort((a, b) => b.threshold_m - a.threshold_m)
|
.sort((a, b) => b.threshold_m - a.threshold_m)
|
||||||
.find((item) => coinM > item.threshold_m)?.correction || 0;
|
.find((item) => coinM > item.threshold_m)?.correction || 0;
|
||||||
}
|
}
|
||||||
@@ -1092,28 +1119,69 @@ function readError(error: unknown, fallback: string) {
|
|||||||
<p class="field-hint">
|
<p class="field-hint">
|
||||||
比例调整:在默认10M/天,每增加10M,出租比例相应+1,请根据实际需求合理设置,感谢您的配合。
|
比例调整:在默认10M/天,每增加10M,出租比例相应+1,请根据实际需求合理设置,感谢您的配合。
|
||||||
</p>
|
</p>
|
||||||
|
<div class="ratio-panel">
|
||||||
|
<div class="ratio-reference">
|
||||||
|
<span>参考比例</span>
|
||||||
|
<strong>{{ calculatedDefaultSaleRatioText }}</strong>
|
||||||
|
</div>
|
||||||
|
<p class="ratio-range">{{ saleRatioRangeText }},比例越高出租速度越快,自行选择。</p>
|
||||||
|
<div class="ratio-mode-grid">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ratio-mode-btn"
|
||||||
|
:class="{ active: !hasAcceleratedSaleRatioInput() }"
|
||||||
|
:disabled="calculatedDefaultSaleRatio <= 0"
|
||||||
|
@click="useReferenceSaleRatio"
|
||||||
|
>
|
||||||
|
<strong>参考比例</strong>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
calculatedDefaultSaleRatio > 0
|
||||||
|
? `1元=${formatNumber(calculatedDefaultSaleRatio)}万`
|
||||||
|
: "自动计算"
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ratio-mode-btn"
|
||||||
|
:class="{ active: hasAcceleratedSaleRatioInput() }"
|
||||||
|
:disabled="maxAcceleratedSaleRatio <= 0"
|
||||||
|
@click="useMaxAcceleratedSaleRatio"
|
||||||
|
>
|
||||||
|
<strong>加速比例</strong>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
maxAcceleratedSaleRatio > 0
|
||||||
|
? `1元=${formatNumber(calculatedDefaultSaleRatio)}~${formatNumber(maxAcceleratedSaleRatio)}万`
|
||||||
|
: "自动计算"
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<van-field
|
<van-field
|
||||||
:model-value="form.accelerated_sale_ratio"
|
:model-value="form.accelerated_sale_ratio"
|
||||||
label="加速出售比例"
|
label="自定比例"
|
||||||
type="number"
|
type="number"
|
||||||
:placeholder="acceleratedSaleRatioPlaceholder"
|
:placeholder="acceleratedSaleRatioPlaceholder"
|
||||||
class="publish-field"
|
class="publish-field ratio-input-field"
|
||||||
@update:model-value="handleAcceleratedSaleRatioInput"
|
@update:model-value="handleAcceleratedSaleRatioInput"
|
||||||
@blur="clampAcceleratedSaleRatioInput"
|
@blur="clampAcceleratedSaleRatioInput"
|
||||||
/>
|
/>
|
||||||
<p class="field-hint">
|
<p class="field-hint ratio-input-hint">
|
||||||
请根据实际需求合理设置,感谢您的配合。
|
留空使用参考比例;填写后范围为参考比例到参考比例+10,请根据实际需求合理设置。
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
<div class="result-grid">
|
<div class="result-grid">
|
||||||
<van-field
|
<van-field
|
||||||
:model-value="calculatedRatioText"
|
:model-value="calculatedRatioText"
|
||||||
label="出售比例"
|
label="卖家比例"
|
||||||
readonly
|
readonly
|
||||||
:placeholder="priceConfig.ratio_description"
|
:placeholder="priceConfig.ratio_description"
|
||||||
class="publish-field result-field"
|
class="publish-field result-field"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="hint-label" :data-hint="priceConfig.ratio_description" tabindex="0">出售比例</span>
|
<span class="hint-label" :data-hint="priceConfig.ratio_description" tabindex="0">卖家比例</span>
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
<van-field
|
<van-field
|
||||||
@@ -1135,8 +1203,8 @@ function readError(error: unknown, fallback: string) {
|
|||||||
class="publish-field result-field"
|
class="publish-field result-field"
|
||||||
/>
|
/>
|
||||||
<van-field
|
<van-field
|
||||||
:model-value="calculatedFinalPrice ? `¥${calculatedFinalPrice}` : ''"
|
:model-value="calculatedSellerPrice ? `¥${calculatedSellerPrice}` : ''"
|
||||||
label="发布总价"
|
label="卖家价格"
|
||||||
readonly
|
readonly
|
||||||
required
|
required
|
||||||
:placeholder="priceConfig.price_placeholder"
|
:placeholder="priceConfig.price_placeholder"
|
||||||
@@ -1144,7 +1212,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p class="price-breakdown-hint">
|
<p class="price-breakdown-hint">
|
||||||
发布总价 = 纯币基础价 + 额外消耗品;加速比例仅影响纯币基础价。
|
卖家价格 = 纯币基础价 + 额外消耗品;加速比例仅影响纯币基础价。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<van-field
|
<van-field
|
||||||
|
|||||||
@@ -36,7 +36,16 @@ async function offline(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listingPrice(row: Listing) {
|
function listingPrice(row: Listing) {
|
||||||
return `¥${Number(row.price_daily || row.price_hourly || 0).toFixed(2)}`
|
return `¥${readSellerPrice(row).toFixed(2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function readSellerPrice(row: Listing) {
|
||||||
|
const breakdown = row.asset_summary?.price_breakdown
|
||||||
|
if (typeof breakdown === 'object' && breakdown !== null) {
|
||||||
|
const price = Number((breakdown as Record<string, unknown>).seller_total_price)
|
||||||
|
if (Number.isFinite(price) && price > 0) return price
|
||||||
|
}
|
||||||
|
return Number(row.price_daily || row.price_hourly || 0)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user