feat(listing): 展示本人人脸信息
This commit is contained in:
@@ -758,6 +758,16 @@
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.spec-item.face-owner-warning {
|
||||
border-color: #fca5a5;
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.spec-item.face-owner-warning .spec-label,
|
||||
.spec-item.face-owner-warning .spec-val {
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.copyable-val {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -51,11 +51,13 @@ import {
|
||||
formatListingCode,
|
||||
formatHafCoinM,
|
||||
getCoinWan,
|
||||
getFaceOwnerText,
|
||||
getListingConsumablePrice,
|
||||
getListingResources,
|
||||
getOnlineTimeText,
|
||||
getResourceQuantity,
|
||||
getSkinNames,
|
||||
isNonOwnFace,
|
||||
readAssetNumber,
|
||||
readAssetString,
|
||||
} from '@/shared/utils/listingDisplay'
|
||||
@@ -1388,6 +1390,13 @@ async function handleScreenshotSaved(payload: { oldUrl: string; newUrl: string }
|
||||
{{ assetText(selectedListing, 'load_level') }}
|
||||
</strong>
|
||||
</div>
|
||||
<div
|
||||
class="spec-item"
|
||||
:class="{ 'face-owner-warning': isNonOwnFace(selectedListing) }"
|
||||
>
|
||||
<span class="spec-label">是否本人人脸</span>
|
||||
<strong class="spec-val">{{ getFaceOwnerText(selectedListing) }}</strong>
|
||||
</div>
|
||||
<div
|
||||
class="spec-item spec-item--full"
|
||||
:class="{ 'ban-warning': hasBanRecord(selectedListing) }"
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
formatRatio,
|
||||
getCoinWan,
|
||||
getDailyLoss,
|
||||
getFaceOwnerText,
|
||||
getListingRentPrice,
|
||||
getResourceQuantity,
|
||||
getSkinGroup,
|
||||
@@ -150,6 +151,7 @@ const screenshotColumns = [
|
||||
width: 420,
|
||||
read: (row: Listing) => characterAndWeaponSkinText(row),
|
||||
},
|
||||
{ label: '人脸', width: 60, read: (row: Listing) => faceOwnerText(row) },
|
||||
{ label: '全英雄', width: 74, read: (row: Listing) => allHeroText(row) },
|
||||
{ label: '号主备注', width: 160, read: (row: Listing) => ownerRemarkText(row) },
|
||||
{ label: '租客纯币价格', width: 105, read: (row: Listing) => renterPureCoinPriceText(row) },
|
||||
@@ -607,6 +609,10 @@ function ownerRemarkText(row: Listing) {
|
||||
return remark || '-'
|
||||
}
|
||||
|
||||
function faceOwnerText(row: Listing) {
|
||||
return getFaceOwnerText(row)
|
||||
}
|
||||
|
||||
/** 全英雄为发布必填项;老数据缺失时显示占位符。 */
|
||||
function allHeroText(row: Listing) {
|
||||
const value = assetText(row, 'all_hero')
|
||||
@@ -996,6 +1002,13 @@ function formatQuantity(value: number) {
|
||||
{{ characterAndWeaponSkinText(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="人脸" width="60">
|
||||
<template #default="{ row }">
|
||||
<span :class="{ 'face-owner-no': faceOwnerText(row) === '否' }">
|
||||
{{ faceOwnerText(row) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="全英雄" width="74">
|
||||
<template #default="{ row }">{{ allHeroText(row) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
getDailyLoss,
|
||||
getListingDisplayPrice,
|
||||
formatListingCode,
|
||||
getFaceOwnerText,
|
||||
getListingChips,
|
||||
getListingResources,
|
||||
getListingSkinGroups,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
formatHafCoinM,
|
||||
readAssetString,
|
||||
isTopTierInsurance,
|
||||
isNonOwnFace,
|
||||
type ListingDisplayResource,
|
||||
} from '@/shared/utils/listingDisplay'
|
||||
|
||||
@@ -39,6 +41,7 @@ const listingCode = computed(() => formatListingCode(props.listing))
|
||||
const dailyLoss = computed(() => getDailyLoss(props.listing))
|
||||
const rentalDuration = computed(() => formatEstimatedRentalDuration(props.listing))
|
||||
const ratioValue = computed(() => getRatioValue(props.listing))
|
||||
const faceOwner = computed(() => getFaceOwnerText(props.listing))
|
||||
|
||||
// 核心三大指标(突出买家最关心的资产)
|
||||
const hafCoinDisplay = computed(() => formatHafCoinM(getCoinWan(props.listing)))
|
||||
@@ -154,6 +157,10 @@ function formatStatNumber(value: number) {
|
||||
<span class="aux-label">消耗</span>
|
||||
<strong class="aux-val">{{ dailyLoss }}/天</strong>
|
||||
</span>
|
||||
<span class="aux-badge face-badge" :class="{ 'is-non-owner': isNonOwnFace(listing) }">
|
||||
<span class="aux-label">本人人脸</span>
|
||||
<strong class="aux-val">{{ faceOwner }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="key-assets-strip">
|
||||
@@ -571,6 +578,27 @@ function formatStatNumber(value: number) {
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
/* 人脸归属:本人使用蓝色,非本人使用红色提醒 */
|
||||
.face-badge {
|
||||
background: #eff6ff;
|
||||
border-color: #dbeafe;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.face-badge .aux-label {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.face-badge.is-non-owner {
|
||||
background: #fef2f2;
|
||||
border-color: #fecaca;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.face-badge.is-non-owner .aux-label {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
/* 核心资产三大卡片(统一家底灰阶底色,品牌橙为主高光,整体干净高级) */
|
||||
.key-assets-strip {
|
||||
display: flex;
|
||||
|
||||
@@ -294,16 +294,16 @@
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 核心资产:有币比时五个并排,没有币比时四个并排 */
|
||||
/* 核心资产:有币比时六个并排,没有币比时五个并排 */
|
||||
.core-assets-bar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.core-assets-bar.has-ratio {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.asset-tile {
|
||||
@@ -336,6 +336,25 @@
|
||||
border-color: #fdba74;
|
||||
}
|
||||
|
||||
.face-tile {
|
||||
background: #eff6ff;
|
||||
border-color: #dbeafe;
|
||||
}
|
||||
|
||||
.face-tile .tile-value {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.face-tile.is-non-owner {
|
||||
background: #fef2f2;
|
||||
border-color: #fecaca;
|
||||
}
|
||||
|
||||
.face-tile.is-non-owner .tile-label,
|
||||
.face-tile.is-non-owner .tile-value {
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.tile-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
@@ -1189,22 +1208,17 @@
|
||||
|
||||
.core-assets-bar,
|
||||
.core-assets-bar.has-ratio {
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.core-assets-bar .asset-tile {
|
||||
grid-column: span 3;
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.core-assets-bar.has-ratio .asset-tile {
|
||||
.core-assets-bar .face-tile {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.core-assets-bar.has-ratio .rental-tile,
|
||||
.core-assets-bar.has-ratio .loss-tile {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.specs-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
formatRatio,
|
||||
getCoinWan,
|
||||
getDailyLoss,
|
||||
getFaceOwnerText,
|
||||
getListingConsumablePrice,
|
||||
getListingChips,
|
||||
getListingDisplayPrice,
|
||||
@@ -40,6 +41,7 @@ import {
|
||||
readAssetString,
|
||||
getRatioValue,
|
||||
isTopTierInsurance,
|
||||
isNonOwnFace,
|
||||
getScreenshotThumbURL,
|
||||
getScreenshotMediumURL,
|
||||
getScreenshotRawURL,
|
||||
@@ -138,6 +140,11 @@ const dailyLoss = computed(() => {
|
||||
return getDailyLoss(listing.value)
|
||||
})
|
||||
|
||||
const faceOwner = computed(() => {
|
||||
if (!listing.value) return '--'
|
||||
return getFaceOwnerText(listing.value)
|
||||
})
|
||||
|
||||
// 平台/区服特性徽标
|
||||
const accessTags = computed(() => {
|
||||
if (!listing.value) return []
|
||||
@@ -526,6 +533,14 @@ function goBack() {
|
||||
<strong class="tile-value text-amber">{{ ratioDisplay }}</strong>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="asset-tile face-tile"
|
||||
:class="{ 'is-non-owner': listing && isNonOwnFace(listing) }"
|
||||
>
|
||||
<span class="tile-label">是否本人人脸</span>
|
||||
<strong class="tile-value">{{ faceOwner }}</strong>
|
||||
</div>
|
||||
|
||||
<div class="asset-tile rental-tile">
|
||||
<span class="tile-label">可租</span>
|
||||
<strong class="tile-value text-green">{{ rentalDuration }}</strong>
|
||||
|
||||
@@ -938,6 +938,18 @@
|
||||
border: 1px solid #fef3c7;
|
||||
}
|
||||
|
||||
.aux-pill.face {
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
border: 1px solid #dbeafe;
|
||||
}
|
||||
|
||||
.aux-pill.face.is-non-owner {
|
||||
background: #fef2f2;
|
||||
color: #b91c1c;
|
||||
border-color: #fecaca;
|
||||
}
|
||||
|
||||
/* 中部:封面 + 信息流 */
|
||||
.mobile-card-body {
|
||||
display: grid;
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
getListingChips,
|
||||
getListingDisplayPrice,
|
||||
getDailyLoss,
|
||||
getFaceOwnerText,
|
||||
getRatioValue,
|
||||
formatListingCode,
|
||||
formatEstimatedRentalDuration,
|
||||
@@ -63,6 +64,7 @@ import {
|
||||
hasAcceleratedSaleRatio,
|
||||
hasGiftResources,
|
||||
isTopTierInsurance,
|
||||
isNonOwnFace,
|
||||
getCoinWan,
|
||||
formatHafCoinM,
|
||||
readAssetString,
|
||||
@@ -1146,6 +1148,9 @@ if (!restoreHomeConfigFromCache()) {
|
||||
<span v-if="getDailyLoss(item)" class="aux-pill loss">
|
||||
耗 {{ getDailyLoss(item) }}/天
|
||||
</span>
|
||||
<span class="aux-pill face" :class="{ 'is-non-owner': isNonOwnFace(item) }">
|
||||
人脸 {{ getFaceOwnerText(item) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@
|
||||
|
||||
.core-assets-bar.has-ratio .rental-tile,
|
||||
.core-assets-bar.has-ratio .loss-tile {
|
||||
grid-column: span 3;
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.asset-tile {
|
||||
@@ -323,6 +323,26 @@
|
||||
border-color: #fdba74;
|
||||
}
|
||||
|
||||
.face-tile {
|
||||
grid-column: span 2;
|
||||
background: #eff6ff;
|
||||
border-color: #dbeafe;
|
||||
}
|
||||
|
||||
.face-tile .tile-value {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.face-tile.is-non-owner {
|
||||
background: #fef2f2;
|
||||
border-color: #fecaca;
|
||||
}
|
||||
|
||||
.face-tile.is-non-owner .tile-label,
|
||||
.face-tile.is-non-owner .tile-value {
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.tile-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
formatRatio,
|
||||
getCoinWan,
|
||||
getDailyLoss,
|
||||
getFaceOwnerText,
|
||||
getListingConsumablePrice,
|
||||
getListingChips,
|
||||
getListingDisplayPrice,
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
readAssetString,
|
||||
getRatioValue,
|
||||
isTopTierInsurance,
|
||||
isNonOwnFace,
|
||||
getScreenshotThumbURL,
|
||||
getScreenshotMediumURL,
|
||||
getScreenshotRawURL,
|
||||
@@ -136,6 +138,11 @@ const dailyLoss = computed(() => {
|
||||
return getDailyLoss(listing.value)
|
||||
})
|
||||
|
||||
const faceOwner = computed(() => {
|
||||
if (!listing.value) return '--'
|
||||
return getFaceOwnerText(listing.value)
|
||||
})
|
||||
|
||||
// 平台/区服特性徽标
|
||||
const accessTags = computed(() => {
|
||||
if (!listing.value) return []
|
||||
@@ -498,6 +505,14 @@ function goBack() {
|
||||
<strong class="tile-value text-slate">{{ ratioDisplay }}</strong>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="asset-tile face-tile"
|
||||
:class="{ 'is-non-owner': listing && isNonOwnFace(listing) }"
|
||||
>
|
||||
<span class="tile-label">是否本人人脸</span>
|
||||
<strong class="tile-value">{{ faceOwner }}</strong>
|
||||
</div>
|
||||
|
||||
<div class="asset-tile rental-tile">
|
||||
<span class="tile-label">可租</span>
|
||||
<strong class="tile-value text-green">{{ rentalDuration }}</strong>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
getFaceOwnerText,
|
||||
getListingChips,
|
||||
getListingResources,
|
||||
getListingSellerCoinPrice,
|
||||
getListingSellerPrice,
|
||||
isNonOwnFace,
|
||||
} from '../listingDisplay'
|
||||
import type { PublicListingItem } from '@/features/listings/api/listings'
|
||||
|
||||
@@ -84,6 +86,19 @@ describe('首页规格芯片', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('本人人脸展示', () => {
|
||||
it('读取发布时填写的本人人脸状态', () => {
|
||||
expect(getFaceOwnerText(buildListing({ face_owner: '是' }))).toBe('是')
|
||||
expect(getFaceOwnerText(buildListing({ face_owner: '否' }))).toBe('否')
|
||||
})
|
||||
|
||||
it('非本人人脸可单独识别,老数据回退占位符', () => {
|
||||
expect(isNonOwnFace(buildListing({ face_owner: '否' }))).toBe(true)
|
||||
expect(isNonOwnFace(buildListing({ face_owner: '是' }))).toBe(false)
|
||||
expect(getFaceOwnerText(buildListing())).toBe('--')
|
||||
})
|
||||
})
|
||||
|
||||
describe('首页规格芯片 · 德穆兰优先级高于全英雄', () => {
|
||||
it('解锁德穆兰时用「解锁德穆兰」替换「全英雄」', () => {
|
||||
const chips = getListingChips(buildListing({ all_hero: '是', unlock_demulan: '是' }))
|
||||
|
||||
@@ -107,6 +107,15 @@ export function getServerRegion(item: PublicListingItem) {
|
||||
return item.server_region.trim()
|
||||
}
|
||||
|
||||
export function getFaceOwnerText(item: PublicListingItem) {
|
||||
const value = readAssetString(item, 'face_owner').trim()
|
||||
return value === '是' || value === '否' ? value : '--'
|
||||
}
|
||||
|
||||
export function isNonOwnFace(item: PublicListingItem) {
|
||||
return getFaceOwnerText(item) === '否'
|
||||
}
|
||||
|
||||
// 把后端机读游戏名转成中文展示名。
|
||||
export function formatGameName(value: unknown, fallback = '-') {
|
||||
const text = String(value || '').trim()
|
||||
|
||||
@@ -1169,6 +1169,11 @@ h1 {
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.admin-listings-table .face-owner-no {
|
||||
color: #dc2626;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.admin-listings-table.el-table .el-table__cell {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user