优化结果

This commit is contained in:
2025-09-15 19:08:47 +08:00
parent 72084a8782
commit dcfa89e63c
357 changed files with 16156 additions and 1589 deletions

View File

@@ -0,0 +1,180 @@
/**
* 懂车帝热搜API模块
* 负责数据获取、验证和格式化
*/
class CarHotTopicsAPI {
constructor() {
this.baseURL = 'https://60s.api.shumengya.top/v2/dongchedi';
this.timeout = 10000; // 10秒超时
this.retryCount = 3;
this.retryDelay = 1000; // 1秒重试延迟
}
/**
* 获取热搜数据
* @param {string} encoding - 编码格式(可选)
* @returns {Promise<Object>} 热搜数据
*/
async fetchHotTopics(encoding = '') {
const url = encoding ? `${this.baseURL}?encoding=${encodeURIComponent(encoding)}` : this.baseURL;
for (let attempt = 1; attempt <= this.retryCount; attempt++) {
try {
console.log(`[API] 尝试获取数据 (${attempt}/${this.retryCount}): ${url}`);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log('[API] 数据获取成功:', data);
// 验证数据格式
const validatedData = this.validateData(data);
return this.formatData(validatedData);
} catch (error) {
console.error(`[API] 第${attempt}次请求失败:`, error.message);
if (attempt === this.retryCount) {
throw new Error(`获取数据失败: ${error.message}`);
}
// 等待后重试
await this.delay(this.retryDelay * attempt);
}
}
}
/**
* 验证API返回数据格式
* @param {Object} data - API返回的原始数据
* @returns {Object} 验证后的数据
*/
validateData(data) {
if (!data || typeof data !== 'object') {
throw new Error('数据格式错误响应不是有效的JSON对象');
}
if (data.code !== 200) {
throw new Error(`API错误${data.message || '未知错误'}`);
}
if (!Array.isArray(data.data)) {
throw new Error('数据格式错误data字段不是数组');
}
// 验证每个热搜项目的必需字段
data.data.forEach((item, index) => {
const requiredFields = ['rank', 'title', 'score', 'score_desc'];
const missingFields = requiredFields.filter(field => !(field in item));
if (missingFields.length > 0) {
console.warn(`[API] 第${index + 1}项数据缺少字段:`, missingFields);
}
});
return data;
}
/**
* 格式化数据
* @param {Object} data - 验证后的数据
* @returns {Object} 格式化后的数据
*/
formatData(data) {
const formattedTopics = data.data.map(item => ({
rank: parseInt(item.rank) || 0,
title: String(item.title || '').trim(),
score: parseInt(item.score) || 0,
scoreDesc: String(item.score_desc || '').trim(),
// 添加一些计算字段
isTop3: parseInt(item.rank) <= 3,
formattedScore: this.formatScore(item.score),
searchUrl: this.generateSearchUrl(item.title)
}));
return {
code: data.code,
message: data.message,
data: formattedTopics,
updateTime: new Date().toLocaleString('zh-CN'),
total: formattedTopics.length
};
}
/**
* 格式化分数显示
* @param {number} score - 原始分数
* @returns {string} 格式化后的分数
*/
formatScore(score) {
if (!score || isNaN(score)) return '0';
if (score >= 10000) {
return (score / 10000).toFixed(1) + 'w';
}
return score.toLocaleString();
}
/**
* 生成搜索URL
* @param {string} title - 热搜标题
* @returns {string} 搜索URL
*/
generateSearchUrl(title) {
const encodedTitle = encodeURIComponent(title);
return `https://www.dongchedi.com/search?query=${encodedTitle}`;
}
/**
* 延迟函数
* @param {number} ms - 延迟毫秒数
* @returns {Promise} Promise对象
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 获取API状态
* @returns {Promise<Object>} API状态信息
*/
async getAPIStatus() {
try {
const startTime = Date.now();
await this.fetchHotTopics();
const responseTime = Date.now() - startTime;
return {
status: 'online',
responseTime: responseTime,
message: 'API服务正常'
};
} catch (error) {
return {
status: 'offline',
responseTime: null,
message: error.message
};
}
}
}
// 导出API实例
window.CarHotTopicsAPI = CarHotTopicsAPI;

View File

