优化结果
This commit is contained in:
180
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/api.js
Normal file
180
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/api.js
Normal 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;
|
||||
313
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/app.js
Normal file
313
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/app.js
Normal 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
|
||||
});
|
||||
55
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/background.css
Normal file
55
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/background.css
Normal 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);
|
||||
}
|
||||
}
|
||||
72
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/index.html
Normal file
72
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/index.html
Normal 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>
|
||||
532
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/styles.css
Normal file
532
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/styles.css
Normal 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);
|
||||
}
|
||||
410
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/ui.js
Normal file
410
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/ui.js
Normal 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;
|
||||
66
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/返回接口.json
Normal file
66
InfoGenie-frontend/public/60sapi/热搜榜单/懂车帝热搜/返回接口.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user