增加渠道上传预统计
This commit is contained in:
@@ -27,9 +27,15 @@ export interface ListingDayStats {
|
||||
trade_leave_count: number
|
||||
}
|
||||
|
||||
export interface ListingChannelDayStats extends ListingDayStats {
|
||||
source_channel: string
|
||||
}
|
||||
|
||||
export interface ListingDailyOverview {
|
||||
today: ListingDayStats
|
||||
trend: ListingDayStats[]
|
||||
today_channels: ListingChannelDayStats[]
|
||||
channel_trend: ListingChannelDayStats[]
|
||||
days: number
|
||||
timezone: string
|
||||
}
|
||||
@@ -82,6 +88,8 @@ export async function fetchAdminDashboard() {
|
||||
listing_daily: {
|
||||
today: listingDaily?.today ?? emptyDayStats(),
|
||||
trend: listingDaily?.trend ?? [],
|
||||
today_channels: listingDaily?.today_channels ?? [],
|
||||
channel_trend: listingDaily?.channel_trend ?? [],
|
||||
days: listingDaily?.days ?? 7,
|
||||
timezone: listingDaily?.timezone ?? 'Asia/Shanghai',
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import {
|
||||
fetchAdminDashboard,
|
||||
type AdminDashboard,
|
||||
type ListingChannelDayStats,
|
||||
type ListingDayStats,
|
||||
} from '@/features/admin/api/adminDashboard'
|
||||
import { useAdminTable } from '@/features/admin/composables/useAdminTable'
|
||||
@@ -40,6 +41,7 @@ const {
|
||||
})
|
||||
|
||||
const listingTrend = computed(() => dashboard.value?.listing_daily?.trend ?? [])
|
||||
const listingChannelTrend = computed(() => dashboard.value?.listing_daily?.channel_trend ?? [])
|
||||
const listingToday = computed(
|
||||
() =>
|
||||
dashboard.value?.listing_daily?.today ?? {
|
||||
@@ -49,6 +51,25 @@ const listingToday = computed(
|
||||
trade_leave_count: 0,
|
||||
}
|
||||
)
|
||||
const channelDisplayOrder = ['咸鱼', '淘宝', '京东', 'QQ', '微信', '自定义', '站内发布', '未填写']
|
||||
const listingTodayChannels = computed<ListingChannelDayStats[]>(() => {
|
||||
const date = listingToday.value.date
|
||||
const rows = dashboard.value?.listing_daily?.today_channels ?? []
|
||||
const byChannel = new Map(rows.map(row => [row.source_channel || '未填写', row]))
|
||||
const ordered = channelDisplayOrder.map(channel => ({
|
||||
date,
|
||||
source_channel: channel,
|
||||
published_count: byChannel.get(channel)?.published_count ?? 0,
|
||||
active_offline_count: byChannel.get(channel)?.active_offline_count ?? 0,
|
||||
trade_leave_count: byChannel.get(channel)?.trade_leave_count ?? 0,
|
||||
}))
|
||||
const extras = rows.filter(row => !channelDisplayOrder.includes(row.source_channel || '未填写'))
|
||||
return [...ordered, ...extras]
|
||||
})
|
||||
|
||||
function leaveCount(row: ListingChannelDayStats | ListingDayStats) {
|
||||
return Number(row.active_offline_count || 0) + Number(row.trade_leave_count || 0)
|
||||
}
|
||||
|
||||
function trendMax(getter: (row: ListingDayStats) => number) {
|
||||
const values = listingTrend.value.map(getter)
|
||||
@@ -248,6 +269,24 @@ function shortDate(date: string) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="listing-channel-summary">
|
||||
<div
|
||||
v-for="row in listingTodayChannels"
|
||||
:key="`today-channel-${row.source_channel}`"
|
||||
class="listing-channel-item"
|
||||
>
|
||||
<div class="listing-channel-item-head">
|
||||
<span>{{ row.source_channel || '未填写' }}</span>
|
||||
<strong>{{ row.published_count + leaveCount(row) }}</strong>
|
||||
</div>
|
||||
<div class="listing-channel-counts">
|
||||
<span>上架 {{ row.published_count }}</span>
|
||||
<span>主动下架 {{ row.active_offline_count }}</span>
|
||||
<span>交易离架 {{ row.trade_leave_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table class="table-panel listing-trend-table" :data="listingTrend" size="small">
|
||||
<el-table-column prop="date" label="日期" min-width="120" />
|
||||
<el-table-column prop="published_count" label="上架" width="100" align="center" />
|
||||
@@ -259,6 +298,35 @@ function shortDate(date: string) {
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div v-if="listingChannelTrend.length" class="listing-channel-block">
|
||||
<div class="listing-channel-title">近 7 日渠道明细</div>
|
||||
<el-table
|
||||
class="table-panel listing-channel-table"
|
||||
:data="listingChannelTrend"
|
||||
size="small"
|
||||
>
|
||||
<el-table-column prop="date" label="日期" min-width="120" />
|
||||
<el-table-column prop="source_channel" label="渠道" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="listing-channel-name">{{ row.source_channel || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="published_count" label="上架" width="100" align="center" />
|
||||
<el-table-column
|
||||
prop="active_offline_count"
|
||||
label="主动下架"
|
||||
width="110"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="trade_leave_count" label="交易离架" width="110" align="center" />
|
||||
<el-table-column label="合计离架" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ leaveCount(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 待处理事项 & 快捷入口 -->
|
||||
@@ -789,10 +857,80 @@ function shortDate(date: string) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.listing-channel-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.listing-channel-item {
|
||||
min-width: 0;
|
||||
border: 1px solid #e8edf4;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.listing-channel-item-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.listing-channel-item-head span {
|
||||
min-width: 0;
|
||||
color: #1b2559;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.listing-channel-item-head strong {
|
||||
flex-shrink: 0;
|
||||
color: #4f7cff;
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.listing-channel-counts {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.listing-channel-counts span {
|
||||
color: #8f9bba;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.listing-channel-block {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.listing-channel-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #1b2559;
|
||||
}
|
||||
|
||||
.listing-channel-name {
|
||||
font-weight: 700;
|
||||
color: #1b2559;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.listing-daily-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.listing-channel-summary {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -802,6 +940,10 @@ function shortDate(date: string) {
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.listing-channel-summary {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -137,7 +137,12 @@ function selectListing(row: Listing) {
|
||||
function replaceListing(next: Listing) {
|
||||
const index = listings.value.findIndex(item => item.id === next.id)
|
||||
if (index >= 0) {
|
||||
listings.value.splice(index, 1, next)
|
||||
const previous = listings.value[index]!
|
||||
listings.value.splice(index, 1, {
|
||||
...previous,
|
||||
...next,
|
||||
source_channel: next.source_channel || previous.source_channel,
|
||||
})
|
||||
} else {
|
||||
listings.value.unshift(next)
|
||||
}
|
||||
@@ -422,6 +427,13 @@ function contactPhone(row: Listing) {
|
||||
return typeof value === 'string' && value.trim() ? value : '-'
|
||||
}
|
||||
|
||||
function sourceChannel(row: Listing) {
|
||||
if (typeof row.source_channel === 'string' && row.source_channel.trim()) {
|
||||
return row.source_channel.trim()
|
||||
}
|
||||
return isExternalUpload(row) ? '未填写' : '站内发布'
|
||||
}
|
||||
|
||||
function ownerOnlineText(row: Listing) {
|
||||
const text = row.asset_summary?.online_time_text
|
||||
if (typeof text === 'string' && text.trim()) return text
|
||||
@@ -456,7 +468,15 @@ function isDefaultScreenshot(url: string) {
|
||||
|
||||
function riskItems(row: Listing): RiskItem[] {
|
||||
const items: RiskItem[] = []
|
||||
if (isExternalUpload(row)) items.push({ label: `外部上传:${uploaderName(row)}`, level: 'info' })
|
||||
if (isExternalUpload(row)) {
|
||||
items.push({
|
||||
label: `外部上传:${uploaderName(row)} / 渠道:${sourceChannel(row)}`,
|
||||
level: 'info',
|
||||
})
|
||||
} else {
|
||||
items.push({ label: `来源渠道:${sourceChannel(row)}`, level: 'info' })
|
||||
}
|
||||
const infoCount = items.length
|
||||
if (!row.screenshot_urls?.length) items.push({ label: '没有账号截图', level: 'danger' })
|
||||
if (hasDefaultScreenshot(row)) items.push({ label: '使用默认截图', level: 'warning' })
|
||||
if (hasBanRecord(row)) items.push({ label: `封禁记录:${banRecordText(row)}`, level: 'danger' })
|
||||
@@ -472,7 +492,7 @@ function riskItems(row: Listing): RiskItem[] {
|
||||
items.push({ label: '烽火等级接近下限', level: 'warning' })
|
||||
if (!readAssetString(row, 'season_insurance'))
|
||||
items.push({ label: '缺少保险格数', level: 'warning' })
|
||||
if (!items.length) items.push({ label: '未发现明显风险', level: 'info' })
|
||||
if (items.length === infoCount) items.push({ label: '未发现明显风险', level: 'info' })
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -495,6 +515,7 @@ function reviewSearchText(row: Listing) {
|
||||
row.login_platform,
|
||||
row.rank_level,
|
||||
uploaderName(row),
|
||||
sourceChannel(row),
|
||||
getSkinNames(row).join(' '),
|
||||
]
|
||||
.join(' ')
|
||||
@@ -602,7 +623,7 @@ async function openScreenshot(url: string) {
|
||||
v-model="filters.keyword"
|
||||
:prefix-icon="Search"
|
||||
clearable
|
||||
placeholder="搜索编号、标题、客服、段位、皮肤"
|
||||
placeholder="搜索编号、标题、客服、渠道、段位、皮肤"
|
||||
/>
|
||||
<el-select v-model="filters.risk" placeholder="风险筛选">
|
||||
<el-option label="全部待审" value="all" />
|
||||
@@ -642,10 +663,17 @@ async function openScreenshot(url: string) {
|
||||
formatCent(item.deposit_amount_cent)
|
||||
}}</span
|
||||
>
|
||||
<el-tag v-if="isExternalUpload(item)" size="small" type="info">{{
|
||||
uploaderName(item)
|
||||
}}</el-tag>
|
||||
<el-tag v-if="hasDefaultScreenshot(item)" size="small" type="warning">默认图</el-tag>
|
||||
<div class="queue-tags">
|
||||
<el-tag size="small" type="primary" effect="plain">{{
|
||||
sourceChannel(item)
|
||||
}}</el-tag>
|
||||
<el-tag v-if="isExternalUpload(item)" size="small" type="info">{{
|
||||
uploaderName(item)
|
||||
}}</el-tag>
|
||||
<el-tag v-if="hasDefaultScreenshot(item)" size="small" type="warning"
|
||||
>默认图</el-tag
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<el-empty
|
||||
@@ -782,11 +810,20 @@ async function openScreenshot(url: string) {
|
||||
<div class="review-panel">
|
||||
<div class="panel-title">
|
||||
<h3>上传信息</h3>
|
||||
<el-tag v-if="isExternalUpload(selectedListing)" size="small" type="info"
|
||||
>外部上传</el-tag
|
||||
>
|
||||
<div class="panel-tags">
|
||||
<el-tag size="small" type="primary" effect="plain">{{
|
||||
sourceChannel(selectedListing)
|
||||
}}</el-tag>
|
||||
<el-tag v-if="isExternalUpload(selectedListing)" size="small" type="info"
|
||||
>外部上传</el-tag
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<dl class="detail-list">
|
||||
<div>
|
||||
<dt>来源渠道</dt>
|
||||
<dd>{{ sourceChannel(selectedListing) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>上传人</dt>
|
||||
<dd>{{ uploaderName(selectedListing) }}</dd>
|
||||
@@ -1206,6 +1243,14 @@ async function openScreenshot(url: string) {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.queue-tags,
|
||||
.panel-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.review-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface Listing {
|
||||
owner_id: number
|
||||
owner_phone?: string
|
||||
owner_nickname?: string
|
||||
source_channel?: string
|
||||
title: string
|
||||
description: string
|
||||
game_name: string
|
||||
|
||||
Reference in New Issue
Block a user