|
|
|
|
|
function toggleSettings(e) { |
|
|
|
|
|
e && e.stopPropagation(); |
|
|
const panel = document.getElementById('settingsPanel'); |
|
|
panel.classList.toggle('show'); |
|
|
} |
|
|
|
|
|
|
|
|
const toastQueue = []; |
|
|
let isShowingToast = false; |
|
|
|
|
|
function showToast(message, type = 'error') { |
|
|
|
|
|
toastQueue.push({ message, type }); |
|
|
|
|
|
|
|
|
if (!isShowingToast) { |
|
|
showNextToast(); |
|
|
} |
|
|
} |
|
|
|
|
|
function showNextToast() { |
|
|
if (toastQueue.length === 0) { |
|
|
isShowingToast = false; |
|
|
return; |
|
|
} |
|
|
|
|
|
isShowingToast = true; |
|
|
const { message, type } = toastQueue.shift(); |
|
|
|
|
|
const toast = document.getElementById('toast'); |
|
|
const toastMessage = document.getElementById('toastMessage'); |
|
|
|
|
|
const bgColors = { |
|
|
'error': 'bg-red-500', |
|
|
'success': 'bg-green-500', |
|
|
'info': 'bg-blue-500', |
|
|
'warning': 'bg-yellow-500' |
|
|
}; |
|
|
|
|
|
const bgColor = bgColors[type] || bgColors.error; |
|
|
toast.className = `fixed top-4 left-1/2 -translate-x-1/2 px-6 py-3 rounded-lg shadow-lg transform transition-all duration-300 ${bgColor} text-white`; |
|
|
toastMessage.textContent = message; |
|
|
|
|
|
|
|
|
toast.style.opacity = '1'; |
|
|
toast.style.transform = 'translateX(-50%) translateY(0)'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
toast.style.opacity = '0'; |
|
|
toast.style.transform = 'translateX(-50%) translateY(-100%)'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
showNextToast(); |
|
|
}, 300); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
|
|
|
let loadingTimeoutId = null; |
|
|
|
|
|
function showLoading(message = '加载中...') { |
|
|
|
|
|
if (loadingTimeoutId) { |
|
|
clearTimeout(loadingTimeoutId); |
|
|
} |
|
|
|
|
|
const loading = document.getElementById('loading'); |
|
|
const messageEl = loading.querySelector('p'); |
|
|
messageEl.textContent = message; |
|
|
loading.style.display = 'flex'; |
|
|
|
|
|
|
|
|
loadingTimeoutId = setTimeout(() => { |
|
|
hideLoading(); |
|
|
showToast('操作超时,请稍后重试', 'warning'); |
|
|
}, 30000); |
|
|
} |
|
|
|
|
|
function hideLoading() { |
|
|
|
|
|
if (loadingTimeoutId) { |
|
|
clearTimeout(loadingTimeoutId); |
|
|
loadingTimeoutId = null; |
|
|
} |
|
|
|
|
|
const loading = document.getElementById('loading'); |
|
|
loading.style.display = 'none'; |
|
|
} |
|
|
|
|
|
function updateSiteStatus(isAvailable) { |
|
|
const statusEl = document.getElementById('siteStatus'); |
|
|
if (isAvailable) { |
|
|
statusEl.innerHTML = '<span class="text-green-500">●</span> 可用'; |
|
|
} else { |
|
|
statusEl.innerHTML = '<span class="text-red-500">●</span> 不可用'; |
|
|
} |
|
|
} |
|
|
|
|
|
function closeModal() { |
|
|
document.getElementById('modal').classList.add('hidden'); |
|
|
|
|
|
document.getElementById('modalContent').innerHTML = ''; |
|
|
} |
|
|
|
|
|
|
|
|
function getSearchHistory() { |
|
|
try { |
|
|
const data = localStorage.getItem(SEARCH_HISTORY_KEY); |
|
|
if (!data) return []; |
|
|
|
|
|
const parsed = JSON.parse(data); |
|
|
|
|
|
|
|
|
if (!Array.isArray(parsed)) return []; |
|
|
|
|
|
|
|
|
return parsed.map(item => { |
|
|
if (typeof item === 'string') { |
|
|
return { text: item, timestamp: 0 }; |
|
|
} |
|
|
return item; |
|
|
}).filter(item => item && item.text); |
|
|
} catch (e) { |
|
|
console.error('获取搜索历史出错:', e); |
|
|
return []; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function saveSearchHistory(query) { |
|
|
if (!query || !query.trim()) return; |
|
|
|
|
|
|
|
|
query = query.trim().substring(0, 50).replace(/</g, '<').replace(/>/g, '>'); |
|
|
|
|
|
let history = getSearchHistory(); |
|
|
|
|
|
|
|
|
const now = Date.now(); |
|
|
|
|
|
|
|
|
history = history.filter(item => |
|
|
typeof item === 'object' && item.timestamp && (now - item.timestamp < 5184000000) |
|
|
); |
|
|
|
|
|
|
|
|
history = history.filter(item => |
|
|
typeof item === 'object' ? item.text !== query : item !== query |
|
|
); |
|
|
|
|
|
|
|
|
history.unshift({ |
|
|
text: query, |
|
|
timestamp: now |
|
|
}); |
|
|
|
|
|
|
|
|
if (history.length > MAX_HISTORY_ITEMS) { |
|
|
history = history.slice(0, MAX_HISTORY_ITEMS); |
|
|
} |
|
|
|
|
|
try { |
|
|
localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(history)); |
|
|
} catch (e) { |
|
|
console.error('保存搜索历史失败:', e); |
|
|
|
|
|
try { |
|
|
localStorage.removeItem(SEARCH_HISTORY_KEY); |
|
|
localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(history.slice(0, 3))); |
|
|
} catch (e2) { |
|
|
console.error('再次保存搜索历史失败:', e2); |
|
|
} |
|
|
} |
|
|
|
|
|
renderSearchHistory(); |
|
|
} |
|
|
|
|
|
|
|
|
function renderSearchHistory() { |
|
|
const historyContainer = document.getElementById('recentSearches'); |
|
|
if (!historyContainer) return; |
|
|
|
|
|
const history = getSearchHistory(); |
|
|
|
|
|
if (history.length === 0) { |
|
|
historyContainer.innerHTML = ''; |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
historyContainer.innerHTML = ` |
|
|
<div class="flex justify-between items-center w-full mb-2"> |
|
|
<div class="text-gray-500">最近搜索:</div> |
|
|
<button id="clearHistoryBtn" class="text-gray-500 hover:text-white transition-colors" |
|
|
onclick="clearSearchHistory()" aria-label="清除搜索历史"> |
|
|
清除搜索历史 |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
history.forEach(item => { |
|
|
const tag = document.createElement('button'); |
|
|
tag.className = 'search-tag'; |
|
|
tag.textContent = item.text; |
|
|
|
|
|
|
|
|
if (item.timestamp) { |
|
|
const date = new Date(item.timestamp); |
|
|
tag.title = `搜索于: ${date.toLocaleString()}`; |
|
|
} |
|
|
|
|
|
tag.onclick = function() { |
|
|
document.getElementById('searchInput').value = item.text; |
|
|
search(); |
|
|
}; |
|
|
historyContainer.appendChild(tag); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function clearSearchHistory() { |
|
|
try { |
|
|
localStorage.removeItem(SEARCH_HISTORY_KEY); |
|
|
renderSearchHistory(); |
|
|
showToast('搜索历史已清除', 'success'); |
|
|
} catch (e) { |
|
|
console.error('清除搜索历史失败:', e); |
|
|
showToast('清除搜索历史失败', 'error'); |
|
|
} |
|
|
} |
|
|
|