发布界面优化

This commit is contained in:
yml2213
2026-05-23 13:47:26 +08:00
parent d90f31360f
commit e6cc9f1255
12 changed files with 607 additions and 65 deletions
@@ -109,6 +109,10 @@ const loginMethodOptions = computed(
() => publishOptions.value.login_method_options
);
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 ratioConfig = computed(() => publishOptions.value.ratio_config);
const skinGroups = computed(() => publishOptions.value.skin_groups);
const quantityItems = computed(() => publishOptions.value.quantity_items);
const screenshotSlots = computed(() => publishOptions.value.screenshot_slots);
@@ -117,7 +121,8 @@ const screenshotUrls = computed(() =>
.map((item) => screenshotFiles[item.key])
.filter((url): url is string => Boolean(url))
);
const coinWanAmount = computed(() => Number(form.haf_coin_amount || 0));
const coinMAmount = computed(() => Number(form.haf_coin_amount || 0));
const coinWanAmount = computed(() => coinMAmount.value * 100);
const calculatedRatio = computed(() => calculatePublishRatio());
const calculatedConsumablePrice = computed(() => calculateConsumablePrice());
const calculatedFinalPrice = computed(() =>
@@ -125,8 +130,11 @@ const calculatedFinalPrice = computed(() =>
? roundMoney(coinWanAmount.value / calculatedRatio.value + calculatedConsumablePrice.value)
: 0
);
const calculatedRatioText = computed(() =>
calculatedRatio.value > 0 ? `1:${formatNumber(calculatedRatio.value)}` : ""
);
const calculatedRentDays = computed(() =>
coinWanAmount.value > 0 ? calculateRentDays(coinWanAmount.value) : 0
coinMAmount.value > 0 ? calculateRentDays(coinMAmount.value) : 0
);
onMounted(() => {
@@ -347,7 +355,7 @@ async function handleSubmit() {
}
loading.value = true;
try {
const title = `${form.server_region} ${form.rank_level} ${form.haf_coin_amount}哈夫币`;
const title = `${form.server_region} ${form.rank_level} ${form.haf_coin_amount}M哈夫币`;
const rentDays = Math.max(calculatedRentDays.value, 1);
const dailyPrice = roundMoney(calculatedFinalPrice.value / rentDays);
const hourlyPrice = Math.max(roundMoney(dailyPrice / 24), 0.01);
@@ -358,7 +366,7 @@ async function handleSubmit() {
server_region: form.server_region,
login_platform: form.login_method,
rank_level: form.rank_level,
haf_coin_amount: Number(form.haf_coin_amount) * 10000,
haf_coin_amount: coinMAmount.value * 1000000,
asset_summary: buildAssetSummary(),
screenshot_urls: screenshotUrls.value,
price_hourly: hourlyPrice,
@@ -383,13 +391,14 @@ async function handleSubmit() {
function validateForm() {
if (!form.server_region) return "请选择区服";
if (Number(form.haf_coin_amount) <= 0) return "请填写哈夫币/";
if (coinMAmount.value <= 0) return "请填写哈夫币/M";
if (!form.rank_level) return "请选择段位";
if (!form.fire_level) return "请填写烽火等级";
if (Number(form.fire_level) < 38) return "烽火等级低于38级的号无法发布";
if (!form.season_insurance) return "请选择赛季保险";
if (!form.stamina_level) return "请选择体力等级";
if (!form.load_level) return "请选择负重等级";
if (banRecordOptions.value.length && !form.ban_record) return "请选择封禁记录";
if (form.deposit_amount === "" || Number(form.deposit_amount) < 0) {
return "请填写押金";
}
@@ -397,13 +406,21 @@ function validateForm() {
return "请完善币数、保险、体力和负重后再发布";
}
for (const item of screenshotSlots.value) {
if (item.required && !screenshotFiles[item.key]) {
if (isScreenshotRequired(item) && !screenshotFiles[item.key]) {
return `请上传${item.label}`;
}
}
return "";
}
function isScreenshotRequired(item: { key: string; required: boolean }) {
return item.required || (item.key === "tencentSecurity" && shouldRequireBanEvidence());
}
function shouldRequireBanEvidence() {
return banEvidenceOptions.value.includes(form.ban_record);
}
function buildAssetSummary() {
return {
face_owner: form.face_owner,
@@ -462,7 +479,7 @@ function readUnitPrice(priceText: string) {
function calculatePublishRatio() {
if (
coinWanAmount.value <= 0 ||
coinMAmount.value <= 0 ||
!form.season_insurance ||
!form.stamina_level ||
!form.load_level
@@ -472,35 +489,26 @@ function calculatePublishRatio() {
const baseRatio = getInsuranceBaseRatio(form.season_insurance);
if (baseRatio <= 0) return 0;
return baseRatio + (5 - getConfigHitCount()) + getCoinCorrection(coinWanAmount.value);
return baseRatio + calculateConfigPenalty() + getCoinCorrection(coinMAmount.value);
}
function getInsuranceBaseRatio(insurance: string) {
const slots = getInsuranceSlots(insurance);
const ratioBySlots: Record<number, number> = {
9: 40,
6: 42,
4: 47,
2: 48,
};
return ratioBySlots[slots] || 0;
return ratioConfig.value.insurance_base_ratios.find(
(item) => item.insurance === insurance
)?.ratio || 0;
}
function getInsuranceSlots(insurance: string) {
const match = insurance.match(/^(\d+)\*(\d+)$/);
if (!match) return 0;
return Number(match[1]) * Number(match[2]);
function calculateConfigPenalty() {
return ratioConfig.value.config_items.reduce((sum, item) => {
return isRatioConfigItemMatched(item) ? sum : sum + Number(item.missing_penalty || 0);
}, 0);
}
function getConfigHitCount() {
const checks = [
hasSelectedSkinGroup("operatorRed"),
hasSelectedSkinGroup("melee"),
isMaxLevel(form.stamina_level),
isMaxLevel(form.load_level),
hasSelectedSkinGroup("weapon"),
];
return checks.filter(Boolean).length;
function isRatioConfigItemMatched(item: { kind: string; group_key?: string }) {
if (item.kind === "skin_group") return hasSelectedSkinGroup(item.group_key || "");
if (item.kind === "max_stamina") return isMaxLevel(form.stamina_level);
if (item.kind === "max_load") return isMaxLevel(form.load_level);
return false;
}
function hasSelectedSkinGroup(groupKey: string) {
@@ -521,18 +529,20 @@ function readLevelNumber(value: string) {
return match ? Number(match[0]) : 0;
}
function getCoinCorrection(coinWan: number) {
if (coinWan > 53000) return 5;
if (coinWan > 38000) return 4;
if (coinWan > 23000) return 2.5;
if (coinWan > 13000) return 1;
return 0;
function getCoinCorrection(coinM: number) {
return [...ratioConfig.value.coin_corrections]
.sort((a, b) => b.threshold_m - a.threshold_m)
.find((item) => coinM > item.threshold_m)?.correction || 0;
}
function calculateRentDays(coinWan: number) {
if (coinWan >= 30000) return 7;
if (coinWan >= 10000) return 3;
return 1;
function calculateRentDays(coinM: number) {
return [...ratioConfig.value.rent_rules]
.sort((a, b) => b.threshold_m - a.threshold_m)
.find((item) => coinM >= item.threshold_m)?.days || 1;
}
function formatNumber(value: number) {
return Number.isInteger(value) ? `${value}` : `${Math.round(value * 10) / 10}`;
}
function readError(error: unknown, fallback: string) {
@@ -595,10 +605,10 @@ function readError(error: unknown, fallback: string) {
<van-field
v-model="form.haf_coin_amount"
label="哈夫币/"
label="哈夫币/M"
type="digit"
required
placeholder="只填写仓库右上角纯币数额,总资产不计算在内(建议清理仓库中所有非保留物品),100M写作10000"
placeholder="只填写仓库右上角纯币数额,总资产不计算在内100M 写 100"
class="publish-field"
/>
@@ -788,15 +798,22 @@ function readError(error: unknown, fallback: string) {
此在线时间指的是百分百能够联系上您的时间若是在此期间联系不上您导致无法上号会扣除您的部分租金或上架押金在线时长太短可能无法上架请预留充足时间用于扫码以及冻结人脸请谨慎填写
</p>
<van-field
v-model="form.ban_record"
label="封禁记录"
type="textarea"
rows="2"
autosize
placeholder="请自行填写封禁等信息"
class="publish-field"
/>
<van-field label="封禁记录" required class="publish-field">
<template #input>
<div class="radio-group">
<button
v-for="opt in banRecordOptions"
:key="opt"
type="button"
class="radio-btn"
:class="{ active: form.ban_record === opt }"
@click="selectRadio(opt, (value) => (form.ban_record = value))"
>
{{ opt }}
</button>
</div>
</template>
</van-field>
<div class="region-panel">
<div class="region-title">
@@ -826,7 +843,7 @@ function readError(error: unknown, fallback: string) {
<div class="upload-list">
<div v-for="slot in screenshotSlots" :key="slot.key" class="upload-line">
<div class="upload-meta">
<strong>{{ slot.label }}<span v-if="slot.required">*</span></strong>
<strong>{{ slot.label }}<span v-if="isScreenshotRequired(slot)">*</span></strong>
<small>{{ slot.hint }}</small>
</div>
<div v-if="screenshotFiles[slot.key]" class="upload-preview">
@@ -862,17 +879,24 @@ function readError(error: unknown, fallback: string) {
label="押金"
type="number"
required
placeholder="温馨提示:本押金用于保障账号安全。若买家在租用期间,使用非纯币类道具/资源(如消耗品、材料等)或导致账号被封禁,相关损失将直接从押金中扣除进行赔付。押金过高会延长出租等待时间(请自行衡量)感谢理解。"
:placeholder="priceConfig.deposit_placeholder"
suffix="元"
class="publish-field"
/>
<div class="result-grid">
<van-field
:model-value="calculatedRatioText"
label="比例"
readonly
:placeholder="priceConfig.ratio_description"
class="publish-field result-field"
/>
<van-field
:model-value="calculatedFinalPrice ? `¥${calculatedFinalPrice}` : ''"
label="价格"
readonly
required
placeholder="填写币数、保险、体力和负重后自动计算"
:placeholder="priceConfig.price_placeholder"
class="publish-field result-field"
/>
</div>