225 lines
7.1 KiB
JavaScript
225 lines
7.1 KiB
JavaScript
// 随机一言 JavaScript 功能实现
|
|
|
|
class HitokotoApp {
|
|
constructor() {
|
|
// API接口列表
|
|
this.apiEndpoints = [
|
|
"https://60s-cf.viki.moe",
|
|
"https://60s.viki.moe",
|
|
"https://60s.b23.run",
|
|
"https://60s.114128.xyz",
|
|
"https://60s-cf.114128.xyz"
|
|
];
|
|
|
|
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 {
|
|
const response = await fetch(`${endpoint}/v2/hitokoto?encoding=text`, {
|
|
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;
|
|
} |