From f1c0cb373069779960d2ac7c921dcfb02b12e1c5 Mon Sep 17 00:00:00 2001
From: zhaoxing <2558691827@qq.com>
Date: Thu, 12 Nov 2026 13:01:02 +0800
Subject: [PATCH] =?UTF-8?q?feat(listings):=20=E8=A1=A5=E5=85=85=E7=A7=9F?=
=?UTF-8?q?=E6=9C=9F=E3=80=81=E4=BA=BA=E8=84=B8=E3=80=81=E5=B0=81=E7=A6=81?=
=?UTF-8?q?=E4=B8=8E=E6=94=B9=E5=90=8D=E5=B1=95=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../features/listings/ListingDetailPage.tsx | 15 ++++++-
.../src/features/listings/ListingListPage.tsx | 24 ++++++++++-
frontend/src/features/listings/labels.ts | 43 +++++++++++++++++++
.../src/features/mobile/MobileHomePage.tsx | 16 ++++++-
.../mobile/MobileListingDetailPage.tsx | 41 +++++++++++++++++-
.../seller/SellerListingDetailPage.tsx | 10 ++++-
frontend/src/styles/mobile-listing.css | 3 ++
7 files changed, 146 insertions(+), 6 deletions(-)
diff --git a/frontend/src/features/listings/ListingDetailPage.tsx b/frontend/src/features/listings/ListingDetailPage.tsx
index 81e62f5..93b8633 100644
--- a/frontend/src/features/listings/ListingDetailPage.tsx
+++ b/frontend/src/features/listings/ListingDetailPage.tsx
@@ -24,6 +24,12 @@ import { Link, useNavigate, useParams } from 'react-router-dom'
import { BrandLogo } from '@/components/BrandLogo'
import { useListingDetail } from '@/features/listings/api'
+import {
+ banRecordText,
+ faceOwnerText,
+ gameNameChangeText,
+ listingRentalDays,
+} from '@/features/listings/labels'
import { recordListingView, useListingCollections } from '@/features/listings/collections'
import {
addRemoteFavorite,
@@ -132,6 +138,10 @@ function coreStats(item: Listing) {
const summary = item.asset_summary ?? {}
const dailyLoss = Number(summary.daily_loss_m ?? 0)
const secretKd = Number(summary.secret_kd ?? 0)
+ const rentalDays = listingRentalDays(item)
+ const faceOwner = faceOwnerText(summary)
+ const banRecord = banRecordText(summary)
+ const gameNameChange = gameNameChangeText(summary)
return [
{ label: '哈夫币', value: formatCoinM(item.haf_coin_amount) },
{ label: '保险格数', value: textOf(summary.season_insurance) },
@@ -140,12 +150,15 @@ function coreStats(item: Listing) {
{ label: '体力', value: textOf(summary.stamina_level) },
{ label: '负重', value: textOf(summary.load_level) },
{ label: '日消耗', value: dailyLoss > 0 ? `${dailyLoss} M / 天` : '' },
+ { label: '预计租期', value: rentalDays ? `${rentalDays} 天` : '' },
{ label: '绝密 KD', value: secretKd > 0 ? String(secretKd) : '' },
{ label: '段位', value: item.rank_level },
{ label: '登录方式', value: item.login_platform },
{ label: '区服', value: item.server_region },
{ label: '在线时段', value: onlineTimeText(summary.online_time) },
- { label: '可改游戏名', value: textOf(summary.modifiable_name) },
+ { label: '可改游戏名', value: gameNameChange },
+ { label: '封禁记录', value: banRecord },
+ { label: '人脸情况', value: faceOwner },
{ label: '全英雄', value: textOf(summary.all_heroes) },
{ label: '凌晨响应', value: textOf(summary.early_morning_response) },
].filter(stat => stat.value)
diff --git a/frontend/src/features/listings/ListingListPage.tsx b/frontend/src/features/listings/ListingListPage.tsx
index 8bdf7d3..31cd97c 100644
--- a/frontend/src/features/listings/ListingListPage.tsx
+++ b/frontend/src/features/listings/ListingListPage.tsx
@@ -27,7 +27,13 @@ import { Link } from 'react-router-dom'
import { BrandLogo } from '@/components/BrandLogo'
import { HomePortalRow } from '@/features/home/HomePortalRow'
import { useListings } from '@/features/listings/api'
-import { onlineTimeText } from '@/features/listings/labels'
+import {
+ banRecordText,
+ faceOwnerText,
+ gameNameChangeText,
+ listingRentalDays,
+ onlineTimeText,
+} from '@/features/listings/labels'
import {
awmRanges,
coinRanges,
@@ -131,6 +137,10 @@ function ListingCover({ item }: { item: Listing }) {
}
function ListingRow({ item }: { item: Listing }) {
+ const rentalDays = listingRentalDays(item)
+ const faceOwner = faceOwnerText(item.asset_summary)
+ const banRecord = banRecordText(item.asset_summary)
+ const gameNameChange = gameNameChangeText(item.asset_summary)
const specs = [
{ label: '哈夫币', value: formatCoinM(item.haf_coin_amount) },
{ label: '保险', value: assetText(item, 'season_insurance') },
@@ -140,6 +150,10 @@ function ListingRow({ item }: { item: Listing }) {
{ label: '登录', value: item.login_platform },
{ label: '区服', value: item.server_region },
{ label: '在线', value: onlineTimeText(item.asset_summary) },
+ { label: '预计租期', value: rentalDays ? `${rentalDays} 天` : '' },
+ { label: '人脸', value: faceOwner },
+ { label: '封禁', value: banRecord },
+ { label: '改名', value: gameNameChange },
{ label: '折算比', value: ratioText(item) ? `1元≈${ratioText(item)}` : '' },
].filter(spec => spec.value)
const skins = skinNames(item)
@@ -230,6 +244,10 @@ function ListingGridCover({ item }: { item: Listing }) {
function ListingGridCard({ item }: { item: Listing }) {
const online = onlineTimeText(item.asset_summary)
+ const rentalDays = listingRentalDays(item)
+ const faceOwner = faceOwnerText(item.asset_summary)
+ const banRecord = banRecordText(item.asset_summary)
+ const gameNameChange = gameNameChangeText(item.asset_summary)
return (
}>
@@ -247,6 +265,10 @@ function ListingGridCard({ item }: { item: Listing }) {
{item.login_platform || '登录方式待定'}
{item.rank_level ? {item.rank_level} : null}
{online ? 在线 {online} : null}
+ {rentalDays ? 预计租期 {rentalDays} 天 : null}
+ {faceOwner ? 人脸 {faceOwner} : null}
+ {banRecord ? 封禁 {banRecord} : null}
+ {gameNameChange ? 改名 {gameNameChange} : null}
哈夫币 {formatCoinM(item.haf_coin_amount)}
diff --git a/frontend/src/features/listings/labels.ts b/frontend/src/features/listings/labels.ts
index 4ab1764..ba13093 100644
--- a/frontend/src/features/listings/labels.ts
+++ b/frontend/src/features/listings/labels.ts
@@ -47,6 +47,10 @@ const assetFieldLabels: Record = {
skin_names: '皮肤名称',
online_time: '在线时间',
online_time_text: '在线时间',
+ face_owner: '人脸情况',
+ ban_record: '封禁记录',
+ can_change_game_name: '可改游戏名',
+ modifiable_name: '可改游戏名',
rank_level: '段位',
haf_coin_amount: '哈夫币',
}
@@ -83,6 +87,45 @@ export function onlineTimeText(summary?: Record | null): string
return `${start} - ${end}`
}
+export function faceOwnerText(summary?: Record | null): string {
+ const value = summary?.face_owner
+ if (typeof value !== 'string') return ''
+ return value.trim()
+}
+
+export function banRecordText(summary?: Record | null): string {
+ const value = summary?.ban_record
+ if (typeof value !== 'string') return ''
+ const text = value.trim()
+ if (!text) return ''
+ if (text.includes('无') || text === '否') return '无封禁'
+ if (text.includes('有') || text === '是') return '有封禁'
+ return text
+}
+
+export function gameNameChangeText(summary?: Record | null): string {
+ const primary = summary?.can_change_game_name
+ const fallback = summary?.modifiable_name
+ const raw = typeof primary === 'string' && primary.trim() ? primary : fallback
+ if (typeof raw !== 'string') return ''
+ const text = raw.trim()
+ if (!text) return ''
+ if (text === '是') return '可改'
+ if (text === '否') return '不可改'
+ return text
+}
+
+export function listingRentalDays(item: {
+ haf_coin_amount: number
+ asset_summary?: Record
+}): number | null {
+ const coinM = Number(item.haf_coin_amount) / 1_000_000
+ const dailyLoss = Number(item.asset_summary?.daily_loss_m ?? 0)
+ if (!Number.isFinite(coinM) || coinM <= 0) return null
+ if (!Number.isFinite(dailyLoss) || dailyLoss <= 0) return null
+ return Math.max(1, Math.ceil(coinM / dailyLoss))
+}
+
export function formatAssetValue(key: string, value: unknown): string {
if (value == null || value === '') return '—'
if (key === 'online_time') {
diff --git a/frontend/src/features/mobile/MobileHomePage.tsx b/frontend/src/features/mobile/MobileHomePage.tsx
index 2fda0a9..9aa30a0 100644
--- a/frontend/src/features/mobile/MobileHomePage.tsx
+++ b/frontend/src/features/mobile/MobileHomePage.tsx
@@ -15,7 +15,13 @@ import { Link } from 'react-router-dom'
import { BrandLogo } from '@/components/BrandLogo'
import { useListings } from '@/features/listings/api'
-import { onlineTimeText } from '@/features/listings/labels'
+import {
+ banRecordText,
+ faceOwnerText,
+ gameNameChangeText,
+ listingRentalDays,
+ onlineTimeText,
+} from '@/features/listings/labels'
import {
coinRanges,
emptyListingFilters,
@@ -90,11 +96,19 @@ function MobileChipGroup({
function MobileListingCard({ item }: { item: Listing }) {
const online = onlineTimeText(item.asset_summary)
+ const rentalDays = listingRentalDays(item)
+ const faceOwner = faceOwnerText(item.asset_summary)
+ const banRecord = banRecordText(item.asset_summary)
+ const gameNameChange = gameNameChangeText(item.asset_summary)
const specs = [
assetText(item, 'season_insurance') ? `保险 ${assetText(item, 'season_insurance')}` : '',
assetText(item, 'stamina_level') ? `体力 ${assetText(item, 'stamina_level')}` : '',
assetText(item, 'load_level') ? `负重 ${assetText(item, 'load_level')}` : '',
online ? `在线 ${online}` : '',
+ rentalDays ? `预计租期 ${rentalDays} 天` : '',
+ faceOwner ? `人脸 ${faceOwner}` : '',
+ banRecord ? `封禁 ${banRecord}` : '',
+ gameNameChange ? `改名 ${gameNameChange}` : '',
].filter(Boolean)
return (
diff --git a/frontend/src/features/mobile/MobileListingDetailPage.tsx b/frontend/src/features/mobile/MobileListingDetailPage.tsx
index 52a7756..cca41d8 100644
--- a/frontend/src/features/mobile/MobileListingDetailPage.tsx
+++ b/frontend/src/features/mobile/MobileListingDetailPage.tsx
@@ -5,6 +5,12 @@ import { useNavigate, useParams } from 'react-router-dom'
import { BrandLogo } from '@/components/BrandLogo'
import { useListingDetail } from '@/features/listings/api'
+import {
+ banRecordText,
+ faceOwnerText,
+ gameNameChangeText,
+ listingRentalDays,
+} from '@/features/listings/labels'
import { ensureSupportConversation } from '@/features/messages/api'
import { createOrder, useOrderAgreements, usePostRentalNotice } from '@/features/orders/api'
import { useSessionStore } from '@/stores/session'
@@ -143,8 +149,17 @@ export function MobileListingDetailPage() {
? (data.asset_summary.online_time as unknown)
: undefined
const assetEntries = Object.entries(data.asset_summary || {}).filter(
- ([key]) => key !== 'online_time'
+ ([key]) =>
+ key !== 'online_time' &&
+ key !== 'face_owner' &&
+ key !== 'ban_record' &&
+ key !== 'can_change_game_name' &&
+ key !== 'modifiable_name'
)
+ const rentalDays = listingRentalDays(data)
+ const faceOwner = faceOwnerText(data.asset_summary)
+ const banRecord = banRecordText(data.asset_summary)
+ const gameNameChange = gameNameChangeText(data.asset_summary)
const isOwner = user?.id === data.owner_id
return (
@@ -210,6 +225,30 @@ export function MobileListingDetailPage() {
未填写
)}
+ {faceOwner ? (
+
+
人脸情况
+ {faceOwner}
+
+ ) : null}
+ {banRecord ? (
+
+
封禁记录
+ {banRecord}
+
+ ) : null}
+ {gameNameChange ? (
+
+
可改游戏名
+ {gameNameChange}
+
+ ) : null}
+ {rentalDays ? (
+
+
预计租期
+ {rentalDays} 天
+
+ ) : null}
在线时间
{assetValue(onlineTime)}
diff --git a/frontend/src/features/seller/SellerListingDetailPage.tsx b/frontend/src/features/seller/SellerListingDetailPage.tsx
index 8efff63..46d6a9d 100644
--- a/frontend/src/features/seller/SellerListingDetailPage.tsx
+++ b/frontend/src/features/seller/SellerListingDetailPage.tsx
@@ -24,7 +24,12 @@ import {
} from '@ant-design/icons'
import { useNavigate, useParams } from 'react-router-dom'
-import { listingReviewStatusMeta, listingStatusMeta } from '@/features/listings/labels'
+import {
+ faceOwnerText,
+ gameNameChangeText,
+ listingReviewStatusMeta,
+ listingStatusMeta,
+} from '@/features/listings/labels'
import type { StatusMeta } from '@/features/admin/labels'
import { useSessionStore } from '@/stores/session'
import { confirmModal } from '@/shared/utils/confirm'
@@ -328,8 +333,9 @@ export function SellerListingDetailPage({ mobile = false }: { mobile?: boolean }
['哈夫币', coinText(listing)],
['每日损耗', summary.daily_loss_m ? `${numberValue(summary.daily_loss_m)}M/天` : '—'],
['在线时间', onlineTimeText(summary)],
+ ['人脸情况', faceOwnerText(summary) || '—'],
['凌晨响应', stringValue(summary.early_morning_response) || '—'],
- ['可改游戏名', stringValue(summary.can_change_game_name) || '—'],
+ ['可改游戏名', gameNameChangeText(summary) || '—'],
['全英雄', stringValue(summary.all_hero) || '—'],
['解锁德穆兰', stringValue(summary.unlock_demulan) || '—'],
['下赛季通行证', stringValue(summary.next_season_pass) || '—'],
diff --git a/frontend/src/styles/mobile-listing.css b/frontend/src/styles/mobile-listing.css
index a58c015..a663c20 100644
--- a/frontend/src/styles/mobile-listing.css
+++ b/frontend/src/styles/mobile-listing.css
@@ -133,6 +133,7 @@
padding: 10px 0;
border-bottom: 1px solid var(--line);
font-size: 12px;
+ min-width: 0;
}
.mobile-detail__assets dt {
@@ -143,6 +144,8 @@
margin: 0;
color: var(--ink);
text-align: right;
+ min-width: 0;
+ overflow-wrap: anywhere;
}
.mobile-detail__notice {