354 lines
11 KiB
JavaScript
354 lines
11 KiB
JavaScript
// UI 渲染和交互模块
|
|
class UIManager {
|
|
constructor() {
|
|
this.elements = {
|
|
loading: document.getElementById('loading'),
|
|
error: document.getElementById('error'),
|
|
hotList: document.getElementById('hotList'),
|
|
refreshBtn: document.getElementById('refreshBtn'),
|
|
updateTime: document.getElementById('updateTime')
|
|
};
|
|
|
|
this.isLoading = false;
|
|
this.initEventListeners();
|
|
}
|
|
|
|
// 初始化事件监听器
|
|
initEventListeners() {
|
|
// 刷新按钮点击事件
|
|
this.elements.refreshBtn.addEventListener('click', () => {
|
|
if (!this.isLoading) {
|
|
this.refreshData();
|
|
}
|
|
});
|
|
|
|
// 键盘快捷键
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) {
|
|
e.preventDefault();
|
|
if (!this.isLoading) {
|
|
this.refreshData();
|
|
}
|
|
}
|
|
});
|
|
|
|
// 下拉刷新(移动端)
|
|
this.initPullToRefresh();
|
|
}
|
|
|
|
// 初始化下拉刷新
|
|
initPullToRefresh() {
|
|
let startY = 0;
|
|
let currentY = 0;
|
|
let isPulling = false;
|
|
const threshold = 80;
|
|
|
|
document.addEventListener('touchstart', (e) => {
|
|
if (window.scrollY === 0) {
|
|
startY = e.touches[0].clientY;
|
|
isPulling = true;
|
|
}
|
|
});
|
|
|
|
document.addEventListener('touchmove', (e) => {
|
|
if (isPulling && window.scrollY === 0) {
|
|
currentY = e.touches[0].clientY;
|
|
const pullDistance = currentY - startY;
|
|
|
|
if (pullDistance > 0) {
|
|
e.preventDefault();
|
|
|
|
// 添加视觉反馈
|
|
if (pullDistance > threshold) {
|
|
this.elements.refreshBtn.style.transform = 'scale(1.1)';
|
|
} else {
|
|
this.elements.refreshBtn.style.transform = 'scale(1)';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
document.addEventListener('touchend', () => {
|
|
if (isPulling) {
|
|
const pullDistance = currentY - startY;
|
|
|
|
if (pullDistance > threshold && !this.isLoading) {
|
|
this.refreshData();
|
|
}
|
|
|
|
this.elements.refreshBtn.style.transform = 'scale(1)';
|
|
isPulling = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
// 显示加载状态
|
|
showLoading() {
|
|
this.isLoading = true;
|
|
this.elements.loading.style.display = 'block';
|
|
this.elements.error.style.display = 'none';
|
|
this.elements.hotList.style.display = 'none';
|
|
this.elements.refreshBtn.classList.add('loading');
|
|
}
|
|
|
|
// 隐藏加载状态
|
|
hideLoading() {
|
|
this.isLoading = false;
|
|
this.elements.loading.style.display = 'none';
|
|
this.elements.refreshBtn.classList.remove('loading');
|
|
}
|
|
|
|
// 显示错误状态
|
|
showError(message = '获取数据失败,请稍后重试') {
|
|
this.hideLoading();
|
|
this.elements.error.style.display = 'block';
|
|
this.elements.hotList.style.display = 'none';
|
|
|
|
const errorText = this.elements.error.querySelector('p');
|
|
if (errorText) {
|
|
errorText.textContent = message;
|
|
}
|
|
}
|
|
|
|
// 显示热搜列表
|
|
showHotList() {
|
|
this.hideLoading();
|
|
this.elements.error.style.display = 'none';
|
|
this.elements.hotList.style.display = 'grid';
|
|
}
|
|
|
|
// 渲染热搜数据
|
|
renderHotTopics(data) {
|
|
if (!data || !data.data || !Array.isArray(data.data)) {
|
|
this.showError('数据格式错误');
|
|
return;
|
|
}
|
|
|
|
const hotTopics = data.data;
|
|
this.elements.hotList.innerHTML = '';
|
|
|
|
hotTopics.forEach((topic, index) => {
|
|
const hotItem = this.createHotItem(topic, index);
|
|
this.elements.hotList.appendChild(hotItem);
|
|
});
|
|
|
|
this.showHotList();
|
|
this.updateTimestamp(data.isCache);
|
|
}
|
|
|
|
// 创建热搜项目元素
|
|
createHotItem(topic, index) {
|
|
const item = document.createElement('div');
|
|
item.className = 'hot-item';
|
|
item.style.animationDelay = `${index * 0.1}s`;
|
|
|
|
// 处理数据
|
|
const rank = topic.rank || (index + 1);
|
|
const title = hotTopicsAPI.truncateText(topic.title, 80);
|
|
const desc = hotTopicsAPI.truncateText(topic.desc, 120);
|
|
const score = hotTopicsAPI.formatScore(topic.score, topic.score_desc);
|
|
const avatarUrl = hotTopicsAPI.processImageUrl(topic.avatar);
|
|
const topicUrl = hotTopicsAPI.processTopicUrl(topic.url);
|
|
const rankClass = hotTopicsAPI.getRankClass(rank);
|
|
|
|
item.innerHTML = `
|
|
<div class="hot-item-header">
|
|
<div class="rank-badge ${rankClass}">
|
|
${rank}
|
|
</div>
|
|
<div class="hot-item-content">
|
|
<h3 class="hot-title">${title}</h3>
|
|
<p class="hot-desc">${desc}</p>
|
|
<div class="hot-meta">
|
|
<div class="hot-score">
|
|
<i class="fas fa-fire"></i>
|
|
<span>${score}</span>
|
|
</div>
|
|
${avatarUrl ? `<img class="hot-avatar" src="${avatarUrl}" alt="话题图片" onerror="this.style.display='none'">` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 添加点击事件
|
|
item.addEventListener('click', () => {
|
|
this.openTopic(topicUrl, title);
|
|
});
|
|
|
|
// 添加长按事件(移动端)
|
|
let pressTimer;
|
|
item.addEventListener('touchstart', (e) => {
|
|
pressTimer = setTimeout(() => {
|
|
this.showTopicMenu(topic, e.touches[0].clientX, e.touches[0].clientY);
|
|
}, 500);
|
|
});
|
|
|
|
item.addEventListener('touchend', () => {
|
|
clearTimeout(pressTimer);
|
|
});
|
|
|
|
item.addEventListener('touchmove', () => {
|
|
clearTimeout(pressTimer);
|
|
});
|
|
|
|
return item;
|
|
}
|
|
|
|
// 打开话题链接
|
|
openTopic(url, title) {
|
|
if (url && url !== '#') {
|
|
// 在新窗口打开
|
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
} else {
|
|
this.showToast('链接暂不可用');
|
|
}
|
|
}
|
|
|
|
// 显示话题菜单(长按)
|
|
showTopicMenu(topic, x, y) {
|
|
const menu = document.createElement('div');
|
|
menu.className = 'topic-menu';
|
|
menu.style.cssText = `
|
|
position: fixed;
|
|
left: ${x}px;
|
|
top: ${y}px;
|
|
background: white;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
|
padding: 8px 0;
|
|
z-index: 1000;
|
|
min-width: 120px;
|
|
`;
|
|
|
|
const actions = [
|
|
{ text: '打开链接', action: () => this.openTopic(hotTopicsAPI.processTopicUrl(topic.url), topic.title) },
|
|
{ text: '复制标题', action: () => this.copyText(topic.title) },
|
|
{ text: '分享', action: () => this.shareContent(topic) }
|
|
];
|
|
|
|
actions.forEach(action => {
|
|
const item = document.createElement('div');
|
|
item.textContent = action.text;
|
|
item.style.cssText = `
|
|
padding: 12px 16px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
color: #333;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
`;
|
|
item.addEventListener('click', () => {
|
|
action.action();
|
|
document.body.removeChild(menu);
|
|
});
|
|
menu.appendChild(item);
|
|
});
|
|
|
|
document.body.appendChild(menu);
|
|
|
|
// 点击其他地方关闭菜单
|
|
setTimeout(() => {
|
|
document.addEventListener('click', function closeMenu() {
|
|
if (document.body.contains(menu)) {
|
|
document.body.removeChild(menu);
|
|
}
|
|
document.removeEventListener('click', closeMenu);
|
|
});
|
|
}, 100);
|
|
}
|
|
|
|
// 复制文本
|
|
async copyText(text) {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
this.showToast('已复制到剪贴板');
|
|
} catch (error) {
|
|
console.error('复制失败:', error);
|
|
this.showToast('复制失败');
|
|
}
|
|
}
|
|
|
|
// 分享内容
|
|
async shareContent(topic) {
|
|
const shareData = {
|
|
title: topic.title,
|
|
text: topic.desc,
|
|
url: hotTopicsAPI.processTopicUrl(topic.url)
|
|
};
|
|
|
|
try {
|
|
if (navigator.share) {
|
|
await navigator.share(shareData);
|
|
} else {
|
|
// 降级到复制链接
|
|
await this.copyText(`${topic.title} - ${shareData.url}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('分享失败:', error);
|
|
}
|
|
}
|
|
|
|
// 显示提示消息
|
|
showToast(message, duration = 2000) {
|
|
const toast = document.createElement('div');
|
|
toast.textContent = message;
|
|
toast.style.cssText = `
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(0,0,0,0.8);
|
|
color: white;
|
|
padding: 12px 20px;
|
|
border-radius: 20px;
|
|
font-size: 14px;
|
|
z-index: 1000;
|
|
animation: fadeInUp 0.3s ease;
|
|
`;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(() => {
|
|
toast.style.animation = 'fadeOut 0.3s ease';
|
|
setTimeout(() => {
|
|
if (document.body.contains(toast)) {
|
|
document.body.removeChild(toast);
|
|
}
|
|
}, 300);
|
|
}, duration);
|
|
}
|
|
|
|
// 更新时间戳
|
|
updateTimestamp(isCache = false) {
|
|
const now = new Date();
|
|
const timeString = now.toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
|
|
const cacheText = isCache ? ' (缓存数据)' : '';
|
|
this.elements.updateTime.textContent = `最后更新:${timeString}${cacheText}`;
|
|
}
|
|
|
|
// 刷新数据
|
|
async refreshData() {
|
|
if (window.loadHotTopics) {
|
|
await window.loadHotTopics();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 导出UI管理器实例
|
|
const uiManager = new UIManager();
|
|
|
|
// 添加CSS动画
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
@keyframes fadeOut {
|
|
from { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
to { opacity: 0; transform: translateX(-50%) translateY(10px); }
|
|
}
|
|
`;
|
|
document.head.appendChild(style); |