diff --git a/frontend/index.html b/frontend/index.html index 4240845..5ad635b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,6 +6,12 @@ Vite App +
diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 28535a9..d33645f 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -9,11 +9,49 @@ const loading = ref(false) const selectedDate = ref('') const currentPage = ref(1) const totalPages = ref(1) +const updateTime = ref('') // 添加更新时间字段 +const showDatePicker = ref(false) // 控制日期选择器显示 +const dateOptions = ref([]) // 日期选项列表 // 初始化日期为今天 const initDate = () => { const today = new Date() selectedDate.value = today.toISOString().split('T')[0] + generateDateOptions() +} + +// 生成日期选项(今天和往前7天) +const generateDateOptions = () => { + const options = [] + const today = new Date() + + for (let i = 0; i < 8; i++) { + const date = new Date(today) + date.setDate(today.getDate() - i) + + const value = date.toISOString().split('T')[0] + const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] + const weekday = weekdays[date.getDay()] + + let label = '' + if (i === 0) { + label = '今天' + } else if (i === 1) { + label = '昨天' + } else { + label = `${i}天前` + } + + const display = `${date.getMonth() + 1}月${date.getDate()}日 ${weekday}` + + options.push({ + value, + label, + display + }) + } + + dateOptions.value = options } // 获取排行榜数据 @@ -33,6 +71,8 @@ const fetchRankingData = async () => { if (response.data.success) { rankingData.value = response.data.data totalPages.value = response.data.pagination.pages + // 获取后端返回的更新时间 + updateTime.value = response.data.update_time || '' } else { console.error('获取数据失败:', response.data.message) rankingData.value = [] @@ -76,7 +116,7 @@ const formatGrowth = (item) => { const changeRate = timelineData.play_vv_change_rate || 0 if (change > 0) { - return `+${formatPlayCount(change)} (${changeRate.toFixed(1)}%)` + return `${formatPlayCount(change)}` } return '暂无数据' } @@ -89,12 +129,85 @@ const switchTab = (tab) => { } } +// 获取图片源地址 +const getImageSrc = (item) => { + // 优先使用 cover_image_url + if (item.cover_image_url) { + return item.cover_image_url + } + + // 如果有备用链接,使用第一个 + if (item.cover_backup_urls && item.cover_backup_urls.length > 0) { + return item.cover_backup_urls[0] + } + + // 最后使用占位符 + return '/placeholder-poster.svg' +} + +// 处理图片加载错误 +const handleImageError = (event, item) => { + const img = event.target + console.log('图片加载失败:', img.src, '视频:', item.title) + + // 如果当前显示的是主链接,尝试备用链接 + if (img.src === item.cover_image_url && item.cover_backup_urls && item.cover_backup_urls.length > 0) { + console.log('尝试备用链接:', item.cover_backup_urls[0]) + // 尝试第一个备用链接 + img.src = item.cover_backup_urls[0] + return + } + + // 如果当前显示的是第一个备用链接,尝试其他备用链接 + if (item.cover_backup_urls && item.cover_backup_urls.length > 1) { + const currentIndex = item.cover_backup_urls.indexOf(img.src) + if (currentIndex >= 0 && currentIndex < item.cover_backup_urls.length - 1) { + console.log('尝试下一个备用链接:', item.cover_backup_urls[currentIndex + 1]) + img.src = item.cover_backup_urls[currentIndex + 1] + return + } + } + + // 所有链接都失败,使用占位符 + console.log('使用占位符图片') + img.src = '/placeholder-poster.svg' +} + // 日期改变处理 const onDateChange = () => { currentPage.value = 1 fetchRankingData() } +// 格式化显示日期 +const formatDisplayDate = (dateStr) => { + if (!dateStr) return '选择日期' + + const date = new Date(dateStr) + const today = new Date() + const diffTime = today.getTime() - date.getTime() + const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)) + return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}` + +} + +// 切换日期选择器显示状态 +const toggleDatePicker = () => { + showDatePicker.value = !showDatePicker.value +} + +// 关闭日期选择器 +const closeDatePicker = () => { + showDatePicker.value = false +} + +// 选择日期 +const selectDate = (dateValue) => { + selectedDate.value = dateValue + showDatePicker.value = false + onDateChange() +} + // 页面加载时初始化 onMounted(() => { initDate() @@ -111,26 +224,44 @@ onMounted(() => {
- -

热播总榜

- + +

抖音AI短剧榜

+
- 基于实时热度排行 {{ getCurrentTime() }}更新 + 基于实时热度排行 {{ updateTime || getCurrentTime() }}更新 🔄
- -
- - + +
+
+ {{ formatDisplayDate(selectedDate) }} + +
+ + +
+
+
+

选择日期

