222 lines
7.0 KiB
JavaScript
Executable File
222 lines
7.0 KiB
JavaScript
Executable File
// 随机一言 JavaScript 功能实现
|
||
|
||
class HitokotoApp {
|
||
constructor() {
|
||
// API接口列表
|
||
this.apiEndpoints = [
|
||
"https://60s.api.shumengya.top"
|
||
];
|
||
|
||
this.currentEndpointIndex = 0;
|
||
this.isLoading = false;
|
||
|
||
// DOM 元素
|
||
this.elements = {
|
||
loading: document.getElementById('loading'),
|
||
quoteDisplay: document.getElementById('quoteDisplay'),
|
||
quoteText: document.getElementById('quoteText'),
|
||
quoteIndex: document.getElementById('quoteIndex'),
|
||
errorMessage: document.getElementById('errorMessage'),
|
||
errorText: document.getElementById('errorText'),
|
||
refreshBtn: document.getElementById('refreshBtn')
|
||
};
|
||
|
||
this.init();
|
||
}
|
||
|
||
// 初始化应用
|
||
init() {
|
||
this.bindEvents();
|
||
this.hideAllStates();
|
||
this.showQuoteDisplay();
|
||
}
|
||
|
||
// 绑定事件
|
||
bindEvents() {
|
||
this.elements.refreshBtn.addEventListener('click', () => {
|
||
this.fetchHitokoto();
|
||
});
|
||
|
||
// 键盘快捷键支持
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.code === 'Space' && !this.isLoading) {
|
||
e.preventDefault();
|
||
this.fetchHitokoto();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 隐藏所有状态
|
||
hideAllStates() {
|
||
this.elements.loading.classList.remove('show');
|
||
this.elements.quoteDisplay.classList.remove('hide');
|
||
this.elements.errorMessage.classList.remove('show');
|
||
}
|
||
|
||
// 显示加载状态
|
||
showLoading() {
|
||
this.hideAllStates();
|
||
this.elements.loading.classList.add('show');
|
||
this.elements.quoteDisplay.classList.add('hide');
|
||
this.elements.refreshBtn.disabled = true;
|
||
this.isLoading = true;
|
||
}
|
||
|
||
// 显示一言内容
|
||
showQuoteDisplay() {
|
||
this.hideAllStates();
|
||
this.elements.quoteDisplay.classList.remove('hide');
|
||
this.elements.refreshBtn.disabled = false;
|
||
this.isLoading = false;
|
||
}
|
||
|
||
// 显示错误信息
|
||
showError(message) {
|
||
this.hideAllStates();
|
||
this.elements.errorMessage.classList.add('show');
|
||
this.elements.errorText.textContent = message;
|
||
this.elements.refreshBtn.disabled = false;
|
||
this.isLoading = false;
|
||
}
|
||
|
||
// 获取一言数据
|
||
async fetchHitokoto() {
|
||
if (this.isLoading) return;
|
||
|
||
this.showLoading();
|
||
|
||
// 尝试所有API接口
|
||
for (let i = 0; i < this.apiEndpoints.length; i++) {
|
||
const endpointIndex = (this.currentEndpointIndex + i) % this.apiEndpoints.length;
|
||
const endpoint = this.apiEndpoints[endpointIndex];
|
||
|
||
try {
|
||
const result = await this.tryFetchFromEndpoint(endpoint);
|
||
if (result.success) {
|
||
this.currentEndpointIndex = endpointIndex;
|
||
this.displayHitokoto(result.data);
|
||
return;
|
||
}
|
||
} catch (error) {
|
||
console.warn(`接口 ${endpoint} 请求失败:`, error.message);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// 所有接口都失败
|
||
this.showError('所有接口都无法访问,请检查网络连接或稍后重试');
|
||
}
|
||
|
||
// 尝试从指定接口获取数据
|
||
async tryFetchFromEndpoint(endpoint) {
|
||
const controller = new AbortController();
|
||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
||
|
||
try {
|
||
// 移除URL中的encoding=text参数,确保返回JSON格式
|
||
const response = await fetch(`${endpoint}/v2/hitokoto`, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'Content-Type': 'application/json'
|
||
},
|
||
signal: controller.signal
|
||
});
|
||
|
||
clearTimeout(timeoutId);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
|
||
// 验证返回数据格式
|
||
if (data.code === 200 && data.data && data.data.hitokoto) {
|
||
return {
|
||
success: true,
|
||
data: data.data
|
||
};
|
||
} else {
|
||
throw new Error('返回数据格式不正确');
|
||
}
|
||
|
||
} catch (error) {
|
||
clearTimeout(timeoutId);
|
||
|
||
if (error.name === 'AbortError') {
|
||
throw new Error('请求超时');
|
||
}
|
||
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 显示一言内容
|
||
displayHitokoto(data) {
|
||
// 更新一言文本
|
||
this.elements.quoteText.textContent = data.hitokoto;
|
||
|
||
// 更新序号信息
|
||
if (data.index) {
|
||
this.elements.quoteIndex.textContent = `第 ${data.index} 条`;
|
||
} else {
|
||
this.elements.quoteIndex.textContent = '';
|
||
}
|
||
|
||
// 添加淡入动画效果
|
||
this.elements.quoteText.style.opacity = '0';
|
||
this.elements.quoteIndex.style.opacity = '0';
|
||
|
||
setTimeout(() => {
|
||
this.elements.quoteText.style.transition = 'opacity 0.5s ease';
|
||
this.elements.quoteIndex.style.transition = 'opacity 0.5s ease';
|
||
this.elements.quoteText.style.opacity = '1';
|
||
this.elements.quoteIndex.style.opacity = '1';
|
||
}, 100);
|
||
|
||
this.showQuoteDisplay();
|
||
|
||
// 控制台输出调试信息
|
||
console.log('一言获取成功:', {
|
||
content: data.hitokoto,
|
||
index: data.index,
|
||
endpoint: this.apiEndpoints[this.currentEndpointIndex]
|
||
});
|
||
}
|
||
|
||
// 获取随机接口(用于负载均衡)
|
||
getRandomEndpoint() {
|
||
const randomIndex = Math.floor(Math.random() * this.apiEndpoints.length);
|
||
return this.apiEndpoints[randomIndex];
|
||
}
|
||
}
|
||
|
||
// 页面加载完成后初始化应用
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const app = new HitokotoApp();
|
||
|
||
// 添加全局错误处理
|
||
window.addEventListener('error', (event) => {
|
||
console.error('页面发生错误:', event.error);
|
||
});
|
||
|
||
window.addEventListener('unhandledrejection', (event) => {
|
||
console.error('未处理的Promise拒绝:', event.reason);
|
||
});
|
||
|
||
// 页面可见性变化时的处理
|
||
document.addEventListener('visibilitychange', () => {
|
||
if (!document.hidden && !app.isLoading) {
|
||
// 页面重新可见时,可以选择刷新内容
|
||
console.log('页面重新可见');
|
||
}
|
||
});
|
||
|
||
console.log('随机一言应用初始化完成');
|
||
});
|
||
|
||
// 导出应用类(如果需要在其他地方使用)
|
||
if (typeof module !== 'undefined' && module.exports) {
|
||
module.exports = HitokotoApp;
|
||
} |