排行榜前端

This commit is contained in:
xbh 2025-10-26 22:41:53 +08:00
parent 06996967ca
commit 278f007b5e
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">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<title>Vite App</title>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="app"></div>

View File

@ -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(() => {
<!-- 标题 -->
<div class="header">
<div class="title-container">
<span class="lightning-icon"></span>
<h1 class="title">热播总</h1>
<span class="lightning-icon"></span>
<i class="bi bi-stars lightning-icon"></i>
<h1 class="title">抖音AI短剧</h1>
<i class="bi bi-stars lightning-icon"></i>
</div>
<div class="update-time">
基于实时热度排行 {{ getCurrentTime() }}更新
基于实时热度排行 {{ updateTime || getCurrentTime() }}更新
<span class="refresh-icon">🔄</span>
</div>
</div>
<!-- 日期选择 -->
<div class="date-selector">
<label for="date-input">选择日期</label>
<input
id="date-input"
type="date"
v-model="selectedDate"
@change="onDateChange"
class="date-input"
/>
<!-- 自定义日期选择器 -->
<div class="custom-date-selector">
<div class="date-display" @click="toggleDatePicker">
<span class="date-text">{{ formatDisplayDate(selectedDate) }}<i class="bi bi-chevron-compact-right"></i></span>
</div>
<!-- 日期选择弹窗 -->
<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>
<!-- 加载状态 -->
@ -154,9 +285,9 @@ onMounted(() => {
<!-- 海报 -->
<div class="poster">
<img
:src="item.cover_image_url || '/placeholder-poster.svg'"
:src="getImageSrc(item)"
:alt="item.title || item.mix_name"
@error="$event.target.src='/placeholder-poster.svg'"
@error="handleImageError($event, item)"
class="poster-img"
/>
</div>
@ -172,15 +303,11 @@ onMounted(() => {
<span class="play-value">{{ formatPlayCount(item.play_vv) }}</span>
</div>
<!-- 简介省略显示 -->
<div class="description">
{{ item.summary || item.title || item.mix_name || '暂无简介' }}
</div>
</div>
<!-- 增长数据 -->
<div class="growth-data">
<span class="growth-icon">🔥</span>
<i class="bi bi-fire"></i>
<span class="growth-number">{{ formatGrowth(item) }}</span>
</div>
</div>
@ -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;
}
}
</style>

View File

@ -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: {