+ +
+
+
+ {{ date.label }} + {{ date.display }} +
+
+
+
@@ -154,9 +285,9 @@ onMounted(() => {
@@ -172,15 +303,11 @@ onMounted(() => { {{ formatPlayCount(item.play_vv) }}
- -
- {{ item.summary || item.title || item.mix_name || '暂无简介' }} -
- 🔥 + {{ formatGrowth(item) }}
@@ -234,7 +361,6 @@ onMounted(() => { .app { min-height: 100vh; - background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding-bottom: 80px; /* 为底部导航留出空间 */ color: white; @@ -251,7 +377,6 @@ onMounted(() => { /* 标题区域 */ .header { text-align: center; - margin-bottom: 20px; } .title-container { @@ -268,11 +393,10 @@ onMounted(() => { } .title { - color: white; + color: #555; font-size: 24px; font-weight: bold; margin: 0; - text-shadow: 0 2px 4px rgba(0,0,0,0.3); } .update-time { @@ -289,35 +413,141 @@ onMounted(() => { color: #4CAF50; } -/* 日期选择器 */ -.date-selector { - background: rgba(255, 255, 255, 0.9); - padding: 15px; - border-radius: 12px; - margin-bottom: 20px; - display: flex; - align-items: center; - gap: 10px; - box-shadow: 0 4px 12px rgba(0,0,0,0.1); +/* 自定义日期选择器 */ +.custom-date-selector { + padding: 16px 0 8px; } -.date-selector label { +.date-display { + display: flex; + align-items: center; + justify-content: flex-end; + cursor: pointer; +} + +.date-text { + font-size: 12px; + font-weight: 500; + color: #999; +} + +/* 日期选择弹窗 */ +.date-picker-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: flex-end; + z-index: 1000; + animation: fadeIn 0.3s ease; +} + +.date-picker-popup { + width: 100%; + max-height: 70vh; + background: white; + border-radius: 20px 20px 0 0; + animation: slideUp 0.3s ease; + overflow: hidden; +} + +.date-picker-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px; + border-bottom: 1px solid #eee; + background: #f8f9fa; +} + +.date-picker-header h3 { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #333; +} + +.close-btn { + background: none; + border: none; + font-size: 24px; + color: #666; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: all 0.2s ease; +} + +.close-btn:hover { + background: #e9ecef; + color: #333; +} + +.date-list { + max-height: 400px; + overflow-y: auto; +} + +.date-option { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + cursor: pointer; + transition: all 0.2s ease; + border-bottom: 1px solid #f0f0f0; +} + +.date-option:hover { + background: #f8f9fa; +} + +.date-option.active { + background: #e3f2fd; + border-left: 4px solid #2196f3; +} + +.date-option.active .date-label { + color: #2196f3; + font-weight: 600; +} + +.date-label { + font-size: 16px; font-weight: 500; color: #333; } -.date-input { - flex: 1; - padding: 8px 12px; - border: 2px solid #e1e5e9; - border-radius: 8px; - font-size: 16px; - background: white; +.date-value { + font-size: 14px; + color: #666; } -.date-input:focus { - outline: none; - border-color: #667eea; +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + transform: translateY(100%); + } + to { + transform: translateY(0); + } } /* 加载状态 */ @@ -350,35 +580,44 @@ onMounted(() => { } .ranking-item { - background: rgba(255, 255, 255, 0.95); border-radius: 16px; - padding: 15px; display: flex; align-items: flex-start; gap: 15px; - box-shadow: 0 4px 20px rgba(0,0,0,0.1); - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.ranking-item:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px rgba(0,0,0,0.15); + padding: 4px; } /* 排名数字 */ .rank-number { - background: linear-gradient(135deg, #ff6b6b, #ee5a24); - color: white; - width: 32px; - height: 32px; - border-radius: 50%; + color: #333; + width: 16px; + height: 80px; display: flex; align-items: center; justify-content: center; font-weight: bold; - font-size: 14px; + font-size: 16px; flex-shrink: 0; - box-shadow: 0 2px 8px rgba(255,107,107,0.3); +} + +/* 前三名特殊样式 */ +.rank-first { + color: #ffd700; + font-size: 24px; +} + +.rank-second { + color: #afe3f6; + font-size: 24px; +} + +.rank-third { + color: #cd7f32; + font-size: 24px; +} + +.rank-normal { + color: #666; } /* 海报 */ @@ -387,8 +626,7 @@ onMounted(() => { } .poster-img { - width: 60px; - height: 80px; + width: 72px; object-fit: cover; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); @@ -403,11 +641,14 @@ onMounted(() => { .drama-name { font-size: 16px; font-weight: bold; - color: #2c3e50; + color: #555; margin: 0 0 8px 0; line-height: 1.3; } - +.growth-data { + color: #e74c3c; + font-size: 14px; +} .growth-info, .play-count { display: flex; align-items: center; @@ -430,16 +671,6 @@ onMounted(() => { font-weight: 600; } -.description { - color: #7f8c8d; - font-size: 12px; - line-height: 1.4; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-top: 8px; -} - /* 空状态 */ .empty-state { text-align: center; @@ -499,61 +730,4 @@ onMounted(() => { font-weight: 500; } -/* 移动端适配 */ -@media (max-width: 768px) { - .main-content { - padding: 15px; - } - - .title { - font-size: 20px; - } - - .ranking-item { - padding: 12px; - gap: 12px; - } - - .poster-img { - width: 50px; - height: 67px; - } - - .drama-name { - font-size: 15px; - } - - .date-selector { - padding: 12px; - flex-direction: column; - align-items: stretch; - gap: 8px; - } - - .date-input { - width: 100%; - } -} - -@media (max-width: 480px) { - .main-content { - padding: 10px; - } - - .ranking-item { - padding: 10px; - gap: 10px; - } - - .poster-img { - width: 45px; - height: 60px; - } - - .rank-number { - width: 28px; - height: 28px; - font-size: 12px; - } -} diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 4217010..63d1997 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -2,13 +2,11 @@ import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' -import vueDevTools from 'vite-plugin-vue-devtools' // https://vite.dev/config/ export default defineConfig({ plugins: [ vue(), - vueDevTools(), ], resolve: { alias: {