60sapi接口搭建完毕,数据库连接测试成功,登录注册部分简单完成
This commit is contained in:
388
frontend/react-app/public/60sapi/日更资讯/历史上的今天/css/style.css
Normal file
388
frontend/react-app/public/60sapi/日更资讯/历史上的今天/css/style.css
Normal file
@@ -0,0 +1,388 @@
|
||||
/* 历史上的今天 - 手机端优先的响应式设计 */
|
||||
|
||||
/* 重置样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: #2c3e50;
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 头部样式 - 手机端优先 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 15px;
|
||||
padding: 20px 15px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 日期显示 */
|
||||
.date-info {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.date-info h2 {
|
||||
font-size: 1.3rem;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.date-info .date-text {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 30px 15px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border: 3px solid #ecf0f1;
|
||||
border-top: 3px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 历史事件容器 */
|
||||
.events-container {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 15px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 统计信息 */
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
/* 事件列表 */
|
||||
.events-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.event-card {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
border-left: 4px solid #667eea;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.event-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 事件类型标签 */
|
||||
.event-type {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.event-type.birth {
|
||||
background: #e8f5e8;
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.event-type.death {
|
||||
background: #fdf2e9;
|
||||
color: #e67e22;
|
||||
}
|
||||
|
||||
.event-type.event {
|
||||
background: #ebf3fd;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
/* 事件年份 */
|
||||
.event-year {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.event-year::before {
|
||||
content: "📅";
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 事件标题 */
|
||||
.event-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 事件描述 */
|
||||
.event-description {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 链接按钮 */
|
||||
.event-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
padding: 6px 12px;
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
border-radius: 15px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.event-link:hover {
|
||||
background: rgba(102, 126, 234, 0.2);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.event-link::after {
|
||||
content: "🔗";
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
background: #fed7d7;
|
||||
color: #c53030;
|
||||
padding: 15px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
border: 1px solid #feb2b2;
|
||||
margin: 15px 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 隐藏类 */
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 淡入动画 */
|
||||
.fade-in {
|
||||
animation: fadeIn 0.6s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 平板端适配 (768px+) */
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 750px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 30px 25px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.date-info {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.date-info h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.events-container {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.event-card {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.event-description {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 电脑端适配 (1024px+) */
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 40px 35px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.events-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.event-card {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.event-description {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏幕优化 (1200px+) */
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.events-list {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(102, 126, 234, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(102, 126, 234, 0.7);
|
||||
}
|
||||
83
frontend/react-app/public/60sapi/日更资讯/历史上的今天/index.html
Normal file
83
frontend/react-app/public/60sapi/日更资讯/历史上的今天/index.html
Normal file
@@ -0,0 +1,83 @@
|
||||
<!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>历史上的今天 - 60s API集合</title>
|
||||
|
||||
<!-- 预加载关键资源 -->
|
||||
<link rel="preconnect" href="https://60s.viki.moe">
|
||||
<link rel="dns-prefetch" href="https://60s.viki.moe">
|
||||
|
||||
<!-- 样式文件 -->
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
|
||||
<!-- 图标 -->
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📚</text></svg>">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 页面容器 -->
|
||||
<div class="container">
|
||||
<!-- 页面头部 -->
|
||||
<header class="header">
|
||||
<h1 class="title">📚 历史上的今天</h1>
|
||||
<p class="subtitle">探索历史,了解今天在历史上发生的重要事件</p>
|
||||
</header>
|
||||
|
||||
<!-- 日期信息 -->
|
||||
<section class="date-section" id="date-info">
|
||||
<div class="date-display">
|
||||
<span class="date-text" id="date-text">加载中...</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div class="loading" id="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>正在加载历史数据...</p>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<main class="main-content" id="history-content" style="display: none;">
|
||||
|
||||
|
||||
<!-- 历史事件列表 -->
|
||||
<section class="events-section">
|
||||
<h2 class="section-title">历史事件</h2>
|
||||
<div class="events-container">
|
||||
<div class="events-list" id="events-list">
|
||||
<!-- 事件卡片将通过 JavaScript 动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- JavaScript 文件 -->
|
||||
<script src="js/script.js"></script>
|
||||
|
||||
<!-- 页面性能监控 -->
|
||||
<script>
|
||||
// 页面加载性能监控
|
||||
window.addEventListener('load', function() {
|
||||
if ('performance' in window) {
|
||||
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
|
||||
console.log(`页面加载时间: ${loadTime}ms`);
|
||||
}
|
||||
});
|
||||
|
||||
// 错误监控
|
||||
window.addEventListener('error', function(event) {
|
||||
console.error('页面错误:', event.error);
|
||||
});
|
||||
|
||||
// 未处理的 Promise 错误
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
console.error('未处理的 Promise 错误:', event.reason);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
295
frontend/react-app/public/60sapi/日更资讯/历史上的今天/js/script.js
vendored
Normal file
295
frontend/react-app/public/60sapi/日更资讯/历史上的今天/js/script.js
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
// 历史上的今天 - JavaScript 功能实现
|
||||
|
||||
// API 配置
|
||||
const API = {
|
||||
endpoints: [],
|
||||
currentIndex: 0,
|
||||
encoding: 'utf-8',
|
||||
// 初始化API接口列表
|
||||
async init() {
|
||||
try {
|
||||
const res = await fetch('./接口集合.json');
|
||||
const endpoints = await res.json();
|
||||
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/today_in_history`);
|
||||
} catch (e) {
|
||||
// 如果无法加载接口集合,使用默认接口
|
||||
this.endpoints = ['https://60s.viki.moe/v2/today_in_history'];
|
||||
}
|
||||
},
|
||||
// 获取当前接口URL
|
||||
getCurrentUrl() {
|
||||
if (this.endpoints.length === 0) return null;
|
||||
const url = new URL(this.endpoints[this.currentIndex]);
|
||||
url.searchParams.append('encoding', this.encoding);
|
||||
return url.toString();
|
||||
},
|
||||
// 切换到下一个接口
|
||||
switchToNext() {
|
||||
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
|
||||
return this.currentIndex < this.endpoints.length;
|
||||
},
|
||||
// 重置到第一个接口
|
||||
reset() {
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// 事件类型映射
|
||||
const EVENT_TYPE_MAP = {
|
||||
'birth': { name: '诞生', icon: '🎂', color: '#27ae60' },
|
||||
'death': { name: '逝世', icon: '🕊️', color: '#e67e22' },
|
||||
'event': { name: '事件', icon: '📰', color: '#3498db' }
|
||||
};
|
||||
|
||||
// DOM 元素
|
||||
let elements = {};
|
||||
let currentData = null;
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initElements();
|
||||
loadTodayInHistory();
|
||||
});
|
||||
|
||||
// 初始化 DOM 元素
|
||||
function initElements() {
|
||||
elements = {
|
||||
loading: document.getElementById('loading'),
|
||||
content: document.getElementById('history-content'),
|
||||
dateInfo: document.getElementById('date-info'),
|
||||
dateText: document.getElementById('date-text'),
|
||||
totalEvents: document.getElementById('total-events'),
|
||||
birthEvents: document.getElementById('birth-events'),
|
||||
deathEvents: document.getElementById('death-events'),
|
||||
otherEvents: document.getElementById('other-events'),
|
||||
eventsList: document.getElementById('events-list')
|
||||
};
|
||||
}
|
||||
|
||||
// 加载历史上的今天数据
|
||||
async function loadTodayInHistory() {
|
||||
try {
|
||||
showLoading(true);
|
||||
|
||||
// 初始化API接口列表
|
||||
await API.init();
|
||||
|
||||
// 重置API索引到第一个接口
|
||||
API.reset();
|
||||
|
||||
// 尝试所有API接口
|
||||
for (let i = 0; i < API.endpoints.length; i++) {
|
||||
try {
|
||||
const url = API.getCurrentUrl();
|
||||
console.log(`尝试接口 ${i + 1}/${API.endpoints.length}: ${url}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
cache: 'no-store',
|
||||
timeout: 10000 // 10秒超时
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('API响应数据:', data);
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
console.log(`接口 ${i + 1} 请求成功`);
|
||||
currentData = data.data;
|
||||
displayHistoryData(data.data);
|
||||
return;
|
||||
} else {
|
||||
throw new Error(data.message || '获取数据失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`接口 ${i + 1} 失败:`, error.message);
|
||||
|
||||
// 如果不是最后一个接口,切换到下一个
|
||||
if (i < API.endpoints.length - 1) {
|
||||
API.switchToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 所有接口都失败了,抛出错误
|
||||
throw new Error('所有接口都无法访问');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载历史数据失败:', error);
|
||||
showError(`加载失败: ${error.message}`);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示历史数据
|
||||
function displayHistoryData(data) {
|
||||
if (!data || !data.items) {
|
||||
showError('没有获取到历史数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新日期信息
|
||||
updateDateInfo(data);
|
||||
|
||||
// 更新统计信息
|
||||
updateStats(data.items);
|
||||
|
||||
// 显示事件列表
|
||||
renderEventsList(data.items);
|
||||
|
||||
// 显示内容区域
|
||||
if (elements.content) {
|
||||
elements.content.classList.add('fade-in');
|
||||
elements.content.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新日期信息
|
||||
function updateDateInfo(data) {
|
||||
if (elements.dateText && data.date) {
|
||||
const today = new Date();
|
||||
const year = today.getFullYear();
|
||||
elements.dateText.textContent = `${year}年${data.month}月${data.day}日`;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计信息
|
||||
function updateStats(items) {
|
||||
const stats = {
|
||||
total: items.length,
|
||||
birth: items.filter(item => item.event_type === 'birth').length,
|
||||
death: items.filter(item => item.event_type === 'death').length,
|
||||
event: items.filter(item => item.event_type === 'event').length
|
||||
};
|
||||
|
||||
if (elements.totalEvents) {
|
||||
elements.totalEvents.textContent = stats.total;
|
||||
}
|
||||
|
||||
if (elements.birthEvents) {
|
||||
elements.birthEvents.textContent = stats.birth;
|
||||
}
|
||||
|
||||
if (elements.deathEvents) {
|
||||
elements.deathEvents.textContent = stats.death;
|
||||
}
|
||||
|
||||
if (elements.otherEvents) {
|
||||
elements.otherEvents.textContent = stats.event;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染事件列表
|
||||
function renderEventsList(items) {
|
||||
if (!elements.eventsList || !items) return;
|
||||
|
||||
// 按年份排序(从今到古)
|
||||
const sortedItems = [...items].sort((a, b) => {
|
||||
return parseInt(b.year) - parseInt(a.year);
|
||||
});
|
||||
|
||||
elements.eventsList.innerHTML = '';
|
||||
|
||||
sortedItems.forEach(item => {
|
||||
const eventCard = createEventCard(item);
|
||||
elements.eventsList.appendChild(eventCard);
|
||||
});
|
||||
}
|
||||
|
||||
// 创建事件卡片
|
||||
function createEventCard(item) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'event-card';
|
||||
|
||||
const eventType = EVENT_TYPE_MAP[item.event_type] || EVENT_TYPE_MAP['event'];
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="event-type ${item.event_type}">${eventType.name}</div>
|
||||
<div class="event-year">${formatYear(item.year)}</div>
|
||||
<div class="event-title">${escapeHtml(item.title)}</div>
|
||||
<div class="event-description">${escapeHtml(item.description)}</div>
|
||||
${item.link ? `<a href="${escapeHtml(item.link)}" target="_blank" rel="noopener noreferrer" class="event-link">了解更多</a>` : ''}
|
||||
`;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// 格式化年份显示
|
||||
function formatYear(year) {
|
||||
const yearNum = parseInt(year);
|
||||
if (yearNum < 0) {
|
||||
return `公元前${Math.abs(yearNum)}年`;
|
||||
} else if (yearNum < 1000) {
|
||||
return `公元${yearNum}年`;
|
||||
} else {
|
||||
return `${yearNum}年`;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
function showLoading(show) {
|
||||
if (elements.loading) {
|
||||
elements.loading.style.display = show ? 'block' : 'none';
|
||||
}
|
||||
|
||||
if (elements.content) {
|
||||
elements.content.style.display = show ? 'none' : 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
function showError(message) {
|
||||
if (elements.content) {
|
||||
elements.content.innerHTML = `
|
||||
<div class="error">
|
||||
<h3>😔 加载失败</h3>
|
||||
<p>${escapeHtml(message)}</p>
|
||||
<button onclick="loadTodayInHistory()" style="
|
||||
margin-top: 10px;
|
||||
padding: 8px 16px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
">重新加载</button>
|
||||
</div>
|
||||
`;
|
||||
elements.content.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// HTML 转义
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
window.addEventListener('error', function(event) {
|
||||
console.error('页面错误:', event.error);
|
||||
});
|
||||
|
||||
// 网络状态监听
|
||||
window.addEventListener('online', function() {
|
||||
console.log('网络已连接');
|
||||
});
|
||||
|
||||
window.addEventListener('offline', function() {
|
||||
console.log('网络已断开');
|
||||
showError('网络连接已断开,请检查网络设置');
|
||||
});
|
||||
|
||||
// 导出全局方法
|
||||
window.TodayInHistory = {
|
||||
loadTodayInHistory,
|
||||
showError,
|
||||
showLoading
|
||||
};
|
||||
7
frontend/react-app/public/60sapi/日更资讯/历史上的今天/接口集合.json
Normal file
7
frontend/react-app/public/60sapi/日更资讯/历史上的今天/接口集合.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
"https://60s-cf.viki.moe",
|
||||
"https://60s.viki.moe",
|
||||
"https://60s.b23.run",
|
||||
"https://60s.114128.xyz",
|
||||
"https://60s-cf.114128.xyz"
|
||||
]
|
||||
102
frontend/react-app/public/60sapi/日更资讯/历史上的今天/返回接口.json
Normal file
102
frontend/react-app/public/60sapi/日更资讯/历史上的今天/返回接口.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"date": "8-19",
|
||||
"month": 8,
|
||||
"day": 19,
|
||||
"items": [
|
||||
{
|
||||
"title": "罗马帝国开国皇帝奥古斯都逝世",
|
||||
"year": "14",
|
||||
"description": "奥古斯都(拉丁文 Augustus的中译,复数型 Augusti)的原意为“神圣的”、“高贵的”,带有宗教与神学式的意味。",
|
||||
"event_type": "death",
|
||||
"link": "https://baike.baidu.com/item/%E5%A5%A5%E5%8F%A4%E6%96%AF%E9%83%BD/14291"
|
||||
},
|
||||
{
|
||||
"title": "近代概率论的奠基者帕斯卡逝世",
|
||||
"year": "1662",
|
||||
"description": "布莱士·帕斯卡(Blaise Pascal ,1623-1662)是法国数学家、物理学家、哲学家、散文家。",
|
||||
"event_type": "death",
|
||||
"link": "https://baike.baidu.com/item/%E5%B8%83%E8%8E%B1%E5%A3%AB%C2%B7%E5%B8%95%E6%96%AF%E5%8D%A1"
|
||||
},
|
||||
{
|
||||
"title": "瑞典国王古斯塔夫三世发动政变夺取权力",
|
||||
"year": "1772",
|
||||
"description": "古斯塔夫三世(Gustavus III,1746-1792)是瑞典历史上褒贬最多的国王(1771-1792)。阿道夫·弗里德里克国王的儿子和继承者。",
|
||||
"event_type": "event",
|
||||
"link": "https://baike.baidu.com/item/%E5%8F%A4%E6%96%AF%E5%A1%94%E5%A4%AB%E4%B8%89%E4%B8%96"
|
||||
},
|
||||
{
|
||||
"title": "美国飞机设计师奥维尔·莱特诞生",
|
||||
"year": "1871",
|
||||
"description": "奥威尔莱特(公元1871~公元1948)。 奥维尔·莱特1871年生于美国俄亥俄州代顿市。上过中学,但实际上未获得毕业文凭。",
|
||||
"event_type": "birth",
|
||||
"link": "https://baike.baidu.com/item/%E5%A5%A5%E7%BB%B4%E5%B0%94%C2%B7%E8%8E%B1%E7%89%B9"
|
||||
},
|
||||
{
|
||||
"title": "法国著名时装设计师、香奈儿品牌创始人加布里埃·香奈儿出生",
|
||||
"year": "1883",
|
||||
"description": "香奈儿儿时入读修女院学校学得一手针线活。后来她与许多上流社会男士有过交往。1910年,毅然放弃嫁入豪门做阔太太的她在巴黎开设了一家女装帽子店,从此开创了香奈儿时尚帝国。",
|
||||
"event_type": "birth",
|
||||
"link": "https://baike.baidu.com/item/%E5%8A%A0%E5%B8%83%E9%87%8C%E5%9F%83%C2%B7%E9%A6%99%E5%A5%88%E5%84%BF/9480318"
|
||||
},
|
||||
{
|
||||
"title": "美国宇航员斯托里·马斯格雷夫出生",
|
||||
"year": "1935",
|
||||
"description": "斯托里·马斯格雷夫(Franklin Story Musgrave,1935年8月19日-),美国宇航员,拥有医学、数学、文学等六个学位,入选美国国家航空航天局(NASA)科学家宇航员。",
|
||||
"event_type": "birth",
|
||||
"link": "https://baike.baidu.com/item/%E6%96%AF%E6%89%98%E9%87%8C%C2%B7%E9%A9%AC%E6%96%AF%E6%A0%BC%E9%9B%B7%E5%A4%AB"
|
||||
},
|
||||
{
|
||||
"title": "纳粹德国陆军元帅京特·冯·克鲁格畏罪自杀",
|
||||
"year": "1944",
|
||||
"description": "汉斯·京特·冯·克卢格(Günther·von·Kluge, 1882年10月30日-1944年8月19日),纳粹德国陆军元帅(1940.7.19),著名军事家、统帅。",
|
||||
"event_type": "death",
|
||||
"link": "https://baike.baidu.com/item/%E4%BA%AC%E7%89%B9%C2%B7%E5%86%AF%C2%B7%E5%85%8B%E9%B2%81%E6%A0%BC"
|
||||
},
|
||||
{
|
||||
"title": "美国第42任总统克林顿出生",
|
||||
"year": "1946",
|
||||
"description": "威廉·杰斐逊·克林顿,美国律师、政治家,美国民主党成员,曾任阿肯色州州长和第42任美国总统。克林顿基金会主席 。",
|
||||
"event_type": "birth",
|
||||
"link": "https://baike.baidu.com/item/%E5%A8%81%E5%BB%89%C2%B7%E6%9D%B0%E6%96%90%E9%80%8A%C2%B7%E5%85%8B%E6%9E%97%E9%A1%BF"
|
||||
},
|
||||
{
|
||||
"title": "美国演员马修·派瑞出生",
|
||||
"year": "1969",
|
||||
"description": "马修·派瑞(Matthew Perry,1969年8月19日—2023年10月28日),出生于美国马萨诸塞州普利茅斯,美国、加拿大籍男演员、编剧。",
|
||||
"event_type": "birth",
|
||||
"link": "https://baike.baidu.com/item/%E9%A9%AC%E4%BF%AE%C2%B7%E6%B4%BE%E7%91%9E"
|
||||
},
|
||||
{
|
||||
"title": "北回归线标志塔在广州落成",
|
||||
"year": "1985",
|
||||
"description": "北回归线标志塔,是标志地理学上北回归线经过地方的建筑物。",
|
||||
"event_type": "event",
|
||||
"link": "https://baike.baidu.com/item/%E5%8C%97%E5%9B%9E%E5%BD%92%E7%BA%BF%E6%A0%87%E5%BF%97%E5%A1%94"
|
||||
},
|
||||
{
|
||||
"title": "“八一九事件”,苏联八月政变",
|
||||
"year": "1991",
|
||||
"description": "八一九事件,又称“苏联政变”、“八月政变”,指1991年8月19日-8月21日在苏联发生的一次政变。",
|
||||
"event_type": "event",
|
||||
"link": "https://baike.baidu.com/item/%E5%85%AB%E4%B8%80%E4%B9%9D%E4%BA%8B%E4%BB%B6"
|
||||
},
|
||||
{
|
||||
"title": "量子化学家莱纳斯·卡尔·鲍林逝世",
|
||||
"year": "1994",
|
||||
"description": "莱纳斯·卡尔·鲍林(Linus Carl Pauling,1901年2月28日—1994年8月19日),出生于美国俄勒冈州波特兰,化学家、美国国家科学院院士、美国艺术与科学院院士,1954年诺贝尔化学奖获得者。",
|
||||
"event_type": "death",
|
||||
"link": "https://baike.baidu.com/item/%E8%8E%B1%E7%BA%B3%E6%96%AF%C2%B7%E5%8D%A1%E5%B0%94%C2%B7%E9%B2%8D%E6%9E%97"
|
||||
},
|
||||
{
|
||||
"title": "中国三江源自然保护区成立",
|
||||
"year": "2000",
|
||||
"description": "青海三江源国家级自然保护区位于青藏高原腹地,青海省南部,地理位置介于东经89°24′~102°23′,北纬31°39′~36°16′之间,青海三江源国家级自然保护区属湿地类型的自然保护区。",
|
||||
"event_type": "event",
|
||||
"link": "https://baike.baidu.com/item/%E4%B8%89%E6%B1%9F%E6%BA%90%E8%87%AA%E7%84%B6%E4%BF%9D%E6%8A%A4%E5%8C%BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
326
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/css/style.css
Normal file
326
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/css/style.css
Normal file
@@ -0,0 +1,326 @@
|
||||
/* 必应每日壁纸 - 淡绿色清新风格样式 */
|
||||
|
||||
/* 重置样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
|
||||
min-height: 100vh;
|
||||
color: #2d5016;
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #2d5016;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #5a7c65;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #e8f5e8;
|
||||
border-top: 4px solid #81c784;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 壁纸容器 */
|
||||
.wallpaper-container {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 壁纸信息 */
|
||||
.wallpaper-info {
|
||||
text-align: center;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.wallpaper-title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
color: #2d5016;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.wallpaper-date {
|
||||
color: #5a7c65;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.wallpaper-description {
|
||||
color: #2d5016;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 壁纸图片 */
|
||||
.wallpaper-image {
|
||||
position: relative;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(45, 80, 22, 0.15);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.wallpaper-image img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.wallpaper-image:hover img {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
/* 下载按钮 */
|
||||
.download-section {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 25px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(129, 199, 132, 0.3);
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.download-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(129, 199, 132, 0.4);
|
||||
}
|
||||
|
||||
.download-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
background: #fed7d7;
|
||||
color: #c53030;
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
text-align: center;
|
||||
border: 1px solid #feb2b2;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* 版权信息 */
|
||||
.copyright {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #5a7c65;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
|
||||
/* 平板端 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.wallpaper-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.wallpaper-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.wallpaper-description {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端 */
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.wallpaper-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.wallpaper-title {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.wallpaper-description {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
padding: 12px 25px;
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
padding: 15px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏幕优化 */
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.wallpaper-container {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.wallpaper-image {
|
||||
max-height: 70vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wallpaper-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
/* 特殊效果 */
|
||||
.fade-in {
|
||||
animation: fadeIn 0.6s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 图片加载效果 */
|
||||
.wallpaper-image img {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.wallpaper-image img.loaded {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(129, 199, 132, 0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(129, 199, 132, 0.7);
|
||||
}
|
||||
42
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/index.html
Normal file
42
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/index.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!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="css/style.css">
|
||||
|
||||
<!-- 网站图标 -->
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🖼️</text></svg>">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 页面头部 -->
|
||||
<header class="header">
|
||||
<h1>
|
||||
<span>🖼️</span>
|
||||
必应每日壁纸
|
||||
</h1>
|
||||
<p>每天为您呈现精美的必应壁纸,发现世界之美</p>
|
||||
</header>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div id="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载今日壁纸...</p>
|
||||
</div>
|
||||
|
||||
<!-- 壁纸内容区域 -->
|
||||
<main id="wallpaper-content" class="content" style="display: none;">
|
||||
<!-- 壁纸内容将通过JavaScript动态加载 -->
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 引入脚本文件 -->
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
315
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/js/script.js
vendored
Normal file
315
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/js/script.js
vendored
Normal file
@@ -0,0 +1,315 @@
|
||||
// 必应每日壁纸 JavaScript 功能
|
||||
|
||||
// API配置
|
||||
const API = {
|
||||
endpoints: [],
|
||||
currentIndex: 0,
|
||||
params: {
|
||||
encoding: 'json'
|
||||
},
|
||||
// 初始化API接口列表
|
||||
async init() {
|
||||
try {
|
||||
const res = await fetch('./接口集合.json');
|
||||
const endpoints = await res.json();
|
||||
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/bing`);
|
||||
} catch (e) {
|
||||
// 如果无法加载接口集合,使用默认接口
|
||||
this.endpoints = ['https://60s.viki.moe/v2/bing'];
|
||||
}
|
||||
},
|
||||
// 获取当前接口URL
|
||||
getCurrentUrl() {
|
||||
if (this.endpoints.length === 0) return null;
|
||||
const url = new URL(this.endpoints[this.currentIndex]);
|
||||
Object.keys(this.params).forEach(key => {
|
||||
url.searchParams.append(key, this.params[key]);
|
||||
});
|
||||
return url.toString();
|
||||
},
|
||||
// 切换到下一个接口
|
||||
switchToNext() {
|
||||
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
|
||||
return this.currentIndex < this.endpoints.length;
|
||||
},
|
||||
// 重置到第一个接口
|
||||
reset() {
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// DOM元素
|
||||
let elements = {};
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initElements();
|
||||
loadWallpaper();
|
||||
});
|
||||
|
||||
// 初始化DOM元素
|
||||
function initElements() {
|
||||
elements = {
|
||||
container: document.getElementById('wallpaper-content'),
|
||||
loading: document.getElementById('loading')
|
||||
};
|
||||
}
|
||||
|
||||
// 加载壁纸数据
|
||||
async function loadWallpaper() {
|
||||
try {
|
||||
showLoading(true);
|
||||
|
||||
// 初始化API接口列表
|
||||
await API.init();
|
||||
|
||||
// 重置API索引到第一个接口
|
||||
API.reset();
|
||||
|
||||
// 尝试所有API接口
|
||||
for (let i = 0; i < API.endpoints.length; i++) {
|
||||
try {
|
||||
const url = API.getCurrentUrl();
|
||||
console.log(`尝试接口 ${i + 1}/${API.endpoints.length}: ${url}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
cache: 'no-store',
|
||||
timeout: 10000 // 10秒超时
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('API响应数据:', data);
|
||||
|
||||
// 检查数据有效性
|
||||
if (data && (data.code === 200 || data.data)) {
|
||||
console.log(`接口 ${i + 1} 请求成功`);
|
||||
displayWallpaper(data);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(data && data.message ? data.message : '接口返回异常');
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`接口 ${i + 1} 失败:`, error.message);
|
||||
|
||||
// 如果不是最后一个接口,切换到下一个
|
||||
if (i < API.endpoints.length - 1) {
|
||||
API.switchToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 所有接口都失败了,抛出错误
|
||||
throw new Error('所有接口都无法访问');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载壁纸失败:', error);
|
||||
showError('加载壁纸失败,请稍后重试');
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示壁纸
|
||||
function displayWallpaper(data) {
|
||||
if (!data) {
|
||||
showError('没有获取到壁纸数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// 提取壁纸信息
|
||||
const wallpaperInfo = extractWallpaperInfo(data);
|
||||
|
||||
if (!wallpaperInfo || !wallpaperInfo.imageUrl) {
|
||||
showError('壁纸图片链接无效');
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成HTML内容
|
||||
const html = generateWallpaperHTML(wallpaperInfo);
|
||||
|
||||
// 显示内容
|
||||
elements.container.innerHTML = html;
|
||||
elements.container.classList.add('fade-in');
|
||||
|
||||
// 绑定图片加载事件
|
||||
bindImageEvents();
|
||||
}
|
||||
|
||||
// 提取壁纸信息
|
||||
function extractWallpaperInfo(data) {
|
||||
// 根据API响应结构提取信息
|
||||
let imageUrl = '';
|
||||
let title = '必应每日壁纸';
|
||||
let description = '';
|
||||
let date = new Date().toLocaleDateString('zh-CN');
|
||||
let copyright = '';
|
||||
|
||||
// 处理新的API响应格式
|
||||
if (data.data) {
|
||||
const wallpaperData = data.data;
|
||||
title = wallpaperData.title || title;
|
||||
description = wallpaperData.description || wallpaperData.main_text || '';
|
||||
copyright = wallpaperData.copyright || '';
|
||||
date = wallpaperData.update_date || date;
|
||||
|
||||
// 提取图片URL,去除反引号
|
||||
if (wallpaperData.cover) {
|
||||
imageUrl = wallpaperData.cover.replace(/`/g, '').trim();
|
||||
}
|
||||
}
|
||||
// 处理其他可能的API响应格式
|
||||
else if (data.url) {
|
||||
imageUrl = data.url;
|
||||
} else if (data.image_url) {
|
||||
imageUrl = data.image_url;
|
||||
} else if (data.images && data.images.length > 0) {
|
||||
imageUrl = data.images[0].url || data.images[0].image_url;
|
||||
title = data.images[0].title || title;
|
||||
description = data.images[0].description || data.images[0].copyright || '';
|
||||
copyright = data.images[0].copyright || '';
|
||||
}
|
||||
|
||||
// 如果是相对路径,转换为完整URL
|
||||
if (imageUrl && imageUrl.startsWith('/')) {
|
||||
imageUrl = 'https://www.bing.com' + imageUrl;
|
||||
}
|
||||
|
||||
// 确保图片URL有效
|
||||
if (!imageUrl || imageUrl === '') {
|
||||
console.error('无法提取图片URL,原始数据:', data);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
imageUrl,
|
||||
title,
|
||||
description: description || copyright,
|
||||
date,
|
||||
copyright
|
||||
};
|
||||
}
|
||||
|
||||
// 生成壁纸HTML
|
||||
function generateWallpaperHTML(info) {
|
||||
return `
|
||||
<div class="wallpaper-container">
|
||||
<div class="wallpaper-info">
|
||||
<h2 class="wallpaper-title">${escapeHtml(info.title)}</h2>
|
||||
<div class="wallpaper-date">${info.date}</div>
|
||||
${info.description ? `<div class="wallpaper-description">${escapeHtml(info.description)}</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="wallpaper-image">
|
||||
<img src="${info.imageUrl}" alt="${escapeHtml(info.title)}" loading="lazy">
|
||||
</div>
|
||||
|
||||
<div class="download-section">
|
||||
<a href="${info.imageUrl}" class="download-btn" download="bing-wallpaper-${info.date}.jpg" target="_blank">
|
||||
<span>📥</span>
|
||||
下载壁纸
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${info.copyright ? `
|
||||
<div class="copyright">
|
||||
<p>${escapeHtml(info.copyright)}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
// 绑定图片事件
|
||||
function bindImageEvents() {
|
||||
const images = elements.container.querySelectorAll('img');
|
||||
|
||||
images.forEach(img => {
|
||||
img.addEventListener('load', function() {
|
||||
this.classList.add('loaded');
|
||||
});
|
||||
|
||||
img.addEventListener('error', function() {
|
||||
console.error('图片加载失败:', this.src);
|
||||
this.parentElement.innerHTML = `
|
||||
<div class="error">
|
||||
<p>🖼️ 图片加载失败</p>
|
||||
<p>请检查网络连接或稍后重试</p>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 显示/隐藏加载状态
|
||||
function showLoading(show) {
|
||||
if (elements.loading) {
|
||||
elements.loading.style.display = show ? 'block' : 'none';
|
||||
}
|
||||
if (elements.container) {
|
||||
elements.container.style.display = show ? 'none' : 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
function showError(message) {
|
||||
if (elements.container) {
|
||||
elements.container.innerHTML = `
|
||||
<div class="error">
|
||||
<h3>⚠️ 加载失败</h3>
|
||||
<p>${escapeHtml(message)}</p>
|
||||
<p>请检查网络连接或稍后重试</p>
|
||||
</div>
|
||||
`;
|
||||
elements.container.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// HTML转义
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(dateString) {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
} catch (error) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
window.addEventListener('error', function(event) {
|
||||
console.error('页面错误:', event.error);
|
||||
});
|
||||
|
||||
// 网络状态监听
|
||||
window.addEventListener('online', function() {
|
||||
console.log('网络已连接');
|
||||
});
|
||||
|
||||
window.addEventListener('offline', function() {
|
||||
console.log('网络已断开');
|
||||
showError('网络连接已断开,请检查网络设置');
|
||||
});
|
||||
|
||||
// 导出函数供外部调用
|
||||
window.BingWallpaper = {
|
||||
loadWallpaper,
|
||||
showError,
|
||||
showLoading
|
||||
};
|
||||
7
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/接口集合.json
Normal file
7
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/接口集合.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
"https://60s-cf.viki.moe",
|
||||
"https://60s.viki.moe",
|
||||
"https://60s.b23.run",
|
||||
"https://60s.114128.xyz",
|
||||
"https://60s-cf.114128.xyz"
|
||||
]
|
||||
15
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/返回接口.json
Normal file
15
frontend/react-app/public/60sapi/日更资讯/必应每日壁纸/返回接口.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"title": "瑟沃格湖,瓦加尔岛,法罗群岛",
|
||||
"headline": "海洋上方的湖泊",
|
||||
"description": "大自然自有其奇妙之处,瑟沃格湖(Sørvágsvatn)便是其中最精彩的之一。世界湖泊日是探索法罗群岛(丹麦王国的一个自治行政区)这片视错觉的绝佳时机。这座位于沃格岛上的湖泊也被称为莱蒂斯湖(Leitisvatn),看似漂浮在海平面之上。实际上,它的海拔不到100英尺。索尔瓦格斯湖是法罗群岛最大的湖泊,面积约1.3平方英里,为Bøsdalafossur瀑布Bøsdalafossur提供水源,瀑布的湖水在那里奔腾而下,最终倾泻而入大海。",
|
||||
"main_text": "该湖位于瓦加尔岛南部,通过Bøsdalafossur瀑布与大西洋相连,形成了壮丽的“悬湖”景观。",
|
||||
"cover": "https://bing.com/th?id=OHR.FaroeLake_ZH-CN3977660997_1920x1080.jpg",
|
||||
"cover_4k": "https://bing.com/th?id=OHR.FaroeLake_ZH-CN3977660997_UHD.jpg",
|
||||
"copyright": "© Anton Petrus/Getty Images",
|
||||
"update_date": "2025-08-27 13:24:37",
|
||||
"update_date_at": 1756301077809
|
||||
}
|
||||
}
|
||||
327
frontend/react-app/public/60sapi/日更资讯/每天60s读懂世界/css/style.css
Normal file
327
frontend/react-app/public/60sapi/日更资讯/每天60s读懂世界/css/style.css
Normal file
@@ -0,0 +1,327 @@
|
||||
/* 每天60s读懂世界 - 清新风格样式 */
|
||||
|
||||
/* 重置样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
|
||||
min-height: 100vh;
|
||||
color: #2d5016;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #2d5016;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #5a7c65;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* 控制面板 */
|
||||
.controls {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.date-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.date-selector label {
|
||||
font-weight: 600;
|
||||
color: #4a5568;
|
||||
}
|
||||
|
||||
.date-selector input {
|
||||
padding: 10px 15px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.date-selector input:focus {
|
||||
outline: none;
|
||||
border-color: #81c784;
|
||||
box-shadow: 0 0 0 3px rgba(129, 199, 132, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 25px;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(129, 199, 132, 0.3);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(129, 199, 132, 0.4);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #e8f5e8;
|
||||
border-top: 4px solid #81c784;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.news-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 25px;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.news-date {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #2d5016;
|
||||
}
|
||||
|
||||
.lunar-date {
|
||||
color: #5a7c65;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 新闻图片 */
|
||||
.news-image {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
height: auto;
|
||||
border-radius: 15px;
|
||||
margin: 20px auto;
|
||||
display: block;
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 新闻列表 */
|
||||
.news-list {
|
||||
margin: 25px 0;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
background: #f1f8e9;
|
||||
border-left: 4px solid #81c784;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 0 10px 10px 0;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.news-item:hover {
|
||||
background: #e8f5e8;
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 4px 15px rgba(45, 80, 22, 0.1);
|
||||
}
|
||||
|
||||
.news-item::before {
|
||||
content: counter(news-counter);
|
||||
counter-increment: news-counter;
|
||||
position: absolute;
|
||||
left: -15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: #81c784;
|
||||
color: white;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.news-list {
|
||||
counter-reset: news-counter;
|
||||
}
|
||||
|
||||
/* 每日一句 */
|
||||
.daily-tip {
|
||||
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
margin: 25px 0;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
font-size: 1.1rem;
|
||||
color: #744210;
|
||||
box-shadow: 0 5px 20px rgba(252, 182, 159, 0.3);
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
background: #fed7d7;
|
||||
color: #c53030;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
border: 1px solid #feb2b2;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
|
||||
/* 平板端 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.date-selector {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.news-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端 */
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
padding: 12px 15px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.news-item::before {
|
||||
left: -10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.daily-tip {
|
||||
padding: 15px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏幕优化 */
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
49
frontend/react-app/public/60sapi/日更资讯/每天60s读懂世界/index.html
Normal file
49
frontend/react-app/public/60sapi/日更资讯/每天60s读懂世界/index.html
Normal file
@@ -0,0 +1,49 @@
|
||||
<!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="每天60秒读懂世界 - 获取最新资讯,了解天下大事">
|
||||
<meta name="keywords" content="新闻,资讯,每日新闻,60秒读懂世界">
|
||||
<title>每天60秒读懂世界 | 最新资讯</title>
|
||||
|
||||
<!-- 引入CSS样式 -->
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
|
||||
<!-- 网站图标 -->
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📰</text></svg>">
|
||||
|
||||
<!-- Open Graph 元数据 -->
|
||||
<meta property="og:title" content="每天60秒读懂世界">
|
||||
<meta property="og:description" content="获取最新资讯,了解天下大事">
|
||||
<meta property="og:type" content="website">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 页面头部 -->
|
||||
<header class="header">
|
||||
<h1>📰 每天60秒读懂世界</h1>
|
||||
<p>获取最新资讯,了解天下大事</p>
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<main id="content" class="content">
|
||||
<!-- 内容将通过JavaScript动态加载 -->
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载今日资讯...</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 页面底部 -->
|
||||
<footer style="text-align: center; padding: 20px; color: rgba(255,255,255,0.8); font-size: 0.9rem;">
|
||||
<p>Made with ❤️ | 数据来源:每天60秒读懂世界API</p>
|
||||
</footer>
|
||||
|
||||
<!-- 引入JavaScript -->
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
305
frontend/react-app/public/60sapi/日更资讯/每天60s读懂世界/js/script.js
vendored
Normal file
305
frontend/react-app/public/60sapi/日更资讯/每天60s读懂世界/js/script.js
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||
// 每天60s读懂世界 - JavaScript功能实现
|
||||
|
||||
const API = {
|
||||
endpoints: [],
|
||||
currentIndex: 0,
|
||||
params: {
|
||||
encoding: 'json'
|
||||
},
|
||||
localFallback: '返回接口.json',
|
||||
// 初始化API接口列表
|
||||
async init() {
|
||||
try {
|
||||
const res = await fetch('./接口集合.json');
|
||||
const endpoints = await res.json();
|
||||
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/60s`);
|
||||
} catch (e) {
|
||||
// 如果无法加载接口集合,使用默认接口
|
||||
this.endpoints = ['https://60s.viki.moe/v2/60s'];
|
||||
}
|
||||
},
|
||||
// 获取当前接口URL
|
||||
getCurrentUrl() {
|
||||
if (this.endpoints.length === 0) return null;
|
||||
const url = new URL(this.endpoints[this.currentIndex]);
|
||||
Object.entries(this.params).forEach(([k, v]) => url.searchParams.append(k, v));
|
||||
return url.toString();
|
||||
},
|
||||
// 切换到下一个接口
|
||||
switchToNext() {
|
||||
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
|
||||
return this.currentIndex < this.endpoints.length;
|
||||
},
|
||||
// 重置到第一个接口
|
||||
reset() {
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
class NewsApp {
|
||||
constructor() {
|
||||
this.apiUrl = 'https://60s.viki.moe/v2/60s';
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.loadTodayNews();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// 移除了刷新按钮,不需要绑定事件
|
||||
}
|
||||
|
||||
formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
showLoading() {
|
||||
const contentDiv = document.getElementById('content');
|
||||
if (contentDiv) {
|
||||
contentDiv.innerHTML = `
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在获取最新资讯...</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
const contentDiv = document.getElementById('content');
|
||||
if (contentDiv) {
|
||||
contentDiv.innerHTML = `
|
||||
<div class="error">
|
||||
<h3>😔 获取失败</h3>
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
async loadNews() {
|
||||
try {
|
||||
this.showLoading();
|
||||
|
||||
// 尝试从API获取数据
|
||||
let data = await this.fetchFromAPI();
|
||||
|
||||
// 如果API失败,尝试本地数据
|
||||
if (!data) {
|
||||
data = await this.fetchFromLocal();
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
throw new Error('无法获取数据,请检查网络连接或稍后重试');
|
||||
}
|
||||
|
||||
if (data.code !== 200) {
|
||||
throw new Error(data.message || '获取数据失败');
|
||||
}
|
||||
|
||||
this.renderNews(data.data);
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取新闻失败:', error);
|
||||
this.showError(error.message || '网络连接失败,请检查网络后重试');
|
||||
}
|
||||
}
|
||||
|
||||
async fetchFromAPI() {
|
||||
// 初始化API接口列表
|
||||
await API.init();
|
||||
|
||||
// 重置API索引到第一个接口
|
||||
API.reset();
|
||||
|
||||
// 尝试所有API接口
|
||||
for (let i = 0; i < API.endpoints.length; i++) {
|
||||
try {
|
||||
const url = API.getCurrentUrl();
|
||||
console.log(`尝试接口 ${i + 1}/${API.endpoints.length}: ${url}`);
|
||||
|
||||
const resp = await fetch(url, {
|
||||
cache: 'no-store'
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
|
||||
}
|
||||
|
||||
const data = await resp.json();
|
||||
|
||||
if (data && data.code === 200) {
|
||||
console.log(`接口 ${i + 1} 请求成功`);
|
||||
return data;
|
||||
}
|
||||
|
||||
throw new Error(data && data.message ? data.message : '接口返回异常');
|
||||
|
||||
} catch (e) {
|
||||
console.warn(`接口 ${i + 1} 失败:`, e.message);
|
||||
|
||||
// 如果不是最后一个接口,切换到下一个
|
||||
if (i < API.endpoints.length - 1) {
|
||||
API.switchToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 所有接口都失败了
|
||||
console.warn('所有远程接口都失败,尝试本地数据');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fetchFromLocal() {
|
||||
try {
|
||||
const resp = await fetch(API.localFallback + `?t=${Date.now()}`);
|
||||
if (!resp.ok) throw new Error(`本地文件HTTP ${resp.status}`);
|
||||
const data = await resp.json();
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error('读取本地返回接口.json失败:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
loadTodayNews() {
|
||||
this.loadNews();
|
||||
}
|
||||
|
||||
renderNews(newsData) {
|
||||
const contentDiv = document.getElementById('content');
|
||||
if (!contentDiv || !newsData) return;
|
||||
|
||||
const {
|
||||
date,
|
||||
day_of_week,
|
||||
lunar_date,
|
||||
news,
|
||||
tip,
|
||||
link
|
||||
} = newsData;
|
||||
|
||||
let newsListHtml = '';
|
||||
if (news && news.length > 0) {
|
||||
newsListHtml = news.map(item => `
|
||||
<div class="news-item">
|
||||
${this.escapeHtml(item)}
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 移除图片显示功能
|
||||
|
||||
const tipHtml = tip ? `
|
||||
<div class="daily-tip">
|
||||
💡 ${this.escapeHtml(tip)}
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
const linkHtml = link ? `
|
||||
<div style="text-align: center; margin-top: 20px;">
|
||||
<a href="${this.escapeHtml(link)}" target="_blank" class="btn" style="text-decoration: none; display: inline-block;">
|
||||
📖 查看原文
|
||||
</a>
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
contentDiv.innerHTML = `
|
||||
<div class="news-header">
|
||||
<div>
|
||||
<div class="news-date">${this.escapeHtml(date)} ${this.escapeHtml(day_of_week || '')}</div>
|
||||
${lunar_date ? `<div class="lunar-date">${this.escapeHtml(lunar_date)}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${tipHtml}
|
||||
|
||||
<div class="news-list">
|
||||
<h3 style="margin-bottom: 20px; color: #2d5016; font-size: 1.3rem;">📰 今日要闻</h3>
|
||||
${newsListHtml}
|
||||
</div>
|
||||
|
||||
${linkHtml}
|
||||
|
||||
<div style="text-align: center; margin-top: 30px; color: #5a7c65; font-size: 0.9rem;">
|
||||
<p>数据来源:每天60秒读懂世界</p>
|
||||
<p>更新时间:${newsData.api_updated || '未知'}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
escapeHtml(text) {
|
||||
if (typeof text !== 'string') return text;
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化应用
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.newsApp = new NewsApp();
|
||||
});
|
||||
|
||||
// 添加一些实用功能
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showToast('已复制到剪贴板');
|
||||
}).catch(() => {
|
||||
showToast('复制失败,请手动复制');
|
||||
});
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
// 创建提示框
|
||||
const toast = document.createElement('div');
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #4a5568;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
z-index: 1000;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
toast.textContent = message;
|
||||
|
||||
// 添加动画样式
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 3秒后自动移除
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
style.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 添加键盘快捷键支持
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Ctrl/Cmd + R 刷新数据
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'r') {
|
||||
e.preventDefault();
|
||||
if (window.newsApp) {
|
||||
window.newsApp.loadNews();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
"https://60s-cf.viki.moe",
|
||||
"https://60s.viki.moe",
|
||||
"https://60s.b23.run",
|
||||
"https://60s.114128.xyz",
|
||||
"https://60s-cf.114128.xyz"
|
||||
]
|
||||
66
frontend/react-app/public/60sapi/日更资讯/每天60s读懂世界/返回接口.json
Normal file
66
frontend/react-app/public/60sapi/日更资讯/每天60s读懂世界/返回接口.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": [
|
||||
{
|
||||
"id": "9aa227e2ba294bb1a95c95fde892eb31",
|
||||
"title": "《Totally Reliable Delivery Service》 Standard Edition",
|
||||
"cover": "https://cdn1.epicgames.com/52b90f9a982a404781b189f6a7903226/offer/EGS_TotallyReliableDeliveryService_WereFiveGames_S1-2560x1440-47e6e9562d62705a75ea7b7096d0b8dc.jpg",
|
||||
"original_price": 52,
|
||||
"original_price_desc": "¥52.00",
|
||||
"description": "穿好护腰护具,发动货车,送货的时间到啦!在一个高度互动的沙盒世界中,与最多三位好友一起随意地完成送货。货物已试投,这就是我们靠谱快递(Totally Reliable Delivery Service)的品质保证!",
|
||||
"seller": "Infogrames LLC",
|
||||
"is_free_now": true,
|
||||
"free_start": "2025/08/14 23:00:00",
|
||||
"free_start_at": 1755183600000,
|
||||
"free_end": "2025/08/21 23:00:00",
|
||||
"free_end_at": 1755788400000,
|
||||
"link": "https://store.epicgames.com/store/zh-CN/p/totally-reliable-delivery-service/home"
|
||||
},
|
||||
{
|
||||
"id": "8ea3500dc38e4f429702bf889c172d3d",
|
||||
"title": "Hidden Folks",
|
||||
"cover": "https://cdn1.epicgames.com/spt-assets/7bfd56b0586348dcb139945d9e59f988/hidden-folks-1b7hh.png",
|
||||
"original_price": 47,
|
||||
"original_price_desc": "¥47.00",
|
||||
"description": "Search for hidden folks in hand-drawn, interactive, miniature landscapes. Unfurl tent flaps, cut through bushes, slam doors, and poke some crocodiles! Rooooaaaarrrr!!!!!",
|
||||
"seller": "Adriaan de Jongh",
|
||||
"is_free_now": true,
|
||||
"free_start": "2025/08/14 23:00:00",
|
||||
"free_start_at": 1755183600000,
|
||||
"free_end": "2025/08/21 23:00:00",
|
||||
"free_end_at": 1755788400000,
|
||||
"link": "https://store.epicgames.com/store/zh-CN/p/hidden-folks-239d16"
|
||||
},
|
||||
{
|
||||
"id": "4cbb6c3704d240f19c3dd5f5cb2b0cb4",
|
||||
"title": "Kamaeru",
|
||||
"cover": "https://cdn1.epicgames.com/spt-assets/44313cfbb62b4df5801d0c8d541c2624/kamaeru-40asc.png",
|
||||
"original_price": 62,
|
||||
"original_price_desc": "¥62.00",
|
||||
"description": "Foster a sanctuary for frogs and restore the biodiversity of the wetlands in Kamaeru, a cozy frog collecting game, where you take pictures of frogs, play mini-games and decorate your habitat. Hop right to it!",
|
||||
"seller": "Armor Games Studios",
|
||||
"is_free_now": false,
|
||||
"free_start": "2025/08/21 23:00:00",
|
||||
"free_start_at": 1755788400000,
|
||||
"free_end": "2025/08/28 23:00:00",
|
||||
"free_end_at": 1756393200000,
|
||||
"link": "https://store.epicgames.com/store/zh-CN/p/kamaeru-0c301e"
|
||||
},
|
||||
{
|
||||
"id": "0d9a533f0e684cc18620a8f408e8e72c",
|
||||
"title": "Strange Horticulture",
|
||||
"cover": "https://cdn1.epicgames.com/spt-assets/15e8e3eba65a4763a815d6eae1d763b2/strange-horticulture-offer-2wghv.png",
|
||||
"original_price": 45,
|
||||
"original_price_desc": "¥45.00",
|
||||
"description": "款神秘学解谜游戏,你将扮演当地植物商店的店主,寻找并识别新的植物,悠闲撸猫,与女巫团体交谈,或加入异教。收集各种强大的植物,用它们来影响故事走向,揭开昂德米尔镇的黑暗谜团。",
|
||||
"seller": "Iceberg Interactive",
|
||||
"is_free_now": false,
|
||||
"free_start": "2025/08/21 23:00:00",
|
||||
"free_start_at": 1755788400000,
|
||||
"free_end": "2025/08/28 23:00:00",
|
||||
"free_end_at": 1756393200000,
|
||||
"link": "https://store.epicgames.com/store/zh-CN/p/strange-horticulture-360e80"
|
||||
}
|
||||
]
|
||||
}
|
||||
409
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/css/style.css
Normal file
409
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/css/style.css
Normal file
@@ -0,0 +1,409 @@
|
||||
/* 每日国际汇率 - 淡绿色清新风格样式 */
|
||||
|
||||
/* 重置样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
|
||||
min-height: 100vh;
|
||||
color: #2d5016;
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #2d5016;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #5a7c65;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* 货币选择器 */
|
||||
.currency-selector {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.currency-selector label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
color: #2d5016;
|
||||
}
|
||||
|
||||
.currency-selector select {
|
||||
padding: 12px 20px;
|
||||
border: 2px solid #c8e6c9;
|
||||
border-radius: 25px;
|
||||
background: white;
|
||||
color: #2d5016;
|
||||
font-size: 1rem;
|
||||
min-width: 200px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.currency-selector select:focus {
|
||||
outline: none;
|
||||
border-color: #81c784;
|
||||
box-shadow: 0 0 0 3px rgba(129, 199, 132, 0.2);
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #e8f5e8;
|
||||
border-top: 4px solid #81c784;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 汇率信息容器 */
|
||||
.exchange-info {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 20px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.base-currency {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.base-currency h2 {
|
||||
font-size: 1.8rem;
|
||||
color: #2d5016;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
color: #5a7c65;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 汇率网格 */
|
||||
.rates-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.rate-card {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 3px 10px rgba(45, 80, 22, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(200, 230, 201, 0.5);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rate-card:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 15px rgba(45, 80, 22, 0.1);
|
||||
border-color: #81c784;
|
||||
}
|
||||
|
||||
.currency-code {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: #2d5016;
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.currency-flag {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.exchange-rate {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #388e3c;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.currency-name {
|
||||
color: #5a7c65;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.search-container {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
border: 2px solid #c8e6c9;
|
||||
border-radius: 25px;
|
||||
background: white;
|
||||
color: #2d5016;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: #81c784;
|
||||
box-shadow: 0 0 0 3px rgba(129, 199, 132, 0.2);
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #81c784;
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
background: #fed7d7;
|
||||
color: #c53030;
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
text-align: center;
|
||||
border: 1px solid #feb2b2;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* 统计信息 */
|
||||
.stats {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
padding: 15px;
|
||||
background: rgba(129, 199, 132, 0.1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #2d5016;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #5a7c65;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
|
||||
/* 平板端 */
|
||||
@media (max-width: 768px) and (min-width: 481px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.rates-grid {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.rate-card {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.currency-selector select {
|
||||
min-width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端 */
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.rates-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.rate-card {
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.currency-code {
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 4px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.currency-flag {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.exchange-rate {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.currency-name {
|
||||
font-size: 0.7rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.currency-selector {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.currency-selector select {
|
||||
min-width: 100%;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏幕优化 */
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 特殊效果 */
|
||||
.fade-in {
|
||||
animation: fadeIn 0.6s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(129, 199, 132, 0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(129, 199, 132, 0.7);
|
||||
}
|
||||
|
||||
/* 隐藏类 */
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
86
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/index.html
Normal file
86
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/index.html
Normal file
@@ -0,0 +1,86 @@
|
||||
<!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="css/style.css">
|
||||
|
||||
<!-- 网站图标 -->
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>💱</text></svg>">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 页面头部 -->
|
||||
<header class="header">
|
||||
<h1>
|
||||
<span>💱</span>
|
||||
每日国际汇率
|
||||
</h1>
|
||||
<p>实时获取全球货币汇率信息,助您掌握汇率动态</p>
|
||||
</header>
|
||||
|
||||
<!-- 货币选择器 -->
|
||||
<div class="currency-selector">
|
||||
<label for="currency-select">选择基础货币:</label>
|
||||
<select id="currency-select">
|
||||
<!-- 货币选项将通过JavaScript动态生成 -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-container">
|
||||
<input
|
||||
type="text"
|
||||
id="search-input"
|
||||
class="search-input"
|
||||
placeholder="🔍 搜索货币代码或名称..."
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div id="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载汇率数据...</p>
|
||||
</div>
|
||||
|
||||
<!-- 汇率内容区域 -->
|
||||
<main id="exchange-content" class="content" style="display: none;">
|
||||
<!-- 汇率信息 -->
|
||||
<div class="exchange-info">
|
||||
<div class="base-currency">
|
||||
<h2 id="base-currency">基础货币</h2>
|
||||
<div id="update-time" class="update-time">更新时间: --</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="stats">
|
||||
<h3>汇率统计</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item">
|
||||
<div id="total-currencies" class="stat-number">--</div>
|
||||
<div class="stat-label">货币总数</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div id="last-update" class="stat-number">--</div>
|
||||
<div class="stat-label">最后更新</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 汇率网格 -->
|
||||
<div id="rates-grid" class="rates-grid">
|
||||
<!-- 汇率卡片将通过JavaScript动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 引入脚本文件 -->
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
520
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/js/script.js
vendored
Normal file
520
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/js/script.js
vendored
Normal file
@@ -0,0 +1,520 @@
|
||||
// 每日国际汇率 JavaScript 功能
|
||||
|
||||
// API配置
|
||||
const API = {
|
||||
endpoints: [],
|
||||
currentIndex: 0,
|
||||
defaultCurrency: 'CNY',
|
||||
localFallback: '返回接口.json',
|
||||
// 初始化API接口列表
|
||||
async init() {
|
||||
try {
|
||||
const res = await fetch('./接口集合.json');
|
||||
const endpoints = await res.json();
|
||||
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/exchange_rate`);
|
||||
} catch (e) {
|
||||
// 如果无法加载接口集合,使用默认接口
|
||||
this.endpoints = ['https://60s.viki.moe/v2/exchange_rate'];
|
||||
}
|
||||
},
|
||||
// 获取当前接口URL
|
||||
getCurrentUrl(currency) {
|
||||
if (this.endpoints.length === 0) return null;
|
||||
const url = new URL(this.endpoints[this.currentIndex]);
|
||||
url.searchParams.append('currency', currency);
|
||||
return url.toString();
|
||||
},
|
||||
// 切换到下一个接口
|
||||
switchToNext() {
|
||||
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
|
||||
return this.currentIndex < this.endpoints.length;
|
||||
},
|
||||
// 重置到第一个接口
|
||||
reset() {
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// 常用货币列表
|
||||
const POPULAR_CURRENCIES = [
|
||||
{ code: 'CNY', name: '人民币', flag: '🇨🇳' },
|
||||
{ code: 'USD', name: '美元', flag: '🇺🇸' },
|
||||
{ code: 'EUR', name: '欧元', flag: '🇪🇺' },
|
||||
{ code: 'JPY', name: '日元', flag: '🇯🇵' },
|
||||
{ code: 'GBP', name: '英镑', flag: '🇬🇧' },
|
||||
{ code: 'AUD', name: '澳元', flag: '🇦🇺' },
|
||||
{ code: 'CAD', name: '加元', flag: '🇨🇦' },
|
||||
{ code: 'CHF', name: '瑞士法郎', flag: '🇨🇭' },
|
||||
{ code: 'HKD', name: '港币', flag: '🇭🇰' },
|
||||
{ code: 'SGD', name: '新加坡元', flag: '🇸🇬' },
|
||||
{ code: 'KRW', name: '韩元', flag: '🇰🇷' },
|
||||
{ code: 'THB', name: '泰铢', flag: '🇹🇭' }
|
||||
];
|
||||
|
||||
// 货币优先级排序 - 经济发达、交易频繁的国家货币优先
|
||||
const CURRENCY_PRIORITY = {
|
||||
// 第一梯队:全球主要储备货币和交易货币
|
||||
'USD': 1, // 美元 - 全球储备货币
|
||||
'EUR': 2, // 欧元 - 欧盟统一货币
|
||||
'JPY': 3, // 日元 - 亚洲主要货币
|
||||
'GBP': 4, // 英镑 - 传统储备货币
|
||||
'CNY': 5, // 人民币 - 中国货币
|
||||
|
||||
// 第二梯队:发达国家货币
|
||||
'CHF': 10, // 瑞士法郎 - 避险货币
|
||||
'CAD': 11, // 加拿大元
|
||||
'AUD': 12, // 澳大利亚元
|
||||
'NZD': 13, // 新西兰元
|
||||
'SEK': 14, // 瑞典克朗
|
||||
'NOK': 15, // 挪威克朗
|
||||
'DKK': 16, // 丹麦克朗
|
||||
|
||||
// 第三梯队:亚洲发达经济体
|
||||
'HKD': 20, // 港币
|
||||
'SGD': 21, // 新加坡元
|
||||
'KRW': 22, // 韩元
|
||||
'TWD': 23, // 新台币
|
||||
|
||||
// 第四梯队:重要新兴市场货币
|
||||
'RUB': 30, // 俄罗斯卢布
|
||||
'INR': 31, // 印度卢比
|
||||
'BRL': 32, // 巴西雷亚尔
|
||||
'MXN': 33, // 墨西哥比索
|
||||
'ZAR': 34, // 南非兰特
|
||||
'TRY': 35, // 土耳其里拉
|
||||
|
||||
// 第五梯队:亚洲重要货币
|
||||
'THB': 40, // 泰铢
|
||||
'MYR': 41, // 马来西亚林吉特
|
||||
'IDR': 42, // 印尼盾
|
||||
'PHP': 43, // 菲律宾比索
|
||||
'VND': 44, // 越南盾
|
||||
|
||||
// 第六梯队:中东石油国家货币
|
||||
'SAR': 50, // 沙特里亚尔
|
||||
'AED': 51, // 阿联酋迪拉姆
|
||||
'QAR': 52, // 卡塔尔里亚尔
|
||||
'KWD': 53, // 科威特第纳尔
|
||||
|
||||
// 第七梯队:欧洲其他货币
|
||||
'PLN': 60, // 波兰兹罗提
|
||||
'CZK': 61, // 捷克克朗
|
||||
'HUF': 62, // 匈牙利福林
|
||||
'RON': 63, // 罗马尼亚列伊
|
||||
'BGN': 64, // 保加利亚列弗
|
||||
'HRK': 65, // 克罗地亚库纳
|
||||
|
||||
// 第八梯队:拉美货币
|
||||
'ARS': 70, // 阿根廷比索
|
||||
'CLP': 71, // 智利比索
|
||||
'COP': 72, // 哥伦比亚比索
|
||||
'PEN': 73, // 秘鲁索尔
|
||||
'UYU': 74, // 乌拉圭比索
|
||||
|
||||
// 其他货币默认优先级为 999
|
||||
};
|
||||
|
||||
// 货币名称映射
|
||||
const CURRENCY_NAMES = {
|
||||
'CNY': '人民币', 'USD': '美元', 'EUR': '欧元', 'JPY': '日元', 'GBP': '英镑',
|
||||
'AUD': '澳元', 'CAD': '加元', 'CHF': '瑞士法郎', 'HKD': '港币', 'SGD': '新加坡元',
|
||||
'KRW': '韩元', 'THB': '泰铢', 'AED': '阿联酋迪拉姆', 'AFN': '阿富汗尼',
|
||||
'ALL': '阿尔巴尼亚列克', 'AMD': '亚美尼亚德拉姆', 'ANG': '荷属安的列斯盾',
|
||||
'AOA': '安哥拉宽扎', 'ARS': '阿根廷比索', 'AWG': '阿鲁巴弗罗林',
|
||||
'AZN': '阿塞拜疆马纳特', 'BAM': '波黑马克', 'BBD': '巴巴多斯元',
|
||||
'BDT': '孟加拉塔卡', 'BGN': '保加利亚列弗', 'BHD': '巴林第纳尔',
|
||||
'BIF': '布隆迪法郎', 'BMD': '百慕大元', 'BND': '文莱元', 'BOB': '玻利维亚诺',
|
||||
'BRL': '巴西雷亚尔', 'BSD': '巴哈马元', 'BTN': '不丹努尔特鲁姆',
|
||||
'BWP': '博茨瓦纳普拉', 'BYN': '白俄罗斯卢布', 'BZD': '伯利兹元',
|
||||
'CDF': '刚果法郎', 'CLP': '智利比索', 'COP': '哥伦比亚比索', 'CRC': '哥斯达黎加科朗',
|
||||
'CUP': '古巴比索', 'CVE': '佛得角埃斯库多', 'CZK': '捷克克朗', 'DJF': '吉布提法郎',
|
||||
'DKK': '丹麦克朗', 'DOP': '多米尼加比索', 'DZD': '阿尔及利亚第纳尔', 'EGP': '埃及镑',
|
||||
'ERN': '厄立特里亚纳克法', 'ETB': '埃塞俄比亚比尔', 'FJD': '斐济元', 'FKP': '福克兰群岛镑',
|
||||
'FOK': '法罗群岛克朗', 'GEL': '格鲁吉亚拉里', 'GGP': '根西岛镑', 'GHS': '加纳塞地',
|
||||
'GIP': '直布罗陀镑', 'GMD': '冈比亚达拉西', 'GNF': '几内亚法郎', 'GTQ': '危地马拉格查尔',
|
||||
'GYD': '圭亚那元', 'HNL': '洪都拉斯伦皮拉', 'HRK': '克罗地亚库纳', 'HTG': '海地古德',
|
||||
'HUF': '匈牙利福林', 'IDR': '印尼盾', 'ILS': '以色列新谢克尔', 'IMP': '马恩岛镑',
|
||||
'INR': '印度卢比', 'IQD': '伊拉克第纳尔', 'IRR': '伊朗里亚尔', 'ISK': '冰岛克朗',
|
||||
'JEP': '泽西岛镑', 'JMD': '牙买加元', 'JOD': '约旦第纳尔', 'KES': '肯尼亚先令',
|
||||
'KGS': '吉尔吉斯斯坦索姆', 'KHR': '柬埔寨瑞尔', 'KID': '基里巴斯元', 'KMF': '科摩罗法郎',
|
||||
'KWD': '科威特第纳尔', 'KYD': '开曼群岛元', 'KZT': '哈萨克斯坦坚戈', 'LAK': '老挝基普',
|
||||
'LBP': '黎巴嫩镑', 'LKR': '斯里兰卡卢比', 'LRD': '利比里亚元', 'LSL': '莱索托洛蒂',
|
||||
'LYD': '利比亚第纳尔', 'MAD': '摩洛哥迪拉姆', 'MDL': '摩尔多瓦列伊', 'MGA': '马达加斯加阿里亚里',
|
||||
'MKD': '北马其顿第纳尔', 'MMK': '缅甸缅元', 'MNT': '蒙古图格里克', 'MOP': '澳门帕塔卡',
|
||||
'MRU': '毛里塔尼亚乌吉亚', 'MUR': '毛里求斯卢比', 'MVR': '马尔代夫拉菲亚', 'MWK': '马拉维克瓦查',
|
||||
'MXN': '墨西哥比索', 'MYR': '马来西亚林吉特', 'MZN': '莫桑比克梅蒂卡尔', 'NAD': '纳米比亚元',
|
||||
'NGN': '尼日利亚奈拉', 'NIO': '尼加拉瓜科多巴', 'NOK': '挪威克朗', 'NPR': '尼泊尔卢比',
|
||||
'NZD': '新西兰元', 'OMR': '阿曼里亚尔', 'PAB': '巴拿马巴波亚', 'PEN': '秘鲁索尔',
|
||||
'PGK': '巴布亚新几内亚基那', 'PHP': '菲律宾比索', 'PKR': '巴基斯坦卢比', 'PLN': '波兰兹罗提',
|
||||
'PYG': '巴拉圭瓜拉尼', 'QAR': '卡塔尔里亚尔', 'RON': '罗马尼亚列伊', 'RSD': '塞尔维亚第纳尔',
|
||||
'RUB': '俄罗斯卢布', 'RWF': '卢旺达法郎', 'SAR': '沙特里亚尔', 'SBD': '所罗门群岛元',
|
||||
'SCR': '塞舌尔卢比', 'SDG': '苏丹镑', 'SEK': '瑞典克朗', 'SHP': '圣赫勒拿镑',
|
||||
'SLE': '塞拉利昂利昂', 'SLL': '塞拉利昂利昂(旧)', 'SOS': '索马里先令', 'SRD': '苏里南元',
|
||||
'SSP': '南苏丹镑', 'STN': '圣多美和普林西比多布拉', 'SYP': '叙利亚镑', 'SZL': '斯威士兰里兰吉尼',
|
||||
'TJS': '塔吉克斯坦索莫尼', 'TMT': '土库曼斯坦马纳特', 'TND': '突尼斯第纳尔', 'TOP': '汤加潘加',
|
||||
'TRY': '土耳其里拉', 'TTD': '特立尼达和多巴哥元', 'TVD': '图瓦卢元', 'TWD': '新台币',
|
||||
'TZS': '坦桑尼亚先令', 'UAH': '乌克兰格里夫纳', 'UGX': '乌干达先令', 'UYU': '乌拉圭比索',
|
||||
'UZS': '乌兹别克斯坦苏姆', 'VES': '委内瑞拉玻利瓦尔', 'VND': '越南盾', 'VUV': '瓦努阿图瓦图',
|
||||
'WST': '萨摩亚塔拉', 'XAF': '中非法郎', 'XCD': '东加勒比元', 'XCG': '加勒比盾',
|
||||
'XDR': '特别提款权', 'XOF': '西非法郎', 'XPF': '太平洋法郎', 'YER': '也门里亚尔',
|
||||
'ZAR': '南非兰特', 'ZMW': '赞比亚克瓦查', 'ZWL': '津巴布韦元'
|
||||
};
|
||||
|
||||
// 货币旗帜映射
|
||||
const CURRENCY_FLAGS = {
|
||||
'CNY': '🇨🇳', 'USD': '🇺🇸', 'EUR': '🇪🇺', 'JPY': '🇯🇵', 'GBP': '🇬🇧',
|
||||
'AUD': '🇦🇺', 'CAD': '🇨🇦', 'CHF': '🇨🇭', 'HKD': '🇭🇰', 'SGD': '🇸🇬',
|
||||
'KRW': '🇰🇷', 'THB': '🇹🇭', 'AED': '🇦🇪', 'AFN': '🇦🇫', 'ALL': '🇦🇱',
|
||||
'AMD': '🇦🇲', 'ANG': '🇳🇱', 'AOA': '🇦🇴', 'ARS': '🇦🇷', 'AWG': '🇦🇼',
|
||||
'AZN': '🇦🇿', 'BAM': '🇧🇦', 'BBD': '🇧🇧', 'BDT': '🇧🇩', 'BGN': '🇧🇬',
|
||||
'BHD': '🇧🇭', 'BIF': '🇧🇮', 'BMD': '🇧🇲', 'BND': '🇧🇳', 'BOB': '🇧🇴',
|
||||
'BRL': '🇧🇷', 'BSD': '🇧🇸', 'BTN': '🇧🇹', 'BWP': '🇧🇼', 'BYN': '🇧🇾',
|
||||
'BZD': '🇧🇿', 'CDF': '🇨🇩', 'CLP': '🇨🇱', 'COP': '🇨🇴', 'CRC': '🇨🇷',
|
||||
'CUP': '🇨🇺', 'CVE': '🇨🇻', 'CZK': '🇨🇿', 'DJF': '🇩🇯', 'DKK': '🇩🇰',
|
||||
'DOP': '🇩🇴', 'DZD': '🇩🇿', 'EGP': '🇪🇬', 'ERN': '🇪🇷', 'ETB': '🇪🇹',
|
||||
'FJD': '🇫🇯', 'FKP': '🇫🇰', 'FOK': '🇫🇴', 'GEL': '🇬🇪', 'GGP': '🇬🇬',
|
||||
'GHS': '🇬🇭', 'GIP': '🇬🇮', 'GMD': '🇬🇲', 'GNF': '🇬🇳', 'GTQ': '🇬🇹',
|
||||
'GYD': '🇬🇾', 'HNL': '🇭🇳', 'HRK': '🇭🇷', 'HTG': '🇭🇹', 'HUF': '🇭🇺',
|
||||
'IDR': '🇮🇩', 'ILS': '🇮🇱', 'IMP': '🇮🇲', 'INR': '🇮🇳', 'IQD': '🇮🇶',
|
||||
'IRR': '🇮🇷', 'ISK': '🇮🇸', 'JEP': '🇯🇪', 'JMD': '🇯🇲', 'JOD': '🇯🇴',
|
||||
'KES': '🇰🇪', 'KGS': '🇰🇬', 'KHR': '🇰🇭', 'KID': '🇰🇮', 'KMF': '🇰🇲',
|
||||
'KWD': '🇰🇼', 'KYD': '🇰🇾', 'KZT': '🇰🇿', 'LAK': '🇱🇦', 'LBP': '🇱🇧',
|
||||
'LKR': '🇱🇰', 'LRD': '🇱🇷', 'LSL': '🇱🇸', 'LYD': '🇱🇾', 'MAD': '🇲🇦',
|
||||
'MDL': '🇲🇩', 'MGA': '🇲🇬', 'MKD': '🇲🇰', 'MMK': '🇲🇲', 'MNT': '🇲🇳',
|
||||
'MOP': '🇲🇴', 'MRU': '🇲🇷', 'MUR': '🇲🇺', 'MVR': '🇲🇻', 'MWK': '🇲🇼',
|
||||
'MXN': '🇲🇽', 'MYR': '🇲🇾', 'MZN': '🇲🇿', 'NAD': '🇳🇦', 'NGN': '🇳🇬',
|
||||
'NIO': '🇳🇮', 'NOK': '🇳🇴', 'NPR': '🇳🇵', 'NZD': '🇳🇿', 'OMR': '🇴🇲',
|
||||
'PAB': '🇵🇦', 'PEN': '🇵🇪', 'PGK': '🇵🇬', 'PHP': '🇵🇭', 'PKR': '🇵🇰',
|
||||
'PLN': '🇵🇱', 'PYG': '🇵🇾', 'QAR': '🇶🇦', 'RON': '🇷🇴', 'RSD': '🇷🇸',
|
||||
'RUB': '🇷🇺', 'RWF': '🇷🇼', 'SAR': '🇸🇦', 'SBD': '🇸🇧', 'SCR': '🇸🇨',
|
||||
'SDG': '🇸🇩', 'SEK': '🇸🇪', 'SHP': '🇸🇭', 'SLE': '🇸🇱', 'SLL': '🇸🇱',
|
||||
'SOS': '🇸🇴', 'SRD': '🇸🇷', 'SSP': '🇸🇸', 'STN': '🇸🇹', 'SYP': '🇸🇾',
|
||||
'SZL': '🇸🇿', 'TJS': '🇹🇯', 'TMT': '🇹🇲', 'TND': '🇹🇳', 'TOP': '🇹🇴',
|
||||
'TRY': '🇹🇷', 'TTD': '🇹🇹', 'TVD': '🇹🇻', 'TWD': '🇹🇼', 'TZS': '🇹🇿',
|
||||
'UAH': '🇺🇦', 'UGX': '🇺🇬', 'UYU': '🇺🇾', 'UZS': '🇺🇿', 'VES': '🇻🇪',
|
||||
'VND': '🇻🇳', 'VUV': '🇻🇺', 'WST': '🇼🇸', 'XAF': '🌍', 'XCD': '🏝️',
|
||||
'XCG': '🏝️', 'XDR': '🌐', 'XOF': '🌍', 'XPF': '🌊', 'YER': '🇾🇪',
|
||||
'ZAR': '🇿🇦', 'ZMW': '🇿🇲', 'ZWL': '🇿🇼'
|
||||
};
|
||||
|
||||
// DOM元素
|
||||
let elements = {};
|
||||
let currentRates = [];
|
||||
let filteredRates = [];
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initElements();
|
||||
initCurrencySelector();
|
||||
bindEvents();
|
||||
loadExchangeRates();
|
||||
});
|
||||
|
||||
// 初始化DOM元素
|
||||
function initElements() {
|
||||
elements = {
|
||||
currencySelect: document.getElementById('currency-select'),
|
||||
searchInput: document.getElementById('search-input'),
|
||||
loading: document.getElementById('loading'),
|
||||
content: document.getElementById('exchange-content'),
|
||||
baseCurrency: document.getElementById('base-currency'),
|
||||
updateTime: document.getElementById('update-time'),
|
||||
ratesGrid: document.getElementById('rates-grid'),
|
||||
totalCurrencies: document.getElementById('total-currencies'),
|
||||
lastUpdate: document.getElementById('last-update')
|
||||
};
|
||||
}
|
||||
|
||||
// 初始化货币选择器
|
||||
function initCurrencySelector() {
|
||||
if (!elements.currencySelect) return;
|
||||
|
||||
// 添加常用货币选项
|
||||
POPULAR_CURRENCIES.forEach(currency => {
|
||||
const option = document.createElement('option');
|
||||
option.value = currency.code;
|
||||
option.textContent = `${currency.flag} ${currency.code} - ${currency.name}`;
|
||||
if (currency.code === API.defaultCurrency) {
|
||||
option.selected = true;
|
||||
}
|
||||
elements.currencySelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
function bindEvents() {
|
||||
// 货币选择变化
|
||||
if (elements.currencySelect) {
|
||||
elements.currencySelect.addEventListener('change', function() {
|
||||
loadExchangeRates(this.value);
|
||||
});
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
if (elements.searchInput) {
|
||||
elements.searchInput.addEventListener('input', function() {
|
||||
filterRates(this.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 加载汇率数据
|
||||
async function loadExchangeRates(currency = API.defaultCurrency) {
|
||||
try {
|
||||
showLoading(true);
|
||||
|
||||
// 尝试从API获取数据
|
||||
const data = await fetchFromAPI(currency);
|
||||
|
||||
if (data && data.code === 200 && data.data) {
|
||||
currentRates = data.data.rates || [];
|
||||
displayExchangeRates(data.data);
|
||||
} else {
|
||||
// 尝试从本地获取数据
|
||||
const localData = await fetchFromLocal();
|
||||
if (localData && localData.code === 200 && localData.data) {
|
||||
currentRates = localData.data.rates || [];
|
||||
displayExchangeRates(localData.data);
|
||||
showError('使用本地数据,可能不是最新汇率');
|
||||
} else {
|
||||
throw new Error('无法获取汇率数据');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载汇率失败:', error);
|
||||
showError('加载汇率数据失败,请稍后重试');
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 从API获取数据
|
||||
async function fetchFromAPI(currency) {
|
||||
// 初始化API接口列表
|
||||
await API.init();
|
||||
|
||||
// 重置API索引到第一个接口
|
||||
API.reset();
|
||||
|
||||
// 尝试所有API接口
|
||||
for (let i = 0; i < API.endpoints.length; i++) {
|
||||
try {
|
||||
const url = API.getCurrentUrl(currency);
|
||||
console.log(`尝试接口 ${i + 1}/${API.endpoints.length}: ${url}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
cache: 'no-store'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.code === 200) {
|
||||
console.log(`接口 ${i + 1} 请求成功`);
|
||||
return data;
|
||||
}
|
||||
|
||||
throw new Error(data && data.message ? data.message : '接口返回异常');
|
||||
|
||||
} catch (e) {
|
||||
console.warn(`接口 ${i + 1} 失败:`, e.message);
|
||||
|
||||
// 如果不是最后一个接口,切换到下一个
|
||||
if (i < API.endpoints.length - 1) {
|
||||
API.switchToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 所有接口都失败了
|
||||
console.warn('所有远程接口都失败,尝试本地数据');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从本地获取数据
|
||||
async function fetchFromLocal() {
|
||||
try {
|
||||
const response = await fetch(API.localFallback + `?t=${Date.now()}`);
|
||||
if (!response.ok) throw new Error(`本地文件HTTP ${response.status}`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error('读取本地返回接口.json失败:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示汇率数据
|
||||
function displayExchangeRates(data) {
|
||||
if (!data || !data.rates) {
|
||||
showError('没有获取到汇率数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新基础货币信息
|
||||
if (elements.baseCurrency) {
|
||||
const baseCurrencyName = CURRENCY_NAMES[data.base_code] || data.base_code;
|
||||
const baseCurrencyFlag = CURRENCY_FLAGS[data.base_code] || '💱';
|
||||
elements.baseCurrency.textContent = `${baseCurrencyFlag} ${data.base_code} - ${baseCurrencyName}`;
|
||||
}
|
||||
|
||||
// 更新时间信息
|
||||
if (elements.updateTime && data.updated) {
|
||||
elements.updateTime.textContent = `更新时间: ${data.updated}`;
|
||||
}
|
||||
|
||||
// 更新统计信息
|
||||
updateStats(data);
|
||||
|
||||
// 显示汇率列表
|
||||
filteredRates = data.rates;
|
||||
renderRates(filteredRates);
|
||||
|
||||
// 显示内容区域
|
||||
if (elements.content) {
|
||||
elements.content.classList.add('fade-in');
|
||||
elements.content.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计信息
|
||||
function updateStats(data) {
|
||||
if (elements.totalCurrencies) {
|
||||
elements.totalCurrencies.textContent = data.rates ? data.rates.length : 0;
|
||||
}
|
||||
|
||||
if (elements.lastUpdate && data.updated) {
|
||||
elements.lastUpdate.textContent = data.updated;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染汇率列表
|
||||
function renderRates(rates) {
|
||||
if (!elements.ratesGrid || !rates) return;
|
||||
|
||||
// 按优先级排序货币
|
||||
const sortedRates = [...rates].sort((a, b) => {
|
||||
const priorityA = CURRENCY_PRIORITY[a.currency] || 999;
|
||||
const priorityB = CURRENCY_PRIORITY[b.currency] || 999;
|
||||
|
||||
// 优先级相同时按货币代码字母顺序排序
|
||||
if (priorityA === priorityB) {
|
||||
return a.currency.localeCompare(b.currency);
|
||||
}
|
||||
|
||||
return priorityA - priorityB;
|
||||
});
|
||||
|
||||
elements.ratesGrid.innerHTML = '';
|
||||
|
||||
sortedRates.forEach(rate => {
|
||||
const rateCard = createRateCard(rate);
|
||||
elements.ratesGrid.appendChild(rateCard);
|
||||
});
|
||||
}
|
||||
|
||||
// 创建汇率卡片
|
||||
function createRateCard(rate) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'rate-card';
|
||||
|
||||
const currencyName = CURRENCY_NAMES[rate.currency] || rate.currency;
|
||||
const currencyFlag = CURRENCY_FLAGS[rate.currency] || '💱';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="currency-code">
|
||||
<span class="currency-flag">${currencyFlag}</span>
|
||||
${rate.currency}
|
||||
</div>
|
||||
<div class="exchange-rate">${formatRate(rate.rate)}</div>
|
||||
<div class="currency-name">${currencyName}</div>
|
||||
`;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// 格式化汇率
|
||||
function formatRate(rate) {
|
||||
if (rate >= 1) {
|
||||
return rate.toFixed(4);
|
||||
} else if (rate >= 0.01) {
|
||||
return rate.toFixed(6);
|
||||
} else {
|
||||
return rate.toFixed(8);
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤汇率数据
|
||||
function filterRates(searchTerm) {
|
||||
if (!searchTerm.trim()) {
|
||||
renderRates(currentRates);
|
||||
return;
|
||||
}
|
||||
|
||||
const filtered = currentRates.filter(rate => {
|
||||
const currencyName = CURRENCY_NAMES[rate.currency] || rate.currency;
|
||||
return rate.currency.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
currencyName.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
});
|
||||
|
||||
renderRates(filtered);
|
||||
}
|
||||
|
||||
// 显示/隐藏加载状态
|
||||
function showLoading(show) {
|
||||
if (elements.loading) {
|
||||
elements.loading.style.display = show ? 'block' : 'none';
|
||||
}
|
||||
if (elements.content) {
|
||||
elements.content.style.display = show ? 'none' : 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
function showError(message) {
|
||||
if (elements.content) {
|
||||
elements.content.innerHTML = `
|
||||
<div class="error">
|
||||
<h3>⚠️ 加载失败</h3>
|
||||
<p>${escapeHtml(message)}</p>
|
||||
<p>请检查网络连接或稍后重试</p>
|
||||
</div>
|
||||
`;
|
||||
elements.content.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// HTML转义
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
window.addEventListener('error', function(event) {
|
||||
console.error('页面错误:', event.error);
|
||||
});
|
||||
|
||||
// 网络状态监听
|
||||
window.addEventListener('online', function() {
|
||||
console.log('网络已连接');
|
||||
});
|
||||
|
||||
window.addEventListener('offline', function() {
|
||||
console.log('网络已断开');
|
||||
showError('网络连接已断开,请检查网络设置');
|
||||
});
|
||||
|
||||
// 导出函数供外部调用
|
||||
window.ExchangeRate = {
|
||||
loadExchangeRates,
|
||||
showError,
|
||||
showLoading
|
||||
};
|
||||
7
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/接口集合.json
Normal file
7
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/接口集合.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
"https://60s-cf.viki.moe",
|
||||
"https://60s.viki.moe",
|
||||
"https://60s.b23.run",
|
||||
"https://60s.114128.xyz",
|
||||
"https://60s-cf.114128.xyz"
|
||||
]
|
||||
1
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/返回接口.json
Normal file
1
frontend/react-app/public/60sapi/日更资讯/每日国际汇率/返回接口.json
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user