Compare commits

...

2 Commits

Author SHA1 Message Date
xbh
74494013f9 Merge commit 'bba47d2fe95f318d35a986a17aff2bdbc97ed5e5' 2025-10-26 22:42:27 +08:00
xbh
278f007b5e 排行榜前端 2025-10-26 22:41:53 +08:00
3 changed files with 314 additions and 136 deletions

View File

@ -6,6 +6,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<title>Vite App</title> <title>Vite App</title>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -9,11 +9,49 @@ const loading = ref(false)
const selectedDate = ref('') const selectedDate = ref('')
const currentPage = ref(1) const currentPage = ref(1)
const totalPages = ref(1) const totalPages = ref(1)
const updateTime = ref('') //
const showDatePicker = ref(false) //
const dateOptions = ref([]) //
// //
const initDate = () => { const initDate = () => {
const today = new Date() const today = new Date()
selectedDate.value = today.toISOString().split('T')[0] 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) { if (response.data.success) {
rankingData.value = response.data.data rankingData.value = response.data.data
totalPages.value = response.data.pagination.pages totalPages.value = response.data.pagination.pages
//
updateTime.value = response.data.update_time || ''
} else { } else {
console.error('获取数据失败:', response.data.message) console.error('获取数据失败:', response.data.message)
rankingData.value = [] rankingData.value = []
@ -76,7 +116,7 @@ const formatGrowth = (item) => {
const changeRate = timelineData.play_vv_change_rate || 0 const changeRate = timelineData.play_vv_change_rate || 0
if (change > 0) { if (change > 0) {
return `+${formatPlayCount(change)} (${changeRate.toFixed(1)}%)` return `${formatPlayCount(change)}`
} }
return '暂无数据' 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 = () => { const onDateChange = () => {
currentPage.value = 1 currentPage.value = 1
fetchRankingData() 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(() => { onMounted(() => {
initDate() initDate()
@ -111,26 +224,44 @@ onMounted(() => {
<!-- 标题 --> <!-- 标题 -->
<div class="header"> <div class="header">
<div class="title-container"> <div class="title-container">
<span class="lightning-icon"></span> <i class="bi bi-stars lightning-icon"></i>
<h1 class="title">热播总</h1> <h1 class="title">抖音AI短剧</h1>
<span class="lightning-icon"></span> <i class="bi bi-stars lightning-icon"></i>
</div> </div>
<div class="update-time"> <div class="update-time">
基于实时热度排行 {{ getCurrentTime() }}更新 基于实时热度排行 {{ updateTime || getCurrentTime() }}更新
<span class="refresh-icon">🔄</span> <span class="refresh-icon">🔄</span>
</div> </div>
</div> </div>
<!-- 日期选择 --> <!-- 自定义日期选择器 -->
<div class="date-selector"> <div class="custom-date-selector">
<label for="date-input">选择日期</label> <div class="date-display" @click="toggleDatePicker">
<input <span class="date-text">{{ formatDisplayDate(selectedDate) }}<i class="bi bi-chevron-compact-right"></i></span>
id="date-input"
type="date" </div>
v-model="selectedDate"
@change="onDateChange" <!-- 日期选择弹窗 -->
class="date-input" <div v-if="showDatePicker" class="date-picker-overlay" @click="closeDatePicker">
/> <div class="date-picker-popup" @click.stop>
<div class="date-picker-header">
<h3>选择日期</h3>
<button class="close-btn" @click="closeDatePicker">×</button>
</div>
<div class="date-list">
<div
v-for="date in dateOptions"
:key="date.value"
class="date-option"
:class="{ active: selectedDate === date.value }"
@click="selectDate(date.value)"
>
<span class="date-label">{{ date.label }}</span>
<span class="date-value">{{ date.display }}</span>
</div>
</div>
</div>
</div>
</div> </div>
<!-- 加载状态 --> <!-- 加载状态 -->
@ -154,9 +285,9 @@ onMounted(() => {
<!-- 海报 --> <!-- 海报 -->
<div class="poster"> <div class="poster">
<img <img
:src="item.cover_image_url || '/placeholder-poster.svg'" :src="getImageSrc(item)"
:alt="item.title || item.mix_name" :alt="item.title || item.mix_name"
@error="$event.target.src='/placeholder-poster.svg'" @error="handleImageError($event, item)"
class="poster-img" class="poster-img"
/> />
</div> </div>
@ -172,15 +303,11 @@ onMounted(() => {
<span class="play-value">{{ formatPlayCount(item.play_vv) }}</span> <span class="play-value">{{ formatPlayCount(item.play_vv) }}</span>
</div> </div>
<!-- 简介省略显示 -->
<div class="description">
{{ item.summary || item.title || item.mix_name || '暂无简介' }}
</div>
</div> </div>
<!-- 增长数据 --> <!-- 增长数据 -->
<div class="growth-data"> <div class="growth-data">
<span class="growth-icon">🔥</span> <i class="bi bi-fire"></i>
<span class="growth-number">{{ formatGrowth(item) }}</span> <span class="growth-number">{{ formatGrowth(item) }}</span>
</div> </div>
</div> </div>
@ -234,7 +361,6 @@ onMounted(() => {
.app { .app {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
padding-bottom: 80px; /* 为底部导航留出空间 */ padding-bottom: 80px; /* 为底部导航留出空间 */
color: white; color: white;
@ -251,7 +377,6 @@ onMounted(() => {
/* 标题区域 */ /* 标题区域 */
.header { .header {
text-align: center; text-align: center;
margin-bottom: 20px;
} }
.title-container { .title-container {
@ -268,11 +393,10 @@ onMounted(() => {
} }
.title { .title {
color: white; color: #555;
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
margin: 0; margin: 0;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
} }
.update-time { .update-time {
@ -289,35 +413,141 @@ onMounted(() => {
color: #4CAF50; color: #4CAF50;
} }
/* 日期选择器 */ /* 自定义日期选择器 */
.date-selector { .custom-date-selector {
background: rgba(255, 255, 255, 0.9); padding: 16px 0 8px;
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);
} }
.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; font-weight: 500;
color: #333; color: #333;
} }
.date-input { .date-value {
flex: 1; font-size: 14px;
padding: 8px 12px; color: #666;
border: 2px solid #e1e5e9;
border-radius: 8px;
font-size: 16px;
background: white;
} }
.date-input:focus { /* 动画效果 */
outline: none; @keyframes fadeIn {
border-color: #667eea; from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
} }
/* 加载状态 */ /* 加载状态 */
@ -350,35 +580,44 @@ onMounted(() => {
} }
.ranking-item { .ranking-item {
background: rgba(255, 255, 255, 0.95);
border-radius: 16px; border-radius: 16px;
padding: 15px;
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: 15px; gap: 15px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1); padding: 4px;
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);
} }
/* 排名数字 */ /* 排名数字 */
.rank-number { .rank-number {
background: linear-gradient(135deg, #ff6b6b, #ee5a24); color: #333;
color: white; width: 16px;
width: 32px; height: 80px;
height: 32px;
border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 16px;
flex-shrink: 0; 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 { .poster-img {
width: 60px; width: 72px;
height: 80px;
object-fit: cover; object-fit: cover;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15); box-shadow: 0 2px 8px rgba(0,0,0,0.15);
@ -403,11 +641,14 @@ onMounted(() => {
.drama-name { .drama-name {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
color: #2c3e50; color: #555;
margin: 0 0 8px 0; margin: 0 0 8px 0;
line-height: 1.3; line-height: 1.3;
} }
.growth-data {
color: #e74c3c;
font-size: 14px;
}
.growth-info, .play-count { .growth-info, .play-count {
display: flex; display: flex;
align-items: center; align-items: center;
@ -430,16 +671,6 @@ onMounted(() => {
font-weight: 600; 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 { .empty-state {
text-align: center; text-align: center;
@ -499,61 +730,4 @@ onMounted(() => {
font-weight: 500; 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;
}
}
</style> </style>

View File

@ -2,13 +2,11 @@ import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
vueDevTools(),
], ],
resolve: { resolve: {
alias: { alias: {