feat(admin-listings): 商品列表与详情补充账号属性并展示赠送资产
This commit is contained in:
@@ -180,6 +180,31 @@
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.resource-item.is-gift {
|
||||
border-color: #fdba74;
|
||||
background: #fff7ed;
|
||||
}
|
||||
|
||||
.resource-item.is-gift strong {
|
||||
color: #c2410c;
|
||||
}
|
||||
|
||||
.resource-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.resource-gift-tag {
|
||||
padding: 1px 6px;
|
||||
border-radius: 999px;
|
||||
background: #ffedd5;
|
||||
color: #c2410c;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.resource-item span {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -140,6 +140,17 @@ const assetRows = computed(() => {
|
||||
{ label: '绝密 KD', value: assetNumberText(listing.value, 'secret_kd') },
|
||||
{ label: '常用地区', value: regionText(listing.value) },
|
||||
{ label: '在线时间', value: getOnlineTimeText(listing.value) || '-' },
|
||||
{
|
||||
label: '是否可以修改游戏名',
|
||||
value: assetText(listing.value, 'can_change_game_name'),
|
||||
},
|
||||
{ label: '是否有全英雄', value: assetText(listing.value, 'all_hero') },
|
||||
{ label: '是否解锁德穆兰', value: assetText(listing.value, 'unlock_demulan') },
|
||||
{
|
||||
label: '凌晨0-2点可响应上号',
|
||||
value: assetText(listing.value, 'early_morning_response'),
|
||||
},
|
||||
{ label: '封禁记录', value: assetText(listing.value, 'ban_record') },
|
||||
]
|
||||
})
|
||||
const ownerRows = computed(() => {
|
||||
@@ -178,17 +189,21 @@ const ownerRows = computed(() => {
|
||||
})
|
||||
const resourceCards = computed(() => {
|
||||
if (!listing.value) return []
|
||||
const core = [
|
||||
{ key: 'awmAmmo', label: 'AWM 子弹', quantity: getResourceQuantity(listing.value, 'awmAmmo') },
|
||||
{ key: 'helmet6', label: '六级头', quantity: getResourceQuantity(listing.value, 'helmet6') },
|
||||
{ key: 'armor6', label: '六级甲', quantity: getResourceQuantity(listing.value, 'armor6') },
|
||||
].filter(item => item.quantity > 0)
|
||||
const extra = getListingResources(listing.value)
|
||||
.filter(item => !core.some(coreItem => coreItem.key === item.key))
|
||||
.slice(0, 6)
|
||||
.map(item => ({ key: item.key, label: item.label, quantity: item.quantity }))
|
||||
return [...core, ...extra]
|
||||
const labelOverrides: Record<string, string> = {
|
||||
awmAmmo: 'AWM 子弹',
|
||||
helmet6: '六级头',
|
||||
armor6: '六级甲',
|
||||
}
|
||||
return getListingResources(listing.value).map(item => ({
|
||||
key: item.key,
|
||||
label: labelOverrides[item.key] || item.label,
|
||||
quantity: item.quantity,
|
||||
mode: item.mode,
|
||||
}))
|
||||
})
|
||||
const giftResourceCount = computed(
|
||||
() => resourceCards.value.filter(item => item.mode === '赠送').length
|
||||
)
|
||||
const skinNames = computed(() => (listing.value ? getSkinNames(listing.value).slice(0, 16) : []))
|
||||
const hiddenSkinCount = computed(() => {
|
||||
if (!listing.value) return 0
|
||||
@@ -839,14 +854,24 @@ async function openScreenshot(url: string) {
|
||||
<div class="panel-heading">
|
||||
<h2>库存资产</h2>
|
||||
<span
|
||||
>{{ resourceCards.length }} 项资源 ·
|
||||
{{ skinNames.length + hiddenSkinCount }} 个皮肤</span
|
||||
>{{ resourceCards.length }} 项资源<template v-if="giftResourceCount">
|
||||
· {{ giftResourceCount }} 项赠送</template
|
||||
>
|
||||
· {{ skinNames.length + hiddenSkinCount }} 个皮肤</span
|
||||
>
|
||||
</div>
|
||||
<div v-if="resourceCards.length" class="resource-grid">
|
||||
<div v-for="item in resourceCards" :key="item.key" class="resource-item">
|
||||
<div
|
||||
v-for="item in resourceCards"
|
||||
:key="item.key"
|
||||
class="resource-item"
|
||||
:class="{ 'is-gift': item.mode === '赠送' }"
|
||||
>
|
||||
<span>{{ item.label }}</span>
|
||||
<strong>{{ item.quantity }}</strong>
|
||||
<div class="resource-value">
|
||||
<strong>{{ item.quantity }}</strong>
|
||||
<small v-if="item.mode === '赠送'" class="resource-gift-tag">赠送</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="empty-line">暂无库存资源</p>
|
||||
|
||||
@@ -155,6 +155,7 @@ const screenshotColumns = [
|
||||
width: 420,
|
||||
read: (row: Listing) => characterAndWeaponSkinText(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) },
|
||||
{
|
||||
@@ -639,6 +640,12 @@ function ownerRemarkText(row: Listing) {
|
||||
return remark || '-'
|
||||
}
|
||||
|
||||
/** 全英雄为发布必填项;老数据缺失时显示占位符。 */
|
||||
function allHeroText(row: Listing) {
|
||||
const value = assetText(row, 'all_hero')
|
||||
return value === '是' || value === '否' ? value : '-'
|
||||
}
|
||||
|
||||
function rentAndDepositText(row: Listing) {
|
||||
return `${listingPrice(row)}/${formatCentWithSymbol(row.deposit_amount_cent)}`
|
||||
}
|
||||
@@ -1024,6 +1031,9 @@ function formatQuantity(value: number) {
|
||||
{{ characterAndWeaponSkinText(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="全英雄" width="74">
|
||||
<template #default="{ row }">{{ allHeroText(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="号主备注" min-width="150">
|
||||
<template #default="{ row }">{{ ownerRemarkText(row) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
getListingChips,
|
||||
getListingResources,
|
||||
getListingSellerCoinPrice,
|
||||
getListingSellerPrice,
|
||||
} from '../listingDisplay'
|
||||
@@ -114,3 +115,53 @@ describe('首页规格芯片 · 德穆兰优先级高于全英雄', () => {
|
||||
expect(chips.find(chip => chip.label === '全英雄')).toEqual({ label: '全英雄', value: '是' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('库存资源解析', () => {
|
||||
it('保留赠送资产及其计费模式,便于后台详情单独标记', () => {
|
||||
const resources = getListingResources(
|
||||
buildListing({
|
||||
resources: [
|
||||
{
|
||||
key: 'awmAmmo',
|
||||
label: 'AWM子弹',
|
||||
price: '0.6元/发',
|
||||
quantity: 31,
|
||||
mode: '收费',
|
||||
},
|
||||
{
|
||||
key: 'giftMed',
|
||||
label: '赠送医疗物资',
|
||||
price: '1元/个',
|
||||
quantity: 4,
|
||||
mode: '赠送',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
expect(resources).toHaveLength(2)
|
||||
expect(resources.find(item => item.key === 'giftMed')).toMatchObject({
|
||||
label: '赠送医疗物资',
|
||||
quantity: 4,
|
||||
mode: '赠送',
|
||||
amount: 0,
|
||||
})
|
||||
expect(resources.find(item => item.key === 'awmAmmo')).toMatchObject({
|
||||
mode: '收费',
|
||||
amount: 18.6,
|
||||
})
|
||||
})
|
||||
|
||||
it('过滤数量为 0 的资产', () => {
|
||||
const resources = getListingResources(
|
||||
buildListing({
|
||||
resources: [
|
||||
{ key: 'zero', label: '零数量物资', price: '1元/个', quantity: 0, mode: '赠送' },
|
||||
{ key: 'gift', label: '赠送物资', price: '1元/个', quantity: 1, mode: '赠送' },
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
expect(resources.map(item => item.key)).toEqual(['gift'])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user