From e37684d401b74f0ee924f0711a0e4170b616c768 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 17 Oct 2026 15:20:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin-listings):=20=E5=95=86=E5=93=81?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E4=B8=8E=E8=AF=A6=E6=83=85=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=B1=9E=E6=80=A7=E5=B9=B6=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E8=B5=A0=E9=80=81=E8=B5=84=E4=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/views/AdminListingDetailView.css | 25 +++++++++ .../admin/views/AdminListingDetailView.vue | 53 ++++++++++++++----- .../admin/views/AdminListingsView.vue | 10 ++++ .../utils/__tests__/listingDisplay.spec.ts | 51 ++++++++++++++++++ 4 files changed, 125 insertions(+), 14 deletions(-) diff --git a/frontend/src/features/admin/views/AdminListingDetailView.css b/frontend/src/features/admin/views/AdminListingDetailView.css index 3ddcc46..ba99576 100644 --- a/frontend/src/features/admin/views/AdminListingDetailView.css +++ b/frontend/src/features/admin/views/AdminListingDetailView.css @@ -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; diff --git a/frontend/src/features/admin/views/AdminListingDetailView.vue b/frontend/src/features/admin/views/AdminListingDetailView.vue index 63ca82f..45cf103 100644 --- a/frontend/src/features/admin/views/AdminListingDetailView.vue +++ b/frontend/src/features/admin/views/AdminListingDetailView.vue @@ -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 = { + 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) {

库存资产

{{ resourceCards.length }} 项资源 · - {{ skinNames.length + hiddenSkinCount }} 个皮肤{{ resourceCards.length }} 项资源 + · {{ skinNames.length + hiddenSkinCount }} 个皮肤
-
+
{{ item.label }} - {{ item.quantity }} +
+ {{ item.quantity }} + 赠送 +

暂无库存资源

diff --git a/frontend/src/features/admin/views/AdminListingsView.vue b/frontend/src/features/admin/views/AdminListingsView.vue index 380ad62..1f47044 100644 --- a/frontend/src/features/admin/views/AdminListingsView.vue +++ b/frontend/src/features/admin/views/AdminListingsView.vue @@ -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) }} + + + diff --git a/frontend/src/shared/utils/__tests__/listingDisplay.spec.ts b/frontend/src/shared/utils/__tests__/listingDisplay.spec.ts index 8687512..e215690 100644 --- a/frontend/src/shared/utils/__tests__/listingDisplay.spec.ts +++ b/frontend/src/shared/utils/__tests__/listingDisplay.spec.ts @@ -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']) + }) +})