@@ -0,0 +1,313 @@
/**
* 懂车帝热搜应用主程序
* 整合API和UI模块管理应用生命周期
*/
class CarHotTopicsApp {
constructor() {
this.api = null;
this.ui = null;
this.autoRefreshInterval = null;
this.autoRefreshDelay = 5 * 60 * 1000; // 5分钟自动刷新
this.isInitialized = false;
this.init();
}
/**
* 初始化应用
*/
async init() {
try {
console.log('[App] 开始初始化懂车帝热搜应用...');
// 等待DOM加载完成
if (document.readyState === 'loading') {
await new Promise(resolve => {
document.addEventListener('DOMContentLoaded', resolve);
});
}
// 检查必需的类是否存在
if (!window.CarHotTopicsAPI || !window.UIManager) {
throw new Error('缺少必需的模块CarHotTopicsAPI 或 UIManager');
}
// 初始化模块
this.api = new window.CarHotTopicsAPI();
this.ui = new window.UIManager();
// 检查必需的DOM元素
this.checkRequiredElements();
// 绑定事件
this.bindEvents();
// 首次加载数据
await this.loadData();
// 设置自动刷新
this.setupAutoRefresh();
// 设置页面可见性监听
this.setupVisibilityListener();
this.isInitialized = true;
console.log('[App] 应用初始化完成');
} catch (error) {
console.error('[App] 初始化失败:', error);
this.handleInitError(error);
}
}
/**
* 检查必需的DOM元素
*/
checkRequiredElements() {
const requiredIds = ['loading', 'error', 'hotList', 'topicsContainer', 'refreshBtn'];
const missingElements = requiredIds.filter(id => !document.getElementById(id));
if (missingElements.length > 0) {
throw new Error(`缺少必需的DOM元素: ${missingElements.join(', ')}`);
}
}
/**
* 绑定事件监听器
*/
bindEvents() {
// 监听UI触发的刷新事件
document.addEventListener('refreshData', () => {
this.handleManualRefresh();
});
// 监听网络状态变化
window.addEventListener('online', () => {
console.log('[App] 网络已连接,尝试刷新数据');
this.ui.showToast('网络已连接');
this.loadData();
});
window.addEventListener('offline', () => {
console.log('[App] 网络已断开');
this.ui.showToast('网络连接已断开');
});
// 监听页面错误
window.addEventListener('error', (event) => {
console.error('[App] 页面错误:', event.error);
});
// 监听未处理的Promise拒绝
window.addEventListener('unhandledrejection', (event) => {
console.error('[App] 未处理的Promise拒绝:', event.reason);
event.preventDefault();
});
}
/**
* 加载热搜数据
* @param {boolean} showLoading - 是否显示加载状态
*/
async loadData(showLoading = true) {
try {
if (showLoading) {
this.ui.showLoading();
}
console.log('[App] 开始加载热搜数据...');
const data = await this.api.fetchHotTopics();
console.log('[App] 数据加载成功:', data);
this.ui.showHotList(data);
// 重置自动刷新计时器
this.resetAutoRefresh();
} catch (error) {
console.error('[App] 数据加载失败:', error);
this.ui.showError(error.message);
// 如果是网络错误,延迟重试
if (this.isNetworkError(error)) {
setTimeout(() => {
if (navigator.onLine) {
this.loadData(false);
}
}, 30000); // 30秒后重试
}
}
}
/**
* 处理手动刷新
*/
async handleManualRefresh() {
console.log('[App] 手动刷新数据');
await this.loadData();
}
/**
* 设置自动刷新
*/
setupAutoRefresh() {
if (this.autoRefreshInterval) {
clearInterval(this.autoRefreshInterval);
}
this.autoRefreshInterval = setInterval(() => {
if (document.visibilityState === 'visible' && navigator.onLine) {
console.log('[App] 自动刷新数据');
this.loadData(false);
}
}, this.autoRefreshDelay);
console.log(`[App] 自动刷新已设置,间隔: ${this.autoRefreshDelay / 1000}`);
}
/**
* 重置自动刷新计时器
*/
resetAutoRefresh() {
this.setupAutoRefresh();
}
/**
* 设置页面可见性监听
*/
setupVisibilityListener() {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
console.log('[App] 页面变为可见');
// 检查数据是否需要更新
const currentData = this.ui.getCurrentData();
if (currentData) {
const lastUpdate = new Date(currentData.updateTime);
const now = new Date();
const timeDiff = now - lastUpdate;
// 如果数据超过3分钟自动刷新
if (timeDiff > 3 * 60 * 1000) {
console.log('[App] 数据已过期,自动刷新');
this.loadData(false);
}
}
} else {
console.log('[App] 页面变为隐藏');
}
});
}
/**
* 判断是否为网络错误
* @param {Error} error - 错误对象
* @returns {boolean} 是否为网络错误
*/
isNetworkError(error) {
const networkErrorMessages = [
'fetch',
'network',
'timeout',
'connection',
'offline'
];
return networkErrorMessages.some(msg =>
error.message.toLowerCase().includes(msg)
);
}
/**
* 处理初始化错误
* @param {Error} error - 错误对象
*/
handleInitError(error) {
// 显示基本错误信息
const errorContainer = document.getElementById('error');
if (errorContainer) {
errorContainer.style.display = 'flex';
const errorMessage = errorContainer.querySelector('.error-message');
if (errorMessage) {
errorMessage.textContent = `初始化失败: ${error.message}`;
}
}
// 隐藏加载状态
const loadingContainer = document.getElementById('loading');
if (loadingContainer) {
loadingContainer.style.display = 'none';
}
}
/**
* 获取应用状态
* @returns {Object} 应用状态信息
*/
getStatus() {
return {
isInitialized: this.isInitialized,
hasData: !!this.ui?.getCurrentData(),
autoRefreshEnabled: !!this.autoRefreshInterval,
isOnline: navigator.onLine,
isVisible: document.visibilityState === 'visible'
};
}
/**
* 销毁应用
*/
destroy() {
console.log('[App] 销毁应用');
if (this.autoRefreshInterval) {
clearInterval(this.autoRefreshInterval);
this.autoRefreshInterval = null;
}
if (this.ui) {
this.ui.clearData();
}
this.isInitialized = false;
}
}
// 全局错误处理
window.addEventListener('error', (event) => {
console.error('[Global] JavaScript错误:', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error
});
});
window.addEventListener('unhandledrejection', (event) => {
console.error('[Global] 未处理的Promise拒绝:', event.reason);
});
// 应用启动
let app;
// 确保在DOM加载完成后启动应用
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
app = new CarHotTopicsApp();
});
} else {
app = new CarHotTopicsApp();
}
// 导出应用实例(用于调试)
window.CarHotTopicsApp = CarHotTopicsApp;
window.app = app;
// 调试信息
console.log('[App] 懂车帝热搜应用脚本已加载');
console.log('[Debug] 可用的全局对象:', {
CarHotTopicsAPI: !!window.CarHotTopicsAPI,
UIManager: !!window.UIManager,
CarHotTopicsApp: !!window.CarHotTopicsApp
});

