发布界面优化

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
+138
View File
@@ -28,6 +28,42 @@ export interface PublishScreenshotSlot {
hint: string
}
export interface PublishPriceConfig {
deposit_placeholder: string
price_placeholder: string
ratio_description: string
}
export interface PublishInsuranceBaseRatio {
insurance: string
ratio: number
}
export interface PublishRatioConfigItem {
key: string
label: string
kind: string
group_key?: string
missing_penalty: number
}
export interface PublishCoinCorrection {
threshold_m: number
correction: number
}
export interface PublishRentRule {
threshold_m: number
days: number
}
export interface PublishRatioConfig {
insurance_base_ratios: PublishInsuranceBaseRatio[]
config_items: PublishRatioConfigItem[]
coin_corrections: PublishCoinCorrection[]
rent_rules: PublishRentRule[]
}
export interface ListingPublishOptions {
server_options: string[]
face_options: string[]
@@ -39,6 +75,10 @@ export interface ListingPublishOptions {
skin_groups: PublishOptionGroup[]
quantity_items: PublishQuantityItem[]
screenshot_slots: PublishScreenshotSlot[]
ban_record_options: string[]
ban_evidence_options: string[]
price_config: PublishPriceConfig
ratio_config: PublishRatioConfig
}
interface ApiResponse<T> {
@@ -58,6 +98,19 @@ export const emptyListingPublishOptions: ListingPublishOptions = {
skin_groups: [],
quantity_items: [],
screenshot_slots: [],
ban_record_options: [],
ban_evidence_options: [],
price_config: {
deposit_placeholder: '',
price_placeholder: '',
ratio_description: '',
},
ratio_config: {
insurance_base_ratios: [],
config_items: [],
coin_corrections: [],
rent_rules: [],
},
}
export async function fetchListingPublishOptions() {
@@ -77,6 +130,10 @@ export function mergeListingPublishOptions(options?: Partial<ListingPublishOptio
skin_groups: normalizeOptionGroups(options?.skin_groups),
quantity_items: normalizeQuantityItems(options?.quantity_items),
screenshot_slots: normalizeScreenshotSlots(options?.screenshot_slots),
ban_record_options: normalizeStringList(options?.ban_record_options),
ban_evidence_options: normalizeStringList(options?.ban_evidence_options),
price_config: normalizePriceConfig(options?.price_config),
ratio_config: normalizeRatioConfig(options?.ratio_config),
}
}
@@ -130,6 +187,87 @@ function normalizeScreenshotSlots(values?: unknown[]): PublishScreenshotSlot[] {
.filter((item) => item.key && item.label)
}
function normalizePriceConfig(value?: unknown): PublishPriceConfig {
const row = isRecord(value) ? value : {}
return {
deposit_placeholder: typeof row.deposit_placeholder === 'string' ? row.deposit_placeholder.trim() : '',
price_placeholder: typeof row.price_placeholder === 'string' ? row.price_placeholder.trim() : '',
ratio_description: typeof row.ratio_description === 'string' ? row.ratio_description.trim() : '',
}
}
function normalizeRatioConfig(value?: unknown): PublishRatioConfig {
const row = isRecord(value) ? value : {}
return {
insurance_base_ratios: normalizeInsuranceBaseRatios(
Array.isArray(row.insurance_base_ratios) ? row.insurance_base_ratios : [],
),
config_items: normalizeRatioConfigItems(
Array.isArray(row.config_items) ? row.config_items : [],
),
coin_corrections: normalizeCoinCorrections(
Array.isArray(row.coin_corrections) ? row.coin_corrections : [],
),
rent_rules: normalizeRentRules(Array.isArray(row.rent_rules) ? row.rent_rules : []),
}
}
function normalizeInsuranceBaseRatios(values: unknown[]): PublishInsuranceBaseRatio[] {
return values
.map((item) => {
const row = isRecord(item) ? item : {}
return {
insurance: typeof row.insurance === 'string' ? row.insurance.trim() : '',
ratio: readNumber(row.ratio),
}
})
.filter((item) => item.insurance && item.ratio > 0)
}
function normalizeRatioConfigItems(values: unknown[]): PublishRatioConfigItem[] {
return values
.map((item) => {
const row = isRecord(item) ? item : {}
return {
key: typeof row.key === 'string' ? row.key.trim() : '',
label: typeof row.label === 'string' ? row.label.trim() : '',
kind: typeof row.kind === 'string' ? row.kind.trim() : '',
group_key: typeof row.group_key === 'string' ? row.group_key.trim() : '',
missing_penalty: readNumber(row.missing_penalty),
}
})
.filter((item) => item.key && item.label && item.kind)
}
function normalizeCoinCorrections(values: unknown[]): PublishCoinCorrection[] {
return values
.map((item) => {
const row = isRecord(item) ? item : {}
return {
threshold_m: readNumber(row.threshold_m),
correction: readNumber(row.correction),
}
})
.filter((item) => item.threshold_m >= 0 && item.correction > 0)
}
function normalizeRentRules(values: unknown[]): PublishRentRule[] {
return values
.map((item) => {
const row = isRecord(item) ? item : {}
return {
threshold_m: readNumber(row.threshold_m),
days: Math.trunc(readNumber(row.days)),
}
})
.filter((item) => item.threshold_m >= 0 && item.days > 0)
}
function readNumber(value: unknown) {
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : 0
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
@@ -197,7 +197,7 @@ function itemsToLines(items: string[]) {
return items.join('\n')
}
function updateOptionLines(key: keyof Pick<ListingPublishOptions, 'server_options' | 'face_options' | 'rank_options' | 'insurance_options' | 'level_options' | 'login_method_options' | 'region_options'>, nextValue: string) {
function updateOptionLines(key: keyof Pick<ListingPublishOptions, 'server_options' | 'face_options' | 'rank_options' | 'insurance_options' | 'level_options' | 'login_method_options' | 'region_options' | 'ban_record_options' | 'ban_evidence_options'>, nextValue: string) {
publishOptionsDraft.value[key] = linesToItems(nextValue)
}
@@ -245,6 +245,53 @@ function removeScreenshotSlot(index: number) {
publishOptionsDraft.value.screenshot_slots.splice(index, 1)
}
function addInsuranceBaseRatio() {
publishOptionsDraft.value.ratio_config.insurance_base_ratios.push({
insurance: '',
ratio: 0,
})
}
function removeInsuranceBaseRatio(index: number) {
publishOptionsDraft.value.ratio_config.insurance_base_ratios.splice(index, 1)
}
function addRatioConfigItem() {
publishOptionsDraft.value.ratio_config.config_items.push({
key: `config_${Date.now()}`,
label: '',
kind: 'skin_group',
group_key: '',
missing_penalty: 1,
})
}
function removeRatioConfigItem(index: number) {
publishOptionsDraft.value.ratio_config.config_items.splice(index, 1)
}
function addCoinCorrection() {
publishOptionsDraft.value.ratio_config.coin_corrections.push({
threshold_m: 0,
correction: 0,
})
}
function removeCoinCorrection(index: number) {
publishOptionsDraft.value.ratio_config.coin_corrections.splice(index, 1)
}
function addRentRule() {
publishOptionsDraft.value.ratio_config.rent_rules.push({
threshold_m: 0,
days: 1,
})
}
function removeRentRule(index: number) {
publishOptionsDraft.value.ratio_config.rent_rules.splice(index, 1)
}
function addHomeBanner() {
homeBannersDraft.value.push({
eyebrow: '首页推荐',
@@ -486,6 +533,134 @@ function readError(error: unknown, fallback: string) {
/>
</el-form-item>
<div class="editor-grid">
<el-form-item label="封禁记录">
<el-input
:model-value="itemsToLines(publishOptionsDraft.ban_record_options)"
type="textarea"
:rows="3"
placeholder="一行一个选项"
@update:model-value="updateOptionLines('ban_record_options', $event)"
/>
</el-form-item>
<el-form-item label="需传安全图">
<el-input
:model-value="itemsToLines(publishOptionsDraft.ban_evidence_options)"
type="textarea"
:rows="3"
placeholder="选择这些封禁记录时,腾讯安全中心截图必传"
@update:model-value="updateOptionLines('ban_evidence_options', $event)"
/>
</el-form-item>
</div>
<div class="editor-block">
<div class="editor-block-title">
<strong>押金与价格提示</strong>
</div>
<el-form-item label="押金提示">
<el-input v-model="publishOptionsDraft.price_config.deposit_placeholder" type="textarea" :rows="3" />
</el-form-item>
<el-form-item label="价格提示">
<el-input v-model="publishOptionsDraft.price_config.price_placeholder" />
</el-form-item>
<el-form-item label="比例说明">
<el-input v-model="publishOptionsDraft.price_config.ratio_description" />
</el-form-item>
</div>
<div class="editor-block">
<div class="editor-block-title">
<strong>比例计算配置</strong>
</div>
<div class="editor-block-title subtle-title">
<span>保险基础比例</span>
<el-button size="small" @click="addInsuranceBaseRatio">添加保险比例</el-button>
</div>
<el-table :data="publishOptionsDraft.ratio_config.insurance_base_ratios" size="small" border>
<el-table-column label="保险" min-width="160">
<template #default="{ row }"><el-input v-model="row.insurance" placeholder="3*3" /></template>
</el-table-column>
<el-table-column label="基础比例" min-width="120">
<template #default="{ row }"><el-input-number v-model="row.ratio" :min="0" :step="0.5" /></template>
</el-table-column>
<el-table-column label="操作" width="90">
<template #default="{ $index }">
<el-button size="small" type="danger" plain @click="removeInsuranceBaseRatio($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="editor-block-title subtle-title">
<span>配置项缺失加成</span>
<el-button size="small" @click="addRatioConfigItem">添加配置项</el-button>
</div>
<el-table :data="publishOptionsDraft.ratio_config.config_items" size="small" border>
<el-table-column label="Key" min-width="140">
<template #default="{ row }"><el-input v-model="row.key" /></template>
</el-table-column>
<el-table-column label="名称" min-width="140">
<template #default="{ row }"><el-input v-model="row.label" /></template>
</el-table-column>
<el-table-column label="类型" min-width="160">
<template #default="{ row }">
<el-select v-model="row.kind">
<el-option label="皮肤分组" value="skin_group" />
<el-option label="满体力" value="max_stamina" />
<el-option label="满负重" value="max_load" />
</el-select>
</template>
</el-table-column>
<el-table-column label="皮肤分组 Key" min-width="150">
<template #default="{ row }"><el-input v-model="row.group_key" /></template>
</el-table-column>
<el-table-column label="缺失加成" min-width="120">
<template #default="{ row }"><el-input-number v-model="row.missing_penalty" :min="0" :step="0.5" /></template>
</el-table-column>
<el-table-column label="操作" width="90">
<template #default="{ $index }">
<el-button size="small" type="danger" plain @click="removeRatioConfigItem($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="editor-block-title subtle-title">
<span>大额币修正</span>
<el-button size="small" @click="addCoinCorrection">添加修正</el-button>
</div>
<el-table :data="publishOptionsDraft.ratio_config.coin_corrections" size="small" border>
<el-table-column label="大于 M" min-width="130">
<template #default="{ row }"><el-input-number v-model="row.threshold_m" :min="0" :step="10" /></template>
</el-table-column>
<el-table-column label="比例加成" min-width="130">
<template #default="{ row }"><el-input-number v-model="row.correction" :min="0" :step="0.5" /></template>
</el-table-column>
<el-table-column label="操作" width="90">
<template #default="{ $index }">
<el-button size="small" type="danger" plain @click="removeCoinCorrection($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="editor-block-title subtle-title">
<span>租期规则</span>
<el-button size="small" @click="addRentRule">添加租期</el-button>
</div>
<el-table :data="publishOptionsDraft.ratio_config.rent_rules" size="small" border>
<el-table-column label="大于等于 M" min-width="130">
<template #default="{ row }"><el-input-number v-model="row.threshold_m" :min="0" :step="10" /></template>
</el-table-column>
<el-table-column label="租期天数" min-width="130">
<template #default="{ row }"><el-input-number v-model="row.days" :min="1" :step="1" /></template>
</el-table-column>
<el-table-column label="操作" width="90">
<template #default="{ $index }">
<el-button size="small" type="danger" plain @click="removeRentRule($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="editor-block">
<div class="editor-block-title">
<strong>皮肤分类</strong>
@@ -786,6 +961,12 @@ function readError(error: unknown, fallback: string) {
gap: 10px;
}
.subtle-title {
margin-top: 6px;
color: #4b5563;
font-size: 13px;
}
.skin-config-row {
display: grid;
grid-template-columns: 140px 180px minmax(0, 1fr) 72px;
@@ -252,7 +252,7 @@ function isNavActive(path: string) {
</span>
</div>
<div class="info-line">
<span class="info-label">比例</span>
<span class="info-label">M单价</span>
<span class="info-text">{{ formatRatio(listing) }}</span>
</div>
<div class="info-line">
@@ -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>
+15 -5
View File
@@ -46,14 +46,19 @@ export function getRatioValue(item: Listing) {
}
export function formatRatio(item: Listing) {
const ratio = getRatioValue(item);
return ratio > 0 ? `1:${Math.round(ratio)}` : "--";
const pricePerM = getPricePerM(item);
return pricePerM > 0 ? `1M=¥${formatMoney(pricePerM)}` : "--";
}
export function getValuePerYuanText(item: Listing) {
const price = getListingDisplayPrice(item);
if (price <= 0) return "";
return `1元=${Math.round(getCoinWan(item) / price)}w哈夫币`;
const pricePerM = getPricePerM(item);
return pricePerM > 0 ? `1M=¥${formatMoney(pricePerM)}` : "";
}
export function getPricePerM(item: Listing) {
const coinM = getCoinM(item);
if (coinM <= 0) return 0;
return roundMoney(getListingDisplayPrice(item) / coinM);
}
export function getLoginMethod(item: Listing) {
@@ -219,3 +224,8 @@ function formatResourceShort(item: Listing, key: string, label: string) {
function roundMoney(value: number) {
return Math.round(value * 100) / 100;
}
function formatMoney(value: number) {
const rounded = roundMoney(value);
return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(2);
}
+3
View File
@@ -22,5 +22,8 @@ export default defineConfig({
proxy: {
"/api": "http://127.0.0.1:8080",
},
allowedHosts: [
'221329.cc.cd'
],
},
});