File size: 6,269 Bytes
c4d0ee2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b877067
c4d0ee2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e04fdc1
c4d0ee2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
821f77b
c4d0ee2
 
ee4e4ea
36fd5c6
 
 
 
ee4e4ea
36fd5c6
 
 
 
ee4e4ea
 
c4d0ee2
 
a9b6650
c4d0ee2
 
 
 
a9b6650
821f77b
c4d0ee2
821f77b
 
 
 
 
c4d0ee2
a9b6650
821f77b
c4d0ee2
 
821f77b
c4d0ee2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d5bab4d
 
 
 
 
 
 
c4d0ee2
 
 
d5bab4d
 
 
c4d0ee2
 
 
 
 
 
 
 
 
 
 
db6c1c9
c4d0ee2
 
 
 
db6c1c9
c4d0ee2
 
db6c1c9
c4d0ee2
 
db6c1c9
c4d0ee2
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
<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>