View File

@@ -0,0 +1,55 @@
/* 背景样式文件 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0f8f0 25%, #f5f9f5 50%, #fafcfa 75%, #ffffff 100%);
background-attachment: fixed;
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 容器背景 */
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
margin-top: 20px;
margin-bottom: 20px;
box-shadow: 0 8px 32px rgba(139, 195, 74, 0.1);
}
/* 移动端背景优化 */
@media (max-width: 767px) {
body {
background-attachment: scroll;
}
.container {
margin-top: 10px;
margin-bottom: 10px;
border-radius: 16px;
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
body {
background: linear-gradient(135deg, #1a2e1a 0%, #2d4a2d 25%, #3a5a3a 50%, #4a6a4a 75%, #5a7a5a 100%);
}
.container {
background: rgba(0, 0, 0, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
}

View File

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="懂车帝热搜榜单 - 实时汽车资讯热点">
<meta name="keywords" content="懂车帝,热搜,汽车,资讯,榜单">
<title>懂车帝热搜榜单</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<div class="container">
<!-- 头部 -->
<header class="header">
<div class="logo">
<i class="fas fa-car"></i>
<h1>懂车帝热搜</h1>
</div>
<button class="refresh-btn" id="refreshBtn">
<i class="fas fa-sync-alt"></i>
<span>刷新</span>
</button>
</header>
<!-- 主内容区域 -->
<main class="main-content">
<!-- 加载状态 -->
<div class="loading" id="loading">
<div class="loading-spinner"></div>
<p>正在加载热搜数据...</p>
</div>
<!-- 错误状态 -->
<div class="error" id="error" style="display: none;">
<div class="error-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<p class="error-message">加载失败,请稍后重试</p>
<button class="retry-btn" onclick="loadHotTopics()">重新加载</button>
</div>
<!-- 热搜列表 -->
<div class="hot-list" id="hotList" style="display: none;">
<div class="list-header">
<h2>汽车热搜榜</h2>
<span class="update-time" id="updateTime"></span>
</div>
<div class="topics-container" id="topicsContainer">
<!-- 动态生成的热搜项目 -->
</div>
</div>
</main>
<!-- 底部信息 -->
<footer class="footer">
<div class="footer-content">
<p class="data-source">数据来源:懂车帝官方</p>
<p class="update-info">实时更新 · 权威数据</p>
</div>
</footer>
</div>
<!-- 提示消息 -->
<div class="toast" id="toast"></div>
<script src="api.js"></script>
<script src="ui.js"></script>
<script src="app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,532 @@
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: #333;
min-height: 100vh;
overflow-x: hidden;
}
/* 容器布局 */
.container {
max-width: 1200px;
margin: 0 auto;
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 0 16px;
}
/* 头部样式 */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid rgba(139, 195, 74, 0.2);
margin-bottom: 20px;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
}
.logo i {
font-size: 28px;
color: #8bc34a;
background: linear-gradient(135deg, #a5d6a7, #81c784);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.logo h1 {
font-size: 24px;
font-weight: 600;
color: #2e7d32;
margin: 0;
}
.refresh-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
background: linear-gradient(135deg, #a5d6a7, #81c784);
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(139, 195, 74, 0.3);
}
.refresh-btn:hover {
background: linear-gradient(135deg, #81c784, #66bb6a);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(139, 195, 74, 0.4);
}
.refresh-btn:active {
transform: translateY(0);
}
.refresh-btn i {
font-size: 14px;
}
.refresh-btn.loading i {
animation: spin 1s linear infinite;
}
/* 主内容区域 */
.main-content {
flex: 1;
position: relative;
}
/* 加载状态 */
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(139, 195, 74, 0.2);
border-top: 3px solid #8bc34a;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
.loading p {
color: #666;
font-size: 16px;
}
/* 错误状态 */
.error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
.error-icon i {
font-size: 48px;
color: #ff7043;
margin-bottom: 16px;
}
.error-message {
color: #666;
font-size: 16px;
margin-bottom: 20px;
}
.retry-btn {
padding: 10px 20px;
background: linear-gradient(135deg, #a5d6a7, #81c784);
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.retry-btn:hover {
background: linear-gradient(135deg, #81c784, #66bb6a);
}
/* 热搜列表 */
.hot-list {
animation: fadeIn 0.5s ease-in;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 2px solid rgba(139, 195, 74, 0.2);
}
.list-header h2 {
font-size: 20px;
font-weight: 600;
color: #2e7d32;
margin: 0;
}
.update-time {
font-size: 12px;
color: #666;
background: rgba(139, 195, 74, 0.1);
padding: 4px 8px;
border-radius: 12px;
}
.topics-container {
display: grid;
gap: 12px;
}
/* 热搜项目 */
.topic-item {
background: white;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid rgba(139, 195, 74, 0.1);
transition: all 0.3s ease;
cursor: pointer;
position: relative;
overflow: hidden;
}
.topic-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(139, 195, 74, 0.15);
border-color: rgba(139, 195, 74, 0.3);
}
.topic-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(135deg, #a5d6a7, #81c784);
opacity: 0;
transition: opacity 0.3s ease;
}
.topic-item:hover::before {
opacity: 1;
}
.topic-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.rank-badge {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 50%;
font-size: 12px;
font-weight: 600;
color: white;
flex-shrink: 0;
}
.rank-badge.top-3 {
background: linear-gradient(135deg, #ffb74d, #ff9800);
box-shadow: 0 2px 6px rgba(255, 152, 0, 0.3);
}
.rank-badge.normal {
background: linear-gradient(135deg, #a5d6a7, #81c784);
box-shadow: 0 2px 6px rgba(139, 195, 74, 0.3);
}
.topic-title {
font-size: 16px;
font-weight: 500;
color: #333;
line-height: 1.4;
flex: 1;
word-break: break-word;
}
.topic-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12px;
padding-top: 8px;
border-top: 1px solid rgba(139, 195, 74, 0.1);
}
.topic-score {
font-size: 14px;
font-weight: 600;
color: #8bc34a;
}
.topic-actions {
display: flex;
gap: 8px;
}
.action-btn {
padding: 4px 8px;
background: rgba(139, 195, 74, 0.1);
color: #8bc34a;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s ease;
}
.action-btn:hover {
background: rgba(139, 195, 74, 0.2);
}
/* 底部样式 */
.footer {
margin-top: 40px;
padding: 20px 0;
border-top: 1px solid rgba(139, 195, 74, 0.2);
text-align: center;
}
.footer-content p {
margin: 4px 0;
font-size: 12px;
color: #666;
}
.data-source {
font-weight: 500;
}
/* 提示消息 */
.toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(46, 125, 50, 0.9);
color: white;
padding: 12px 20px;
border-radius: 20px;
font-size: 14px;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
}
.toast.show {
opacity: 1;
visibility: visible;
}
/* 动画效果 */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* 平板端适配 */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 0 24px;
}
.topics-container {
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.topic-item {
padding: 18px;
}
.logo h1 {
font-size: 26px;
}
.topic-title {
font-size: 17px;
}
}
/* 桌面端适配 */
@media (min-width: 1025px) {
.container {
padding: 0 32px;
}
.header {
padding: 24px 0;
}
.topics-container {
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.topic-item {
padding: 20px;
}
.logo h1 {
font-size: 28px;
}
.topic-title {
font-size: 18px;
}
.refresh-btn {
padding: 12px 20px;
font-size: 15px;
}
}
/* 移动端优化 */
@media (max-width: 767px) {
.container {
padding: 0 12px;
}
.header {
padding: 16px 0;
margin-bottom: 16px;
}
.logo h1 {
font-size: 20px;
}
.logo i {
font-size: 24px;
}
.refresh-btn {
padding: 8px 12px;
font-size: 13px;
}
.refresh-btn span {
display: none;
}
.topics-container {
grid-template-columns: 1fr;
gap: 10px;
}
.topic-item {
padding: 14px;
}
.topic-title {
font-size: 15px;
line-height: 1.3;
}
.rank-badge {
width: 22px;
height: 22px;
font-size: 11px;
}
.topic-score {
font-size: 13px;
}
.action-btn {
padding: 3px 6px;
font-size: 11px;
}
.list-header h2 {
font-size: 18px;
}
.update-time {
font-size: 11px;
}
.footer {
margin-top: 30px;
padding: 16px 0;
}
.toast {
left: 12px;
right: 12px;
transform: none;
border-radius: 12px;
}
}
/* 超小屏幕优化 */
@media (max-width: 360px) {
.container {
padding: 0 8px;
}
.topic-item {
padding: 12px;
}
.topic-title {
font-size: 14px;
}
.logo h1 {
font-size: 18px;
}
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: rgba(139, 195, 74, 0.1);
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: rgba(139, 195, 74, 0.5);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(139, 195, 74, 0.7);
}

View File

@@ -0,0 +1,410 @@
/**
* UI管理模块
* 负责页面渲染、交互和状态管理
*/
class UIManager {
constructor() {
this.elements = {};
this.isLoading = false;
this.currentData = null;
this.touchStartY = 0;
this.pullThreshold = 80;
this.isPulling = false;
this.initElements();
this.bindEvents();
}
/**
* 初始化DOM元素引用
*/
initElements() {
this.elements = {
loading: document.getElementById('loading'),
error: document.getElementById('error'),
hotList: document.getElementById('hotList'),
topicsContainer: document.getElementById('topicsContainer'),
refreshBtn: document.getElementById('refreshBtn'),
updateTime: document.getElementById('updateTime'),
toast: document.getElementById('toast')
};
// 检查必需元素
const missingElements = Object.entries(this.elements)
.filter(([key, element]) => !element)
.map(([key]) => key);
if (missingElements.length > 0) {
console.error('[UI] 缺少必需的DOM元素:', missingElements);
}
}
/**
* 绑定事件监听器
*/
bindEvents() {
// 刷新按钮点击事件
if (this.elements.refreshBtn) {
this.elements.refreshBtn.addEventListener('click', () => {
this.handleRefresh();
});
}
// 键盘快捷键
document.addEventListener('keydown', (e) => {
if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) {
e.preventDefault();
this.handleRefresh();
}
});
// 移动端下拉刷新
this.initPullToRefresh();
// 页面可见性变化
document.addEventListener('visibilitychange', () => {
if (!document.hidden && this.currentData) {
// 页面重新可见时检查数据是否过期5分钟
const lastUpdate = new Date(this.currentData.updateTime);
const now = new Date();
if (now - lastUpdate > 5 * 60 * 1000) {
this.handleRefresh();
}
}
});
}
/**
* 初始化下拉刷新功能
*/
initPullToRefresh() {
let startY = 0;
let currentY = 0;
let pullDistance = 0;
document.addEventListener('touchstart', (e) => {
if (window.scrollY === 0) {
startY = e.touches[0].clientY;
this.isPulling = true;
}
}, { passive: true });
document.addEventListener('touchmove', (e) => {
if (!this.isPulling || this.isLoading) return;
currentY = e.touches[0].clientY;
pullDistance = currentY - startY;
if (pullDistance > 0 && window.scrollY === 0) {
e.preventDefault();
// 添加视觉反馈
const progress = Math.min(pullDistance / this.pullThreshold, 1);
document.body.style.transform = `translateY(${pullDistance * 0.3}px)`;
document.body.style.opacity = 1 - progress * 0.1;
}
}, { passive: false });
document.addEventListener('touchend', () => {
if (this.isPulling) {
document.body.style.transform = '';
document.body.style.opacity = '';
if (pullDistance > this.pullThreshold && !this.isLoading) {
this.handleRefresh();
}
this.isPulling = false;
pullDistance = 0;
}
});
}
/**
* 处理刷新操作
*/
async handleRefresh() {
if (this.isLoading) return;
this.showToast('正在刷新数据...');
// 触发自定义刷新事件
const refreshEvent = new CustomEvent('refreshData');
document.dispatchEvent(refreshEvent);
}
/**
* 显示加载状态
*/
showLoading() {
this.isLoading = true;
this.hideAllStates();
if (this.elements.loading) {
this.elements.loading.style.display = 'flex';
}
// 刷新按钮加载状态
if (this.elements.refreshBtn) {
this.elements.refreshBtn.classList.add('loading');
this.elements.refreshBtn.disabled = true;
}
}
/**
* 显示错误状态
* @param {string} message - 错误消息
*/
showError(message = '加载失败,请稍后重试') {
this.isLoading = false;
this.hideAllStates();
if (this.elements.error) {
this.elements.error.style.display = 'flex';
const errorMessage = this.elements.error.querySelector('.error-message');
if (errorMessage) {
errorMessage.textContent = message;
}
}
this.resetRefreshButton();
}
/**
* 显示热搜列表
* @param {Object} data - 热搜数据
*/
showHotList(data) {
this.isLoading = false;
this.currentData = data;
this.hideAllStates();
if (this.elements.hotList) {
this.elements.hotList.style.display = 'block';
}
this.renderTopics(data.data);
this.updateTime(data.updateTime);
this.resetRefreshButton();
this.showToast(`已更新 ${data.total} 条热搜数据`);
}
/**
* 隐藏所有状态
*/
hideAllStates() {
['loading', 'error', 'hotList'].forEach(state => {
if (this.elements[state]) {
this.elements[state].style.display = 'none';
}
});
}
/**
* 重置刷新按钮状态
*/
resetRefreshButton() {
if (this.elements.refreshBtn) {
this.elements.refreshBtn.classList.remove('loading');
this.elements.refreshBtn.disabled = false;
}
}
/**
* 渲染热搜话题列表
* @param {Array} topics - 话题数组
*/
renderTopics(topics) {
if (!this.elements.topicsContainer) return;
this.elements.topicsContainer.innerHTML = '';
topics.forEach((topic, index) => {
const topicElement = this.createTopicElement(topic, index);
this.elements.topicsContainer.appendChild(topicElement);
});
}
/**
* 创建单个话题元素
* @param {Object} topic - 话题数据
* @param {number} index - 索引
* @returns {HTMLElement} 话题元素
*/
createTopicElement(topic, index) {
const item = document.createElement('div');
item.className = 'topic-item';
item.style.animationDelay = `${index * 0.1}s`;
item.innerHTML = `
<div class="topic-header">
<div class="rank-badge ${topic.isTop3 ? 'top-3' : 'normal'}">
${topic.rank}
</div>
<div class="topic-title">${this.escapeHtml(topic.title)}</div>
</div>
<div class="topic-footer">
<div class="topic-score">${topic.scoreDesc}</div>
<div class="topic-actions">
<button class="action-btn copy-btn" data-text="${this.escapeHtml(topic.title)}">
<i class="fas fa-copy"></i>
</button>
<button class="action-btn search-btn" data-url="${topic.searchUrl}">
<i class="fas fa-search"></i>
</button>
</div>
</div>
`;
this.bindTopicEvents(item, topic);
return item;
}
/**
* 绑定话题元素事件
* @param {HTMLElement} element - 话题元素
* @param {Object} topic - 话题数据
*/
bindTopicEvents(element, topic) {
// 点击整个项目跳转搜索
element.addEventListener('click', (e) => {
if (!e.target.closest('.action-btn')) {
window.open(topic.searchUrl, '_blank');
}
});
// 复制按钮
const copyBtn = element.querySelector('.copy-btn');
if (copyBtn) {
copyBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.copyToClipboard(topic.title);
});
}
// 搜索按钮
const searchBtn = element.querySelector('.search-btn');
if (searchBtn) {
searchBtn.addEventListener('click', (e) => {
e.stopPropagation();
window.open(topic.searchUrl, '_blank');
});
}
// 长按显示详情
let longPressTimer;
element.addEventListener('touchstart', () => {
longPressTimer = setTimeout(() => {
this.showTopicDetails(topic);
}, 800);
});
element.addEventListener('touchend', () => {
clearTimeout(longPressTimer);
});
element.addEventListener('touchmove', () => {
clearTimeout(longPressTimer);
});
}
/**
* 显示话题详情
* @param {Object} topic - 话题数据
*/
showTopicDetails(topic) {
const details = `
标题: ${topic.title}
排名: 第${topic.rank}
热度: ${topic.scoreDesc}
原始分数: ${topic.score.toLocaleString()}
`;
this.showToast(details, 3000);
}
/**
* 复制文本到剪贴板
* @param {string} text - 要复制的文本
*/
async copyToClipboard(text) {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
} else {
// 降级方案
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}
this.showToast('已复制到剪贴板');
} catch (error) {
console.error('[UI] 复制失败:', error);
this.showToast('复制失败');
}
}
/**
* 更新时间显示
* @param {string} time - 更新时间
*/
updateTime(time) {
if (this.elements.updateTime) {
this.elements.updateTime.textContent = `更新时间: ${time}`;
}
}
/**
* 显示提示消息
* @param {string} message - 消息内容
* @param {number} duration - 显示时长(毫秒)
*/
showToast(message, duration = 2000) {
if (!this.elements.toast) return;
this.elements.toast.textContent = message;
this.elements.toast.classList.add('show');
setTimeout(() => {
this.elements.toast.classList.remove('show');
}, duration);
}
/**
* HTML转义
* @param {string} text - 原始文本
* @returns {string} 转义后的文本
*/
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* 获取当前数据
* @returns {Object|null} 当前数据
*/
getCurrentData() {
return this.currentData;
}
/**
* 清除当前数据
*/
clearData() {
this.currentData = null;
if (this.elements.topicsContainer) {
this.elements.topicsContainer.innerHTML = '';
}
}
}
// 导出UI管理器
window.UIManager = UIManager;

View File

@@ -0,0 +1,66 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": [
{
"rank": 1,
"title": "吉利银河星耀6将于9月5日发布",
"score": 846099,
"score_desc": "84.6w"
},
{
"rank": 2,
"title": "销售称Model Y L日均订单近万辆",
"score": 602350,
"score_desc": "60.2w"
},
{
"rank": 3,
"title": "极氪9X将于9月底上市",
"score": 241114,
"score_desc": "24.1w"
},
{
"rank": 4,
"title": "比亚迪“全家福”亮相齐鲁秋季车展",
"score": 239586,
"score_desc": "24.0w"
},
{
"rank": 5,
"title": "新一代宝马X5内饰曝光",
"score": 209847,
"score_desc": "21.0w"
},
{
"rank": 6,
"title": "LOL前职业选手PDD喜提理想MEGA",
"score": 204628,
"score_desc": "20.5w"
},
{
"rank": 7,
"title": "零跑Lafa5伪装车曝光",
"score": 143127,
"score_desc": "14.3w"
},
{
"rank": 8,
"title": "方程豹钛7将于9月9日上市",
"score": 135759,
"score_desc": "13.6w"
},
{
"rank": 9,
"title": "零跑9月购车政策",
"score": 94419,
"score_desc": "9.4w"
},
{
"rank": 10,
"title": "捷途X70L将于9月10日预售",
"score": 74292,
"score_desc": "7.4w"
}
]
}