把大致框架搭好1
This commit is contained in:
123
frontend/60sapi/热搜榜单/网易云榜单详情/css/background.css
Normal file
123
frontend/60sapi/热搜榜单/网易云榜单详情/css/background.css
Normal file
@@ -0,0 +1,123 @@
|
||||
/* 背景样式文件 */
|
||||
body {
|
||||
background: linear-gradient(135deg, #E8F5E8 0%, #F1F8E9 25%, #E0F2F1 50%, #E8F5E8 75%, #F3E5F5 100%);
|
||||
background-size: 400% 400%;
|
||||
animation: gradientShift 15s ease infinite;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 背景渐变动画 */
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 背景装饰元素 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(129, 199, 132, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(165, 214, 167, 0.08) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
/* 背景粒子效果 */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
|
||||
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.2), transparent),
|
||||
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
|
||||
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.2), transparent),
|
||||
radial-gradient(2px 2px at 160px 30px, rgba(129, 199, 132, 0.3), transparent);
|
||||
background-repeat: repeat;
|
||||
background-size: 200px 100px;
|
||||
animation: particleFloat 20s linear infinite;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@keyframes particleFloat {
|
||||
0% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式背景调整 */
|
||||
@media (max-width: 768px) {
|
||||
body::after {
|
||||
background-size: 150px 75px;
|
||||
animation-duration: 25s;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body::after {
|
||||
background-size: 100px 50px;
|
||||
animation-duration: 30s;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高性能模式 - 减少动画 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body {
|
||||
animation: none;
|
||||
background: linear-gradient(135deg, #E8F5E8 0%, #F1F8E9 50%, #E0F2F1 100%);
|
||||
}
|
||||
|
||||
body::after {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色模式支持 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: linear-gradient(135deg, #1B5E20 0%, #2E7D32 25%, #388E3C 50%, #43A047 75%, #4CAF50 100%);
|
||||
}
|
||||
|
||||
body::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.05) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.03) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(255, 255, 255, 0.02) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
body::after {
|
||||
background-image:
|
||||
radial-gradient(1px 1px at 20px 30px, rgba(255, 255, 255, 0.1), transparent),
|
||||
radial-gradient(1px 1px at 40px 70px, rgba(255, 255, 255, 0.08), transparent),
|
||||
radial-gradient(1px 1px at 90px 40px, rgba(255, 255, 255, 0.06), transparent),
|
||||
radial-gradient(1px 1px at 130px 80px, rgba(255, 255, 255, 0.04), transparent),
|
||||
radial-gradient(1px 1px at 160px 30px, rgba(255, 255, 255, 0.05), transparent);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
483
frontend/60sapi/热搜榜单/网易云榜单详情/css/style.css
Normal file
483
frontend/60sapi/热搜榜单/网易云榜单详情/css/style.css
Normal file
@@ -0,0 +1,483 @@
|
||||
/* 基础样式重置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #2c3e50;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding: 40px 20px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #4CAF50, #81C784);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 10px;
|
||||
animation: titleGlow 3s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes titleGlow {
|
||||
from { filter: drop-shadow(0 0 5px rgba(76, 175, 80, 0.3)); }
|
||||
to { filter: drop-shadow(0 0 15px rgba(76, 175, 80, 0.6)); }
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 榜单信息样式 */
|
||||
.rank-info {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 6px 25px rgba(76, 175, 80, 0.1);
|
||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
|
||||
.rank-header {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.rank-cover {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.rank-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.rank-name {
|
||||
font-size: 1.8rem;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.rank-description {
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.rank-meta {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.update-time, .update-frequency {
|
||||
background: linear-gradient(135deg, #E8F5E8, #C8E6C9);
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9rem;
|
||||
color: #2E7D32;
|
||||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
/* 控制区域样式 */
|
||||
.controls {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 6px 25px rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
#rankId {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #E0E0E0;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
#rankId:focus {
|
||||
outline: none;
|
||||
border-color: #4CAF50;
|
||||
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.load-btn {
|
||||
padding: 12px 24px;
|
||||
background: linear-gradient(135deg, #4CAF50, #66BB6A);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.load-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.load-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.load-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #E0E0E0;
|
||||
border-top: 4px solid #4CAF50;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
background: rgba(255, 245, 245, 0.95);
|
||||
border: 2px solid #ffcdd2;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #c62828;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
padding: 10px 20px;
|
||||
background: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: #d32f2f;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 歌曲列表样式 */
|
||||
.song-list {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 6px 25px rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 25px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #E8F5E8;
|
||||
}
|
||||
|
||||
.list-header h3 {
|
||||
font-size: 1.5rem;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.song-count {
|
||||
background: linear-gradient(135deg, #4CAF50, #66BB6A);
|
||||
color: white;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 歌曲项样式 */
|
||||
.song-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
margin-bottom: 12px;
|
||||
background: rgba(248, 255, 248, 0.8);
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(76, 175, 80, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.song-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(76, 175, 80, 0.15);
|
||||
background: rgba(232, 245, 232, 0.9);
|
||||
}
|
||||
|
||||
.song-rank {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: #4CAF50;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.song-rank.top3 {
|
||||
background: linear-gradient(135deg, #FFD700, #FFA000);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.song-info {
|
||||
flex: 1;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.song-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 5px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.song-artist {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.song-album {
|
||||
color: #888;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.song-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.song-duration {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.song-popularity {
|
||||
background: linear-gradient(135deg, #4CAF50, #66BB6A);
|
||||
color: white;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 返回按钮 */
|
||||
.back-to-list {
|
||||
text-align: center;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
background: linear-gradient(135deg, #81C784, #A5D6A7);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(129, 199, 132, 0.3);
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(129, 199, 132, 0.4);
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 30px 20px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 15px;
|
||||
margin-top: 40px;
|
||||
color: #666;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.rank-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rank-cover {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
min-width: auto;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#rankId {
|
||||
min-width: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.song-item {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.song-info {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.song-meta {
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.title {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 30px 15px;
|
||||
}
|
||||
|
||||
.rank-info, .controls, .song-list {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.song-item {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 平板端适配 */
|
||||
@media (min-width: 769px) and (max-width: 1024px) {
|
||||
.container {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.song-item {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.rank-cover {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
}
|
||||
}
|
||||
69
frontend/60sapi/热搜榜单/网易云榜单详情/index.html
Normal file
69
frontend/60sapi/热搜榜单/网易云榜单详情/index.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>网易云榜单详情</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link rel="stylesheet" href="css/background.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1 class="title">🎵 网易云榜单详情</h1>
|
||||
<p class="subtitle">发现音乐的魅力</p>
|
||||
</header>
|
||||
|
||||
<div class="rank-info" id="rankInfo" style="display: none;">
|
||||
<div class="rank-header">
|
||||
<img class="rank-cover" id="rankCover" src="" alt="榜单封面">
|
||||
<div class="rank-details">
|
||||
<h2 class="rank-name" id="rankName"></h2>
|
||||
<p class="rank-description" id="rankDescription"></p>
|
||||
<div class="rank-meta">
|
||||
<span class="update-time" id="updateTime"></span>
|
||||
<span class="update-frequency" id="updateFrequency"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="input-group">
|
||||
<label for="rankId">榜单ID:</label>
|
||||
<input type="text" id="rankId" placeholder="请输入榜单ID,如:3778678">
|
||||
<button id="loadBtn" class="load-btn">加载榜单</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="loading" id="loading" style="display: none;">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载榜单数据...</p>
|
||||
</div>
|
||||
|
||||
<div class="error" id="error" style="display: none;">
|
||||
<div class="error-icon">⚠️</div>
|
||||
<p class="error-message" id="errorMessage"></p>
|
||||
<button class="retry-btn" id="retryBtn">重试</button>
|
||||
</div>
|
||||
|
||||
<div class="song-list" id="songList" style="display: none;">
|
||||
<div class="list-header">
|
||||
<h3>歌曲列表</h3>
|
||||
<div class="song-count" id="songCount"></div>
|
||||
</div>
|
||||
<div class="songs" id="songs"></div>
|
||||
</div>
|
||||
|
||||
<div class="back-to-list">
|
||||
<a href="../网易云榜单列表/index.html" class="back-btn">← 返回榜单列表</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<p>© 2025 网易云榜单详情 - 数据来源于官方API</p>
|
||||
</footer>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
349
frontend/60sapi/热搜榜单/网易云榜单详情/js/script.js
Normal file
349
frontend/60sapi/热搜榜单/网易云榜单详情/js/script.js
Normal file
@@ -0,0 +1,349 @@
|
||||
// 网易云榜单详情 JavaScript
|
||||
class NeteaseMusicRankDetail {
|
||||
constructor() {
|
||||
this.apiUrls = [];
|
||||
this.currentApiIndex = 0;
|
||||
this.rankData = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await this.loadApiUrls();
|
||||
this.bindEvents();
|
||||
this.checkUrlParams();
|
||||
} catch (error) {
|
||||
console.error('初始化失败:', error);
|
||||
this.showError('初始化失败,请刷新页面重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 加载API接口列表
|
||||
async loadApiUrls() {
|
||||
try {
|
||||
const response = await fetch('./接口集合.json');
|
||||
if (!response.ok) {
|
||||
throw new Error('无法加载API接口配置');
|
||||
}
|
||||
this.apiUrls = await response.json();
|
||||
console.log('API接口加载成功:', this.apiUrls);
|
||||
} catch (error) {
|
||||
console.error('加载API接口失败:', error);
|
||||
// 使用默认接口
|
||||
this.apiUrls = [
|
||||
'https://60s-cf.viki.moe',
|
||||
'https://60s.viki.moe',
|
||||
'https://60s.b23.run',
|
||||
'https://60s.114128.xyz',
|
||||
'https://60s-cf.114128.xyz'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvents() {
|
||||
const loadBtn = document.getElementById('loadBtn');
|
||||
const rankIdInput = document.getElementById('rankId');
|
||||
const retryBtn = document.getElementById('retryBtn');
|
||||
|
||||
loadBtn.addEventListener('click', () => this.loadRankDetail());
|
||||
retryBtn.addEventListener('click', () => this.loadRankDetail());
|
||||
|
||||
// 回车键加载
|
||||
rankIdInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.loadRankDetail();
|
||||
}
|
||||
});
|
||||
|
||||
// 输入验证
|
||||
rankIdInput.addEventListener('input', (e) => {
|
||||
const value = e.target.value.trim();
|
||||
loadBtn.disabled = !value || !/^\d+$/.test(value);
|
||||
});
|
||||
}
|
||||
|
||||
// 检查URL参数
|
||||
checkUrlParams() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const rankId = urlParams.get('id');
|
||||
const rankName = urlParams.get('name');
|
||||
|
||||
if (rankId && /^\d+$/.test(rankId)) {
|
||||
document.getElementById('rankId').value = rankId;
|
||||
|
||||
// 如果有榜单名称,更新页面标题
|
||||
if (rankName) {
|
||||
document.title = `${decodeURIComponent(rankName)} - 网易云榜单详情`;
|
||||
document.querySelector('.title').textContent = `🎵 ${decodeURIComponent(rankName)}`;
|
||||
document.querySelector('.subtitle').textContent = '正在加载榜单详情...';
|
||||
}
|
||||
|
||||
this.loadRankDetail();
|
||||
}
|
||||
}
|
||||
|
||||
// 加载榜单详情
|
||||
async loadRankDetail() {
|
||||
const rankId = document.getElementById('rankId').value.trim();
|
||||
|
||||
if (!rankId) {
|
||||
this.showError('请输入榜单ID');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(rankId)) {
|
||||
this.showError('榜单ID必须是数字');
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading();
|
||||
this.currentApiIndex = 0;
|
||||
|
||||
try {
|
||||
const data = await this.fetchRankDetail(rankId);
|
||||
this.displayRankDetail(data);
|
||||
this.hideLoading();
|
||||
|
||||
// 更新URL
|
||||
const newUrl = new URL(window.location);
|
||||
newUrl.searchParams.set('id', rankId);
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载榜单详情失败:', error);
|
||||
this.hideLoading();
|
||||
this.showError(error.message || '加载失败,请检查榜单ID是否正确');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取榜单详情数据
|
||||
async fetchRankDetail(rankId) {
|
||||
let lastError = null;
|
||||
|
||||
for (let i = 0; i < this.apiUrls.length; i++) {
|
||||
try {
|
||||
const apiUrl = this.apiUrls[this.currentApiIndex];
|
||||
const url = `${apiUrl}/v2/ncm-rank/${rankId}`;
|
||||
|
||||
console.log(`尝试API ${this.currentApiIndex + 1}:`, url);
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
|
||||
const response = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code !== 200) {
|
||||
throw new Error(data.message || '获取数据失败');
|
||||
}
|
||||
|
||||
console.log('API调用成功:', data);
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`API ${this.currentApiIndex + 1} 失败:`, error.message);
|
||||
lastError = error;
|
||||
this.currentApiIndex = (this.currentApiIndex + 1) % this.apiUrls.length;
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
lastError = new Error('请求超时,请重试');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error('所有API接口都无法访问');
|
||||
}
|
||||
|
||||
// 显示榜单详情
|
||||
displayRankDetail(data) {
|
||||
this.rankData = data;
|
||||
const songs = data.data || [];
|
||||
|
||||
// 显示榜单信息(如果有的话)
|
||||
this.displayRankInfo(songs[0]);
|
||||
|
||||
// 显示歌曲列表
|
||||
this.displaySongList(songs);
|
||||
|
||||
// 显示相关区域
|
||||
document.getElementById('songList').style.display = 'block';
|
||||
document.getElementById('error').style.display = 'none';
|
||||
}
|
||||
|
||||
// 显示榜单信息
|
||||
displayRankInfo(firstSong) {
|
||||
const rankInfo = document.getElementById('rankInfo');
|
||||
|
||||
if (firstSong && firstSong.rank_name) {
|
||||
document.getElementById('rankName').textContent = firstSong.rank_name;
|
||||
document.getElementById('rankDescription').textContent = `${firstSong.rank_name} - 网易云音乐官方榜单`;
|
||||
|
||||
// 如果有专辑封面,使用第一首歌的专辑封面作为榜单封面
|
||||
if (firstSong.album && firstSong.album.cover) {
|
||||
document.getElementById('rankCover').src = firstSong.album.cover;
|
||||
document.getElementById('rankCover').alt = firstSong.rank_name;
|
||||
}
|
||||
|
||||
document.getElementById('updateTime').textContent = `更新时间: ${this.formatDate(new Date())}`;
|
||||
document.getElementById('updateFrequency').textContent = '实时更新';
|
||||
|
||||
rankInfo.style.display = 'block';
|
||||
} else {
|
||||
rankInfo.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示歌曲列表
|
||||
displaySongList(songs) {
|
||||
const songsContainer = document.getElementById('songs');
|
||||
const songCount = document.getElementById('songCount');
|
||||
|
||||
songCount.textContent = `共 ${songs.length} 首歌曲`;
|
||||
|
||||
songsContainer.innerHTML = '';
|
||||
|
||||
songs.forEach((song, index) => {
|
||||
const songElement = this.createSongElement(song, index);
|
||||
songsContainer.appendChild(songElement);
|
||||
});
|
||||
}
|
||||
|
||||
// 创建歌曲元素
|
||||
createSongElement(song) {
|
||||
const songDiv = document.createElement('div');
|
||||
songDiv.className = 'song-item';
|
||||
|
||||
// 处理艺术家信息
|
||||
const artists = Array.isArray(song.artist) ? song.artist : [song.artist].filter(Boolean);
|
||||
const artistNames = artists.map(artist =>
|
||||
typeof artist === 'object' ? artist.name : artist
|
||||
).join(', ') || '未知艺术家';
|
||||
|
||||
// 处理专辑信息
|
||||
const albumName = song.album && song.album.name ? song.album.name : '未知专辑';
|
||||
|
||||
// 处理时长
|
||||
const duration = song.duration_desc || this.formatDuration(song.duration);
|
||||
|
||||
// 处理热度
|
||||
const popularity = song.popularity || song.score || 0;
|
||||
|
||||
songDiv.innerHTML = `
|
||||
<div class="song-rank ${song.rank <= 3 ? 'top3' : ''}">${song.rank}</div>
|
||||
<div class="song-info">
|
||||
<div class="song-title">${this.escapeHtml(song.title)}</div>
|
||||
<div class="song-artist">${this.escapeHtml(artistNames)}</div>
|
||||
<div class="song-album">${this.escapeHtml(albumName)}</div>
|
||||
</div>
|
||||
<div class="song-meta">
|
||||
<div class="song-duration">${duration}</div>
|
||||
<div class="song-popularity">${popularity}%</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 添加点击事件
|
||||
if (song.link) {
|
||||
songDiv.style.cursor = 'pointer';
|
||||
songDiv.addEventListener('click', () => {
|
||||
window.open(song.link, '_blank');
|
||||
});
|
||||
}
|
||||
|
||||
return songDiv;
|
||||
}
|
||||
|
||||
// 格式化时长
|
||||
formatDuration(duration) {
|
||||
if (!duration) return '--:--';
|
||||
|
||||
const seconds = Math.floor(duration / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
|
||||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
formatDate(date) {
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
// HTML转义
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
showLoading() {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('error').style.display = 'none';
|
||||
document.getElementById('songList').style.display = 'none';
|
||||
document.getElementById('loadBtn').disabled = true;
|
||||
}
|
||||
|
||||
// 隐藏加载状态
|
||||
hideLoading() {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('loadBtn').disabled = false;
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
showError(message) {
|
||||
document.getElementById('error').style.display = 'block';
|
||||
document.getElementById('errorMessage').textContent = message;
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('songList').style.display = 'none';
|
||||
document.getElementById('loadBtn').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 全局错误处理
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error('全局错误:', event.error);
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('未处理的Promise拒绝:', event.reason);
|
||||
});
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new NeteaseMusicRankDetail();
|
||||
});
|
||||
|
||||
// 添加CSS动画类
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 为页面元素添加淡入动画
|
||||
const elements = document.querySelectorAll('.container > *');
|
||||
elements.forEach((el, index) => {
|
||||
el.style.opacity = '0';
|
||||
el.style.transform = 'translateY(20px)';
|
||||
el.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
|
||||
|
||||
setTimeout(() => {
|
||||
el.style.opacity = '1';
|
||||
el.style.transform = 'translateY(0)';
|
||||
}, index * 100);
|
||||
});
|
||||
});
|
||||
7
frontend/60sapi/热搜榜单/网易云榜单详情/接口集合.json
Normal file
7
frontend/60sapi/热搜榜单/网易云榜单详情/接口集合.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
"https://60s-cf.viki.moe",
|
||||
"https://60s.viki.moe",
|
||||
"https://60s.b23.run",
|
||||
"https://60s.114128.xyz",
|
||||
"https://60s-cf.114128.xyz"
|
||||
]
|
||||
5612
frontend/60sapi/热搜榜单/网易云榜单详情/返回接口.json
Normal file
5612
frontend/60sapi/热搜榜单/网易云榜单详情/返回接口.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user