From f9007fdd08eb99647a0bebf1381b893ab482e103 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 18 Jul 2026 21:56:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B1=95=E7=A4=BA=E6=9E=AA=E7=9A=AE=E4=B8=8E?= =?UTF-8?q?=E7=BA=A2=E7=9A=AE=EF=BC=8C=E5=A4=9A=E7=9A=AE=E8=82=A4=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=82=AC=E6=B5=AE=E6=9F=A5=E7=9C=8B=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 列表按分组展示红皮/枪皮,超出预览数时悬浮显示全部;复制信息输出完整皮肤名单。 --- src/App.tsx | 116 ++++++++++++++++++++++++++++++++++-------- src/listingDisplay.ts | 71 +++++++++++++++++++++++--- src/styles.css | 86 +++++++++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 29 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 226c064..8054ba5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,7 +13,7 @@ import Pagination from "antd/es/pagination"; import Spin from "antd/es/spin"; import Tag from "antd/es/tag"; import Tooltip from "antd/es/tooltip"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState, type ReactNode } from "react"; import { fetchListingsPage, fetchMobileHomeConfig, @@ -30,6 +30,7 @@ import { formatListingInfoText, formatMoney, formatRatio, + formatSkinPreview, getConsumablePrice, getListingDeposit, getListingPrice, @@ -38,7 +39,7 @@ import { getRegions, getResourceQuantity, getResources, - getSkinNames, + getSkinGroup, readAssetNumber, readAssetString, } from "./listingDisplay"; @@ -730,27 +731,35 @@ function ListingRow({ codeCopied, infoCopied, item, onCopyCode, onCopyInfo }: Li const rentalDuration = formatEstimatedRentalDuration(item); const remark = getAssetText(item, "remark"); const regions = getRegions(item); - const skinNames = getSkinNames(item); + const redSkins = getSkinGroup(item, "operatorRed"); + const weaponSkins = getSkinGroup(item, "weapon"); const fireLevel = readAssetNumber(item, "fire_level"); const resources = getResources(item); const resourceTooltip = ( ); - const details = [ - ["哈夫币", formatCoin(item)], - ["保险格", readAssetString(item, "season_insurance") || "-"], - ["体力", readAssetString(item, "stamina_level") || "-"], - ["负重", readAssetString(item, "load_level") || "-"], - ["六头", String(getResourceQuantity(item, "helmet6") || "-")], - ["六甲", String(getResourceQuantity(item, "armor6") || "-")], - ["AW子弹", String(getResourceQuantity(item, "awmAmmo") || "-")], - ["绝密KD", String(readAssetNumber(item, "secret_kd") || "0.00")], - ["比例", formatRatio(item)], - ["每日消耗", formatDailyLoss(item)], - ["封禁记录", formatBanRecord(item)], - ["红皮", skinNames.slice(0, 2).join("、") || "-"], - ["登录方式", item.login_platform || "-"], - ["IP", regions[0] || item.server_region || "-"], + const details: Array<{ label: string; value: ReactNode }> = [ + { label: "哈夫币", value: formatCoin(item) }, + { label: "保险格", value: readAssetString(item, "season_insurance") || "-" }, + { label: "体力", value: readAssetString(item, "stamina_level") || "-" }, + { label: "负重", value: readAssetString(item, "load_level") || "-" }, + { label: "六头", value: String(getResourceQuantity(item, "helmet6") || "-") }, + { label: "六甲", value: String(getResourceQuantity(item, "armor6") || "-") }, + { label: "AW子弹", value: String(getResourceQuantity(item, "awmAmmo") || "-") }, + { label: "绝密KD", value: String(readAssetNumber(item, "secret_kd") || "0.00") }, + { label: "比例", value: formatRatio(item) }, + { label: "每日消耗", value: formatDailyLoss(item) }, + { label: "封禁记录", value: formatBanRecord(item) }, + { + label: "红皮", + value: , + }, + { + label: "枪皮", + value: , + }, + { label: "登录方式", value: item.login_platform || "-" }, + { label: "IP", value: regions[0] || item.server_region || "-" }, ]; return ( @@ -775,10 +784,14 @@ function ListingRow({ codeCopied, infoCopied, item, onCopyCode, onCopyInfo }: Li
- {details.map(([label, value]) => ( -
- {label} - {value} + {details.map((itemDetail) => ( +
+ {itemDetail.label} + {typeof itemDetail.value === "string" || typeof itemDetail.value === "number" ? ( + {itemDetail.value} + ) : ( + itemDetail.value + )}
))}
@@ -844,6 +857,65 @@ function ListingRow({ codeCopied, infoCopied, item, onCopyCode, onCopyInfo }: Li ); } +const SKIN_PREVIEW_LIMIT = 2; + +function SkinDetailValue({ label, names }: { label: string; names: string[] }) { + if (!names.length) { + return -; + } + + const preview = formatSkinPreview(names, SKIN_PREVIEW_LIMIT); + const hasMore = names.length > SKIN_PREVIEW_LIMIT; + + if (!hasMore) { + return ( + + {preview} + + ); + } + + return ( + } + > + + {names.slice(0, SKIN_PREVIEW_LIMIT).join("、")} + +{names.length - SKIN_PREVIEW_LIMIT} + + + ); +} + +function SkinTooltipContent({ label, names }: { label: string; names: string[] }) { + return ( +
+
+ {label} + 共 {names.length} 个 +
+
    + {names.map((name) => ( +
  • {name}
  • + ))} +
+
+ ); +} + function ResourceTooltipContent({ consumable, resources, diff --git a/src/listingDisplay.ts b/src/listingDisplay.ts index 7b8ecb1..026cd5c 100644 --- a/src/listingDisplay.ts +++ b/src/listingDisplay.ts @@ -138,7 +138,7 @@ export function formatResourceDetailLines(item: Listing) { }); } -/** 复制当前卡片展示的完整信息(非仅编号) */ +/** 复制当前卡片展示的完整信息(非仅编号);皮肤按分组完整输出 */ export function formatListingInfoText(item: Listing) { const code = formatListingCode(item); const title = item.title?.trim() || `纯币${formatCoin(item)}资源号`; @@ -147,11 +147,14 @@ export function formatListingInfoText(item: Listing) { const consumable = getConsumablePrice(item); const total = Math.round((price + deposit) * 100) / 100; const regions = getRegions(item); - const skinNames = getSkinNames(item); const remark = typeof item.asset_summary?.remark === "string" ? item.asset_summary.remark : ""; const online = getOnlineTimeText(item); const fireLevel = readAssetNumber(item, "fire_level"); const resourceLines = formatResourceDetailLines(item); + const redSkins = getSkinGroup(item, "operatorRed"); + const weaponSkins = getSkinGroup(item, "weapon"); + const meleeSkins = getSkinGroup(item, "melee"); + const goldSkins = getSkinGroup(item, "operatorGold"); const lines = [ `${title} 编号 ${code}`, @@ -164,7 +167,10 @@ export function formatListingInfoText(item: Listing) { `AW子弹:${getResourceQuantity(item, "awmAmmo") || "-"}`, `绝密KD:${readAssetNumber(item, "secret_kd") || "0.00"}`, `比例:${formatRatio(item)}`, - `红皮:${skinNames.slice(0, 2).join("、") || "-"}`, + `红皮:${formatSkinList(redSkins)}`, + `枪皮:${formatSkinList(weaponSkins)}`, + meleeSkins.length ? `刀皮:${formatSkinList(meleeSkins)}` : "", + goldSkins.length ? `金皮:${formatSkinList(goldSkins)}` : "", `登录方式:${item.login_platform || "-"}`, `IP:${regions[0] || item.server_region || "-"}`, `封禁记录:${formatBanRecord(item)}`, @@ -199,12 +205,63 @@ export function readAssetNumber(item: Listing, key: string) { return 0; } -export function getSkinNames(item: Listing) { +export interface ListingSkinGroup { + key: string; + title: string; + options: string[]; +} + +const SKIN_GROUP_TITLES: Record = { + melee: "刀皮", + operator: "干员", + operatorGold: "金皮", + operatorRed: "红皮", + weapon: "枪皮", +}; + +/** 读取某个皮肤分组(如 operatorRed / weapon / melee) */ +export function getSkinGroup(item: Listing, groupKey: string): string[] { const skinGroups = item.asset_summary?.skin_groups; if (typeof skinGroups !== "object" || skinGroups === null) return []; - return Object.values(skinGroups as Record) - .flatMap((group) => (Array.isArray(group) ? group : [])) - .filter((skin): skin is string => typeof skin === "string" && skin.trim() !== ""); + const value = (skinGroups as Record)[groupKey]; + if (!Array.isArray(value)) return []; + return value.filter((skin): skin is string => typeof skin === "string" && skin.trim() !== ""); +} + +/** 全部有数据的皮肤分组 */ +export function getListingSkinGroups(item: Listing): ListingSkinGroup[] { + const skinGroups = item.asset_summary?.skin_groups; + if (typeof skinGroups !== "object" || skinGroups === null) return []; + return Object.entries(skinGroups as Record) + .map(([key, value]) => ({ + key, + title: SKIN_GROUP_TITLES[key] || key, + options: Array.isArray(value) + ? value.filter((skin): skin is string => typeof skin === "string" && skin.trim() !== "") + : [], + })) + .filter((group) => group.options.length > 0); +} + +/** 扁平全部皮肤名(用于标签等) */ +export function getSkinNames(item: Listing) { + return getListingSkinGroups(item).flatMap((group) => group.options); +} + +/** 皮肤列表文案:无则 `-`,有则顿号拼接全部 */ +export function formatSkinList(names: string[]) { + if (!names.length) return "-"; + return names.join("、"); +} + +/** + * 列表预览文案:超过 limit 时截断并附 `+N` + * 例如 `蚀金玫瑰、水墨云图 +3` + */ +export function formatSkinPreview(names: string[], limit = 2) { + if (!names.length) return "-"; + if (names.length <= limit) return names.join("、"); + return `${names.slice(0, limit).join("、")} +${names.length - limit}`; } export function getRegions(item: Listing) { diff --git a/src/styles.css b/src/styles.css index 57c216a..d0134a0 100644 --- a/src/styles.css +++ b/src/styles.css @@ -724,6 +724,92 @@ input { white-space: nowrap; } +.detail-item .skin-value { + display: inline-block; + max-width: 100%; + vertical-align: top; + cursor: default; +} + +.detail-item .skin-value.has-more { + cursor: pointer; + color: #1f2a37; +} + +.detail-item .skin-value.has-more:hover { + color: var(--brand-dark); +} + +.detail-item .skin-more { + margin-left: 4px; + color: var(--brand); + font-size: 12px; + font-style: normal; + font-weight: 900; +} + +/* 红皮/枪皮完整列表悬浮卡片 */ +.skin-tooltip-root .ant-tooltip-container, +.skin-tooltip-root .ant-tooltip-inner { + width: 280px !important; + max-width: min(280px, calc(100vw - 24px)) !important; + padding: 0 !important; + overflow: hidden; + border-radius: 12px !important; + border: 1px solid #e8edf3 !important; + background: #fff !important; + color: var(--text-main) !important; + box-shadow: 0 12px 32px rgba(21, 32, 43, 0.14) !important; +} + +.skin-popover { + min-width: 0; + text-align: left; +} + +.skin-popover-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 11px 14px; + background: linear-gradient(180deg, #fff8f1, #fff); + border-bottom: 1px solid #f0f2f5; +} + +.skin-popover-head strong { + color: var(--text-main); + font-size: 13px; + font-weight: 900; +} + +.skin-popover-head span { + color: #98a2b3; + font-size: 12px; + font-weight: 700; +} + +.skin-popover-list { + margin: 0; + max-height: 260px; + overflow: auto; + padding: 8px 0; + list-style: none; +} + +.skin-popover-list li { + padding: 7px 14px; + color: #1f2a37; + font-size: 12px; + font-weight: 700; + line-height: 1.4; + word-break: break-all; +} + +.skin-popover-list li:hover { + background: #fafbfc; +} + .listing-meta { min-width: 0; display: flex;