增加前端格式检查配置

This commit is contained in:
yml2213
2026-06-08 06:40:33 +08:00
parent 79ea3a476c
commit 500f511185
150 changed files with 7254 additions and 4013 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ export function clearAuthStorage(scope: AuthScope) {
const keys = keysFor(scope)
localStorage.removeItem(keys.accessToken)
localStorage.removeItem(keys.refreshToken)
keys.profile.forEach((key) => localStorage.removeItem(key))
keys.profile.forEach(key => localStorage.removeItem(key))
notifyAuthStorageChanged(scope)
}
+43 -43
View File
@@ -1,84 +1,84 @@
const IMAGE_UPLOAD_MAX_SIDE: Record<string, number> = {
avatar: 512,
chat: 1280,
"home-banner": 1920,
'home-banner': 1920,
listing: 1920,
dispute: 1920,
handoff: 1920,
realname: 1920,
};
}
const IMAGE_UPLOAD_QUALITY: Record<string, number> = {
avatar: 0.82,
chat: 0.8,
"home-banner": 0.84,
'home-banner': 0.84,
listing: 0.84,
dispute: 0.86,
handoff: 0.86,
realname: 0.86,
};
}
export async function optimizeImageForUpload(file: File, scene: string) {
if (!file.type.startsWith("image/")) return file;
if (!["image/jpeg", "image/png", "image/webp"].includes(file.type)) return file;
if (typeof document === "undefined") return file;
if (!file.type.startsWith('image/')) return file
if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) return file
if (typeof document === 'undefined') return file
try {
const image = await loadImage(file);
const maxSide = IMAGE_UPLOAD_MAX_SIDE[scene] || 1600;
const quality = IMAGE_UPLOAD_QUALITY[scene] || 0.82;
const { width, height } = fitSize(image.naturalWidth, image.naturalHeight, maxSide);
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const context = canvas.getContext("2d");
if (!context) return file;
context.fillStyle = "#ffffff";
context.fillRect(0, 0, width, height);
context.drawImage(image, 0, 0, width, height);
const blob = await canvasToBlob(canvas, "image/webp", quality);
if (!blob || blob.size >= file.size) return file;
return new File([blob], replaceFileExt(file.name, "webp"), {
type: "image/webp",
const image = await loadImage(file)
const maxSide = IMAGE_UPLOAD_MAX_SIDE[scene] || 1600
const quality = IMAGE_UPLOAD_QUALITY[scene] || 0.82
const { width, height } = fitSize(image.naturalWidth, image.naturalHeight, maxSide)
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const context = canvas.getContext('2d')
if (!context) return file
context.fillStyle = '#ffffff'
context.fillRect(0, 0, width, height)
context.drawImage(image, 0, 0, width, height)
const blob = await canvasToBlob(canvas, 'image/webp', quality)
if (!blob || blob.size >= file.size) return file
return new File([blob], replaceFileExt(file.name, 'webp'), {
type: 'image/webp',
lastModified: Date.now(),
});
})
} catch {
return file;
return file
}
}
function loadImage(file: File) {
return new Promise<HTMLImageElement>((resolve, reject) => {
const url = URL.createObjectURL(file);
const image = new Image();
const url = URL.createObjectURL(file)
const image = new Image()
image.onload = () => {
URL.revokeObjectURL(url);
resolve(image);
};
URL.revokeObjectURL(url)
resolve(image)
}
image.onerror = () => {
URL.revokeObjectURL(url);
reject(new Error("图片读取失败"));
};
image.src = url;
});
URL.revokeObjectURL(url)
reject(new Error('图片读取失败'))
}
image.src = url
})
}
function fitSize(width: number, height: number, maxSide: number) {
if (width <= 0 || height <= 0) return { width: 1, height: 1 };
const scale = Math.min(1, maxSide / Math.max(width, height));
if (width <= 0 || height <= 0) return { width: 1, height: 1 }
const scale = Math.min(1, maxSide / Math.max(width, height))
return {
width: Math.max(1, Math.round(width * scale)),
height: Math.max(1, Math.round(height * scale)),
};
}
}
function canvasToBlob(canvas: HTMLCanvasElement, type: string, quality: number) {
return new Promise<Blob | null>((resolve) => {
canvas.toBlob(resolve, type, quality);
});
return new Promise<Blob | null>(resolve => {
canvas.toBlob(resolve, type, quality)
})
}
function replaceFileExt(filename: string, ext: string) {
const base = filename.replace(/\.[^.]+$/, "");
return `${base || "image"}.${ext}`;
const base = filename.replace(/\.[^.]+$/, '')
return `${base || 'image'}.${ext}`
}
+152 -149
View File
@@ -1,307 +1,310 @@
import type { Listing } from "@/features/listings/api/listings";
import type { Listing } from '@/features/listings/api/listings'
export interface ListingDisplayChip {
label: string;
value: string;
label: string
value: string
}
export interface ListingDisplayResource {
key: string;
label: string;
price: string;
quantity: number;
mode: string;
amount: number;
key: string
label: string
price: string
quantity: number
mode: string
amount: number
}
export function getCoinWan(item: Listing) {
return Math.round(Number(item.haf_coin_amount || 0) / 10000);
return Math.round(Number(item.haf_coin_amount || 0) / 10000)
}
export function getCoinM(item: Listing) {
return getCoinWan(item) / 100;
return getCoinWan(item) / 100
}
export function formatListingCode(item: Listing) {
return `SP${String(item.id).padStart(6, "0")}`;
return `SP${String(item.id).padStart(6, '0')}`
}
export function formatHafCoinM(amountWan: number) {
const amountM = amountWan / 100;
const rounded = Math.round(amountM * 10) / 10;
return `${Number.isInteger(rounded) ? rounded : rounded.toFixed(1)}M`;
const amountM = amountWan / 100
const rounded = Math.round(amountM * 10) / 10
return `${Number.isInteger(rounded) ? rounded : rounded.toFixed(1)}M`
}
export function getListingDisplayPrice(item: Listing) {
return Number(item.price || 0);
return Number(item.price || 0)
}
export function getListingRentPrice(item: Listing) {
const buyerCoinBasePrice = readPriceBreakdownNumber(item, "buyer_coin_base_price");
if (buyerCoinBasePrice > 0) return roundMoney(buyerCoinBasePrice);
return Math.max(0, roundMoney(getListingDisplayPrice(item) - getListingConsumablePrice(item)));
const buyerCoinBasePrice = readPriceBreakdownNumber(item, 'buyer_coin_base_price')
if (buyerCoinBasePrice > 0) return roundMoney(buyerCoinBasePrice)
return Math.max(0, roundMoney(getListingDisplayPrice(item) - getListingConsumablePrice(item)))
}
export function getListingConsumablePrice(item: Listing) {
const consumablePrice = readPriceBreakdownNumber(item, "consumable_price");
if (consumablePrice > 0) return roundMoney(consumablePrice);
return getListingResources(item).reduce((sum, resource) => sum + resource.amount, 0);
const consumablePrice = readPriceBreakdownNumber(item, 'consumable_price')
if (consumablePrice > 0) return roundMoney(consumablePrice)
return getListingResources(item).reduce((sum, resource) => sum + resource.amount, 0)
}
export function getListingSellerPrice(item: Listing) {
const priceBreakdown = item.asset_summary?.price_breakdown;
if (typeof priceBreakdown === "object" && priceBreakdown !== null) {
const price = readUnknownNumber((priceBreakdown as Record<string, unknown>).seller_total_price);
if (price > 0) return price;
const priceBreakdown = item.asset_summary?.price_breakdown
if (typeof priceBreakdown === 'object' && priceBreakdown !== null) {
const price = readUnknownNumber((priceBreakdown as Record<string, unknown>).seller_total_price)
if (price > 0) return price
}
return getListingDisplayPrice(item);
return getListingDisplayPrice(item)
}
export function getRatioValue(item: Listing) {
const ratio = readAssetNumber(item, "publish_ratio");
if (ratio > 0) return ratio;
const price = getListingDisplayPrice(item);
if (price <= 0) return 0;
return getCoinWan(item) / price;
const ratio = readAssetNumber(item, 'publish_ratio')
if (ratio > 0) return ratio
const price = getListingDisplayPrice(item)
if (price <= 0) return 0
return getCoinWan(item) / price
}
export function formatRatio(item: Listing) {
const ratio = getRatioValue(item);
return ratio > 0 ? `1:${formatRatioNumber(ratio)}` : "--";
const ratio = getRatioValue(item)
return ratio > 0 ? `1:${formatRatioNumber(ratio)}` : '--'
}
export function getValuePerYuanText(item: Listing) {
return formatRatio(item);
return formatRatio(item)
}
export function getLoginMethod(item: Listing) {
return item.login_platform.trim();
return item.login_platform.trim()
}
export function getServerRegion(item: Listing) {
return item.server_region.trim();
return item.server_region.trim()
}
export function getListingTitle(item: Listing) {
const parts = [
`纯币${formatHafCoinM(getCoinWan(item))}`,
formatInsuranceSlotText(readAssetString(item, "season_insurance")),
formatLevelShort(readAssetString(item, "stamina_level"), "体"),
formatLevelShort(readAssetString(item, "load_level"), "负"),
formatResourceShort(item, "armor6", "六甲"),
formatResourceShort(item, "helmet6", "六头"),
formatInsuranceSlotText(readAssetString(item, 'season_insurance')),
formatLevelShort(readAssetString(item, 'stamina_level'), '体'),
formatLevelShort(readAssetString(item, 'load_level'), '负'),
formatResourceShort(item, 'armor6', '六甲'),
formatResourceShort(item, 'helmet6', '六头'),
...getSkinNames(item).slice(0, 4),
].filter(Boolean);
return parts.join("/");
].filter(Boolean)
return parts.join('/')
}
export function getListingSubtitle(item: Listing) {
return getValuePerYuanText(item);
return getValuePerYuanText(item)
}
export function getListingChips(item: Listing): ListingDisplayChip[] {
const totalAsset = readAssetNumber(item, "total_asset_wan");
const totalAsset = readAssetNumber(item, 'total_asset_wan')
const chips: ListingDisplayChip[] = [
{ label: "哈夫币", value: formatHafCoinM(getCoinWan(item)) },
{ label: "保险格数", value: readAssetString(item, "season_insurance") },
{ label: "体力", value: readAssetString(item, "stamina_level") },
{ label: "负重", value: readAssetString(item, "load_level") },
{ label: "段位", value: item.rank_level },
];
const awmAmmo = getResourceQuantity(item, "awmAmmo");
{ label: '哈夫币', value: formatHafCoinM(getCoinWan(item)) },
{ label: '保险格数', value: readAssetString(item, 'season_insurance') },
{ label: '体力', value: readAssetString(item, 'stamina_level') },
{ label: '负重', value: readAssetString(item, 'load_level') },
{ label: '段位', value: item.rank_level },
]
const awmAmmo = getResourceQuantity(item, 'awmAmmo')
if (awmAmmo > 0) {
chips.push({ label: "AWM", value: `${awmAmmo}` });
chips.push({ label: 'AWM', value: `${awmAmmo}` })
}
if (totalAsset > 0) {
chips.push({ label: "总资产", value: formatHafCoinM(totalAsset) });
chips.push({ label: '总资产', value: formatHafCoinM(totalAsset) })
}
const online = getOnlineTimeText(item);
const online = getOnlineTimeText(item)
if (online) {
chips.push({ label: "方便上号", value: online });
chips.push({ label: '方便上号', value: online })
}
return chips.filter((chip) => chip.value);
return chips.filter(chip => chip.value)
}
export function getListingResources(item: Listing): ListingDisplayResource[] {
const resources = item.asset_summary?.resources;
if (!Array.isArray(resources)) return [];
const resources = item.asset_summary?.resources
if (!Array.isArray(resources)) return []
return resources
.map((resource) => {
if (typeof resource !== "object" || resource === null) return null;
const row = resource as Record<string, unknown>;
.map(resource => {
if (typeof resource !== 'object' || resource === null) return null
const row = resource as Record<string, unknown>
return {
key: typeof row.key === "string" ? row.key : "",
label: typeof row.label === "string" ? row.label : "",
price: typeof row.price === "string" ? row.price : "",
key: typeof row.key === 'string' ? row.key : '',
label: typeof row.label === 'string' ? row.label : '',
price: typeof row.price === 'string' ? row.price : '',
quantity: readUnknownNumber(row.quantity),
mode: typeof row.mode === "string" ? row.mode : "",
mode: typeof row.mode === 'string' ? row.mode : '',
amount:
row.mode === "收费"
? roundMoney(readUnknownNumber(row.quantity) * readUnitPrice(typeof row.price === "string" ? row.price : ""))
row.mode === '收费'
? roundMoney(
readUnknownNumber(row.quantity) *
readUnitPrice(typeof row.price === 'string' ? row.price : '')
)
: 0,
};
}
})
.filter((resource): resource is ListingDisplayResource => {
return Boolean(resource?.key && resource.label && resource.quantity > 0);
});
return Boolean(resource?.key && resource.label && resource.quantity > 0)
})
}
export function getResourceQuantity(item: Listing, resourceKey: string) {
return getListingResources(item).find((resource) => resource.key === resourceKey)?.quantity || 0;
return getListingResources(item).find(resource => resource.key === resourceKey)?.quantity || 0
}
export function hasGiftResources(item: Listing) {
return getListingResources(item).some((resource) => resource.mode === "赠送");
return getListingResources(item).some(resource => resource.mode === '赠送')
}
export function hasAcceleratedSaleRatio(item: Listing) {
if (item.is_accelerated_sale) return true;
if (item.is_accelerated_sale) return true
const priceBreakdown = item.asset_summary?.price_breakdown;
if (typeof priceBreakdown !== "object" || priceBreakdown === null) return false;
const priceBreakdown = item.asset_summary?.price_breakdown
if (typeof priceBreakdown !== 'object' || priceBreakdown === null) return false
const row = priceBreakdown as Record<string, unknown>;
const referenceRatio = readUnknownNumber(row.seller_reference_ratio);
const sellerRatio = readUnknownNumber(row.seller_ratio);
const acceleratedRatio = readUnknownNumber(row.accelerated_sale_ratio);
const row = priceBreakdown as Record<string, unknown>
const referenceRatio = readUnknownNumber(row.seller_reference_ratio)
const sellerRatio = readUnknownNumber(row.seller_ratio)
const acceleratedRatio = readUnknownNumber(row.accelerated_sale_ratio)
if (referenceRatio <= 0) return false;
return sellerRatio > referenceRatio || acceleratedRatio > referenceRatio;
if (referenceRatio <= 0) return false
return sellerRatio > referenceRatio || acceleratedRatio > referenceRatio
}
export function getSkinGroup(item: Listing, groupKey: string) {
const skinGroups = item.asset_summary?.skin_groups;
const skinGroups = item.asset_summary?.skin_groups
if (
typeof skinGroups !== "object" ||
typeof skinGroups !== 'object' ||
skinGroups === null ||
!Array.isArray((skinGroups as Record<string, unknown>)[groupKey])
) {
return [];
return []
}
return ((skinGroups as Record<string, unknown>)[groupKey] as unknown[]).filter(
(skin): skin is string => typeof skin === "string"
);
(skin): skin is string => typeof skin === 'string'
)
}
export function getSkinNames(item: Listing) {
const skinGroups = item.asset_summary?.skin_groups;
if (typeof skinGroups !== "object" || skinGroups === null) return [];
const skinGroups = item.asset_summary?.skin_groups
if (typeof skinGroups !== 'object' || skinGroups === null) return []
return Object.values(skinGroups as Record<string, unknown>)
.flatMap((group) => (Array.isArray(group) ? group : []))
.filter((skin): skin is string => typeof skin === "string");
.flatMap(group => (Array.isArray(group) ? group : []))
.filter((skin): skin is string => typeof skin === 'string')
}
export function assetRegions(item: Listing) {
const regions = item.asset_summary?.common_regions;
const regions = item.asset_summary?.common_regions
return Array.isArray(regions)
? regions.filter((region): region is string => typeof region === "string")
: [];
? regions.filter((region): region is string => typeof region === 'string')
: []
}
export function getOnlineTimeText(item: Listing) {
const onlineTime = item.asset_summary?.online_time;
if (typeof onlineTime !== "object" || onlineTime === null) return "";
const start = (onlineTime as Record<string, unknown>).start;
const end = (onlineTime as Record<string, unknown>).end;
if (typeof start !== "string" || typeof end !== "string" || !start || !end) return "";
if (start === "全天" || end === "全天" || (start === "00:00" && end === "23:59")) return "全天";
return `${start.replace(":00", "")}-${end.replace(":00", "")}`;
const onlineTime = item.asset_summary?.online_time
if (typeof onlineTime !== 'object' || onlineTime === null) return ''
const start = (onlineTime as Record<string, unknown>).start
const end = (onlineTime as Record<string, unknown>).end
if (typeof start !== 'string' || typeof end !== 'string' || !start || !end) return ''
if (start === '全天' || end === '全天' || (start === '00:00' && end === '23:59')) return '全天'
return `${start.replace(':00', '')}-${end.replace(':00', '')}`
}
export function getDailyLoss(item: Listing) {
const dailyLossM = getDailyLossM(item);
return dailyLossM > 0 ? `${formatCompactNumber(dailyLossM)}M` : "";
const dailyLossM = getDailyLossM(item)
return dailyLossM > 0 ? `${formatCompactNumber(dailyLossM)}M` : ''
}
export function getDailyLossM(item: Listing) {
const configuredLoss = readAssetNumber(item, "daily_loss_m");
if (configuredLoss > 0) return configuredLoss;
const configuredLoss = readAssetNumber(item, 'daily_loss_m')
if (configuredLoss > 0) return configuredLoss
const coinWan = getCoinWan(item);
if (coinWan >= 30000) return 30;
if (coinWan >= 10000) return 20;
return 10;
const coinWan = getCoinWan(item)
if (coinWan >= 30000) return 30
if (coinWan >= 10000) return 20
return 10
}
export function getEstimatedRentalDays(item: Listing) {
const coinM = getCoinM(item);
const dailyLossM = getDailyLossM(item);
if (coinM <= 0 || dailyLossM <= 0) return 0;
return coinM / dailyLossM;
const coinM = getCoinM(item)
const dailyLossM = getDailyLossM(item)
if (coinM <= 0 || dailyLossM <= 0) return 0
return coinM / dailyLossM
}
export function formatEstimatedRentalDuration(item: Listing) {
const days = getEstimatedRentalDays(item);
if (days <= 0) return "--";
return `${Math.max(1, Math.round(days))}`;
const days = getEstimatedRentalDays(item)
if (days <= 0) return '--'
return `${Math.max(1, Math.round(days))}`
}
export function readAssetString(item: Listing, key: string) {
const value = item.asset_summary?.[key];
return typeof value === "string" ? value : "";
const value = item.asset_summary?.[key]
return typeof value === 'string' ? value : ''
}
export function readAssetNumber(item: Listing, key: string) {
return readUnknownNumber(item.asset_summary?.[key]);
return readUnknownNumber(item.asset_summary?.[key])
}
function readUnknownNumber(value: unknown) {
if (typeof value === "number") return value;
if (typeof value === "string" && value.trim()) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : 0;
if (typeof value === 'number') return value
if (typeof value === 'string' && value.trim()) {
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : 0
}
return 0;
return 0
}
function readPriceBreakdownNumber(item: Listing, key: string) {
const priceBreakdown = item.asset_summary?.price_breakdown;
if (typeof priceBreakdown !== "object" || priceBreakdown === null) return 0;
return readUnknownNumber((priceBreakdown as Record<string, unknown>)[key]);
const priceBreakdown = item.asset_summary?.price_breakdown
if (typeof priceBreakdown !== 'object' || priceBreakdown === null) return 0
return readUnknownNumber((priceBreakdown as Record<string, unknown>)[key])
}
function readUnitPrice(priceText: string) {
const normalized = priceText.replace(//g, ",").trim();
const fractionMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元\s*\/\s*(\d+(?:\.\d+)?)/);
const normalized = priceText.replace(//g, ',').trim()
const fractionMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元\s*\/\s*(\d+(?:\.\d+)?)/)
if (fractionMatch) {
const amount = Number(fractionMatch[1]);
const count = Number(fractionMatch[2]);
return count > 0 ? amount / count : 0;
const amount = Number(fractionMatch[1])
const count = Number(fractionMatch[2])
return count > 0 ? amount / count : 0
}
const singleMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元/);
return singleMatch ? Number(singleMatch[1]) : 0;
const singleMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元/)
return singleMatch ? Number(singleMatch[1]) : 0
}
function formatInsuranceSlotText(value: string) {
const parts = value.split("*").map((item) => Number(item));
const rows = parts[0] || 0;
const cols = parts[1] || 0;
const parts = value.split('*').map(item => Number(item))
const rows = parts[0] || 0
const cols = parts[1] || 0
if (!Number.isFinite(rows) || !Number.isFinite(cols) || rows <= 0 || cols <= 0) {
return value;
return value
}
return `${rows * cols}`;
return `${rows * cols}`
}
function formatLevelShort(value: string, suffix: string) {
const level = value.match(/\d+/)?.[0];
return level ? `${level}${suffix}` : value;
const level = value.match(/\d+/)?.[0]
return level ? `${level}${suffix}` : value
}
function formatResourceShort(item: Listing, key: string, label: string) {
const quantity = getResourceQuantity(item, key);
return quantity > 0 ? `${quantity}${label}` : "";
const quantity = getResourceQuantity(item, key)
return quantity > 0 ? `${quantity}${label}` : ''
}
function roundMoney(value: number) {
return Math.round(value * 100) / 100;
return Math.round(value * 100) / 100
}
function formatRatioNumber(value: number) {
const rounded = roundMoney(value);
return rounded.toFixed(1);
const rounded = roundMoney(value)
return rounded.toFixed(1)
}
function formatCompactNumber(value: number) {
const rounded = Math.round(value * 10) / 10;
return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(1);
const rounded = Math.round(value * 10) / 10
return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(1)
}
+65 -26
View File
@@ -10,7 +10,17 @@ import type {
import type { DepositBreakdownItem, PublishForm, PublishPlatformPricing } from '@/types/publish'
export const dailyLossOptions = [10, 20, 30, 40, 50]
export const commonOnlineTimes = ['00:00', '08:00', '10:00', '12:00', '14:00', '18:00', '20:00', '22:00', '23:59']
export const commonOnlineTimes = [
'00:00',
'08:00',
'10:00',
'12:00',
'14:00',
'18:00',
'20:00',
'22:00',
'23:59',
]
/**
* 将金额四舍五入到角精度(0.1元)
@@ -58,7 +68,10 @@ export function isGridCardQuantityItem(item: { key: string; label: string }) {
return item.key === 'gridCard9' || item.label.includes('9格体验卡')
}
export function isQuantityItemDisabledForInsurance(item: { key: string; label: string }, seasonInsurance: string) {
export function isQuantityItemDisabledForInsurance(
item: { key: string; label: string },
seasonInsurance: string
) {
return seasonInsurance === '3*3' && isGridCardQuantityItem(item)
}
@@ -85,9 +98,9 @@ export function calculateRecommendedDeposit(options: {
}) {
const baseAmount = Number(options.depositRecommendConfig.base_amount || 0)
const skinAmount = options.depositRecommendConfig.skin_group_rules.reduce((sum, rule) => {
const group = options.skinGroups.find((item) => item.key === rule.group_key)
const group = options.skinGroups.find(item => item.key === rule.group_key)
if (!group) return sum
const selectedCount = group.options.filter((skin) => options.selectedSkins.includes(skin)).length
const selectedCount = group.options.filter(skin => options.selectedSkins.includes(skin)).length
return sum + selectedCount * Number(rule.amount_per_item || 0)
}, 0)
return roundMoney(baseAmount + skinAmount)
@@ -106,9 +119,9 @@ export function buildDepositBreakdownItems(options: {
},
]
for (const rule of options.depositRecommendConfig.skin_group_rules) {
const group = options.skinGroups.find((item) => item.key === rule.group_key)
const group = options.skinGroups.find(item => item.key === rule.group_key)
if (!group) continue
const count = group.options.filter((skin) => options.selectedSkins.includes(skin)).length
const count = group.options.filter(skin => options.selectedSkins.includes(skin)).length
if (count <= 0) continue
items.push({
label: rule.label,
@@ -129,7 +142,8 @@ export function calculateSellerReferenceRatio(options: {
dailyLossRatioAdjustment: number
}) {
const { coinMAmount, form, ratioConfig } = options
if (coinMAmount <= 0 || !form.season_insurance || !form.stamina_level || !form.load_level) return 0
if (coinMAmount <= 0 || !form.season_insurance || !form.stamina_level || !form.load_level)
return 0
const baseRatio = getInsuranceBaseRatio(ratioConfig, form.season_insurance)
if (baseRatio <= 0) return 0
return (
@@ -140,7 +154,11 @@ export function calculateSellerReferenceRatio(options: {
)
}
export function readFinalSaleRatio(defaultRatio: number, acceleratedSaleRatio: number | '', maxAcceleratedSaleRatio: number) {
export function readFinalSaleRatio(
defaultRatio: number,
acceleratedSaleRatio: number | '',
maxAcceleratedSaleRatio: number
) {
if (defaultRatio <= 0) return 0
if (!hasAcceleratedSaleRatioInput(acceleratedSaleRatio)) return defaultRatio
const ratio = Number(acceleratedSaleRatio)
@@ -167,14 +185,18 @@ export function calculatePlatformPricing(options: {
return buildPlatformPricing(
roundMoney(options.sellerCoinBasePrice + Number(fixedRule.markup_amount || 0)),
'fixed_markup',
options,
options
)
}
const ratioRule = findSaleRatioAdjustmentRule(options.salePriceConfig, options.coinMAmount)
const ratioSubtract = ratioRule ? Number(ratioRule.ratio_subtract || 0) : 0
const buyerRatio = options.sellerRatio - ratioSubtract
if (buyerRatio > 0 && ratioRule) {
return buildPlatformPricing(roundMoney(options.coinWanAmount / buyerRatio), 'ratio_subtract', options)
return buildPlatformPricing(
roundMoney(options.coinWanAmount / buyerRatio),
'ratio_subtract',
options
)
}
return buildPlatformPricing(options.sellerCoinBasePrice, 'none', options)
}
@@ -196,7 +218,7 @@ function buildPlatformPricing(
coinWanAmount: number
sellerTotalPrice: number
consumablePrice: number
},
}
): PublishPlatformPricing {
const buyerTotalPrice = roundMoney(buyerCoinBasePrice + options.consumablePrice)
return {
@@ -211,13 +233,17 @@ function buildPlatformPricing(
function findSaleFixedMarkupRule(config: PublishSalePriceConfig, coinMAmount: number) {
return [...config.fixed_markup_rules]
.sort((a, b) => a.min_m - b.min_m)
.find((item, index, rules) => isCoinInSaleRange(item, index, rules, coinMAmount, { includeLastMax: true }))
.find((item, index, rules) =>
isCoinInSaleRange(item, index, rules, coinMAmount, { includeLastMax: true })
)
}
function findSaleRatioAdjustmentRule(config: PublishSalePriceConfig, coinMAmount: number) {
return [...config.ratio_adjustment_rules]
.sort((a, b) => a.min_m - b.min_m)
.find((item, index, rules) => isCoinInSaleRange(item, index, rules, coinMAmount, { excludeFirstMin: true }))
.find((item, index, rules) =>
isCoinInSaleRange(item, index, rules, coinMAmount, { excludeFirstMin: true })
)
}
function isCoinInSaleRange(
@@ -225,13 +251,15 @@ function isCoinInSaleRange(
index: number,
rules: Array<{ min_m: number; max_m: number }>,
coinMAmount: number,
options: { includeLastMax?: boolean; excludeFirstMin?: boolean } = {},
options: { includeLastMax?: boolean; excludeFirstMin?: boolean } = {}
) {
const maxM = Number(item.max_m || 0)
const minM = Number(item.min_m || 0)
const minMatched = options.excludeFirstMin && index === 0 ? coinMAmount > minM : coinMAmount >= minM
const minMatched =
options.excludeFirstMin && index === 0 ? coinMAmount > minM : coinMAmount >= minM
const isLastRule = index === rules.length - 1
const maxMatched = maxM <= 0 || coinMAmount < maxM || (options.includeLastMax && isLastRule && coinMAmount <= maxM)
const maxMatched =
maxM <= 0 || coinMAmount < maxM || (options.includeLastMax && isLastRule && coinMAmount <= maxM)
return minMatched && maxMatched
}
@@ -240,8 +268,11 @@ function calculateEffectiveRatio(coinWanAmount: number, price: number) {
return roundRatio(coinWanAmount / price)
}
function getInsuranceBaseRatio(config: Pick<ListingPublishOptions['ratio_config'], 'insurance_base_ratios'>, insurance: string) {
return config.insurance_base_ratios.find((item) => item.insurance === insurance)?.ratio || 0
function getInsuranceBaseRatio(
config: Pick<ListingPublishOptions['ratio_config'], 'insurance_base_ratios'>,
insurance: string
) {
return config.insurance_base_ratios.find(item => item.insurance === insurance)?.ratio || 0
}
function calculateConfigPenalty(
@@ -251,7 +282,7 @@ function calculateConfigPenalty(
skinGroups: PublishOptionGroup[]
selectedSkins: string[]
levelOptions: string[]
},
}
) {
return config.config_items.reduce((sum, item) => {
return isRatioConfigItemMatched(item, options) ? sum : sum + Number(item.missing_penalty || 0)
@@ -265,10 +296,11 @@ function isRatioConfigItemMatched(
skinGroups: PublishOptionGroup[]
selectedSkins: string[]
levelOptions: string[]
},
}
) {
if (item.kind === 'skin_group') return hasSelectedSkinGroup(item.group_key || '', options)
if (item.kind === 'max_stamina') return isMaxLevel(options.form.stamina_level, options.levelOptions)
if (item.kind === 'max_stamina')
return isMaxLevel(options.form.stamina_level, options.levelOptions)
if (item.kind === 'max_load') return isMaxLevel(options.form.load_level, options.levelOptions)
return false
}
@@ -278,11 +310,11 @@ function hasSelectedSkinGroup(
options: {
skinGroups: PublishOptionGroup[]
selectedSkins: string[]
},
}
) {
const group = options.skinGroups.find((item) => item.key === groupKey)
const group = options.skinGroups.find(item => item.key === groupKey)
if (!group) return false
return group.options.some((skin) => options.selectedSkins.includes(skin))
return group.options.some(skin => options.selectedSkins.includes(skin))
}
function isMaxLevel(value: string, levelOptions: string[]) {
@@ -297,6 +329,13 @@ function readLevelNumber(value: string) {
return match ? Number(match[0]) : 0
}
function getCoinCorrection(config: Pick<ListingPublishOptions['ratio_config'], 'coin_corrections'>, coinM: number) {
return [...config.coin_corrections].sort((a, b) => b.threshold_m - a.threshold_m).find((item) => coinM > item.threshold_m)?.correction || 0
function getCoinCorrection(
config: Pick<ListingPublishOptions['ratio_config'], 'coin_corrections'>,
coinM: number
) {
return (
[...config.coin_corrections]
.sort((a, b) => b.threshold_m - a.threshold_m)
.find(item => coinM > item.threshold_m)?.correction || 0
)
}
@@ -60,6 +60,6 @@ export function getSystemConfigSelectOptions(key: string) {
}
export function formatSystemConfigSelectValue(key: string, value: string) {
const option = getSystemConfigSelectOptions(key)?.find((item) => item.value === value)
const option = getSystemConfigSelectOptions(key)?.find(item => item.value === value)
return option?.label || null
}
+4 -5
View File
@@ -6,11 +6,10 @@ export function formatDateTime(value: DateInput, fallback = '-') {
if (Number.isNaN(date.getTime())) {
return typeof value === 'string' && value.trim() ? value.replace('T', ' ') : fallback
}
return [
date.getFullYear(),
pad(date.getMonth() + 1),
pad(date.getDate()),
].join('-') + ` ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
return (
[date.getFullYear(), pad(date.getMonth() + 1), pad(date.getDate())].join('-') +
` ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
)
}
export function formatDateMinute(value: DateInput, fallback = '-') {