257 lines
8.5 KiB
TypeScript
257 lines
8.5 KiB
TypeScript
import type { Listing } from "@/api/listings";
|
|
|
|
export interface ListingDisplayChip {
|
|
label: string;
|
|
value: string;
|
|
}
|
|
|
|
export interface ListingDisplayResource {
|
|
key: string;
|
|
label: string;
|
|
quantity: number;
|
|
mode: string;
|
|
}
|
|
|
|
export function getCoinWan(item: Listing) {
|
|
const assetCoin = readAssetNumber(item, "haf_coin_wan");
|
|
if (assetCoin > 0) return assetCoin;
|
|
return Math.round(Number(item.haf_coin_amount || 0) / 10000);
|
|
}
|
|
|
|
export function getCoinM(item: Listing) {
|
|
return getCoinWan(item) / 100;
|
|
}
|
|
|
|
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`;
|
|
}
|
|
|
|
export function getListingDisplayPrice(item: Listing) {
|
|
const assetPrice = readAssetNumber(item, "price");
|
|
if (assetPrice > 0) return assetPrice;
|
|
const rentDays = getRentDays(item);
|
|
if (rentDays > 0 && item.price_daily > 0) {
|
|
return roundMoney(item.price_daily * rentDays);
|
|
}
|
|
return Number(item.price_daily || item.price_hourly || 0);
|
|
}
|
|
|
|
export function getRentDays(item: Listing) {
|
|
const assetDays = readAssetNumber(item, "rent_days");
|
|
if (assetDays > 0) return assetDays;
|
|
if (item.max_rent_hours >= 24) return Math.max(Math.round(item.max_rent_hours / 24), 1);
|
|
return 1;
|
|
}
|
|
|
|
export function getRatioValue(item: Listing) {
|
|
const ratio = readAssetNumber(item, "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:${Math.round(ratio)}` : "--";
|
|
}
|
|
|
|
export function getValuePerYuanText(item: Listing) {
|
|
const price = getListingDisplayPrice(item);
|
|
if (price <= 0) return "";
|
|
return `1元=${Math.round(getCoinWan(item) / price)}w哈夫币`;
|
|
}
|
|
|
|
export function getLoginMethod(item: Listing) {
|
|
return normalizeLoginMethod(readAssetString(item, "login_method") || item.login_platform);
|
|
}
|
|
|
|
export function getServerRegion(item: Listing) {
|
|
return normalizeServerRegion(item.server_region);
|
|
}
|
|
|
|
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", "六头"),
|
|
...getSkinNames(item).slice(0, 4),
|
|
].filter(Boolean);
|
|
return parts.join("/");
|
|
}
|
|
|
|
export function getListingSubtitle(item: Listing) {
|
|
return [getValuePerYuanText(item), `租期${getRentDays(item)}天`]
|
|
.filter(Boolean)
|
|
.join(" ");
|
|
}
|
|
|
|
export function getListingChips(item: Listing): ListingDisplayChip[] {
|
|
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");
|
|
if (awmAmmo > 0) {
|
|
chips.push({ label: "AWM", value: `${awmAmmo}发` });
|
|
}
|
|
if (totalAsset > 0) {
|
|
chips.push({ label: "总资产", value: formatHafCoinM(totalAsset) });
|
|
}
|
|
const online = getOnlineTimeText(item);
|
|
if (online) {
|
|
chips.push({ label: "方便上号", value: online });
|
|
}
|
|
return chips.filter((chip) => chip.value);
|
|
}
|
|
|
|
export function getListingResources(item: Listing): ListingDisplayResource[] {
|
|
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>;
|
|
return {
|
|
key: typeof row.key === "string" ? row.key : "",
|
|
label: typeof row.label === "string" ? row.label : "",
|
|
quantity: readUnknownNumber(row.quantity),
|
|
mode: typeof row.mode === "string" ? row.mode : "",
|
|
};
|
|
})
|
|
.filter((resource): resource is ListingDisplayResource => {
|
|
return Boolean(resource?.key && resource.label && resource.quantity > 0);
|
|
});
|
|
}
|
|
|
|
export function getResourceQuantity(item: Listing, resourceKey: string) {
|
|
const keys = getResourceKeyAliases(resourceKey);
|
|
return getListingResources(item).find((resource) => keys.includes(resource.key))?.quantity || 0;
|
|
}
|
|
|
|
export function hasGiftResources(item: Listing) {
|
|
return getListingResources(item).some((resource) => resource.mode === "赠送");
|
|
}
|
|
|
|
export function getSkinGroup(item: Listing, groupKey: string) {
|
|
const skinGroups = item.asset_summary?.skin_groups;
|
|
if (
|
|
typeof skinGroups !== "object" ||
|
|
skinGroups === null ||
|
|
!Array.isArray((skinGroups as Record<string, unknown>)[groupKey])
|
|
) {
|
|
return [];
|
|
}
|
|
return ((skinGroups as Record<string, unknown>)[groupKey] as unknown[]).filter(
|
|
(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 Object.values(skinGroups as Record<string, unknown>)
|
|
.flatMap((group) => (Array.isArray(group) ? group : []))
|
|
.filter((skin): skin is string => typeof skin === "string");
|
|
}
|
|
const skins = item.asset_summary?.skins;
|
|
return Array.isArray(skins) ? skins.filter((skin): skin is string => typeof skin === "string") : [];
|
|
}
|
|
|
|
export function assetRegions(item: Listing) {
|
|
const regions = item.asset_summary?.common_regions;
|
|
return Array.isArray(regions)
|
|
? regions.filter((region): region is string => typeof region === "string")
|
|
: [];
|
|
}
|
|
|
|
export function getScreenshotMap(item: Listing) {
|
|
const screenshots = item.asset_summary?.screenshots;
|
|
return typeof screenshots === "object" && screenshots !== null
|
|
? (screenshots as Record<string, unknown>)
|
|
: {};
|
|
}
|
|
|
|
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 "";
|
|
return `${start.replace(":00", "")}-${end.replace(":00", "")}点`;
|
|
}
|
|
|
|
export function readAssetString(item: Listing, key: string) {
|
|
const value = item.asset_summary?.[key];
|
|
return typeof value === "string" ? value : "";
|
|
}
|
|
|
|
export function readAssetNumber(item: Listing, key: string) {
|
|
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;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
export function normalizeServerRegion(value: string) {
|
|
const text = value.trim();
|
|
const upper = text.toUpperCase();
|
|
if (upper.includes("STEAM")) return "Steam";
|
|
if (upper.includes("QQ")) return "QQ";
|
|
if (text.includes("微信")) return "微信";
|
|
return text;
|
|
}
|
|
|
|
export function normalizeLoginMethod(value: string) {
|
|
const text = value.trim();
|
|
if (!text) return "";
|
|
if (text.includes("扫码")) return "扫码";
|
|
if (text.includes("账密") || text.includes("账号密码")) return "账号密码";
|
|
return text;
|
|
}
|
|
|
|
function formatInsuranceSlotText(value: string) {
|
|
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 `${rows * cols}格`;
|
|
}
|
|
|
|
function formatLevelShort(value: string, suffix: string) {
|
|
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}` : "";
|
|
}
|
|
|
|
function getResourceKeyAliases(key: string) {
|
|
if (key === "level6Ammo") return ["level6Ammo", "armor6Ammo"];
|
|
if (key === "armor6Ammo") return ["armor6Ammo", "level6Ammo"];
|
|
return [key];
|
|
}
|
|
|
|
function roundMoney(value: number) {
|
|
return Math.round(value * 100) / 100;
|
|
}
|