HotTrack / frontend /src /views /TopViewed.vue
chanfasf's picture
Style: improve video title alignment with consistent layout
e04fdc1
Raw
History Blame Contribute Delete
6.27 kB
<template>
<div class="page-container">
<div class="page-header">
<h1 class="page-title">🏆 播放排行</h1>
<p class="page-subtitle">YouTube & TikTok 播放量最高的视频排名,含收入估算明细</p>
</div>
<div class="filter-bar">
<select v-model="platform">
<option value="">全部平台</option>
<option value="youtube">YouTube</option>
<option value="tiktok">TikTok</option>
</select>
<select v-model="limit">
<option value="20">Top 20</option>
<option value="50">Top 50</option>
<option value="100">Top 100</option>
</select>
<button class="btn btn-primary" @click="loadData" :disabled="loading">🔄 刷新数据</button>
</div>
<div class="card">
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="videos.length === 0" class="empty-state">
<div class="empty-icon">🏆</div>
<p>暂无数据,请先到数据采集页面抓取</p>
</div>
<div v-else class="table-wrapper topviewed-table-wrapper">
<table class="video-table">
<thead>
<tr>
<th>排名</th>
<th>平台</th>
<th>视频</th>
<th>播放量</th>
<th>点赞</th>
<th>发布时间</th>
<th>收入明细</th>
</tr>
</thead>
<tbody>
<tr v-for="v in videos" :key="v.id">
<td><span class="rank-number data-value">{{ v.rank }}</span></td>
<td>
<span class="platform-badge" :class="v.platform">{{ v.platform === 'youtube' ? 'YT' : 'TT' }}</span>
</td>
<td>
<div class="video-info">
<img :src="v.thumbnail_url" class="video-thumb" @error="handleImgError" />
<div class="video-info-content">
<a :href="v.video_url" target="_blank" class="video-title" :title="v.title">{{ v.title }}</a>
<div class="video-channel">{{ v.channel_title }}</div>
</div>
</div>
</td>
<td class="data-value">{{ formatNumber(v.view_count) }}</td>
<td class="data-value">{{ formatNumber(v.like_count) }}</td>
<td class="data-value">{{ formatTimeAgo(v.published_at) }}</td>
<td class="revenue-cell">
<span class="revenue-main">${{ formatRevenue(v.estimated_revenue_mid) }}</span>
<span class="revenue-range">${{ formatRevenue(v.estimated_revenue_low) }} ~ ${{ formatRevenue(v.estimated_revenue_high) }}</span>
<span class="revenue-detail">广告 ${{ formatRevenue(v.ad_revenue) }} · 分成 ${{ formatRevenue(v.creator_share_amount) }}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import { videosApi } from '../api'
export default {
name: 'TopViewed',
data() {
return {
videos: [],
platform: '',
limit: '50',
loading: false,
}
},
mounted() {
console.log('[TopViewed] Component mounted, calling loadData...')
this.loadData()
},
watch: {
platform(newVal, oldVal) {
if (oldVal !== undefined) {
this.loadData()
}
},
limit(newVal, oldVal) {
if (oldVal !== undefined) {
this.loadData()
}
},
},
methods: {
async loadData() {
console.log('[TopViewed] loadData called, platform:', this.platform, 'limit:', this.limit)
this.loading = true
try {
const params = { limit: parseInt(this.limit) }
if (this.platform) params.platform = this.platform
console.log('[TopViewed] API params:', params)
console.log('[TopViewed] Calling API...')
const res = await videosApi.getTopViewed(params)
console.log('[TopViewed] API response status:', res.status)
console.log('[TopViewed] API response data:', res.data)
console.log('[TopViewed] API response length:', res.data?.length)
this.videos = res.data || []
console.log('[TopViewed] videos set to:', this.videos.length, 'items')
} catch (e) {
console.error('[TopViewed] 加载失败:', e)
console.error('[TopViewed] Error details:', e.message, e.stack)
} finally {
this.loading = false
console.log('[TopViewed] Loading complete, videos:', this.videos.length)
}
},
formatNumber(num) {
if (!num) return '0'
if (num >= 1e9) return (num / 1e9).toFixed(1) + 'B'
if (num >= 1e6) return (num / 1e6).toFixed(1) + 'M'
if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K'
return num.toString()
},
formatRevenue(num) {
if (!num) return '0'
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'
if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K'
return num.toFixed(2)
},
formatTimeAgo(dateStr) {
if (!dateStr) return '-'
const date = new Date(dateStr)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
if (diffMs < 0) return '刚刚'
const diffSeconds = Math.floor(diffMs / 1000)
const diffMinutes = Math.floor(diffSeconds / 60)
const diffHours = Math.floor(diffMinutes / 60)
if (diffMinutes < 1) return '刚刚'
if (diffHours < 1) return diffMinutes + '分钟前'
if (diffHours < 24) return diffHours + '小时前'
const diffDays = Math.floor(diffHours / 24)
if (diffDays < 30) return diffDays + '天前'
const diffMonths = Math.floor(diffDays / 30)
if (diffMonths < 12) return diffMonths + '个月前'
return Math.floor(diffMonths / 12) + '年前'
},
handleImgError(e) {
e.target.src = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="96" height="54"><rect fill="%23f1f5f9" width="96" height="54" rx="8"/></svg>'
},
},
}
</script>
<style scoped>
.revenue-cell {
white-space: nowrap;
text-align: right;
}
.revenue-main {
color: #059669;
font-weight: 700;
font-size: 13px;
}
.revenue-range {
display: none;
}
.revenue-detail {
display: none;
}
</style>